@snagtag/design-system 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs ADDED
@@ -0,0 +1,3239 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ BUILT_IN_PRESETS: () => BUILT_IN_PRESETS,
24
+ COLOUR_BLINDNESS_TYPES: () => COLOUR_BLINDNESS_TYPES,
25
+ DENSITY_CONFIGS: () => DENSITY_CONFIGS,
26
+ EASING_PRESETS: () => EASING_PRESETS,
27
+ FONT_PAIRINGS: () => FONT_PAIRINGS,
28
+ SCALE_RATIOS: () => SCALE_RATIOS,
29
+ SURFACE_DEFINITIONS: () => SURFACE_DEFINITIONS,
30
+ THEME_CATEGORIES: () => THEME_CATEGORIES,
31
+ THEME_DEFAULTS: () => THEME_DEFAULTS,
32
+ THEME_KEY_TO_CSS: () => THEME_KEY_TO_CSS,
33
+ TOKEN_REGISTRY: () => TOKEN_REGISTRY,
34
+ addCustomCategory: () => addCustomCategory,
35
+ addExtractedToken: () => addExtractedToken,
36
+ adjustSemanticForDark: () => adjustSemanticForDark,
37
+ applyAddCategory: () => applyAddCategory,
38
+ applyBulkChange: () => applyBulkChange,
39
+ applyBulkMove: () => applyBulkMove,
40
+ applyChange: () => applyChange,
41
+ applyDarkModeToDOM: () => applyDarkModeToDOM,
42
+ applyDensity: () => applyDensity,
43
+ applyDensityToDOM: () => applyDensityToDOM,
44
+ applyExtract: () => applyExtract,
45
+ applyMove: () => applyMove,
46
+ applySurface: () => applySurface,
47
+ applySurfaceToDOM: () => applySurfaceToDOM,
48
+ applyThemeToDOM: () => applyThemeToDOM,
49
+ auditReportToMarkdown: () => auditReportToMarkdown,
50
+ bulkMoveTokens: () => bulkMoveTokens,
51
+ calculateQualityScore: () => calculateQualityScore2,
52
+ captureSnapshot: () => captureSnapshot,
53
+ checkConsistency: () => checkConsistency2,
54
+ clampControlPoint: () => clampControlPoint,
55
+ clearSnapshots: () => clearSnapshots,
56
+ clearSurface: () => clearSurface,
57
+ clearThemeFromDOM: () => clearThemeFromDOM,
58
+ clearUsageCache: () => clearUsageCache,
59
+ contrastRatio: () => contrastRatio,
60
+ countTokenUsage: () => countTokenUsage,
61
+ createOverlay: () => createOverlay,
62
+ createReassignmentState: () => createReassignmentState,
63
+ createThemeEditorState: () => createThemeEditorState,
64
+ darken: () => darken,
65
+ downloadAuditReport: () => downloadAuditReport,
66
+ downloadStyleGuide: () => downloadStyleGuide,
67
+ downloadThemeAsJSON: () => downloadThemeAsJSON,
68
+ downloadW3CTokens: () => downloadW3CTokens,
69
+ ensureContrastOnWhite: () => ensureContrastOnWhite,
70
+ exportTheme: () => exportTheme,
71
+ exportToW3CFormat: () => exportToW3CFormat,
72
+ extractToken: () => extractToken,
73
+ findContrastIssues: () => findContrastIssues2,
74
+ findPreset: () => findPreset,
75
+ findPresetByValue: () => findPresetByValue,
76
+ findToken: () => findToken,
77
+ findUnusedTokens: () => findUnusedTokens,
78
+ formatCubicBezier: () => formatCubicBezier,
79
+ generateAuditReport: () => generateAuditReport,
80
+ generateDarkTheme: () => generateDarkTheme,
81
+ generateScale: () => generateScale,
82
+ generateStyleGuide: () => generateStyleGuide,
83
+ generateTypeScale: () => generateTypeScale,
84
+ getActiveDensity: () => getActiveDensity,
85
+ getActiveSurface: () => getActiveSurface,
86
+ getAllCategoryLabels: () => getAllCategoryLabels,
87
+ getDensityCSS: () => getDensityCSS,
88
+ getDependents: () => getDependents,
89
+ getEffectiveCategories: () => getEffectiveCategories,
90
+ getOpeningSnapshot: () => getOpeningSnapshot,
91
+ getPresetsByCategory: () => getPresetsByCategory,
92
+ getSnapshot: () => getSnapshot,
93
+ getSurfaceDefinition: () => getSurfaceDefinition,
94
+ getSurfaceOverrides: () => getSurfaceOverrides,
95
+ getTokenCategory: () => getTokenCategory,
96
+ getTokenChain: () => getTokenChain,
97
+ getTokenUsageCount: () => getTokenUsageCount,
98
+ getTokenUsageStats: () => getTokenUsageStats,
99
+ hasExtractedToken: () => hasExtractedToken,
100
+ hexToHSL: () => hexToHSL,
101
+ hexToHsl: () => hexToHsl,
102
+ hslToHex: () => hslToHex,
103
+ hslToHexDark: () => hslToHex2,
104
+ invertLightness: () => invertLightness,
105
+ isDark: () => isDark,
106
+ isDarkMode: () => isDarkMode,
107
+ lighten: () => lighten,
108
+ luminance: () => luminance,
109
+ makePreset: () => makePreset,
110
+ markSaved: () => markSaved,
111
+ moveToken: () => moveToken,
112
+ parseCubicBezier: () => parseCubicBezier,
113
+ readSnapshots: () => readSnapshots,
114
+ readThemeFromFile: () => readThemeFromFile,
115
+ redo: () => redo,
116
+ redoReassignment: () => redoReassignment,
117
+ resetToDefaults: () => resetToDefaults,
118
+ resolveMode: () => resolveMode,
119
+ rotateHue: () => rotateHue,
120
+ sampleBezierCurve: () => sampleBezierCurve,
121
+ setDarkMode: () => setDarkMode,
122
+ simulateColourBlindness: () => simulateColourBlindness,
123
+ simulateThemeColourBlindness: () => simulateThemeColourBlindness,
124
+ snapToGrid: () => snapToGrid,
125
+ suggestFix: () => suggestFix,
126
+ suggestPairings: () => suggestPairings,
127
+ suggestPalettes: () => suggestPalettes,
128
+ suggestTokenName: () => suggestTokenName,
129
+ systemPrefersDark: () => systemPrefersDark,
130
+ themesAreEqual: () => themesAreEqual,
131
+ typeScaleToCSSVars: () => typeScaleToCSSVars,
132
+ undo: () => undo,
133
+ undoReassignment: () => undoReassignment,
134
+ validateImport: () => validateImport,
135
+ validateTokenName: () => validateTokenName
136
+ });
137
+ module.exports = __toCommonJS(index_exports);
138
+
139
+ // src/colour-utils.ts
140
+ function lighten(hex, amount) {
141
+ const r = parseInt(hex.slice(1, 3), 16);
142
+ const g = parseInt(hex.slice(3, 5), 16);
143
+ const b = parseInt(hex.slice(5, 7), 16);
144
+ const lr = Math.round(r + (255 - r) * amount);
145
+ const lg = Math.round(g + (255 - g) * amount);
146
+ const lb = Math.round(b + (255 - b) * amount);
147
+ return `#${lr.toString(16).padStart(2, "0")}${lg.toString(16).padStart(2, "0")}${lb.toString(16).padStart(2, "0")}`;
148
+ }
149
+ function darken(hex, amount) {
150
+ const r = parseInt(hex.slice(1, 3), 16);
151
+ const g = parseInt(hex.slice(3, 5), 16);
152
+ const b = parseInt(hex.slice(5, 7), 16);
153
+ const dr = Math.round(r * (1 - amount));
154
+ const dg = Math.round(g * (1 - amount));
155
+ const db = Math.round(b * (1 - amount));
156
+ return `#${dr.toString(16).padStart(2, "0")}${dg.toString(16).padStart(2, "0")}${db.toString(16).padStart(2, "0")}`;
157
+ }
158
+ function luminance(hex) {
159
+ const r = parseInt(hex.slice(1, 3), 16) / 255;
160
+ const g = parseInt(hex.slice(3, 5), 16) / 255;
161
+ const b = parseInt(hex.slice(5, 7), 16) / 255;
162
+ const sR = r <= 0.03928 ? r / 12.92 : ((r + 0.055) / 1.055) ** 2.4;
163
+ const sG = g <= 0.03928 ? g / 12.92 : ((g + 0.055) / 1.055) ** 2.4;
164
+ const sB = b <= 0.03928 ? b / 12.92 : ((b + 0.055) / 1.055) ** 2.4;
165
+ return 0.2126 * sR + 0.7152 * sG + 0.0722 * sB;
166
+ }
167
+ function isDark(hex) {
168
+ return luminance(hex) < 0.4;
169
+ }
170
+ function contrastRatio(fg, bg) {
171
+ const l1 = luminance(fg);
172
+ const l2 = luminance(bg);
173
+ const lighter = Math.max(l1, l2);
174
+ const darker = Math.min(l1, l2);
175
+ return (lighter + 0.05) / (darker + 0.05);
176
+ }
177
+ function ensureContrastOnWhite(hex) {
178
+ const white = "#ffffff";
179
+ let colour = hex;
180
+ let attempts = 0;
181
+ while (contrastRatio(colour, white) < 4.5 && attempts < 20) {
182
+ colour = darken(colour, 0.05);
183
+ attempts++;
184
+ }
185
+ return colour;
186
+ }
187
+ function hexToHSL(hex) {
188
+ const r = parseInt(hex.slice(1, 3), 16) / 255;
189
+ const g = parseInt(hex.slice(3, 5), 16) / 255;
190
+ const b = parseInt(hex.slice(5, 7), 16) / 255;
191
+ const max = Math.max(r, g, b);
192
+ const min = Math.min(r, g, b);
193
+ const l = (max + min) / 2;
194
+ let h = 0;
195
+ let s = 0;
196
+ if (max !== min) {
197
+ const d = max - min;
198
+ s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
199
+ switch (max) {
200
+ case r:
201
+ h = ((g - b) / d + (g < b ? 6 : 0)) * 60;
202
+ break;
203
+ case g:
204
+ h = ((b - r) / d + 2) * 60;
205
+ break;
206
+ case b:
207
+ h = ((r - g) / d + 4) * 60;
208
+ break;
209
+ }
210
+ }
211
+ return {
212
+ h: Math.round(h * 10) / 10,
213
+ s: Math.round(s * 1e3) / 10,
214
+ l: Math.round(l * 1e3) / 10
215
+ };
216
+ }
217
+ function hslToHex(hsl) {
218
+ const { h, s: sp, l: lp } = hsl;
219
+ const s = sp / 100;
220
+ const l = lp / 100;
221
+ const c = (1 - Math.abs(2 * l - 1)) * s;
222
+ const x = c * (1 - Math.abs(h / 60 % 2 - 1));
223
+ const m = l - c / 2;
224
+ let r = 0;
225
+ let g = 0;
226
+ let b = 0;
227
+ if (h < 60) {
228
+ r = c;
229
+ g = x;
230
+ b = 0;
231
+ } else if (h < 120) {
232
+ r = x;
233
+ g = c;
234
+ b = 0;
235
+ } else if (h < 180) {
236
+ r = 0;
237
+ g = c;
238
+ b = x;
239
+ } else if (h < 240) {
240
+ r = 0;
241
+ g = x;
242
+ b = c;
243
+ } else if (h < 300) {
244
+ r = x;
245
+ g = 0;
246
+ b = c;
247
+ } else {
248
+ r = c;
249
+ g = 0;
250
+ b = x;
251
+ }
252
+ const toHex = (v) => Math.round((v + m) * 255).toString(16).padStart(2, "0");
253
+ return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
254
+ }
255
+ function rotateHue(hsl, degrees) {
256
+ return {
257
+ ...hsl,
258
+ h: ((hsl.h + degrees) % 360 + 360) % 360
259
+ };
260
+ }
261
+ function generateScale(hex) {
262
+ return [
263
+ lighten(hex, 0.95),
264
+ lighten(hex, 0.9),
265
+ lighten(hex, 0.8),
266
+ lighten(hex, 0.65),
267
+ lighten(hex, 0.45),
268
+ hex,
269
+ darken(hex, 0.1),
270
+ darken(hex, 0.2),
271
+ darken(hex, 0.35),
272
+ darken(hex, 0.5),
273
+ darken(hex, 0.65)
274
+ ];
275
+ }
276
+ function averageContrast(colours) {
277
+ const white = "#ffffff";
278
+ const dark = "#1e293b";
279
+ let total = 0;
280
+ let count = 0;
281
+ for (const c of colours) {
282
+ total += contrastRatio(c, white);
283
+ total += contrastRatio(c, dark);
284
+ count += 2;
285
+ }
286
+ return count > 0 ? Math.round(total / count * 10) / 10 : 0;
287
+ }
288
+ function suggestPalettes(primaryHex) {
289
+ const hsl = hexToHSL(primaryHex);
290
+ const complementary = hslToHex(rotateHue(hsl, 180));
291
+ const analogous1 = hslToHex(rotateHue(hsl, 30));
292
+ const analogous2 = hslToHex(rotateHue(hsl, -30));
293
+ const triadic1 = hslToHex(rotateHue(hsl, 120));
294
+ const triadic2 = hslToHex(rotateHue(hsl, -120));
295
+ const split1 = hslToHex(rotateHue(hsl, 150));
296
+ const split2 = hslToHex(rotateHue(hsl, 210));
297
+ return [
298
+ {
299
+ type: "complementary",
300
+ colours: [primaryHex, complementary],
301
+ label: "Complementary palette",
302
+ description: "Opposite on the colour wheel - maximum contrast and visual energy.",
303
+ contrastScore: averageContrast([primaryHex, complementary])
304
+ },
305
+ {
306
+ type: "analogous",
307
+ colours: [analogous2, primaryHex, analogous1],
308
+ label: "Analogous palette",
309
+ description: "Neighbouring hues - harmonious and easy on the eye.",
310
+ contrastScore: averageContrast([analogous2, primaryHex, analogous1])
311
+ },
312
+ {
313
+ type: "triadic",
314
+ colours: [primaryHex, triadic1, triadic2],
315
+ label: "Triadic palette",
316
+ description: "Three equally-spaced hues - vibrant and balanced.",
317
+ contrastScore: averageContrast([primaryHex, triadic1, triadic2])
318
+ },
319
+ {
320
+ type: "split-complementary",
321
+ colours: [primaryHex, split1, split2],
322
+ label: "Split-complementary palette",
323
+ description: "Primary plus two colours adjacent to its complement - strong contrast with less tension.",
324
+ contrastScore: averageContrast([primaryHex, split1, split2])
325
+ }
326
+ ];
327
+ }
328
+
329
+ // src/theme-defaults.ts
330
+ var THEME_DEFAULTS = {
331
+ // Primitives
332
+ primaryColor: "#2563eb",
333
+ primaryHoverColor: "#1d4ed8",
334
+ primaryLightColor: "#dbeafe",
335
+ secondaryColor: "#64748b",
336
+ accentColor: "#f59e0b",
337
+ // Sidebar
338
+ sidebarBg: "#ffffff",
339
+ sidebarText: "#475569",
340
+ sidebarActiveBg: "",
341
+ // empty = auto-derive from primary
342
+ // Semantic colours
343
+ successColor: "#10b981",
344
+ successLightColor: "#d1fae5",
345
+ warningColor: "#f59e0b",
346
+ warningLightColor: "#fef3c7",
347
+ errorColor: "#ef4444",
348
+ errorLightColor: "#fee2e2",
349
+ infoColor: "#3b82f6",
350
+ infoLightColor: "#dbeafe",
351
+ // Surface
352
+ surfacePageColor: "#f8fafc",
353
+ surfaceCardColor: "#ffffff",
354
+ surfaceModalColor: "#ffffff",
355
+ // Interactive
356
+ focusRingColor: "#3b82f6",
357
+ linkColor: "#2563eb",
358
+ linkHoverColor: "#1d4ed8",
359
+ // Button - primary
360
+ btnPrimaryBg: "#2563eb",
361
+ btnPrimaryText: "#ffffff",
362
+ btnPrimaryHover: "#1d4ed8",
363
+ btnPrimaryDisabled: "#93c5fd",
364
+ // Button - secondary
365
+ btnSecondaryBg: "#ffffff",
366
+ btnSecondaryText: "#334155",
367
+ btnSecondaryBorder: "#cbd5e1",
368
+ btnSecondaryHover: "#f8fafc",
369
+ // Button - danger
370
+ btnDangerBg: "#ef4444",
371
+ btnDangerText: "#ffffff",
372
+ btnDangerHover: "#dc2626",
373
+ btnDangerDisabled: "#fca5a5",
374
+ // Button - ghost
375
+ btnGhostText: "#475569",
376
+ btnGhostHoverBg: "#f1f5f9",
377
+ btnGhostHoverText: "#0f172a",
378
+ // Input
379
+ inputBg: "#ffffff",
380
+ inputText: "#0f172a",
381
+ inputBorder: "#cbd5e1",
382
+ inputPlaceholder: "#94a3b8",
383
+ inputFocusBorder: "#3b82f6",
384
+ inputFocusRing: "#3b82f6",
385
+ inputDisabledBg: "#f8fafc",
386
+ inputDisabledText: "#64748b",
387
+ inputErrorBorder: "#f87171",
388
+ inputErrorFocus: "#ef4444",
389
+ // Card
390
+ cardBg: "#ffffff",
391
+ cardBorder: "#e2e8f0",
392
+ // Modal
393
+ modalBg: "#ffffff",
394
+ modalBorder: "#e2e8f0",
395
+ // Table
396
+ tableHeaderBg: "#f8fafc",
397
+ tableHeaderText: "#334155",
398
+ tableBorder: "#e2e8f0",
399
+ tableRowHover: "#f8fafc",
400
+ tableRowSelected: "#eff6ff",
401
+ tableStripe: "#f8fafc",
402
+ // Badge
403
+ badgeNeutralBg: "#f1f5f9",
404
+ badgeNeutralText: "#334155",
405
+ badgeInfoBg: "#dbeafe",
406
+ badgeInfoText: "#1d4ed8",
407
+ badgeSuccessBg: "#d1fae5",
408
+ badgeSuccessText: "#047857",
409
+ badgeWarningBg: "#fef3c7",
410
+ badgeWarningText: "#b45309",
411
+ badgeDangerBg: "#fee2e2",
412
+ badgeDangerText: "#b91c1c"
413
+ };
414
+ var THEME_KEY_TO_CSS = {
415
+ // Primitives
416
+ primaryColor: "--color-brand-600",
417
+ primaryHoverColor: "--color-brand-700",
418
+ primaryLightColor: "--color-brand-100",
419
+ secondaryColor: "--color-ink-500",
420
+ accentColor: "--color-accent",
421
+ // Sidebar
422
+ sidebarBg: "--color-sidebar-bg",
423
+ sidebarText: "--color-sidebar-text",
424
+ // Semantic colours
425
+ successColor: "--color-success",
426
+ successLightColor: "--color-success-light",
427
+ warningColor: "--color-warning",
428
+ warningLightColor: "--color-warning-light",
429
+ errorColor: "--color-error",
430
+ errorLightColor: "--color-error-light",
431
+ infoColor: "--color-info",
432
+ infoLightColor: "--color-info-light",
433
+ // Surface
434
+ surfacePageColor: "--color-surface-page",
435
+ surfaceCardColor: "--color-surface-card",
436
+ surfaceModalColor: "--color-surface-modal",
437
+ // Interactive
438
+ focusRingColor: "--color-focus-ring",
439
+ linkColor: "--color-link",
440
+ linkHoverColor: "--color-link-hover",
441
+ // Button - primary
442
+ btnPrimaryBg: "--color-btn-primary-bg",
443
+ btnPrimaryText: "--color-btn-primary-text",
444
+ btnPrimaryHover: "--color-btn-primary-hover",
445
+ btnPrimaryDisabled: "--color-btn-primary-disabled",
446
+ // Button - secondary
447
+ btnSecondaryBg: "--color-btn-secondary-bg",
448
+ btnSecondaryText: "--color-btn-secondary-text",
449
+ btnSecondaryBorder: "--color-btn-secondary-border",
450
+ btnSecondaryHover: "--color-btn-secondary-hover",
451
+ // Button - danger
452
+ btnDangerBg: "--color-btn-danger-bg",
453
+ btnDangerText: "--color-btn-danger-text",
454
+ btnDangerHover: "--color-btn-danger-hover",
455
+ btnDangerDisabled: "--color-btn-danger-disabled",
456
+ // Button - ghost
457
+ btnGhostText: "--color-btn-ghost-text",
458
+ btnGhostHoverBg: "--color-btn-ghost-hover-bg",
459
+ btnGhostHoverText: "--color-btn-ghost-hover-text",
460
+ // Input
461
+ inputBg: "--color-input-bg",
462
+ inputText: "--color-input-text",
463
+ inputBorder: "--color-input-border",
464
+ inputPlaceholder: "--color-input-placeholder",
465
+ inputFocusBorder: "--color-input-focus-border",
466
+ inputFocusRing: "--color-input-focus-ring",
467
+ inputDisabledBg: "--color-input-disabled-bg",
468
+ inputDisabledText: "--color-input-disabled-text",
469
+ inputErrorBorder: "--color-input-error-border",
470
+ inputErrorFocus: "--color-input-error-focus",
471
+ // Card
472
+ cardBg: "--color-card-bg",
473
+ cardBorder: "--color-card-border",
474
+ // Modal
475
+ modalBg: "--color-modal-bg",
476
+ modalBorder: "--color-modal-border",
477
+ // Table
478
+ tableHeaderBg: "--color-table-header-bg",
479
+ tableHeaderText: "--color-table-header-text",
480
+ tableBorder: "--color-table-border",
481
+ tableRowHover: "--color-table-row-hover",
482
+ tableRowSelected: "--color-table-row-selected",
483
+ tableStripe: "--color-table-stripe",
484
+ // Badge
485
+ badgeNeutralBg: "--color-badge-neutral-bg",
486
+ badgeNeutralText: "--color-badge-neutral-text",
487
+ badgeInfoBg: "--color-badge-info-bg",
488
+ badgeInfoText: "--color-badge-info-text",
489
+ badgeSuccessBg: "--color-badge-success-bg",
490
+ badgeSuccessText: "--color-badge-success-text",
491
+ badgeWarningBg: "--color-badge-warning-bg",
492
+ badgeWarningText: "--color-badge-warning-text",
493
+ badgeDangerBg: "--color-badge-danger-bg",
494
+ badgeDangerText: "--color-badge-danger-text"
495
+ };
496
+ var THEME_CATEGORIES = [
497
+ {
498
+ label: "Brand",
499
+ keys: ["primaryColor", "primaryHoverColor", "primaryLightColor", "secondaryColor", "accentColor"]
500
+ },
501
+ {
502
+ label: "Sidebar",
503
+ keys: ["sidebarBg", "sidebarText", "sidebarActiveBg"]
504
+ },
505
+ {
506
+ label: "Semantic Colours",
507
+ keys: [
508
+ "successColor",
509
+ "successLightColor",
510
+ "warningColor",
511
+ "warningLightColor",
512
+ "errorColor",
513
+ "errorLightColor",
514
+ "infoColor",
515
+ "infoLightColor"
516
+ ]
517
+ },
518
+ {
519
+ label: "Surface",
520
+ keys: ["surfacePageColor", "surfaceCardColor", "surfaceModalColor"]
521
+ },
522
+ {
523
+ label: "Interactive",
524
+ keys: ["focusRingColor", "linkColor", "linkHoverColor"]
525
+ },
526
+ {
527
+ label: "Button - Primary",
528
+ keys: ["btnPrimaryBg", "btnPrimaryText", "btnPrimaryHover", "btnPrimaryDisabled"]
529
+ },
530
+ {
531
+ label: "Button - Secondary",
532
+ keys: ["btnSecondaryBg", "btnSecondaryText", "btnSecondaryBorder", "btnSecondaryHover"]
533
+ },
534
+ {
535
+ label: "Button - Danger",
536
+ keys: ["btnDangerBg", "btnDangerText", "btnDangerHover", "btnDangerDisabled"]
537
+ },
538
+ {
539
+ label: "Button - Ghost",
540
+ keys: ["btnGhostText", "btnGhostHoverBg", "btnGhostHoverText"]
541
+ },
542
+ {
543
+ label: "Input",
544
+ keys: [
545
+ "inputBg",
546
+ "inputText",
547
+ "inputBorder",
548
+ "inputPlaceholder",
549
+ "inputFocusBorder",
550
+ "inputFocusRing",
551
+ "inputDisabledBg",
552
+ "inputDisabledText",
553
+ "inputErrorBorder",
554
+ "inputErrorFocus"
555
+ ]
556
+ },
557
+ {
558
+ label: "Card",
559
+ keys: ["cardBg", "cardBorder"]
560
+ },
561
+ {
562
+ label: "Modal",
563
+ keys: ["modalBg", "modalBorder"]
564
+ },
565
+ {
566
+ label: "Table",
567
+ keys: ["tableHeaderBg", "tableHeaderText", "tableBorder", "tableRowHover", "tableRowSelected", "tableStripe"]
568
+ },
569
+ {
570
+ label: "Badge",
571
+ keys: [
572
+ "badgeNeutralBg",
573
+ "badgeNeutralText",
574
+ "badgeInfoBg",
575
+ "badgeInfoText",
576
+ "badgeSuccessBg",
577
+ "badgeSuccessText",
578
+ "badgeWarningBg",
579
+ "badgeWarningText",
580
+ "badgeDangerBg",
581
+ "badgeDangerText"
582
+ ]
583
+ }
584
+ ];
585
+
586
+ // src/theme-editor-state.ts
587
+ var MAX_UNDO = 10;
588
+ function computeDirty(current, saved) {
589
+ const keys = /* @__PURE__ */ new Set([...Object.keys(current), ...Object.keys(saved)]);
590
+ for (const key of keys) {
591
+ if ((current[key] ?? "") !== (saved[key] ?? "")) return true;
592
+ }
593
+ return false;
594
+ }
595
+ function createThemeEditorState(initial) {
596
+ return {
597
+ current: { ...initial },
598
+ saved: { ...initial },
599
+ undoStack: [],
600
+ redoStack: [],
601
+ isDirty: false
602
+ };
603
+ }
604
+ function applyChange(state, key, value) {
605
+ const undoStack = [...state.undoStack, { ...state.current }];
606
+ if (undoStack.length > MAX_UNDO) undoStack.shift();
607
+ const current = { ...state.current, [key]: value };
608
+ return {
609
+ current,
610
+ saved: state.saved,
611
+ undoStack,
612
+ redoStack: [],
613
+ // new change clears redo
614
+ isDirty: computeDirty(current, state.saved)
615
+ };
616
+ }
617
+ function applyBulkChange(state, changes) {
618
+ const undoStack = [...state.undoStack, { ...state.current }];
619
+ if (undoStack.length > MAX_UNDO) undoStack.shift();
620
+ const current = { ...state.current, ...changes };
621
+ return {
622
+ current,
623
+ saved: state.saved,
624
+ undoStack,
625
+ redoStack: [],
626
+ isDirty: computeDirty(current, state.saved)
627
+ };
628
+ }
629
+ function undo(state) {
630
+ if (state.undoStack.length === 0) return state;
631
+ const undoStack = [...state.undoStack];
632
+ const previous = undoStack.pop();
633
+ return {
634
+ current: previous,
635
+ saved: state.saved,
636
+ undoStack,
637
+ redoStack: [...state.redoStack, { ...state.current }],
638
+ isDirty: computeDirty(previous, state.saved)
639
+ };
640
+ }
641
+ function redo(state) {
642
+ if (state.redoStack.length === 0) return state;
643
+ const redoStack = [...state.redoStack];
644
+ const next = redoStack.pop();
645
+ return {
646
+ current: next,
647
+ saved: state.saved,
648
+ undoStack: [...state.undoStack, { ...state.current }],
649
+ redoStack,
650
+ isDirty: computeDirty(next, state.saved)
651
+ };
652
+ }
653
+ function markSaved(state) {
654
+ return {
655
+ ...state,
656
+ saved: { ...state.current },
657
+ isDirty: false
658
+ };
659
+ }
660
+ function resetToDefaults(state, defaults) {
661
+ const undoStack = [...state.undoStack, { ...state.current }];
662
+ if (undoStack.length > MAX_UNDO) undoStack.shift();
663
+ return {
664
+ current: { ...defaults },
665
+ saved: state.saved,
666
+ undoStack,
667
+ redoStack: [],
668
+ isDirty: computeDirty(defaults, state.saved)
669
+ };
670
+ }
671
+
672
+ // src/token-registry.ts
673
+ var BRAND_PRIMITIVES = [
674
+ { cssVar: "--color-brand-50", label: "Brand 50", category: "colour", defaultValue: "#eff6ff" },
675
+ { cssVar: "--color-brand-100", label: "Brand 100", category: "colour", defaultValue: "#dbeafe", themeKey: "primaryLightColor" },
676
+ { cssVar: "--color-brand-200", label: "Brand 200", category: "colour", defaultValue: "#bfdbfe" },
677
+ { cssVar: "--color-brand-300", label: "Brand 300", category: "colour", defaultValue: "#93c5fd" },
678
+ { cssVar: "--color-brand-400", label: "Brand 400", category: "colour", defaultValue: "#60a5fa" },
679
+ { cssVar: "--color-brand-500", label: "Brand 500", category: "colour", defaultValue: "#3b82f6" },
680
+ { cssVar: "--color-brand-600", label: "Brand 600", category: "colour", defaultValue: "#2563eb", themeKey: "primaryColor" },
681
+ { cssVar: "--color-brand-700", label: "Brand 700", category: "colour", defaultValue: "#1d4ed8", themeKey: "primaryHoverColor" },
682
+ { cssVar: "--color-brand-800", label: "Brand 800", category: "colour", defaultValue: "#1e40af" },
683
+ { cssVar: "--color-brand-900", label: "Brand 900", category: "colour", defaultValue: "#1e3a8a" },
684
+ { cssVar: "--color-brand-950", label: "Brand 950", category: "colour", defaultValue: "#172554" }
685
+ ];
686
+ var INK_PRIMITIVES = [
687
+ { cssVar: "--color-ink-50", label: "Ink 50", category: "colour", defaultValue: "#f8fafc" },
688
+ { cssVar: "--color-ink-100", label: "Ink 100", category: "colour", defaultValue: "#f1f5f9" },
689
+ { cssVar: "--color-ink-200", label: "Ink 200", category: "colour", defaultValue: "#e2e8f0" },
690
+ { cssVar: "--color-ink-300", label: "Ink 300", category: "colour", defaultValue: "#cbd5e1" },
691
+ { cssVar: "--color-ink-400", label: "Ink 400", category: "colour", defaultValue: "#94a3b8" },
692
+ { cssVar: "--color-ink-500", label: "Ink 500", category: "colour", defaultValue: "#64748b", themeKey: "secondaryColor" },
693
+ { cssVar: "--color-ink-600", label: "Ink 600", category: "colour", defaultValue: "#475569" },
694
+ { cssVar: "--color-ink-700", label: "Ink 700", category: "colour", defaultValue: "#334155" },
695
+ { cssVar: "--color-ink-800", label: "Ink 800", category: "colour", defaultValue: "#1e293b" },
696
+ { cssVar: "--color-ink-900", label: "Ink 900", category: "colour", defaultValue: "#0f172a" },
697
+ { cssVar: "--color-ink-950", label: "Ink 950", category: "colour", defaultValue: "#020617" }
698
+ ];
699
+ var ACCENT_PRIMITIVES = [
700
+ { cssVar: "--color-accent", label: "Accent", category: "colour", defaultValue: "#f59e0b", themeKey: "accentColor" },
701
+ { cssVar: "--color-accent-light", label: "Accent Light", category: "colour", defaultValue: "#fef3c7" },
702
+ { cssVar: "--color-accent-hover", label: "Accent Hover", category: "colour", defaultValue: "#d97706" }
703
+ ];
704
+ var SIDEBAR_TOKENS = [
705
+ { cssVar: "--color-sidebar-bg", label: "Sidebar Background", category: "colour", defaultValue: "#ffffff", themeKey: "sidebarBg" },
706
+ { cssVar: "--color-sidebar-text", label: "Sidebar Text", category: "colour", defaultValue: "#475569", themeKey: "sidebarText" },
707
+ { cssVar: "--color-sidebar-border", label: "Sidebar Border", category: "colour", defaultValue: "#e2e8f0" },
708
+ { cssVar: "--color-sidebar-hover", label: "Sidebar Hover", category: "colour", defaultValue: "#f1f5f9" },
709
+ { cssVar: "--color-sidebar-active-bg", label: "Sidebar Active Background", category: "colour", defaultValue: "#eff6ff", references: "--color-brand-50", themeKey: "sidebarActiveBg" },
710
+ { cssVar: "--color-sidebar-active-text", label: "Sidebar Active Text", category: "colour", defaultValue: "#1d4ed8", references: "--color-brand-700" }
711
+ ];
712
+ var SEMANTIC_COLOURS = [
713
+ { cssVar: "--color-success", label: "Success", category: "colour", defaultValue: "#10b981", themeKey: "successColor" },
714
+ { cssVar: "--color-success-light", label: "Success Light", category: "colour", defaultValue: "#d1fae5", themeKey: "successLightColor" },
715
+ { cssVar: "--color-warning", label: "Warning", category: "colour", defaultValue: "#f59e0b", themeKey: "warningColor" },
716
+ { cssVar: "--color-warning-light", label: "Warning Light", category: "colour", defaultValue: "#fef3c7", themeKey: "warningLightColor" },
717
+ { cssVar: "--color-error", label: "Error", category: "colour", defaultValue: "#ef4444", themeKey: "errorColor" },
718
+ { cssVar: "--color-error-light", label: "Error Light", category: "colour", defaultValue: "#fee2e2", themeKey: "errorLightColor" },
719
+ { cssVar: "--color-info", label: "Info", category: "colour", defaultValue: "#3b82f6", themeKey: "infoColor" },
720
+ { cssVar: "--color-info-light", label: "Info Light", category: "colour", defaultValue: "#dbeafe", themeKey: "infoLightColor" }
721
+ ];
722
+ var SURFACE_TOKENS = [
723
+ { cssVar: "--color-surface-page", label: "Page Background", category: "colour", defaultValue: "#f8fafc", references: "--color-ink-50", themeKey: "surfacePageColor" },
724
+ { cssVar: "--color-surface-card", label: "Card Surface", category: "colour", defaultValue: "#ffffff", themeKey: "surfaceCardColor" },
725
+ { cssVar: "--color-surface-modal", label: "Modal Surface", category: "colour", defaultValue: "#ffffff", themeKey: "surfaceModalColor" },
726
+ { cssVar: "--color-surface-overlay", label: "Overlay", category: "colour", defaultValue: "rgb(15 23 42 / 0.4)" }
727
+ ];
728
+ var INTERACTIVE_TOKENS = [
729
+ { cssVar: "--color-focus-ring", label: "Focus Ring", category: "colour", defaultValue: "#3b82f6", references: "--color-brand-500", themeKey: "focusRingColor" },
730
+ { cssVar: "--color-link", label: "Link", category: "colour", defaultValue: "#2563eb", references: "--color-brand-600", themeKey: "linkColor" },
731
+ { cssVar: "--color-link-hover", label: "Link Hover", category: "colour", defaultValue: "#1d4ed8", references: "--color-brand-700", themeKey: "linkHoverColor" }
732
+ ];
733
+ var BUTTON_TOKENS = [
734
+ { cssVar: "--color-btn-primary-bg", label: "Button Primary Background", category: "colour", defaultValue: "#2563eb", references: "--color-brand-600", themeKey: "btnPrimaryBg" },
735
+ { cssVar: "--color-btn-primary-text", label: "Button Primary Text", category: "colour", defaultValue: "#ffffff", themeKey: "btnPrimaryText" },
736
+ { cssVar: "--color-btn-primary-hover", label: "Button Primary Hover", category: "colour", defaultValue: "#1d4ed8", references: "--color-brand-700", themeKey: "btnPrimaryHover" },
737
+ { cssVar: "--color-btn-primary-disabled", label: "Button Primary Disabled", category: "colour", defaultValue: "#93c5fd", references: "--color-brand-300", themeKey: "btnPrimaryDisabled" },
738
+ { cssVar: "--color-btn-secondary-bg", label: "Button Secondary Background", category: "colour", defaultValue: "#ffffff", themeKey: "btnSecondaryBg" },
739
+ { cssVar: "--color-btn-secondary-text", label: "Button Secondary Text", category: "colour", defaultValue: "#334155", references: "--color-ink-700", themeKey: "btnSecondaryText" },
740
+ { cssVar: "--color-btn-secondary-border", label: "Button Secondary Border", category: "colour", defaultValue: "#cbd5e1", references: "--color-ink-300", themeKey: "btnSecondaryBorder" },
741
+ { cssVar: "--color-btn-secondary-hover", label: "Button Secondary Hover", category: "colour", defaultValue: "#f8fafc", references: "--color-ink-50", themeKey: "btnSecondaryHover" },
742
+ { cssVar: "--color-btn-danger-bg", label: "Button Danger Background", category: "colour", defaultValue: "#ef4444", references: "--color-error", themeKey: "btnDangerBg" },
743
+ { cssVar: "--color-btn-danger-text", label: "Button Danger Text", category: "colour", defaultValue: "#ffffff", themeKey: "btnDangerText" },
744
+ { cssVar: "--color-btn-danger-hover", label: "Button Danger Hover", category: "colour", defaultValue: "#dc2626", themeKey: "btnDangerHover" },
745
+ { cssVar: "--color-btn-danger-disabled", label: "Button Danger Disabled", category: "colour", defaultValue: "#fca5a5", themeKey: "btnDangerDisabled" },
746
+ { cssVar: "--color-btn-ghost-text", label: "Button Ghost Text", category: "colour", defaultValue: "#475569", references: "--color-ink-600", themeKey: "btnGhostText" },
747
+ { cssVar: "--color-btn-ghost-hover-bg", label: "Button Ghost Hover Background", category: "colour", defaultValue: "#f1f5f9", references: "--color-ink-100", themeKey: "btnGhostHoverBg" },
748
+ { cssVar: "--color-btn-ghost-hover-text", label: "Button Ghost Hover Text", category: "colour", defaultValue: "#0f172a", references: "--color-ink-900", themeKey: "btnGhostHoverText" }
749
+ ];
750
+ var INPUT_TOKENS = [
751
+ { cssVar: "--color-input-bg", label: "Input Background", category: "colour", defaultValue: "#ffffff", themeKey: "inputBg" },
752
+ { cssVar: "--color-input-text", label: "Input Text", category: "colour", defaultValue: "#0f172a", references: "--color-ink-900", themeKey: "inputText" },
753
+ { cssVar: "--color-input-border", label: "Input Border", category: "colour", defaultValue: "#cbd5e1", references: "--color-ink-300", themeKey: "inputBorder" },
754
+ { cssVar: "--color-input-placeholder", label: "Input Placeholder", category: "colour", defaultValue: "#94a3b8", references: "--color-ink-400", themeKey: "inputPlaceholder" },
755
+ { cssVar: "--color-input-focus-border", label: "Input Focus Border", category: "colour", defaultValue: "#3b82f6", references: "--color-brand-500", themeKey: "inputFocusBorder" },
756
+ { cssVar: "--color-input-focus-ring", label: "Input Focus Ring", category: "colour", defaultValue: "#3b82f6", references: "--color-brand-500", themeKey: "inputFocusRing" },
757
+ { cssVar: "--color-input-disabled-bg", label: "Input Disabled Background", category: "colour", defaultValue: "#f8fafc", references: "--color-ink-50", themeKey: "inputDisabledBg" },
758
+ { cssVar: "--color-input-disabled-text", label: "Input Disabled Text", category: "colour", defaultValue: "#64748b", references: "--color-ink-500", themeKey: "inputDisabledText" },
759
+ { cssVar: "--color-input-error-border", label: "Input Error Border", category: "colour", defaultValue: "#f87171", themeKey: "inputErrorBorder" },
760
+ { cssVar: "--color-input-error-focus", label: "Input Error Focus", category: "colour", defaultValue: "#ef4444", references: "--color-error", themeKey: "inputErrorFocus" }
761
+ ];
762
+ var CARD_TOKENS = [
763
+ { cssVar: "--color-card-bg", label: "Card Background", category: "colour", defaultValue: "#ffffff", themeKey: "cardBg" },
764
+ { cssVar: "--color-card-border", label: "Card Border", category: "colour", defaultValue: "#e2e8f0", references: "--color-ink-200", themeKey: "cardBorder" }
765
+ ];
766
+ var MODAL_TOKENS = [
767
+ { cssVar: "--color-modal-bg", label: "Modal Background", category: "colour", defaultValue: "#ffffff", themeKey: "modalBg" },
768
+ { cssVar: "--color-modal-border", label: "Modal Border", category: "colour", defaultValue: "#e2e8f0", references: "--color-ink-200", themeKey: "modalBorder" },
769
+ { cssVar: "--color-modal-overlay", label: "Modal Overlay", category: "colour", defaultValue: "rgb(15 23 42 / 0.4)" }
770
+ ];
771
+ var TABLE_TOKENS = [
772
+ { cssVar: "--color-table-header-bg", label: "Table Header Background", category: "colour", defaultValue: "#f8fafc", references: "--color-ink-50", themeKey: "tableHeaderBg" },
773
+ { cssVar: "--color-table-header-text", label: "Table Header Text", category: "colour", defaultValue: "#334155", references: "--color-ink-700", themeKey: "tableHeaderText" },
774
+ { cssVar: "--color-table-border", label: "Table Border", category: "colour", defaultValue: "#e2e8f0", references: "--color-ink-200", themeKey: "tableBorder" },
775
+ { cssVar: "--color-table-row-hover", label: "Table Row Hover", category: "colour", defaultValue: "#f8fafc", references: "--color-ink-50", themeKey: "tableRowHover" },
776
+ { cssVar: "--color-table-row-selected", label: "Table Row Selected", category: "colour", defaultValue: "#eff6ff", references: "--color-brand-50", themeKey: "tableRowSelected" },
777
+ { cssVar: "--color-table-stripe", label: "Table Stripe", category: "colour", defaultValue: "#f8fafc", references: "--color-ink-50", themeKey: "tableStripe" }
778
+ ];
779
+ var BADGE_TOKENS = [
780
+ { cssVar: "--color-badge-neutral-bg", label: "Badge Neutral Background", category: "colour", defaultValue: "#f1f5f9", references: "--color-ink-100", themeKey: "badgeNeutralBg" },
781
+ { cssVar: "--color-badge-neutral-text", label: "Badge Neutral Text", category: "colour", defaultValue: "#334155", references: "--color-ink-700", themeKey: "badgeNeutralText" },
782
+ { cssVar: "--color-badge-info-bg", label: "Badge Info Background", category: "colour", defaultValue: "#dbeafe", references: "--color-info-light", themeKey: "badgeInfoBg" },
783
+ { cssVar: "--color-badge-info-text", label: "Badge Info Text", category: "colour", defaultValue: "#1d4ed8", themeKey: "badgeInfoText" },
784
+ { cssVar: "--color-badge-success-bg", label: "Badge Success Background", category: "colour", defaultValue: "#d1fae5", references: "--color-success-light", themeKey: "badgeSuccessBg" },
785
+ { cssVar: "--color-badge-success-text", label: "Badge Success Text", category: "colour", defaultValue: "#047857", themeKey: "badgeSuccessText" },
786
+ { cssVar: "--color-badge-warning-bg", label: "Badge Warning Background", category: "colour", defaultValue: "#fef3c7", references: "--color-warning-light", themeKey: "badgeWarningBg" },
787
+ { cssVar: "--color-badge-warning-text", label: "Badge Warning Text", category: "colour", defaultValue: "#b45309", themeKey: "badgeWarningText" },
788
+ { cssVar: "--color-badge-danger-bg", label: "Badge Danger Background", category: "colour", defaultValue: "#fee2e2", references: "--color-error-light", themeKey: "badgeDangerBg" },
789
+ { cssVar: "--color-badge-danger-text", label: "Badge Danger Text", category: "colour", defaultValue: "#b91c1c", themeKey: "badgeDangerText" }
790
+ ];
791
+ var TYPOGRAPHY_TOKENS = [
792
+ { cssVar: "--font-sans", label: "Font Sans", category: "typography", defaultValue: 'system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif' },
793
+ { cssVar: "--font-mono", label: "Font Mono", category: "typography", defaultValue: 'ui-monospace, "SF Mono", "Cascadia Code", Consolas, monospace' },
794
+ { cssVar: "--font-display", label: "Font Display", category: "typography", defaultValue: "var(--font-sans)", references: "--font-sans" },
795
+ { cssVar: "--text-h1", label: "Heading 1 Size", category: "typography", defaultValue: "1.875rem" },
796
+ { cssVar: "--text-h2", label: "Heading 2 Size", category: "typography", defaultValue: "1.5rem" },
797
+ { cssVar: "--text-h3", label: "Heading 3 Size", category: "typography", defaultValue: "1.25rem" },
798
+ { cssVar: "--text-h4", label: "Heading 4 Size", category: "typography", defaultValue: "1.125rem" },
799
+ { cssVar: "--text-body-lg", label: "Body Large", category: "typography", defaultValue: "1rem" },
800
+ { cssVar: "--text-body-md", label: "Body Medium", category: "typography", defaultValue: "0.875rem" },
801
+ { cssVar: "--text-body-sm", label: "Body Small", category: "typography", defaultValue: "0.75rem" },
802
+ { cssVar: "--text-label", label: "Label", category: "typography", defaultValue: "0.875rem" },
803
+ { cssVar: "--text-caption", label: "Caption", category: "typography", defaultValue: "0.75rem" },
804
+ { cssVar: "--leading-h1", label: "Heading 1 Line Height", category: "typography", defaultValue: "1.2" },
805
+ { cssVar: "--leading-h2", label: "Heading 2 Line Height", category: "typography", defaultValue: "1.25" },
806
+ { cssVar: "--leading-h3", label: "Heading 3 Line Height", category: "typography", defaultValue: "1.3" },
807
+ { cssVar: "--leading-body", label: "Body Line Height", category: "typography", defaultValue: "1.5" },
808
+ { cssVar: "--leading-tight", label: "Tight Line Height", category: "typography", defaultValue: "1.25" },
809
+ { cssVar: "--weight-normal", label: "Weight Normal", category: "typography", defaultValue: "400" },
810
+ { cssVar: "--weight-medium", label: "Weight Medium", category: "typography", defaultValue: "500" },
811
+ { cssVar: "--weight-semibold", label: "Weight Semibold", category: "typography", defaultValue: "600" },
812
+ { cssVar: "--weight-bold", label: "Weight Bold", category: "typography", defaultValue: "700" }
813
+ ];
814
+ var SPACING_TOKENS = [
815
+ { cssVar: "--space-xs", label: "Space Extra Small", category: "spacing", defaultValue: "0.25rem" },
816
+ { cssVar: "--space-sm", label: "Space Small", category: "spacing", defaultValue: "0.5rem" },
817
+ { cssVar: "--space-md", label: "Space Medium", category: "spacing", defaultValue: "1rem" },
818
+ { cssVar: "--space-lg", label: "Space Large", category: "spacing", defaultValue: "1.5rem" },
819
+ { cssVar: "--space-xl", label: "Space Extra Large", category: "spacing", defaultValue: "2rem" },
820
+ { cssVar: "--space-2xl", label: "Space 2x Large", category: "spacing", defaultValue: "3rem" }
821
+ ];
822
+ var BORDER_TOKENS = [
823
+ { cssVar: "--border-width-none", label: "Border None", category: "border", defaultValue: "0px" },
824
+ { cssVar: "--border-width-thin", label: "Border Thin", category: "border", defaultValue: "1px" },
825
+ { cssVar: "--border-width-medium", label: "Border Medium", category: "border", defaultValue: "2px" },
826
+ { cssVar: "--radius-none", label: "Radius None", category: "border", defaultValue: "0px" },
827
+ { cssVar: "--radius-sm", label: "Radius Small", category: "border", defaultValue: "0.25rem" },
828
+ { cssVar: "--radius-md", label: "Radius Medium", category: "border", defaultValue: "0.375rem" },
829
+ { cssVar: "--radius-lg", label: "Radius Large", category: "border", defaultValue: "0.5rem" },
830
+ { cssVar: "--radius-xl", label: "Radius Extra Large", category: "border", defaultValue: "0.75rem" },
831
+ { cssVar: "--radius-full", label: "Radius Full", category: "border", defaultValue: "9999px" }
832
+ ];
833
+ var SHADOW_TOKENS = [
834
+ { cssVar: "--shadow-none", label: "Shadow None", category: "shadow", defaultValue: "none" },
835
+ { cssVar: "--shadow-sm", label: "Shadow Small", category: "shadow", defaultValue: "0 1px 2px 0 rgb(0 0 0 / 0.05)" },
836
+ { cssVar: "--shadow-md", label: "Shadow Medium", category: "shadow", defaultValue: "0 4px 6px -1px rgb(0 0 0 / 0.1)" },
837
+ { cssVar: "--shadow-lg", label: "Shadow Large", category: "shadow", defaultValue: "0 10px 15px -3px rgb(0 0 0 / 0.1)" },
838
+ { cssVar: "--shadow-xl", label: "Shadow Extra Large", category: "shadow", defaultValue: "0 20px 25px -5px rgb(0 0 0 / 0.1)" }
839
+ ];
840
+ var MOTION_TOKENS = [
841
+ { cssVar: "--duration-instant", label: "Duration Instant", category: "motion", defaultValue: "0ms" },
842
+ { cssVar: "--duration-fast", label: "Duration Fast", category: "motion", defaultValue: "100ms" },
843
+ { cssVar: "--duration-normal", label: "Duration Normal", category: "motion", defaultValue: "200ms" },
844
+ { cssVar: "--duration-moderate", label: "Duration Moderate", category: "motion", defaultValue: "300ms" },
845
+ { cssVar: "--duration-slow", label: "Duration Slow", category: "motion", defaultValue: "500ms" },
846
+ { cssVar: "--ease-sharp", label: "Ease Sharp", category: "motion", defaultValue: "cubic-bezier(0.4, 0, 0.2, 1)" },
847
+ { cssVar: "--ease-smooth", label: "Ease Smooth", category: "motion", defaultValue: "cubic-bezier(0.4, 0, 0, 1)" },
848
+ { cssVar: "--ease-bounce", label: "Ease Bounce", category: "motion", defaultValue: "cubic-bezier(0.34, 1.56, 0.64, 1)" }
849
+ ];
850
+ var TOKEN_REGISTRY = [
851
+ { label: "Brand Colours", tokens: BRAND_PRIMITIVES },
852
+ { label: "Ink (Neutral) Colours", tokens: INK_PRIMITIVES },
853
+ { label: "Accent Colours", tokens: ACCENT_PRIMITIVES },
854
+ { label: "Sidebar", tokens: SIDEBAR_TOKENS },
855
+ { label: "Semantic Colours", tokens: SEMANTIC_COLOURS },
856
+ { label: "Surface", tokens: SURFACE_TOKENS },
857
+ { label: "Interactive", tokens: INTERACTIVE_TOKENS },
858
+ { label: "Button", tokens: BUTTON_TOKENS },
859
+ { label: "Input", tokens: INPUT_TOKENS },
860
+ { label: "Card", tokens: CARD_TOKENS },
861
+ { label: "Modal", tokens: MODAL_TOKENS },
862
+ { label: "Table", tokens: TABLE_TOKENS },
863
+ { label: "Badge", tokens: BADGE_TOKENS },
864
+ { label: "Typography", tokens: TYPOGRAPHY_TOKENS },
865
+ { label: "Spacing", tokens: SPACING_TOKENS },
866
+ { label: "Border", tokens: BORDER_TOKENS },
867
+ { label: "Shadow", tokens: SHADOW_TOKENS },
868
+ { label: "Motion", tokens: MOTION_TOKENS }
869
+ ];
870
+ var ALL_TOKENS = TOKEN_REGISTRY.flatMap((c) => c.tokens);
871
+ var TOKEN_MAP = /* @__PURE__ */ new Map();
872
+ for (const token of ALL_TOKENS) {
873
+ TOKEN_MAP.set(token.cssVar, token);
874
+ }
875
+ function findToken(cssVar) {
876
+ return TOKEN_MAP.get(cssVar);
877
+ }
878
+ function getTokenChain(cssVar) {
879
+ const chain = [];
880
+ const visited = /* @__PURE__ */ new Set();
881
+ let current = cssVar;
882
+ while (current && !visited.has(current)) {
883
+ visited.add(current);
884
+ const token = TOKEN_MAP.get(current);
885
+ if (!token) break;
886
+ chain.push(token);
887
+ if (token.references) {
888
+ current = token.references;
889
+ } else {
890
+ break;
891
+ }
892
+ }
893
+ return chain;
894
+ }
895
+ function getDependents(cssVar) {
896
+ return ALL_TOKENS.filter((t) => t.references === cssVar);
897
+ }
898
+
899
+ // src/colour-blindness.ts
900
+ var COLOUR_BLINDNESS_TYPES = [
901
+ { type: "protanopia", label: "Protanopia", description: "Red-blind - difficulty distinguishing red and green", prevalence: "1% of males" },
902
+ { type: "deuteranopia", label: "Deuteranopia", description: "Green-blind - most common form of colour blindness", prevalence: "5% of males" },
903
+ { type: "tritanopia", label: "Tritanopia", description: "Blue-blind - difficulty distinguishing blue and yellow", prevalence: "0.01% of population" },
904
+ { type: "achromatopsia", label: "Achromatopsia", description: "Total colour blindness - sees only shades of grey", prevalence: "0.003% of population" }
905
+ ];
906
+ function hexToRGB(hex) {
907
+ const r = parseInt(hex.slice(1, 3), 16);
908
+ const g = parseInt(hex.slice(3, 5), 16);
909
+ const b = parseInt(hex.slice(5, 7), 16);
910
+ return [r, g, b];
911
+ }
912
+ function rgbToHex(r, g, b) {
913
+ const clamp = (v) => Math.max(0, Math.min(255, Math.round(v)));
914
+ return `#${clamp(r).toString(16).padStart(2, "0")}${clamp(g).toString(16).padStart(2, "0")}${clamp(b).toString(16).padStart(2, "0")}`;
915
+ }
916
+ var MATRICES = {
917
+ protanopia: [
918
+ [0.567, 0.433, 0],
919
+ [0.558, 0.442, 0],
920
+ [0, 0.242, 0.758]
921
+ ],
922
+ deuteranopia: [
923
+ [0.625, 0.375, 0],
924
+ [0.7, 0.3, 0],
925
+ [0, 0.3, 0.7]
926
+ ],
927
+ tritanopia: [
928
+ [0.95, 0.05, 0],
929
+ [0, 0.433, 0.567],
930
+ [0, 0.475, 0.525]
931
+ ]
932
+ };
933
+ function applyMatrix(rgb, matrix) {
934
+ return [
935
+ rgb[0] * matrix[0][0] + rgb[1] * matrix[0][1] + rgb[2] * matrix[0][2],
936
+ rgb[0] * matrix[1][0] + rgb[1] * matrix[1][1] + rgb[2] * matrix[1][2],
937
+ rgb[0] * matrix[2][0] + rgb[1] * matrix[2][1] + rgb[2] * matrix[2][2]
938
+ ];
939
+ }
940
+ function simulateColourBlindness(hex, type) {
941
+ if (!hex || !hex.startsWith("#") || hex.length < 7) return hex;
942
+ const rgb = hexToRGB(hex);
943
+ if (type === "achromatopsia") {
944
+ const grey = Math.round(0.299 * rgb[0] + 0.587 * rgb[1] + 0.114 * rgb[2]);
945
+ return rgbToHex(grey, grey, grey);
946
+ }
947
+ const transformed = applyMatrix(rgb, MATRICES[type]);
948
+ return rgbToHex(transformed[0], transformed[1], transformed[2]);
949
+ }
950
+ function simulateThemeColourBlindness(theme, type) {
951
+ const result = {};
952
+ for (const [key, value] of Object.entries(theme)) {
953
+ if (value.startsWith("#") && value.length >= 7) {
954
+ result[key] = simulateColourBlindness(value, type);
955
+ } else {
956
+ result[key] = value;
957
+ }
958
+ }
959
+ return result;
960
+ }
961
+
962
+ // src/theme-snapshots.ts
963
+ var STORAGE_PREFIX = "snagtag-ds-snapshots";
964
+ var MAX_SNAPSHOTS = 50;
965
+ function storageKey(tenantId) {
966
+ return `${STORAGE_PREFIX}:${tenantId}`;
967
+ }
968
+ function generateId() {
969
+ return `snap-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 5)}`;
970
+ }
971
+ function countDiffs(a, b) {
972
+ const allKeys = /* @__PURE__ */ new Set([...Object.keys(a), ...Object.keys(b)]);
973
+ let count = 0;
974
+ for (const key of allKeys) {
975
+ if ((a[key] ?? "") !== (b[key] ?? "")) count++;
976
+ }
977
+ return count;
978
+ }
979
+ function themesAreEqual(a, b) {
980
+ return countDiffs(a, b) === 0;
981
+ }
982
+ function readSnapshots(tenantId) {
983
+ try {
984
+ const raw = localStorage.getItem(storageKey(tenantId));
985
+ if (!raw) return [];
986
+ return JSON.parse(raw);
987
+ } catch {
988
+ return [];
989
+ }
990
+ }
991
+ function writeSnapshots(tenantId, snapshots) {
992
+ while (snapshots.length > MAX_SNAPSHOTS) {
993
+ const firstNonOpening = snapshots.findIndex((s, i) => i > 0 && s.label !== "opening");
994
+ if (firstNonOpening > 0) {
995
+ snapshots.splice(firstNonOpening, 1);
996
+ } else {
997
+ snapshots.shift();
998
+ }
999
+ }
1000
+ try {
1001
+ localStorage.setItem(storageKey(tenantId), JSON.stringify(snapshots));
1002
+ } catch {
1003
+ const half = Math.floor(snapshots.length / 2);
1004
+ snapshots.splice(1, half);
1005
+ try {
1006
+ localStorage.setItem(storageKey(tenantId), JSON.stringify(snapshots));
1007
+ } catch {
1008
+ }
1009
+ }
1010
+ }
1011
+ function captureSnapshot(tenantId, values, label) {
1012
+ const snapshots = readSnapshots(tenantId);
1013
+ if (label === "auto" && snapshots.length > 0) {
1014
+ const last = snapshots[snapshots.length - 1];
1015
+ if (themesAreEqual(last.values, values)) return null;
1016
+ }
1017
+ const previous = snapshots.length > 0 ? snapshots[snapshots.length - 1].values : {};
1018
+ const snapshot = {
1019
+ id: generateId(),
1020
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1021
+ label,
1022
+ values: { ...values },
1023
+ changedKeysFromPrevious: countDiffs(previous, values)
1024
+ };
1025
+ snapshots.push(snapshot);
1026
+ writeSnapshots(tenantId, snapshots);
1027
+ return snapshot;
1028
+ }
1029
+ function getSnapshot(tenantId, snapshotId) {
1030
+ const snapshots = readSnapshots(tenantId);
1031
+ return snapshots.find((s) => s.id === snapshotId) ?? null;
1032
+ }
1033
+ function clearSnapshots(tenantId) {
1034
+ try {
1035
+ localStorage.removeItem(storageKey(tenantId));
1036
+ } catch {
1037
+ }
1038
+ }
1039
+ function getOpeningSnapshot(tenantId) {
1040
+ const snapshots = readSnapshots(tenantId);
1041
+ return snapshots.find((s) => s.label === "opening") ?? null;
1042
+ }
1043
+
1044
+ // src/font-pairings.ts
1045
+ var FONT_PAIRINGS = [
1046
+ {
1047
+ display: "Georgia, serif",
1048
+ body: "system-ui, -apple-system, sans-serif",
1049
+ label: "Classic editorial",
1050
+ description: "Georgia headings with system sans body - a timeless pairing that balances warmth with clarity.",
1051
+ category: "mixed"
1052
+ },
1053
+ {
1054
+ display: "system-ui, -apple-system, sans-serif",
1055
+ body: "system-ui, -apple-system, sans-serif",
1056
+ label: "System native",
1057
+ description: "Uses the operating system font throughout - fastest loading and feels native on every device.",
1058
+ category: "sans"
1059
+ },
1060
+ {
1061
+ display: '"Segoe UI", Tahoma, Geneva, sans-serif',
1062
+ body: '"Segoe UI", Tahoma, Geneva, sans-serif',
1063
+ label: "Segoe modern",
1064
+ description: "Clean, modern look popular in business applications. Excellent readability at all sizes.",
1065
+ category: "sans"
1066
+ },
1067
+ {
1068
+ display: '"Trebuchet MS", "Lucida Sans", sans-serif',
1069
+ body: "Verdana, Geneva, sans-serif",
1070
+ label: "Friendly sans",
1071
+ description: "Approachable and informal - works well for consumer-facing products.",
1072
+ category: "sans"
1073
+ },
1074
+ {
1075
+ display: '"Palatino Linotype", "Book Antiqua", Palatino, serif',
1076
+ body: '"Lucida Sans", "Lucida Grande", sans-serif',
1077
+ label: "Elegant contrast",
1078
+ description: "Serif headings with sans-serif body - classic typographic hierarchy with strong personality.",
1079
+ category: "mixed"
1080
+ },
1081
+ {
1082
+ display: 'Cambria, "Hoefler Text", serif',
1083
+ body: 'Calibri, "Gill Sans", sans-serif',
1084
+ label: "Office classic",
1085
+ description: "Professional and readable - familiar from office documents, well-kerned at all sizes.",
1086
+ category: "mixed"
1087
+ },
1088
+ {
1089
+ display: '"Helvetica Neue", Helvetica, Arial, sans-serif',
1090
+ body: '"Helvetica Neue", Helvetica, Arial, sans-serif',
1091
+ label: "Swiss minimal",
1092
+ description: "Clean Helvetica throughout - a design industry standard for neutral, professional UIs.",
1093
+ category: "sans"
1094
+ },
1095
+ {
1096
+ display: 'Impact, "Arial Black", sans-serif',
1097
+ body: 'Arial, "Helvetica Neue", sans-serif',
1098
+ label: "Bold statement",
1099
+ description: "High-impact headings with clean body text - ideal for dashboards and marketing.",
1100
+ category: "sans"
1101
+ },
1102
+ {
1103
+ display: '"Times New Roman", Times, serif',
1104
+ body: "Georgia, serif",
1105
+ label: "Traditional serif",
1106
+ description: "All-serif pairing with strong heritage - suits formal, document-heavy applications.",
1107
+ category: "serif"
1108
+ },
1109
+ {
1110
+ display: "Verdana, Geneva, sans-serif",
1111
+ body: "Georgia, serif",
1112
+ label: "Screen optimised",
1113
+ description: "Both fonts designed for screen readability - excellent at small sizes and low resolutions.",
1114
+ category: "mixed"
1115
+ }
1116
+ ];
1117
+ function relevanceScore(pairing, currentFont) {
1118
+ const current = currentFont.toLowerCase();
1119
+ const display = pairing.display.toLowerCase();
1120
+ const body = pairing.body.toLowerCase();
1121
+ if (display.includes(current) || body.includes(current)) return 0;
1122
+ const currentIsSerif = current.includes("serif") && !current.includes("sans-serif");
1123
+ const currentIsSans = current.includes("sans") || current.includes("system-ui");
1124
+ if (pairing.category === "mixed") return 1;
1125
+ if (currentIsSerif && pairing.category === "serif") return 2;
1126
+ if (currentIsSans && pairing.category === "sans") return 2;
1127
+ return 3;
1128
+ }
1129
+ function suggestPairings(currentFont) {
1130
+ return [...FONT_PAIRINGS].sort(
1131
+ (a, b) => relevanceScore(a, currentFont) - relevanceScore(b, currentFont)
1132
+ );
1133
+ }
1134
+
1135
+ // src/type-scale.ts
1136
+ var SCALE_RATIOS = {
1137
+ "minor-third": { value: 1.2, label: "Minor Third", description: "Subtle, compact - good for data-heavy UIs" },
1138
+ "major-third": { value: 1.25, label: "Major Third", description: "Balanced, readable - good default" },
1139
+ "perfect-fourth": { value: 1.333, label: "Perfect Fourth", description: "Clear hierarchy - good for content sites" },
1140
+ "augmented-fourth": { value: 1.414, label: "Augmented Fourth", description: "Strong hierarchy - editorial feel" },
1141
+ "perfect-fifth": { value: 1.5, label: "Perfect Fifth", description: "Dramatic hierarchy - display/portfolio" },
1142
+ "golden-ratio": { value: 1.618, label: "Golden Ratio", description: "Maximum contrast - hero/landing pages" }
1143
+ };
1144
+ var LEVEL_DEFS = [
1145
+ { name: "h1", step: 4, lineHeight: 1.15, weight: 700 },
1146
+ { name: "h2", step: 3, lineHeight: 1.2, weight: 600 },
1147
+ { name: "h3", step: 2, lineHeight: 1.25, weight: 600 },
1148
+ { name: "h4", step: 1, lineHeight: 1.3, weight: 600 },
1149
+ { name: "body-lg", step: 0.5, lineHeight: 1.5, weight: 400 },
1150
+ { name: "body-md", step: 0, lineHeight: 1.5, weight: 400 },
1151
+ { name: "body-sm", step: -1, lineHeight: 1.5, weight: 400 },
1152
+ { name: "caption", step: -2, lineHeight: 1.4, weight: 400 }
1153
+ ];
1154
+ function roundTo(value, decimals) {
1155
+ const factor = 10 ** decimals;
1156
+ return Math.round(value * factor) / factor;
1157
+ }
1158
+ function pxToRem(px) {
1159
+ return `${roundTo(px / 16, 3)}rem`;
1160
+ }
1161
+ function generateTypeScale(baseSizePx, ratio) {
1162
+ const ratioValue = SCALE_RATIOS[ratio].value;
1163
+ const levels = LEVEL_DEFS.map((def) => {
1164
+ const size = roundTo(baseSizePx * Math.pow(ratioValue, def.step), 2);
1165
+ return {
1166
+ name: def.name,
1167
+ size,
1168
+ sizeRem: pxToRem(size),
1169
+ lineHeight: def.lineHeight,
1170
+ weight: def.weight
1171
+ };
1172
+ });
1173
+ return { baseSizePx, ratio, levels };
1174
+ }
1175
+ function typeScaleToCSSVars(scale) {
1176
+ const vars = {};
1177
+ for (const level of scale.levels) {
1178
+ const key = level.name;
1179
+ vars[`--font-size-${key}`] = level.sizeRem;
1180
+ vars[`--line-height-${key}`] = String(level.lineHeight);
1181
+ vars[`--font-weight-${key}`] = String(level.weight);
1182
+ }
1183
+ return vars;
1184
+ }
1185
+
1186
+ // src/bezier-utils.ts
1187
+ function parseCubicBezier(value) {
1188
+ if (!value) return null;
1189
+ const named = {
1190
+ "linear": [0, 0, 1, 1],
1191
+ "ease": [0.25, 0.1, 0.25, 1],
1192
+ "ease-in": [0.42, 0, 1, 1],
1193
+ "ease-out": [0, 0, 0.58, 1],
1194
+ "ease-in-out": [0.42, 0, 0.58, 1]
1195
+ };
1196
+ const trimmed = value.trim().toLowerCase();
1197
+ if (named[trimmed]) return named[trimmed];
1198
+ const match = trimmed.match(
1199
+ /cubic-bezier\s*\(\s*(-?[\d.]+)\s*,\s*(-?[\d.]+)\s*,\s*(-?[\d.]+)\s*,\s*(-?[\d.]+)\s*\)/
1200
+ );
1201
+ if (match) {
1202
+ const nums = [
1203
+ parseFloat(match[1]),
1204
+ parseFloat(match[2]),
1205
+ parseFloat(match[3]),
1206
+ parseFloat(match[4])
1207
+ ];
1208
+ if (nums.some(isNaN)) return null;
1209
+ return nums;
1210
+ }
1211
+ const parts = trimmed.split(",").map((s) => parseFloat(s.trim()));
1212
+ if (parts.length === 4 && parts.every((n) => !isNaN(n))) {
1213
+ return parts;
1214
+ }
1215
+ return null;
1216
+ }
1217
+ function formatCubicBezier(x1, y1, x2, y2) {
1218
+ const fmt = (n) => parseFloat(n.toFixed(2)).toString();
1219
+ return `cubic-bezier(${fmt(x1)}, ${fmt(y1)}, ${fmt(x2)}, ${fmt(y2)})`;
1220
+ }
1221
+ function sampleBezierCurve(x1, y1, x2, y2, steps = 50) {
1222
+ const points = [];
1223
+ for (let i = 0; i <= steps; i++) {
1224
+ const t = i / steps;
1225
+ const u = 1 - t;
1226
+ const x = 3 * x1 * t * u * u + 3 * x2 * t * t * u + t * t * t;
1227
+ const y = 3 * y1 * t * u * u + 3 * y2 * t * t * u + t * t * t;
1228
+ points.push({ x, y });
1229
+ }
1230
+ return points;
1231
+ }
1232
+ function clampControlPoint(value, axis) {
1233
+ if (axis === "x") {
1234
+ return Math.max(0, Math.min(1, value));
1235
+ }
1236
+ return Math.max(-0.5, Math.min(1.5, value));
1237
+ }
1238
+ function snapToGrid(value, gridSize) {
1239
+ return Math.round(value / gridSize) * gridSize;
1240
+ }
1241
+
1242
+ // src/easing-presets.ts
1243
+ var EASING_PRESETS = [
1244
+ // Basic
1245
+ {
1246
+ label: "Linear",
1247
+ value: "linear",
1248
+ description: "No acceleration or deceleration",
1249
+ category: "basic"
1250
+ },
1251
+ {
1252
+ label: "Ease",
1253
+ value: "ease",
1254
+ description: "Default browser easing",
1255
+ category: "basic"
1256
+ },
1257
+ {
1258
+ label: "Ease In",
1259
+ value: "ease-in",
1260
+ description: "Starts slow, ends fast",
1261
+ category: "basic"
1262
+ },
1263
+ {
1264
+ label: "Ease Out",
1265
+ value: "ease-out",
1266
+ description: "Starts fast, ends slow",
1267
+ category: "basic"
1268
+ },
1269
+ {
1270
+ label: "Ease In Out",
1271
+ value: "ease-in-out",
1272
+ description: "Slow start and end, fast middle",
1273
+ category: "basic"
1274
+ },
1275
+ // Material Design
1276
+ {
1277
+ label: "Standard",
1278
+ value: "cubic-bezier(0.4, 0, 0.2, 1)",
1279
+ description: "Material Design standard curve",
1280
+ category: "material"
1281
+ },
1282
+ {
1283
+ label: "Decelerate",
1284
+ value: "cubic-bezier(0, 0, 0.2, 1)",
1285
+ description: "Material Design deceleration curve",
1286
+ category: "material"
1287
+ },
1288
+ {
1289
+ label: "Accelerate",
1290
+ value: "cubic-bezier(0.4, 0, 1, 1)",
1291
+ description: "Material Design acceleration curve",
1292
+ category: "material"
1293
+ },
1294
+ // Expressive
1295
+ {
1296
+ label: "Bounce Out",
1297
+ value: "cubic-bezier(0.34, 1.56, 0.64, 1)",
1298
+ description: "Overshoots then settles",
1299
+ category: "expressive"
1300
+ },
1301
+ {
1302
+ label: "Spring",
1303
+ value: "cubic-bezier(0.5, 1.5, 0.5, 1)",
1304
+ description: "Spring-like overshoot",
1305
+ category: "expressive"
1306
+ },
1307
+ {
1308
+ label: "Snap",
1309
+ value: "cubic-bezier(0.1, 0.85, 0.15, 1)",
1310
+ description: "Quick snap into place",
1311
+ category: "expressive"
1312
+ }
1313
+ ];
1314
+ function getPresetsByCategory() {
1315
+ const groups = {
1316
+ basic: [],
1317
+ material: [],
1318
+ expressive: []
1319
+ };
1320
+ for (const preset of EASING_PRESETS) {
1321
+ groups[preset.category].push(preset);
1322
+ }
1323
+ return groups;
1324
+ }
1325
+ function findPresetByValue(value) {
1326
+ const normalised = value.trim().toLowerCase();
1327
+ return EASING_PRESETS.find((p) => p.value.toLowerCase() === normalised);
1328
+ }
1329
+
1330
+ // src/registry-overlay.ts
1331
+ function createOverlay() {
1332
+ return {
1333
+ moves: /* @__PURE__ */ new Map(),
1334
+ customCategories: [],
1335
+ extractedTokens: []
1336
+ };
1337
+ }
1338
+ function moveToken(overlay, cssVar, targetCategory) {
1339
+ const moves = new Map(overlay.moves);
1340
+ moves.set(cssVar, targetCategory);
1341
+ return { ...overlay, moves };
1342
+ }
1343
+ function bulkMoveTokens(overlay, cssVars, targetCategory) {
1344
+ const moves = new Map(overlay.moves);
1345
+ for (const cssVar of cssVars) {
1346
+ moves.set(cssVar, targetCategory);
1347
+ }
1348
+ return { ...overlay, moves };
1349
+ }
1350
+ function addCustomCategory(overlay, label) {
1351
+ if (overlay.customCategories.includes(label)) return overlay;
1352
+ if (TOKEN_REGISTRY.some((c) => c.label === label)) return overlay;
1353
+ return { ...overlay, customCategories: [...overlay.customCategories, label] };
1354
+ }
1355
+ function addExtractedToken(overlay, token) {
1356
+ return { ...overlay, extractedTokens: [...overlay.extractedTokens, token] };
1357
+ }
1358
+ function getEffectiveCategories(overlay) {
1359
+ const categoryMap = /* @__PURE__ */ new Map();
1360
+ for (const cat of TOKEN_REGISTRY) {
1361
+ categoryMap.set(cat.label, []);
1362
+ }
1363
+ for (const label of overlay.customCategories) {
1364
+ if (!categoryMap.has(label)) {
1365
+ categoryMap.set(label, []);
1366
+ }
1367
+ }
1368
+ for (const cat of TOKEN_REGISTRY) {
1369
+ for (const token of cat.tokens) {
1370
+ const targetLabel = overlay.moves.get(token.cssVar) ?? cat.label;
1371
+ if (!categoryMap.has(targetLabel)) {
1372
+ categoryMap.set(targetLabel, []);
1373
+ }
1374
+ categoryMap.get(targetLabel).push(token);
1375
+ }
1376
+ }
1377
+ for (const ext of overlay.extractedTokens) {
1378
+ if (!categoryMap.has(ext.category)) {
1379
+ categoryMap.set(ext.category, []);
1380
+ }
1381
+ const tokenDef = {
1382
+ cssVar: ext.cssVar,
1383
+ label: ext.label,
1384
+ category: "colour",
1385
+ defaultValue: ext.defaultValue,
1386
+ ...ext.referencesVar ? { references: ext.referencesVar } : {}
1387
+ };
1388
+ categoryMap.get(ext.category).push(tokenDef);
1389
+ }
1390
+ const result = [];
1391
+ for (const cat of TOKEN_REGISTRY) {
1392
+ const tokens = categoryMap.get(cat.label) ?? [];
1393
+ if (tokens.length > 0) {
1394
+ result.push({ label: cat.label, tokens });
1395
+ }
1396
+ }
1397
+ for (const label of overlay.customCategories) {
1398
+ if (TOKEN_REGISTRY.some((c) => c.label === label)) continue;
1399
+ const tokens = categoryMap.get(label) ?? [];
1400
+ if (tokens.length > 0) {
1401
+ result.push({ label, tokens });
1402
+ }
1403
+ }
1404
+ for (const label of overlay.customCategories) {
1405
+ if (!result.some((c) => c.label === label)) {
1406
+ result.push({ label, tokens: [] });
1407
+ }
1408
+ }
1409
+ return result;
1410
+ }
1411
+ function getAllCategoryLabels(overlay) {
1412
+ const labels = TOKEN_REGISTRY.map((c) => c.label);
1413
+ for (const label of overlay.customCategories) {
1414
+ if (!labels.includes(label)) {
1415
+ labels.push(label);
1416
+ }
1417
+ }
1418
+ return labels;
1419
+ }
1420
+ function getTokenCategory(overlay, cssVar) {
1421
+ const moved = overlay.moves.get(cssVar);
1422
+ if (moved) return moved;
1423
+ const extracted = overlay.extractedTokens.find((t) => t.cssVar === cssVar);
1424
+ if (extracted) return extracted.category;
1425
+ for (const cat of TOKEN_REGISTRY) {
1426
+ if (cat.tokens.some((t) => t.cssVar === cssVar)) {
1427
+ return cat.label;
1428
+ }
1429
+ }
1430
+ return void 0;
1431
+ }
1432
+
1433
+ // src/token-extraction.ts
1434
+ function validateTokenName(name) {
1435
+ if (!name) {
1436
+ return { valid: false, error: "Token name is required" };
1437
+ }
1438
+ if (!name.startsWith("--")) {
1439
+ return { valid: false, error: "Token name must start with --" };
1440
+ }
1441
+ if (name.length < 4) {
1442
+ return { valid: false, error: "Token name is too short" };
1443
+ }
1444
+ if (!/^--[a-z][a-z0-9-]*$/.test(name)) {
1445
+ return { valid: false, error: "Token name must be lowercase with hyphens only (e.g. --color-my-token)" };
1446
+ }
1447
+ if (findToken(name)) {
1448
+ return { valid: false, error: "A token with this name already exists in the registry" };
1449
+ }
1450
+ return { valid: true };
1451
+ }
1452
+ function suggestTokenName(value, category) {
1453
+ const prefix = categoryToPrefix(category);
1454
+ const suffix = valueToSuffix(value);
1455
+ return `--${prefix}-${suffix}`;
1456
+ }
1457
+ function categoryToPrefix(category) {
1458
+ const lower = category.toLowerCase();
1459
+ if (lower.includes("colour") || lower.includes("color")) return "color";
1460
+ if (lower.includes("brand")) return "color-brand";
1461
+ if (lower.includes("ink") || lower.includes("neutral")) return "color-ink";
1462
+ if (lower.includes("accent")) return "color-accent";
1463
+ if (lower.includes("button") || lower.includes("btn")) return "color-btn";
1464
+ if (lower.includes("input")) return "color-input";
1465
+ if (lower.includes("surface")) return "color-surface";
1466
+ if (lower.includes("typography")) return "font";
1467
+ if (lower.includes("spacing")) return "space";
1468
+ if (lower.includes("border")) return "border";
1469
+ if (lower.includes("shadow")) return "shadow";
1470
+ if (lower.includes("motion")) return "duration";
1471
+ return "custom";
1472
+ }
1473
+ function valueToSuffix(value) {
1474
+ const trimmed = value.trim().toLowerCase();
1475
+ if (trimmed.startsWith("#")) {
1476
+ return trimmed.slice(1, 4);
1477
+ }
1478
+ const numMatch = trimmed.match(/^([\d.]+)(rem|px|em|ms|s|%)$/);
1479
+ if (numMatch) {
1480
+ return numMatch[1].replace(".", "-") + numMatch[2];
1481
+ }
1482
+ return "custom";
1483
+ }
1484
+ function extractToken(overlay, token) {
1485
+ const allStaticLabels = TOKEN_REGISTRY.map((c) => c.label);
1486
+ let updated = overlay;
1487
+ if (!allStaticLabels.includes(token.category) && !overlay.customCategories.includes(token.category)) {
1488
+ updated = addCustomCategory(updated, token.category);
1489
+ }
1490
+ return addExtractedToken(updated, token);
1491
+ }
1492
+ function hasExtractedToken(overlay, cssVar) {
1493
+ return overlay.extractedTokens.some((t) => t.cssVar === cssVar);
1494
+ }
1495
+
1496
+ // src/reassignment-state.ts
1497
+ var MAX_UNDO2 = 20;
1498
+ function createReassignmentState() {
1499
+ return {
1500
+ overlay: createOverlay(),
1501
+ undoStack: [],
1502
+ redoStack: []
1503
+ };
1504
+ }
1505
+ function pushAction(state, action) {
1506
+ const undoStack = [...state.undoStack, action];
1507
+ if (undoStack.length > MAX_UNDO2) undoStack.shift();
1508
+ return {
1509
+ overlay: action.after,
1510
+ undoStack,
1511
+ redoStack: []
1512
+ };
1513
+ }
1514
+ function applyMove(state, cssVar, targetCategory, tokenLabel) {
1515
+ const before = state.overlay;
1516
+ const after = moveToken(before, cssVar, targetCategory);
1517
+ return pushAction(state, {
1518
+ type: "move",
1519
+ before,
1520
+ after,
1521
+ description: `Moved ${tokenLabel ?? cssVar} to ${targetCategory}`
1522
+ });
1523
+ }
1524
+ function applyBulkMove(state, cssVars, targetCategory) {
1525
+ const before = state.overlay;
1526
+ const after = bulkMoveTokens(before, cssVars, targetCategory);
1527
+ return pushAction(state, {
1528
+ type: "bulk-move",
1529
+ before,
1530
+ after,
1531
+ description: `Moved ${cssVars.length} token${cssVars.length === 1 ? "" : "s"} to ${targetCategory}`
1532
+ });
1533
+ }
1534
+ function applyExtract(state, token) {
1535
+ const before = state.overlay;
1536
+ const after = extractToken(before, token);
1537
+ return pushAction(state, {
1538
+ type: "extract",
1539
+ before,
1540
+ after,
1541
+ description: `Extracted ${token.label} (${token.cssVar})`
1542
+ });
1543
+ }
1544
+ function applyAddCategory(state, label) {
1545
+ const before = state.overlay;
1546
+ const after = addCustomCategory(before, label);
1547
+ if (after === before) return state;
1548
+ return pushAction(state, {
1549
+ type: "add-category",
1550
+ before,
1551
+ after,
1552
+ description: `Created category "${label}"`
1553
+ });
1554
+ }
1555
+ function undoReassignment(state) {
1556
+ if (state.undoStack.length === 0) return state;
1557
+ const undoStack = [...state.undoStack];
1558
+ const action = undoStack.pop();
1559
+ return {
1560
+ overlay: action.before,
1561
+ undoStack,
1562
+ redoStack: [...state.redoStack, action]
1563
+ };
1564
+ }
1565
+ function redoReassignment(state) {
1566
+ if (state.redoStack.length === 0) return state;
1567
+ const redoStack = [...state.redoStack];
1568
+ const action = redoStack.pop();
1569
+ return {
1570
+ overlay: action.after,
1571
+ undoStack: [...state.undoStack, action],
1572
+ redoStack
1573
+ };
1574
+ }
1575
+
1576
+ // src/token-usage.ts
1577
+ var usageCache = null;
1578
+ var lastScanTime = 0;
1579
+ var MIN_SCAN_INTERVAL_MS = 2e3;
1580
+ function getPropertiesForCategory(category) {
1581
+ switch (category) {
1582
+ case "colour":
1583
+ return ["color", "background-color", "border-color", "outline-color", "fill", "stroke"];
1584
+ case "typography":
1585
+ return ["font-family", "font-size", "font-weight", "line-height"];
1586
+ case "spacing":
1587
+ return ["padding", "padding-top", "padding-right", "padding-bottom", "padding-left", "margin", "margin-top", "margin-right", "margin-bottom", "margin-left", "gap"];
1588
+ case "border":
1589
+ return ["border-width", "border-radius"];
1590
+ case "shadow":
1591
+ return ["box-shadow"];
1592
+ case "motion":
1593
+ return ["transition-duration", "animation-duration"];
1594
+ }
1595
+ }
1596
+ function normaliseColour(value) {
1597
+ if (!value || value === "transparent" || value === "none") return value;
1598
+ const trimmed = value.trim().toLowerCase();
1599
+ if (trimmed.startsWith("#")) {
1600
+ const temp = document.createElement("div");
1601
+ temp.style.color = trimmed;
1602
+ document.body.appendChild(temp);
1603
+ const normalised = getComputedStyle(temp).color;
1604
+ document.body.removeChild(temp);
1605
+ return normalised;
1606
+ }
1607
+ return trimmed;
1608
+ }
1609
+ function countTokenUsage(cssVar) {
1610
+ const now = Date.now();
1611
+ if (!cssVar && usageCache && now - lastScanTime < MIN_SCAN_INTERVAL_MS) {
1612
+ return usageCache;
1613
+ }
1614
+ const tokensToCheck = cssVar ? (() => {
1615
+ const t = findToken(cssVar);
1616
+ return t ? [t] : [];
1617
+ })() : TOKEN_REGISTRY.flatMap((c) => c.tokens);
1618
+ if (tokensToCheck.length === 0) return [];
1619
+ const root = document.documentElement;
1620
+ const rootStyles = getComputedStyle(root);
1621
+ const results = /* @__PURE__ */ new Map();
1622
+ const resolvedTokens = /* @__PURE__ */ new Map();
1623
+ for (const token of tokensToCheck) {
1624
+ const resolvedValue = rootStyles.getPropertyValue(token.cssVar).trim();
1625
+ if (!resolvedValue) continue;
1626
+ const normalised = token.category === "colour" ? normaliseColour(resolvedValue) : resolvedValue;
1627
+ resolvedTokens.set(token.cssVar, {
1628
+ token,
1629
+ resolvedValue: normalised,
1630
+ props: getPropertiesForCategory(token.category)
1631
+ });
1632
+ results.set(token.cssVar, { cssVar: token.cssVar, label: token.label, count: 0, elements: [] });
1633
+ }
1634
+ const elements = document.body.querySelectorAll("*");
1635
+ for (const el of elements) {
1636
+ if (!(el instanceof HTMLElement)) continue;
1637
+ const computed = getComputedStyle(el);
1638
+ for (const [varName, { token, resolvedValue, props }] of resolvedTokens) {
1639
+ let matched = false;
1640
+ for (const prop of props) {
1641
+ let computedValue = computed.getPropertyValue(prop).trim();
1642
+ if (!computedValue) continue;
1643
+ if (token.category === "colour") {
1644
+ computedValue = computedValue.toLowerCase();
1645
+ }
1646
+ if (computedValue === resolvedValue) {
1647
+ matched = true;
1648
+ break;
1649
+ }
1650
+ }
1651
+ if (matched) {
1652
+ const result = results.get(varName);
1653
+ result.count++;
1654
+ if (result.elements.length < 5) {
1655
+ result.elements.push(el);
1656
+ }
1657
+ }
1658
+ }
1659
+ }
1660
+ const sorted = Array.from(results.values()).filter((r) => r.count > 0).sort((a, b) => b.count - a.count);
1661
+ if (!cssVar) {
1662
+ usageCache = sorted;
1663
+ lastScanTime = now;
1664
+ }
1665
+ return sorted;
1666
+ }
1667
+ function getTokenUsageCount(cssVar) {
1668
+ if (usageCache) {
1669
+ const cached = usageCache.find((r) => r.cssVar === cssVar);
1670
+ if (cached) return cached.count;
1671
+ }
1672
+ const results = countTokenUsage(cssVar);
1673
+ return results.length > 0 ? results[0].count : 0;
1674
+ }
1675
+ function clearUsageCache() {
1676
+ usageCache = null;
1677
+ lastScanTime = 0;
1678
+ }
1679
+
1680
+ // src/unused-tokens.ts
1681
+ function scanUsedProperties() {
1682
+ const used = /* @__PURE__ */ new Set();
1683
+ try {
1684
+ for (const sheet of document.styleSheets) {
1685
+ try {
1686
+ for (const rule of sheet.cssRules) {
1687
+ const text = rule.cssText;
1688
+ const varMatches = text.matchAll(/var\((--[^,)]+)/g);
1689
+ for (const match of varMatches) {
1690
+ used.add(match[1]);
1691
+ }
1692
+ }
1693
+ } catch {
1694
+ }
1695
+ }
1696
+ } catch {
1697
+ }
1698
+ const allElements = document.querySelectorAll("*");
1699
+ for (const el of allElements) {
1700
+ const style = el.style?.cssText;
1701
+ if (style) {
1702
+ const varMatches = style.matchAll(/var\((--[^,)]+)/g);
1703
+ for (const match of varMatches) {
1704
+ used.add(match[1]);
1705
+ }
1706
+ }
1707
+ }
1708
+ return used;
1709
+ }
1710
+ function findChainDependencies() {
1711
+ const deps = /* @__PURE__ */ new Set();
1712
+ const allTokens = TOKEN_REGISTRY.flatMap((c) => c.tokens);
1713
+ for (const token of allTokens) {
1714
+ if (token.references) {
1715
+ deps.add(token.references);
1716
+ }
1717
+ }
1718
+ return deps;
1719
+ }
1720
+ function findUnusedTokens() {
1721
+ const usedProperties = scanUsedProperties();
1722
+ const chainDeps = findChainDependencies();
1723
+ const allTokens = TOKEN_REGISTRY.flatMap((c) => c.tokens);
1724
+ const unused = [];
1725
+ for (const token of allTokens) {
1726
+ if (!usedProperties.has(token.cssVar)) {
1727
+ unused.push({
1728
+ token,
1729
+ isChainDependency: chainDeps.has(token.cssVar)
1730
+ });
1731
+ }
1732
+ }
1733
+ return unused;
1734
+ }
1735
+ function getTokenUsageStats() {
1736
+ const usedProperties = scanUsedProperties();
1737
+ const chainDeps = findChainDependencies();
1738
+ const allTokens = TOKEN_REGISTRY.flatMap((c) => c.tokens);
1739
+ let used = 0;
1740
+ let chainOnly = 0;
1741
+ for (const token of allTokens) {
1742
+ if (usedProperties.has(token.cssVar)) {
1743
+ used++;
1744
+ } else if (chainDeps.has(token.cssVar)) {
1745
+ chainOnly++;
1746
+ }
1747
+ }
1748
+ return {
1749
+ total: allTokens.length,
1750
+ used,
1751
+ unused: allTokens.length - used,
1752
+ chainDependencies: chainOnly,
1753
+ usageRate: Math.round(used / allTokens.length * 100)
1754
+ };
1755
+ }
1756
+
1757
+ // src/dark-mode.ts
1758
+ function hexToHsl(hex) {
1759
+ const r = parseInt(hex.slice(1, 3), 16) / 255;
1760
+ const g = parseInt(hex.slice(3, 5), 16) / 255;
1761
+ const b = parseInt(hex.slice(5, 7), 16) / 255;
1762
+ const max = Math.max(r, g, b);
1763
+ const min = Math.min(r, g, b);
1764
+ const l = (max + min) / 2;
1765
+ if (max === min) return { h: 0, s: 0, l: Math.round(l * 100) };
1766
+ const d = max - min;
1767
+ const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
1768
+ let h = 0;
1769
+ if (max === r) h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
1770
+ else if (max === g) h = ((b - r) / d + 2) / 6;
1771
+ else h = ((r - g) / d + 4) / 6;
1772
+ return { h: Math.round(h * 360), s: Math.round(s * 100), l: Math.round(l * 100) };
1773
+ }
1774
+ function hslToHex2(h, s, l) {
1775
+ const sn = s / 100;
1776
+ const ln = l / 100;
1777
+ const c = (1 - Math.abs(2 * ln - 1)) * sn;
1778
+ const x = c * (1 - Math.abs(h / 60 % 2 - 1));
1779
+ const m = ln - c / 2;
1780
+ let r = 0, g = 0, b = 0;
1781
+ if (h < 60) {
1782
+ r = c;
1783
+ g = x;
1784
+ } else if (h < 120) {
1785
+ r = x;
1786
+ g = c;
1787
+ } else if (h < 180) {
1788
+ g = c;
1789
+ b = x;
1790
+ } else if (h < 240) {
1791
+ g = x;
1792
+ b = c;
1793
+ } else if (h < 300) {
1794
+ r = x;
1795
+ b = c;
1796
+ } else {
1797
+ r = c;
1798
+ b = x;
1799
+ }
1800
+ const toHex = (v) => Math.round((v + m) * 255).toString(16).padStart(2, "0");
1801
+ return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
1802
+ }
1803
+ function invertLightness(hex) {
1804
+ const { h, s, l } = hexToHsl(hex);
1805
+ return hslToHex2(h, s, 100 - l);
1806
+ }
1807
+ function adjustSemanticForDark(hex, _semanticType) {
1808
+ const { h, s, l } = hexToHsl(hex);
1809
+ const newL = l < 50 ? Math.min(l + 30, 75) : Math.min(l + 10, 80);
1810
+ const newS = Math.max(s - 10, 20);
1811
+ return hslToHex2(h, newS, newL);
1812
+ }
1813
+ var SURFACE_VARS = {
1814
+ "--color-ink-50": "#f8fafc",
1815
+ "--color-ink-100": "#f1f5f9",
1816
+ "--color-sidebar-bg": "#ffffff",
1817
+ "--color-sidebar-hover": "#f1f5f9"
1818
+ };
1819
+ var TEXT_VARS = {
1820
+ "--color-ink-900": "#0f172a",
1821
+ "--color-ink-800": "#1e293b",
1822
+ "--color-ink-700": "#334155",
1823
+ "--color-ink-600": "#475569",
1824
+ "--color-ink-500": "#64748b",
1825
+ "--color-sidebar-text": "#475569"
1826
+ };
1827
+ var BORDER_VARS = {
1828
+ "--color-ink-200": "#e2e8f0",
1829
+ "--color-ink-300": "#cbd5e1",
1830
+ "--color-sidebar-border": "#e2e8f0"
1831
+ };
1832
+ var SEMANTIC_VARS = {
1833
+ "--color-brand-500": "#3b82f6",
1834
+ "--color-brand-600": "#2563eb",
1835
+ "--color-brand-700": "#1d4ed8",
1836
+ "--color-accent": "#f59e0b",
1837
+ "--color-accent-hover": "#d97706"
1838
+ };
1839
+ function generateDarkTheme(lightTheme) {
1840
+ const result = {};
1841
+ const theme = lightTheme ?? {};
1842
+ for (const [cssVar, defaultHex] of Object.entries(SURFACE_VARS)) {
1843
+ const hex = theme[cssVar] || defaultHex;
1844
+ result[cssVar] = invertLightness(hex);
1845
+ }
1846
+ for (const [cssVar, defaultHex] of Object.entries(TEXT_VARS)) {
1847
+ const hex = theme[cssVar] || defaultHex;
1848
+ result[cssVar] = invertLightness(hex);
1849
+ }
1850
+ for (const [cssVar, defaultHex] of Object.entries(BORDER_VARS)) {
1851
+ const hex = theme[cssVar] || defaultHex;
1852
+ const { h, s } = hexToHsl(hex);
1853
+ result[cssVar] = hslToHex2(h, Math.max(s - 5, 0), 25);
1854
+ }
1855
+ for (const [cssVar, defaultHex] of Object.entries(SEMANTIC_VARS)) {
1856
+ const hex = theme[cssVar] || defaultHex;
1857
+ result[cssVar] = adjustSemanticForDark(hex);
1858
+ }
1859
+ result["--color-brand-50"] = "#0c1929";
1860
+ result["--color-brand-100"] = "#132744";
1861
+ result["--color-brand-200"] = "#1a3a5c";
1862
+ result["--color-brand-300"] = "#2a5a8a";
1863
+ result["--color-brand-800"] = "#93bbf0";
1864
+ result["--color-brand-900"] = "#bdd5f7";
1865
+ result["--color-brand-950"] = "#dbeafe";
1866
+ result["--color-ink-400"] = "#64748b";
1867
+ result["--color-ink-950"] = "#f8fafc";
1868
+ result["--color-sidebar-active-bg"] = "#1a3a5c";
1869
+ result["--color-sidebar-active-text"] = "#93bbf0";
1870
+ result["--color-accent-light"] = "#422006";
1871
+ return result;
1872
+ }
1873
+ function isDarkMode() {
1874
+ return document.documentElement.classList.contains("dark");
1875
+ }
1876
+ function setDarkMode(enabled) {
1877
+ const root = document.documentElement;
1878
+ if (enabled) {
1879
+ root.classList.add("dark");
1880
+ root.classList.remove("light");
1881
+ } else {
1882
+ root.classList.remove("dark");
1883
+ root.classList.add("light");
1884
+ }
1885
+ }
1886
+ function systemPrefersDark() {
1887
+ if (typeof window === "undefined") return false;
1888
+ return window.matchMedia("(prefers-color-scheme: dark)").matches;
1889
+ }
1890
+ function resolveMode(mode) {
1891
+ if (mode === "system") return systemPrefersDark() ? "dark" : "light";
1892
+ return mode;
1893
+ }
1894
+ function applyDarkModeToDOM(mode) {
1895
+ const resolved = resolveMode(mode);
1896
+ setDarkMode(resolved === "dark");
1897
+ }
1898
+
1899
+ // src/surface-overrides.ts
1900
+ var SURFACE_DEFINITIONS = [
1901
+ {
1902
+ id: "default",
1903
+ name: "Default",
1904
+ description: "Standard display - no overrides applied.",
1905
+ overrides: {},
1906
+ cssClass: ""
1907
+ },
1908
+ {
1909
+ id: "kiosk",
1910
+ name: "Kiosk",
1911
+ description: "Larger text, high contrast for touchscreen and distance viewing.",
1912
+ overrides: {
1913
+ // Font size multipliers applied via CSS custom properties
1914
+ "--surface-font-scale": "1.25",
1915
+ "--surface-min-tap-target": "48px",
1916
+ "--surface-border-width": "2px",
1917
+ // High contrast overrides
1918
+ "--color-ink-900": "#000000",
1919
+ "--color-ink-800": "#0a0a0a",
1920
+ "--color-ink-50": "#ffffff",
1921
+ "--color-ink-100": "#fafafa"
1922
+ },
1923
+ cssClass: "surface-kiosk"
1924
+ },
1925
+ {
1926
+ id: "print",
1927
+ name: "Print",
1928
+ description: "No backgrounds, black text, thin borders - optimised for printing.",
1929
+ overrides: {
1930
+ "--color-ink-50": "#ffffff",
1931
+ "--color-ink-100": "#ffffff",
1932
+ "--color-ink-200": "#cccccc",
1933
+ "--color-ink-300": "#cccccc",
1934
+ "--color-ink-900": "#000000",
1935
+ "--color-ink-800": "#111111",
1936
+ "--color-ink-700": "#222222",
1937
+ "--color-ink-600": "#333333",
1938
+ "--color-sidebar-bg": "#ffffff",
1939
+ "--color-sidebar-text": "#000000",
1940
+ "--surface-shadow": "none",
1941
+ "--surface-border-width": "1px"
1942
+ },
1943
+ cssClass: "surface-print"
1944
+ },
1945
+ {
1946
+ id: "outdoor",
1947
+ name: "Outdoor",
1948
+ description: "Maximum contrast and saturation for visibility in sunlight.",
1949
+ overrides: {
1950
+ "--color-ink-900": "#000000",
1951
+ "--color-ink-800": "#050505",
1952
+ "--color-ink-50": "#ffffff",
1953
+ "--color-ink-100": "#fcfcfc",
1954
+ "--surface-font-weight-adjust": "100",
1955
+ "--surface-saturation-boost": "1.2"
1956
+ },
1957
+ cssClass: "surface-outdoor"
1958
+ },
1959
+ {
1960
+ id: "low-light",
1961
+ name: "Low Light",
1962
+ description: "Reduced brightness and warmer tones for low-light environments.",
1963
+ overrides: {
1964
+ "--color-ink-50": "#f5f0e8",
1965
+ "--color-ink-100": "#ebe5d9",
1966
+ "--color-ink-900": "#2a2520",
1967
+ "--color-ink-800": "#3d362e",
1968
+ "--surface-brightness": "0.85",
1969
+ "--surface-warmth": "1.1"
1970
+ },
1971
+ cssClass: "surface-low-light"
1972
+ }
1973
+ ];
1974
+ function getSurfaceOverrides(surfaceType) {
1975
+ const def = SURFACE_DEFINITIONS.find((s) => s.id === surfaceType);
1976
+ return def?.overrides ?? {};
1977
+ }
1978
+ function getSurfaceDefinition(surfaceType) {
1979
+ return SURFACE_DEFINITIONS.find((s) => s.id === surfaceType);
1980
+ }
1981
+ function applySurface(surfaceType) {
1982
+ const root = document.documentElement;
1983
+ clearSurface();
1984
+ if (surfaceType === "default") return;
1985
+ const def = getSurfaceDefinition(surfaceType);
1986
+ if (!def) return;
1987
+ if (def.cssClass) {
1988
+ root.classList.add(def.cssClass);
1989
+ }
1990
+ for (const [prop, value] of Object.entries(def.overrides)) {
1991
+ root.style.setProperty(prop, value);
1992
+ }
1993
+ }
1994
+ function clearSurface() {
1995
+ const root = document.documentElement;
1996
+ for (const def of SURFACE_DEFINITIONS) {
1997
+ if (def.cssClass) {
1998
+ root.classList.remove(def.cssClass);
1999
+ }
2000
+ }
2001
+ for (const def of SURFACE_DEFINITIONS) {
2002
+ for (const prop of Object.keys(def.overrides)) {
2003
+ root.style.removeProperty(prop);
2004
+ }
2005
+ }
2006
+ }
2007
+ function getActiveSurface() {
2008
+ const root = document.documentElement;
2009
+ for (const def of SURFACE_DEFINITIONS) {
2010
+ if (def.cssClass && root.classList.contains(def.cssClass)) {
2011
+ return def.id;
2012
+ }
2013
+ }
2014
+ return "default";
2015
+ }
2016
+ function applySurfaceToDOM(surface) {
2017
+ applySurface(surface);
2018
+ }
2019
+
2020
+ // src/density-modes.ts
2021
+ var DENSITY_CONFIGS = {
2022
+ compact: {
2023
+ mode: "compact",
2024
+ label: "Compact",
2025
+ description: "Tighter spacing - more content visible at once.",
2026
+ spacingMultiplier: 0.75,
2027
+ fontSizeAdjust: 0.95,
2028
+ lineHeightAdjust: 0.95
2029
+ },
2030
+ default: {
2031
+ mode: "default",
2032
+ label: "Default",
2033
+ description: "Standard spacing for everyday use.",
2034
+ spacingMultiplier: 1,
2035
+ fontSizeAdjust: 1,
2036
+ lineHeightAdjust: 1
2037
+ },
2038
+ comfortable: {
2039
+ mode: "comfortable",
2040
+ label: "Comfortable",
2041
+ description: "More breathing room between elements.",
2042
+ spacingMultiplier: 1.25,
2043
+ fontSizeAdjust: 1.05,
2044
+ lineHeightAdjust: 1.1
2045
+ }
2046
+ };
2047
+ var SPACING_TOKENS2 = {
2048
+ "--spacing-0": 0,
2049
+ "--spacing-0-5": 0.125,
2050
+ "--spacing-1": 0.25,
2051
+ "--spacing-1-5": 0.375,
2052
+ "--spacing-2": 0.5,
2053
+ "--spacing-2-5": 0.625,
2054
+ "--spacing-3": 0.75,
2055
+ "--spacing-3-5": 0.875,
2056
+ "--spacing-4": 1,
2057
+ "--spacing-5": 1.25,
2058
+ "--spacing-6": 1.5,
2059
+ "--spacing-7": 1.75,
2060
+ "--spacing-8": 2,
2061
+ "--spacing-9": 2.25,
2062
+ "--spacing-10": 2.5,
2063
+ "--spacing-11": 2.75,
2064
+ "--spacing-12": 3,
2065
+ "--spacing-14": 3.5,
2066
+ "--spacing-16": 4,
2067
+ "--spacing-20": 5,
2068
+ "--spacing-24": 6,
2069
+ "--spacing-28": 7,
2070
+ "--spacing-32": 8
2071
+ };
2072
+ function getDensityCSS(mode) {
2073
+ const config = DENSITY_CONFIGS[mode];
2074
+ const result = {};
2075
+ for (const [token, baseRem] of Object.entries(SPACING_TOKENS2)) {
2076
+ const adjusted = baseRem * config.spacingMultiplier;
2077
+ result[token] = `${adjusted}rem`;
2078
+ }
2079
+ result["--density-font-scale"] = String(config.fontSizeAdjust);
2080
+ result["--density-line-height-scale"] = String(config.lineHeightAdjust);
2081
+ result["--density-spacing-multiplier"] = String(config.spacingMultiplier);
2082
+ return result;
2083
+ }
2084
+ function applyDensity(mode) {
2085
+ const root = document.documentElement;
2086
+ root.classList.remove("density-compact", "density-comfortable");
2087
+ if (mode === "compact") root.classList.add("density-compact");
2088
+ if (mode === "comfortable") root.classList.add("density-comfortable");
2089
+ if (mode === "default") {
2090
+ for (const token of Object.keys(SPACING_TOKENS2)) {
2091
+ root.style.removeProperty(token);
2092
+ }
2093
+ root.style.removeProperty("--density-font-scale");
2094
+ root.style.removeProperty("--density-line-height-scale");
2095
+ root.style.removeProperty("--density-spacing-multiplier");
2096
+ } else {
2097
+ const css = getDensityCSS(mode);
2098
+ for (const [prop, value] of Object.entries(css)) {
2099
+ root.style.setProperty(prop, value);
2100
+ }
2101
+ }
2102
+ }
2103
+ function getActiveDensity() {
2104
+ const root = document.documentElement;
2105
+ if (root.classList.contains("density-compact")) return "compact";
2106
+ if (root.classList.contains("density-comfortable")) return "comfortable";
2107
+ return "default";
2108
+ }
2109
+ function applyDensityToDOM(density) {
2110
+ applyDensity(density);
2111
+ }
2112
+
2113
+ // src/theme-presets.ts
2114
+ function makePreset(base) {
2115
+ return {
2116
+ id: base.id,
2117
+ name: base.name,
2118
+ description: base.description,
2119
+ category: base.category,
2120
+ preview: base.preview,
2121
+ values: { ...THEME_DEFAULTS, ...base.overrides }
2122
+ };
2123
+ }
2124
+ var BUILT_IN_PRESETS = [
2125
+ makePreset({
2126
+ id: "professional-blue",
2127
+ name: "Professional Blue",
2128
+ description: "Corporate and trustworthy with blue primary and grey surfaces",
2129
+ category: "professional",
2130
+ preview: { primary: "#2563eb", surface: "#f8fafc", text: "#0f172a", accent: "#f59e0b" },
2131
+ overrides: {}
2132
+ }),
2133
+ makePreset({
2134
+ id: "warm-earth",
2135
+ name: "Warm Earth",
2136
+ description: "Terracotta and amber tones with cream surfaces for an organic feel",
2137
+ category: "warm",
2138
+ preview: { primary: "#c2410c", surface: "#fefce8", text: "#431407", accent: "#d97706" },
2139
+ overrides: {
2140
+ primaryColor: "#c2410c",
2141
+ primaryHoverColor: "#9a3412",
2142
+ primaryLightColor: "#ffedd5",
2143
+ secondaryColor: "#78716c",
2144
+ accentColor: "#d97706",
2145
+ sidebarBg: "#fef3c7",
2146
+ sidebarText: "#78716c",
2147
+ surfacePageColor: "#fefce8",
2148
+ surfaceCardColor: "#fffbeb",
2149
+ surfaceModalColor: "#ffffff",
2150
+ focusRingColor: "#c2410c",
2151
+ linkColor: "#c2410c",
2152
+ linkHoverColor: "#9a3412",
2153
+ btnPrimaryBg: "#c2410c",
2154
+ btnPrimaryText: "#ffffff",
2155
+ btnPrimaryHover: "#9a3412",
2156
+ btnPrimaryDisabled: "#fdba74",
2157
+ btnSecondaryBg: "#fffbeb",
2158
+ btnSecondaryText: "#431407",
2159
+ btnSecondaryBorder: "#fed7aa",
2160
+ btnSecondaryHover: "#fef3c7",
2161
+ btnGhostText: "#78716c",
2162
+ btnGhostHoverBg: "#fef3c7",
2163
+ btnGhostHoverText: "#431407",
2164
+ inputBg: "#fffbeb",
2165
+ inputText: "#431407",
2166
+ inputBorder: "#fed7aa",
2167
+ inputFocusBorder: "#c2410c",
2168
+ inputFocusRing: "#c2410c",
2169
+ cardBg: "#fffbeb",
2170
+ cardBorder: "#fed7aa",
2171
+ tableHeaderBg: "#fef3c7",
2172
+ tableHeaderText: "#431407",
2173
+ tableBorder: "#fed7aa",
2174
+ tableRowHover: "#fef3c7",
2175
+ tableRowSelected: "#ffedd5",
2176
+ tableStripe: "#fef3c7"
2177
+ }
2178
+ }),
2179
+ makePreset({
2180
+ id: "festival-bright",
2181
+ name: "Festival Bright",
2182
+ description: "High-energy saturated colours with a festival vibe",
2183
+ category: "vibrant",
2184
+ preview: { primary: "#7c3aed", surface: "#faf5ff", text: "#1e1b4b", accent: "#f43f5e" },
2185
+ overrides: {
2186
+ primaryColor: "#7c3aed",
2187
+ primaryHoverColor: "#6d28d9",
2188
+ primaryLightColor: "#ede9fe",
2189
+ secondaryColor: "#6366f1",
2190
+ accentColor: "#f43f5e",
2191
+ sidebarBg: "#faf5ff",
2192
+ sidebarText: "#6366f1",
2193
+ surfacePageColor: "#faf5ff",
2194
+ surfaceCardColor: "#ffffff",
2195
+ surfaceModalColor: "#ffffff",
2196
+ focusRingColor: "#7c3aed",
2197
+ linkColor: "#7c3aed",
2198
+ linkHoverColor: "#6d28d9",
2199
+ btnPrimaryBg: "#7c3aed",
2200
+ btnPrimaryText: "#ffffff",
2201
+ btnPrimaryHover: "#6d28d9",
2202
+ btnPrimaryDisabled: "#c4b5fd",
2203
+ btnSecondaryBg: "#ffffff",
2204
+ btnSecondaryText: "#4c1d95",
2205
+ btnSecondaryBorder: "#c4b5fd",
2206
+ btnSecondaryHover: "#ede9fe",
2207
+ btnGhostText: "#6366f1",
2208
+ btnGhostHoverBg: "#ede9fe",
2209
+ btnGhostHoverText: "#4c1d95",
2210
+ inputBg: "#ffffff",
2211
+ inputText: "#1e1b4b",
2212
+ inputBorder: "#c4b5fd",
2213
+ inputFocusBorder: "#7c3aed",
2214
+ inputFocusRing: "#7c3aed",
2215
+ cardBg: "#ffffff",
2216
+ cardBorder: "#e9d5ff",
2217
+ tableHeaderBg: "#ede9fe",
2218
+ tableHeaderText: "#4c1d95",
2219
+ tableBorder: "#e9d5ff",
2220
+ tableRowHover: "#ede9fe",
2221
+ tableRowSelected: "#f5f3ff",
2222
+ tableStripe: "#faf5ff",
2223
+ badgeInfoBg: "#ede9fe",
2224
+ badgeInfoText: "#6d28d9"
2225
+ }
2226
+ }),
2227
+ makePreset({
2228
+ id: "midnight-dark",
2229
+ name: "Midnight Dark",
2230
+ description: "Dark surfaces with bright accents for a modern feel",
2231
+ category: "dark",
2232
+ preview: { primary: "#6366f1", surface: "#0f172a", text: "#e2e8f0", accent: "#22d3ee" },
2233
+ overrides: {
2234
+ primaryColor: "#6366f1",
2235
+ primaryHoverColor: "#818cf8",
2236
+ primaryLightColor: "#312e81",
2237
+ secondaryColor: "#94a3b8",
2238
+ accentColor: "#22d3ee",
2239
+ sidebarBg: "#1e293b",
2240
+ sidebarText: "#94a3b8",
2241
+ surfacePageColor: "#0f172a",
2242
+ surfaceCardColor: "#1e293b",
2243
+ surfaceModalColor: "#1e293b",
2244
+ successColor: "#34d399",
2245
+ successLightColor: "#064e3b",
2246
+ warningColor: "#fbbf24",
2247
+ warningLightColor: "#78350f",
2248
+ errorColor: "#f87171",
2249
+ errorLightColor: "#7f1d1d",
2250
+ infoColor: "#60a5fa",
2251
+ infoLightColor: "#1e3a5f",
2252
+ focusRingColor: "#6366f1",
2253
+ linkColor: "#818cf8",
2254
+ linkHoverColor: "#a5b4fc",
2255
+ btnPrimaryBg: "#6366f1",
2256
+ btnPrimaryText: "#ffffff",
2257
+ btnPrimaryHover: "#818cf8",
2258
+ btnPrimaryDisabled: "#4338ca",
2259
+ btnSecondaryBg: "#1e293b",
2260
+ btnSecondaryText: "#e2e8f0",
2261
+ btnSecondaryBorder: "#334155",
2262
+ btnSecondaryHover: "#334155",
2263
+ btnDangerBg: "#dc2626",
2264
+ btnDangerText: "#ffffff",
2265
+ btnDangerHover: "#f87171",
2266
+ btnDangerDisabled: "#991b1b",
2267
+ btnGhostText: "#94a3b8",
2268
+ btnGhostHoverBg: "#334155",
2269
+ btnGhostHoverText: "#e2e8f0",
2270
+ inputBg: "#1e293b",
2271
+ inputText: "#e2e8f0",
2272
+ inputBorder: "#334155",
2273
+ inputPlaceholder: "#64748b",
2274
+ inputFocusBorder: "#6366f1",
2275
+ inputFocusRing: "#6366f1",
2276
+ inputDisabledBg: "#0f172a",
2277
+ inputDisabledText: "#475569",
2278
+ inputErrorBorder: "#f87171",
2279
+ inputErrorFocus: "#ef4444",
2280
+ cardBg: "#1e293b",
2281
+ cardBorder: "#334155",
2282
+ modalBg: "#1e293b",
2283
+ modalBorder: "#334155",
2284
+ tableHeaderBg: "#1e293b",
2285
+ tableHeaderText: "#e2e8f0",
2286
+ tableBorder: "#334155",
2287
+ tableRowHover: "#334155",
2288
+ tableRowSelected: "#312e81",
2289
+ tableStripe: "#1e293b",
2290
+ badgeNeutralBg: "#334155",
2291
+ badgeNeutralText: "#e2e8f0",
2292
+ badgeInfoBg: "#1e3a5f",
2293
+ badgeInfoText: "#60a5fa",
2294
+ badgeSuccessBg: "#064e3b",
2295
+ badgeSuccessText: "#34d399",
2296
+ badgeWarningBg: "#78350f",
2297
+ badgeWarningText: "#fbbf24",
2298
+ badgeDangerBg: "#7f1d1d",
2299
+ badgeDangerText: "#f87171"
2300
+ }
2301
+ }),
2302
+ makePreset({
2303
+ id: "forest-green",
2304
+ name: "Forest Green",
2305
+ description: "Nature-inspired greens with warm neutrals",
2306
+ category: "warm",
2307
+ preview: { primary: "#15803d", surface: "#f0fdf4", text: "#14532d", accent: "#eab308" },
2308
+ overrides: {
2309
+ primaryColor: "#15803d",
2310
+ primaryHoverColor: "#166534",
2311
+ primaryLightColor: "#dcfce7",
2312
+ secondaryColor: "#57534e",
2313
+ accentColor: "#eab308",
2314
+ sidebarBg: "#f0fdf4",
2315
+ sidebarText: "#57534e",
2316
+ surfacePageColor: "#f0fdf4",
2317
+ surfaceCardColor: "#ffffff",
2318
+ surfaceModalColor: "#ffffff",
2319
+ focusRingColor: "#15803d",
2320
+ linkColor: "#15803d",
2321
+ linkHoverColor: "#166534",
2322
+ btnPrimaryBg: "#15803d",
2323
+ btnPrimaryText: "#ffffff",
2324
+ btnPrimaryHover: "#166534",
2325
+ btnPrimaryDisabled: "#86efac",
2326
+ btnSecondaryBg: "#ffffff",
2327
+ btnSecondaryText: "#14532d",
2328
+ btnSecondaryBorder: "#bbf7d0",
2329
+ btnSecondaryHover: "#dcfce7",
2330
+ btnGhostText: "#57534e",
2331
+ btnGhostHoverBg: "#dcfce7",
2332
+ btnGhostHoverText: "#14532d",
2333
+ inputBg: "#ffffff",
2334
+ inputText: "#14532d",
2335
+ inputBorder: "#bbf7d0",
2336
+ inputFocusBorder: "#15803d",
2337
+ inputFocusRing: "#15803d",
2338
+ cardBg: "#ffffff",
2339
+ cardBorder: "#bbf7d0",
2340
+ tableHeaderBg: "#dcfce7",
2341
+ tableHeaderText: "#14532d",
2342
+ tableBorder: "#bbf7d0",
2343
+ tableRowHover: "#dcfce7",
2344
+ tableRowSelected: "#f0fdf4",
2345
+ tableStripe: "#f0fdf4"
2346
+ }
2347
+ }),
2348
+ makePreset({
2349
+ id: "sunset-orange",
2350
+ name: "Sunset Orange",
2351
+ description: "Warm gradient feel with orange and coral primary",
2352
+ category: "vibrant",
2353
+ preview: { primary: "#ea580c", surface: "#fff7ed", text: "#431407", accent: "#e11d48" },
2354
+ overrides: {
2355
+ primaryColor: "#ea580c",
2356
+ primaryHoverColor: "#c2410c",
2357
+ primaryLightColor: "#fed7aa",
2358
+ secondaryColor: "#78716c",
2359
+ accentColor: "#e11d48",
2360
+ sidebarBg: "#fff7ed",
2361
+ sidebarText: "#78716c",
2362
+ surfacePageColor: "#fff7ed",
2363
+ surfaceCardColor: "#ffffff",
2364
+ surfaceModalColor: "#ffffff",
2365
+ focusRingColor: "#ea580c",
2366
+ linkColor: "#ea580c",
2367
+ linkHoverColor: "#c2410c",
2368
+ btnPrimaryBg: "#ea580c",
2369
+ btnPrimaryText: "#ffffff",
2370
+ btnPrimaryHover: "#c2410c",
2371
+ btnPrimaryDisabled: "#fdba74",
2372
+ btnSecondaryBg: "#ffffff",
2373
+ btnSecondaryText: "#431407",
2374
+ btnSecondaryBorder: "#fdba74",
2375
+ btnSecondaryHover: "#fed7aa",
2376
+ btnGhostText: "#78716c",
2377
+ btnGhostHoverBg: "#fed7aa",
2378
+ btnGhostHoverText: "#431407",
2379
+ inputBg: "#ffffff",
2380
+ inputText: "#431407",
2381
+ inputBorder: "#fdba74",
2382
+ inputFocusBorder: "#ea580c",
2383
+ inputFocusRing: "#ea580c",
2384
+ cardBg: "#ffffff",
2385
+ cardBorder: "#fdba74",
2386
+ tableHeaderBg: "#fed7aa",
2387
+ tableHeaderText: "#431407",
2388
+ tableBorder: "#fdba74",
2389
+ tableRowHover: "#fed7aa",
2390
+ tableRowSelected: "#ffedd5",
2391
+ tableStripe: "#fff7ed"
2392
+ }
2393
+ })
2394
+ ];
2395
+ function findPreset(id) {
2396
+ return BUILT_IN_PRESETS.find((p) => p.id === id);
2397
+ }
2398
+
2399
+ // src/theme-export.ts
2400
+ function exportTheme(current, name, metadata) {
2401
+ return {
2402
+ version: 1,
2403
+ name,
2404
+ exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
2405
+ values: { ...current },
2406
+ metadata
2407
+ };
2408
+ }
2409
+ function validateImport(data) {
2410
+ const errors = [];
2411
+ const warnings = [];
2412
+ if (!data || typeof data !== "object") {
2413
+ return { valid: false, errors: ["Invalid JSON structure - expected an object"], warnings };
2414
+ }
2415
+ const obj = data;
2416
+ if (obj.version !== 1) {
2417
+ errors.push(`Unsupported version: ${String(obj.version ?? "missing")}. Expected 1.`);
2418
+ }
2419
+ if (typeof obj.name !== "string" || !obj.name) {
2420
+ errors.push('Missing or invalid "name" field');
2421
+ }
2422
+ if (!obj.values || typeof obj.values !== "object") {
2423
+ errors.push('Missing or invalid "values" field - expected an object');
2424
+ return { valid: false, errors, warnings };
2425
+ }
2426
+ const values = obj.values;
2427
+ const knownKeys = new Set(Object.keys(THEME_DEFAULTS));
2428
+ const parsedValues = {};
2429
+ for (const [key, value] of Object.entries(values)) {
2430
+ if (typeof value !== "string") {
2431
+ errors.push(`Value for key "${key}" must be a string, got ${typeof value}`);
2432
+ continue;
2433
+ }
2434
+ if (!knownKeys.has(key)) {
2435
+ warnings.push(`Unknown key "${key}" - will be ignored`);
2436
+ } else {
2437
+ parsedValues[key] = value;
2438
+ }
2439
+ }
2440
+ if (errors.length > 0) {
2441
+ return { valid: false, errors, warnings };
2442
+ }
2443
+ return {
2444
+ valid: true,
2445
+ errors: [],
2446
+ warnings,
2447
+ parsed: {
2448
+ version: 1,
2449
+ name: obj.name,
2450
+ exportedAt: obj.exportedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
2451
+ values: parsedValues,
2452
+ metadata: obj.metadata
2453
+ }
2454
+ };
2455
+ }
2456
+ function downloadThemeAsJSON(theme) {
2457
+ const json = JSON.stringify(theme, null, 2);
2458
+ const blob = new Blob([json], { type: "application/json" });
2459
+ const url = URL.createObjectURL(blob);
2460
+ const dateStr = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
2461
+ const safeName = theme.name.toLowerCase().replace(/[^a-z0-9]+/g, "-");
2462
+ const filename = `snagtag-theme-${safeName}-${dateStr}.json`;
2463
+ const a = document.createElement("a");
2464
+ a.href = url;
2465
+ a.download = filename;
2466
+ document.body.appendChild(a);
2467
+ a.click();
2468
+ document.body.removeChild(a);
2469
+ URL.revokeObjectURL(url);
2470
+ }
2471
+ async function readThemeFromFile(file) {
2472
+ const text = await file.text();
2473
+ let data;
2474
+ try {
2475
+ data = JSON.parse(text);
2476
+ } catch {
2477
+ throw new Error("Invalid JSON file - could not parse");
2478
+ }
2479
+ const result = validateImport(data);
2480
+ if (!result.valid || !result.parsed) {
2481
+ throw new Error(`Invalid theme file: ${result.errors.join("; ")}`);
2482
+ }
2483
+ return result.parsed;
2484
+ }
2485
+
2486
+ // src/w3c-tokens.ts
2487
+ function categoryToW3CType(category) {
2488
+ switch (category) {
2489
+ case "colour":
2490
+ return "color";
2491
+ case "typography":
2492
+ return "dimension";
2493
+ case "spacing":
2494
+ return "dimension";
2495
+ case "border":
2496
+ return "dimension";
2497
+ case "shadow":
2498
+ return "shadow";
2499
+ case "motion":
2500
+ return "duration";
2501
+ default:
2502
+ return "dimension";
2503
+ }
2504
+ }
2505
+ function cssVarToTokenName(cssVar) {
2506
+ return cssVar.replace(/^--/, "").replace(/-/g, ".");
2507
+ }
2508
+ function resolveValue(token, overrides) {
2509
+ if (token.themeKey && overrides?.[token.themeKey]) {
2510
+ return overrides[token.themeKey];
2511
+ }
2512
+ if (token.themeKey && THEME_DEFAULTS[token.themeKey]) {
2513
+ return THEME_DEFAULTS[token.themeKey];
2514
+ }
2515
+ return token.defaultValue;
2516
+ }
2517
+ function setNestedValue(obj, path, value) {
2518
+ const parts = path.split(".");
2519
+ let current = obj;
2520
+ for (let i = 0; i < parts.length - 1; i++) {
2521
+ if (!current[parts[i]] || typeof current[parts[i]] !== "object" || "$type" in current[parts[i]]) {
2522
+ current[parts[i]] = {};
2523
+ }
2524
+ current = current[parts[i]];
2525
+ }
2526
+ current[parts[parts.length - 1]] = value;
2527
+ }
2528
+ function exportToW3CFormat(overrides) {
2529
+ const root = {};
2530
+ for (const category of TOKEN_REGISTRY) {
2531
+ for (const token of category.tokens) {
2532
+ const name = cssVarToTokenName(token.cssVar);
2533
+ const w3cToken = {
2534
+ $type: categoryToW3CType(token.category),
2535
+ $value: resolveValue(token, overrides),
2536
+ $description: token.label
2537
+ };
2538
+ if (token.references) {
2539
+ w3cToken.$extensions = {
2540
+ "com.snagtag.references": token.references
2541
+ };
2542
+ }
2543
+ if (token.themeKey) {
2544
+ w3cToken.$extensions = {
2545
+ ...w3cToken.$extensions,
2546
+ "com.snagtag.themeKey": token.themeKey
2547
+ };
2548
+ }
2549
+ setNestedValue(root, name, w3cToken);
2550
+ }
2551
+ }
2552
+ return root;
2553
+ }
2554
+ function downloadW3CTokens(overrides) {
2555
+ const tokens = exportToW3CFormat(overrides);
2556
+ const json = JSON.stringify(tokens, null, 2);
2557
+ const blob = new Blob([json], { type: "application/json" });
2558
+ const url = URL.createObjectURL(blob);
2559
+ const dateStr = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
2560
+ const a = document.createElement("a");
2561
+ a.href = url;
2562
+ a.download = `snagtag-design-tokens-w3c-${dateStr}.json`;
2563
+ document.body.appendChild(a);
2564
+ a.click();
2565
+ document.body.removeChild(a);
2566
+ URL.revokeObjectURL(url);
2567
+ }
2568
+
2569
+ // src/style-guide-export.ts
2570
+ function generateStyleGuide(overrides) {
2571
+ const theme = { ...THEME_DEFAULTS, ...overrides };
2572
+ const qualityScore = 0;
2573
+ const lines = [];
2574
+ lines.push("# SnagTag Design System Style Guide");
2575
+ lines.push("");
2576
+ lines.push(`Quality score: ${qualityScore}/100`);
2577
+ lines.push("");
2578
+ lines.push("## Colour Palette");
2579
+ lines.push("");
2580
+ for (const cat of THEME_CATEGORIES) {
2581
+ lines.push(`### ${cat.label}`);
2582
+ lines.push("");
2583
+ lines.push("| Token | Value |");
2584
+ lines.push("|-------|-------|");
2585
+ for (const key of cat.keys) {
2586
+ const value = theme[key] ?? THEME_DEFAULTS[key] ?? "";
2587
+ lines.push(`| ${key} | ${value} |`);
2588
+ }
2589
+ lines.push("");
2590
+ }
2591
+ lines.push("## Token Reference");
2592
+ lines.push("");
2593
+ for (const category of TOKEN_REGISTRY) {
2594
+ lines.push(`### ${category.label}`);
2595
+ lines.push("");
2596
+ lines.push("| CSS Variable | Label | Default | Themeable |");
2597
+ lines.push("|-------------|-------|---------|-----------|");
2598
+ for (const token of category.tokens) {
2599
+ const themeable = token.themeKey ? `Yes (${token.themeKey})` : "No";
2600
+ lines.push(`| \`${token.cssVar}\` | ${token.label} | ${token.defaultValue} | ${themeable} |`);
2601
+ }
2602
+ lines.push("");
2603
+ }
2604
+ lines.push("## Typography");
2605
+ lines.push("");
2606
+ const typoTokens = TOKEN_REGISTRY.flatMap((c) => c.tokens).filter((t) => t.category === "typography");
2607
+ if (typoTokens.length > 0) {
2608
+ lines.push("| Token | Value |");
2609
+ lines.push("|-------|-------|");
2610
+ for (const t of typoTokens) {
2611
+ lines.push(`| \`${t.cssVar}\` | ${t.defaultValue} |`);
2612
+ }
2613
+ lines.push("");
2614
+ }
2615
+ lines.push("## Spacing Scale");
2616
+ lines.push("");
2617
+ const spacingTokens = TOKEN_REGISTRY.flatMap((c) => c.tokens).filter((t) => t.category === "spacing");
2618
+ if (spacingTokens.length > 0) {
2619
+ lines.push("| Token | Value |");
2620
+ lines.push("|-------|-------|");
2621
+ for (const t of spacingTokens) {
2622
+ lines.push(`| \`${t.cssVar}\` | ${t.defaultValue} |`);
2623
+ }
2624
+ lines.push("");
2625
+ }
2626
+ lines.push("## Customisation");
2627
+ lines.push("");
2628
+ lines.push("Theme values can be customised at three levels:");
2629
+ lines.push("");
2630
+ lines.push("1. **Platform defaults** - set by the platform administrator");
2631
+ lines.push("2. **Tenant defaults** - set per vendor via Settings > Design System");
2632
+ lines.push("3. **User overrides** - personal preferences per user");
2633
+ lines.push("");
2634
+ lines.push("Higher layers override lower layers. Locked tokens (set by platform admin) cannot be overridden.");
2635
+ lines.push("");
2636
+ lines.push(`Total themeable tokens: ${TOKEN_REGISTRY.flatMap((c) => c.tokens).filter((t) => t.themeKey).length}`);
2637
+ return lines.join("\n");
2638
+ }
2639
+ function downloadStyleGuide(overrides) {
2640
+ const md = generateStyleGuide(overrides);
2641
+ const blob = new Blob([md], { type: "text/markdown" });
2642
+ const url = URL.createObjectURL(blob);
2643
+ const dateStr = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
2644
+ const a = document.createElement("a");
2645
+ a.href = url;
2646
+ a.download = `snagtag-style-guide-${dateStr}.md`;
2647
+ document.body.appendChild(a);
2648
+ a.click();
2649
+ document.body.removeChild(a);
2650
+ URL.revokeObjectURL(url);
2651
+ }
2652
+
2653
+ // src/audit-report.ts
2654
+ function gradeLabel(score) {
2655
+ if (score >= 90) return "Excellent";
2656
+ if (score >= 80) return "Good";
2657
+ if (score >= 60) return "Needs improvement";
2658
+ return "Poor";
2659
+ }
2660
+ function calculateQualityScore(_overrides) {
2661
+ return { overall: 0, colourHarmony: 0, contrastCompliance: 0, typographyConsistency: 0, spacingRegularity: 0 };
2662
+ }
2663
+ function findContrastIssues(_overrides) {
2664
+ return [];
2665
+ }
2666
+ function checkConsistency(_overrides) {
2667
+ return [];
2668
+ }
2669
+ function generateAuditReport(overrides) {
2670
+ const _theme = { ...THEME_DEFAULTS, ...overrides };
2671
+ const quality = calculateQualityScore(overrides);
2672
+ const contrastIssues = findContrastIssues(overrides);
2673
+ const consistencyIssues = checkConsistency(overrides);
2674
+ const allTokens = TOKEN_REGISTRY.flatMap((c) => c.tokens);
2675
+ const byCategory = {};
2676
+ for (const token of allTokens) {
2677
+ byCategory[token.category] = (byCategory[token.category] ?? 0) + 1;
2678
+ }
2679
+ const themeable = allTokens.filter((t) => t.themeKey).length;
2680
+ const summary = [
2681
+ `Design system quality: ${quality.overall}/100 (${gradeLabel(quality.overall)})`,
2682
+ `Contrast compliance: ${quality.contrastCompliance}% (${contrastIssues.length} failing pair${contrastIssues.length !== 1 ? "s" : ""})`,
2683
+ `Consistency issues: ${consistencyIssues.length}`,
2684
+ `Token coverage: ${allTokens.length} tokens across ${Object.keys(byCategory).length} categories (${themeable} themeable)`
2685
+ ].join(". ");
2686
+ return {
2687
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
2688
+ quality,
2689
+ contrastIssues,
2690
+ consistencyIssues,
2691
+ tokenStats: { total: allTokens.length, byCategory, themeable },
2692
+ summary
2693
+ };
2694
+ }
2695
+ function auditReportToMarkdown(report) {
2696
+ const lines = [];
2697
+ const date = new Date(report.generatedAt).toLocaleDateString("en-GB", {
2698
+ day: "numeric",
2699
+ month: "long",
2700
+ year: "numeric"
2701
+ });
2702
+ lines.push("# Design System Audit Report");
2703
+ lines.push(`Generated: ${date}`);
2704
+ lines.push("");
2705
+ lines.push("## Summary");
2706
+ lines.push(report.summary);
2707
+ lines.push("");
2708
+ lines.push("## Quality Score");
2709
+ lines.push("| Metric | Score | Grade |");
2710
+ lines.push("|--------|-------|-------|");
2711
+ lines.push(`| Overall | ${report.quality.overall}/100 | ${gradeLabel(report.quality.overall)} |`);
2712
+ lines.push(`| Colour Harmony | ${report.quality.colourHarmony}/100 | ${gradeLabel(report.quality.colourHarmony)} |`);
2713
+ lines.push(`| Contrast Compliance | ${report.quality.contrastCompliance}/100 | ${gradeLabel(report.quality.contrastCompliance)} |`);
2714
+ lines.push(`| Typography Consistency | ${report.quality.typographyConsistency}/100 | ${gradeLabel(report.quality.typographyConsistency)} |`);
2715
+ lines.push(`| Spacing Regularity | ${report.quality.spacingRegularity}/100 | ${gradeLabel(report.quality.spacingRegularity)} |`);
2716
+ lines.push("");
2717
+ if (report.contrastIssues.length > 0) {
2718
+ lines.push("## Contrast Issues");
2719
+ lines.push(`${report.contrastIssues.length} pair${report.contrastIssues.length !== 1 ? "s" : ""} failing WCAG AA:`);
2720
+ lines.push("");
2721
+ lines.push("| Foreground | Background | Ratio | Required | Suggested Fix |");
2722
+ lines.push("|------------|------------|-------|----------|---------------|");
2723
+ for (const issue of report.contrastIssues) {
2724
+ lines.push(`| ${issue.foregroundLabel} (${issue.foregroundHex}) | ${issue.backgroundLabel} (${issue.backgroundHex}) | ${issue.ratio}:1 | ${issue.requiredRatio}:1 | ${issue.suggestedFix} |`);
2725
+ }
2726
+ lines.push("");
2727
+ }
2728
+ if (report.consistencyIssues.length > 0) {
2729
+ lines.push("## Consistency Issues");
2730
+ for (const issue of report.consistencyIssues) {
2731
+ lines.push(`- **${issue.severity.toUpperCase()}** (${issue.category}): ${issue.message}`);
2732
+ lines.push(` Suggestion: ${issue.suggestion}`);
2733
+ }
2734
+ lines.push("");
2735
+ }
2736
+ lines.push("## Token Statistics");
2737
+ lines.push(`- Total tokens: ${report.tokenStats.total}`);
2738
+ lines.push(`- Themeable: ${report.tokenStats.themeable}`);
2739
+ for (const [cat, count] of Object.entries(report.tokenStats.byCategory)) {
2740
+ lines.push(`- ${cat}: ${count}`);
2741
+ }
2742
+ return lines.join("\n");
2743
+ }
2744
+ function downloadAuditReport(report) {
2745
+ const md = auditReportToMarkdown(report);
2746
+ const blob = new Blob([md], { type: "text/markdown" });
2747
+ const url = URL.createObjectURL(blob);
2748
+ const dateStr = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
2749
+ const a = document.createElement("a");
2750
+ a.href = url;
2751
+ a.download = `snagtag-audit-report-${dateStr}.md`;
2752
+ document.body.appendChild(a);
2753
+ a.click();
2754
+ document.body.removeChild(a);
2755
+ URL.revokeObjectURL(url);
2756
+ }
2757
+
2758
+ // src/dom.ts
2759
+ function applyThemeToDOM(merged) {
2760
+ const root = document.documentElement;
2761
+ if (merged.primaryColor) {
2762
+ merged.primaryColor = ensureContrastOnWhite(merged.primaryColor);
2763
+ }
2764
+ if (merged.primaryHoverColor) {
2765
+ merged.primaryHoverColor = ensureContrastOnWhite(merged.primaryHoverColor);
2766
+ }
2767
+ for (const [key, cssVar] of Object.entries(THEME_KEY_TO_CSS)) {
2768
+ const value = merged[key];
2769
+ if (value) {
2770
+ root.style.setProperty(cssVar, value);
2771
+ } else {
2772
+ root.style.removeProperty(cssVar);
2773
+ }
2774
+ }
2775
+ const sidebarBg = merged.sidebarBg || THEME_DEFAULTS.sidebarBg;
2776
+ const dark = isDark(sidebarBg);
2777
+ root.style.setProperty("--color-sidebar-border", dark ? lighten(sidebarBg, 0.15) : "#e2e8f0");
2778
+ root.style.setProperty("--color-sidebar-hover", dark ? lighten(sidebarBg, 0.1) : "#f1f5f9");
2779
+ const explicitActiveBg = merged.sidebarActiveBg;
2780
+ const primaryColor = merged.primaryColor || THEME_DEFAULTS.primaryColor;
2781
+ const primaryLight = merged.primaryLightColor || THEME_DEFAULTS.primaryLightColor;
2782
+ const primaryHover = merged.primaryHoverColor || THEME_DEFAULTS.primaryHoverColor;
2783
+ if (explicitActiveBg) {
2784
+ root.style.setProperty("--color-sidebar-active-bg", explicitActiveBg);
2785
+ root.style.setProperty("--color-sidebar-active-text", isDark(explicitActiveBg) ? "#ffffff" : primaryHover);
2786
+ } else if (dark) {
2787
+ root.style.setProperty("--color-sidebar-active-bg", primaryColor + "30");
2788
+ root.style.setProperty("--color-sidebar-active-text", primaryLight);
2789
+ } else {
2790
+ root.style.setProperty("--color-sidebar-active-bg", primaryLight);
2791
+ root.style.setProperty("--color-sidebar-active-text", primaryHover);
2792
+ }
2793
+ const accent = merged.accentColor || THEME_DEFAULTS.accentColor;
2794
+ root.style.setProperty("--color-accent-light", lighten(accent, 0.85));
2795
+ root.style.setProperty("--color-accent-hover", darken(accent, 0.15));
2796
+ }
2797
+ function clearThemeFromDOM() {
2798
+ const root = document.documentElement;
2799
+ for (const cssVar of Object.values(THEME_KEY_TO_CSS)) {
2800
+ root.style.removeProperty(cssVar);
2801
+ }
2802
+ for (const v of [
2803
+ "--color-sidebar-border",
2804
+ "--color-sidebar-hover",
2805
+ "--color-sidebar-active-bg",
2806
+ "--color-sidebar-active-text",
2807
+ "--color-accent-light",
2808
+ "--color-accent-hover"
2809
+ ]) {
2810
+ root.style.removeProperty(v);
2811
+ }
2812
+ }
2813
+
2814
+ // src/contrast-checker.ts
2815
+ var CONTRAST_PAIRS = [
2816
+ // Button text on button bg
2817
+ { fgKey: "btnPrimaryText", bgKey: "btnPrimaryBg", fgLabel: "Button Primary Text", bgLabel: "Button Primary Background", fgVar: "--color-btn-primary-text", bgVar: "--color-btn-primary-bg", requiredRatio: 4.5, level: "AA" },
2818
+ { fgKey: "btnSecondaryText", bgKey: "btnSecondaryBg", fgLabel: "Button Secondary Text", bgLabel: "Button Secondary Background", fgVar: "--color-btn-secondary-text", bgVar: "--color-btn-secondary-bg", requiredRatio: 4.5, level: "AA" },
2819
+ { fgKey: "btnDangerText", bgKey: "btnDangerBg", fgLabel: "Button Danger Text", bgLabel: "Button Danger Background", fgVar: "--color-btn-danger-text", bgVar: "--color-btn-danger-bg", requiredRatio: 4.5, level: "AA" },
2820
+ { fgKey: "btnGhostText", bgKey: "surfaceCardColor", fgLabel: "Button Ghost Text", bgLabel: "Card Surface", fgVar: "--color-btn-ghost-text", bgVar: "--color-surface-card", requiredRatio: 4.5, level: "AA" },
2821
+ // Input text on input bg
2822
+ { fgKey: "inputText", bgKey: "inputBg", fgLabel: "Input Text", bgLabel: "Input Background", fgVar: "--color-input-text", bgVar: "--color-input-bg", requiredRatio: 4.5, level: "AA" },
2823
+ { fgKey: "inputPlaceholder", bgKey: "inputBg", fgLabel: "Input Placeholder", bgLabel: "Input Background", fgVar: "--color-input-placeholder", bgVar: "--color-input-bg", requiredRatio: 3, level: "AA" },
2824
+ // Badge text on badge bg (all 5 tones)
2825
+ { fgKey: "badgeNeutralText", bgKey: "badgeNeutralBg", fgLabel: "Badge Neutral Text", bgLabel: "Badge Neutral Background", fgVar: "--color-badge-neutral-text", bgVar: "--color-badge-neutral-bg", requiredRatio: 4.5, level: "AA" },
2826
+ { fgKey: "badgeInfoText", bgKey: "badgeInfoBg", fgLabel: "Badge Info Text", bgLabel: "Badge Info Background", fgVar: "--color-badge-info-text", bgVar: "--color-badge-info-bg", requiredRatio: 4.5, level: "AA" },
2827
+ { fgKey: "badgeSuccessText", bgKey: "badgeSuccessBg", fgLabel: "Badge Success Text", bgLabel: "Badge Success Background", fgVar: "--color-badge-success-text", bgVar: "--color-badge-success-bg", requiredRatio: 4.5, level: "AA" },
2828
+ { fgKey: "badgeWarningText", bgKey: "badgeWarningBg", fgLabel: "Badge Warning Text", bgLabel: "Badge Warning Background", fgVar: "--color-badge-warning-text", bgVar: "--color-badge-warning-bg", requiredRatio: 4.5, level: "AA" },
2829
+ { fgKey: "badgeDangerText", bgKey: "badgeDangerBg", fgLabel: "Badge Danger Text", bgLabel: "Badge Danger Background", fgVar: "--color-badge-danger-text", bgVar: "--color-badge-danger-bg", requiredRatio: 4.5, level: "AA" },
2830
+ // Body text on surface colours
2831
+ { fgKey: "inputText", bgKey: "surfacePageColor", fgLabel: "Body Text", bgLabel: "Page Background", fgVar: "--color-input-text", bgVar: "--color-surface-page", requiredRatio: 4.5, level: "AA" },
2832
+ { fgKey: "inputText", bgKey: "surfaceCardColor", fgLabel: "Body Text", bgLabel: "Card Surface", fgVar: "--color-input-text", bgVar: "--color-surface-card", requiredRatio: 4.5, level: "AA" },
2833
+ // Sidebar text on sidebar bg
2834
+ { fgKey: "sidebarText", bgKey: "sidebarBg", fgLabel: "Sidebar Text", bgLabel: "Sidebar Background", fgVar: "--color-sidebar-text", bgVar: "--color-sidebar-bg", requiredRatio: 4.5, level: "AA" },
2835
+ // Link colour on card surface
2836
+ { fgKey: "linkColor", bgKey: "surfaceCardColor", fgLabel: "Link", bgLabel: "Card Surface", fgVar: "--color-link", bgVar: "--color-surface-card", requiredRatio: 4.5, level: "AA" },
2837
+ // Table header text on table header bg
2838
+ { fgKey: "tableHeaderText", bgKey: "tableHeaderBg", fgLabel: "Table Header Text", bgLabel: "Table Header Background", fgVar: "--color-table-header-text", bgVar: "--color-table-header-bg", requiredRatio: 4.5, level: "AA" }
2839
+ ];
2840
+ function suggestFix(fg, bg, targetRatio) {
2841
+ const bgLum = luminance(bg);
2842
+ const adjustFn = bgLum < 0.5 ? lighten : darken;
2843
+ let adjusted = fg;
2844
+ let step = 0.02;
2845
+ let attempts = 0;
2846
+ while (contrastRatio(adjusted, bg) < targetRatio && attempts < 50) {
2847
+ adjusted = adjustFn(adjusted, step);
2848
+ attempts++;
2849
+ if (attempts % 10 === 0) step += 0.01;
2850
+ }
2851
+ return adjusted;
2852
+ }
2853
+ function findContrastIssues2(overrides) {
2854
+ const theme = { ...THEME_DEFAULTS, ...overrides };
2855
+ const issues = [];
2856
+ for (const pair of CONTRAST_PAIRS) {
2857
+ const fg = theme[pair.fgKey];
2858
+ const bg = theme[pair.bgKey];
2859
+ if (!fg || !bg || !fg.startsWith("#") || !bg.startsWith("#")) continue;
2860
+ if (fg.length < 7 || bg.length < 7) continue;
2861
+ const ratio = contrastRatio(fg, bg);
2862
+ if (ratio < pair.requiredRatio) {
2863
+ issues.push({
2864
+ foregroundVar: pair.fgVar,
2865
+ backgroundVar: pair.bgVar,
2866
+ foregroundLabel: pair.fgLabel,
2867
+ backgroundLabel: pair.bgLabel,
2868
+ foregroundHex: fg,
2869
+ backgroundHex: bg,
2870
+ ratio: Math.round(ratio * 10) / 10,
2871
+ requiredRatio: pair.requiredRatio,
2872
+ suggestedFix: suggestFix(fg, bg, pair.requiredRatio),
2873
+ level: pair.level,
2874
+ themeKey: pair.fgKey
2875
+ });
2876
+ }
2877
+ }
2878
+ return issues;
2879
+ }
2880
+
2881
+ // src/consistency-checker.ts
2882
+ function parseToPx(value) {
2883
+ if (value === "none" || value === "0" || value === "0px") return 0;
2884
+ const remMatch = value.match(/^([\d.]+)rem$/);
2885
+ if (remMatch) return parseFloat(remMatch[1]) * 16;
2886
+ const pxMatch = value.match(/^([\d.]+)px$/);
2887
+ if (pxMatch) return parseFloat(pxMatch[1]);
2888
+ return null;
2889
+ }
2890
+ function getTokensByCategory(category) {
2891
+ return TOKEN_REGISTRY.flatMap((c) => c.tokens).filter((t) => t.category === category);
2892
+ }
2893
+ function checkRadiusConsistency(overrides) {
2894
+ const issues = [];
2895
+ const borderTokens = getTokensByCategory("border");
2896
+ const radiusTokens = borderTokens.filter((t) => t.cssVar.startsWith("--radius-"));
2897
+ const radiusValues = /* @__PURE__ */ new Map();
2898
+ for (const token of radiusTokens) {
2899
+ if (token.cssVar === "--radius-none" || token.cssVar === "--radius-full") continue;
2900
+ const value = overrides?.[token.cssVar] ?? token.defaultValue;
2901
+ const px = parseToPx(value);
2902
+ if (px !== null) {
2903
+ radiusValues.set(token.cssVar, px);
2904
+ }
2905
+ }
2906
+ const ordered = ["--radius-sm", "--radius-md", "--radius-lg", "--radius-xl"];
2907
+ const values = ordered.map((v) => radiusValues.get(v)).filter((v) => v !== null && v !== void 0);
2908
+ for (let i = 1; i < values.length; i++) {
2909
+ if (values[i] <= values[i - 1]) {
2910
+ issues.push({
2911
+ severity: "warning",
2912
+ category: "radius",
2913
+ message: `Radius scale is not strictly increasing: ${ordered[i - 1]} (${values[i - 1]}px) is not smaller than ${ordered[i]} (${values[i]}px).`,
2914
+ affectedTokens: [ordered[i - 1], ordered[i]],
2915
+ suggestion: `Ensure radius values increase progressively: sm < md < lg < xl.`
2916
+ });
2917
+ }
2918
+ }
2919
+ return issues;
2920
+ }
2921
+ function checkBorderWidthConsistency(overrides) {
2922
+ const issues = [];
2923
+ const borderTokens = getTokensByCategory("border");
2924
+ const widthTokens = borderTokens.filter((t) => t.cssVar.startsWith("--border-width-"));
2925
+ const widthMap = /* @__PURE__ */ new Map();
2926
+ for (const token of widthTokens) {
2927
+ const value = overrides?.[token.cssVar] ?? token.defaultValue;
2928
+ const px = parseToPx(value);
2929
+ if (px !== null) {
2930
+ widthMap.set(token.cssVar, px);
2931
+ }
2932
+ }
2933
+ const ordered = ["--border-width-none", "--border-width-thin", "--border-width-medium"];
2934
+ const values = ordered.map((v) => widthMap.get(v)).filter((v) => v !== null && v !== void 0);
2935
+ for (let i = 1; i < values.length; i++) {
2936
+ if (values[i] <= values[i - 1]) {
2937
+ issues.push({
2938
+ severity: "warning",
2939
+ category: "border",
2940
+ message: `Border width scale is not strictly increasing: ${ordered[i - 1]} (${values[i - 1]}px) vs ${ordered[i]} (${values[i]}px).`,
2941
+ affectedTokens: [ordered[i - 1], ordered[i]],
2942
+ suggestion: `Ensure border widths increase: none (0) < thin (1px) < medium (2px).`
2943
+ });
2944
+ }
2945
+ }
2946
+ const nonZeroVals = /* @__PURE__ */ new Map();
2947
+ for (const [name, px] of widthMap) {
2948
+ if (px === 0) continue;
2949
+ const existing = nonZeroVals.get(px) ?? [];
2950
+ existing.push(name);
2951
+ nonZeroVals.set(px, existing);
2952
+ }
2953
+ for (const [px, names] of nonZeroVals) {
2954
+ if (names.length > 1) {
2955
+ issues.push({
2956
+ severity: "info",
2957
+ category: "border",
2958
+ message: `Multiple border width tokens share the same value (${px}px): ${names.join(", ")}.`,
2959
+ affectedTokens: names,
2960
+ suggestion: `Consider differentiating these values or consolidating to a single token.`
2961
+ });
2962
+ }
2963
+ }
2964
+ return issues;
2965
+ }
2966
+ function checkSpacingConsistency(overrides) {
2967
+ const issues = [];
2968
+ const spacingTokens = getTokensByCategory("spacing");
2969
+ const values = [];
2970
+ for (const token of spacingTokens) {
2971
+ const value = overrides?.[token.cssVar] ?? token.defaultValue;
2972
+ const px = parseToPx(value);
2973
+ if (px !== null) {
2974
+ values.push({ name: token.cssVar, px });
2975
+ }
2976
+ }
2977
+ for (let i = 1; i < values.length; i++) {
2978
+ if (values[i].px < values[i - 1].px) {
2979
+ issues.push({
2980
+ severity: "warning",
2981
+ category: "spacing",
2982
+ message: `Spacing scale is not increasing: ${values[i - 1].name} (${values[i - 1].px}px) > ${values[i].name} (${values[i].px}px).`,
2983
+ affectedTokens: [values[i - 1].name, values[i].name],
2984
+ suggestion: `Ensure spacing tokens increase progressively from xs to 2xl.`
2985
+ });
2986
+ }
2987
+ }
2988
+ if (values.length >= 3) {
2989
+ const ratios = [];
2990
+ for (let i = 1; i < values.length; i++) {
2991
+ if (values[i - 1].px > 0) {
2992
+ ratios.push(values[i].px / values[i - 1].px);
2993
+ }
2994
+ }
2995
+ if (ratios.length >= 2) {
2996
+ const avg = ratios.reduce((a, b) => a + b, 0) / ratios.length;
2997
+ for (let i = 0; i < ratios.length; i++) {
2998
+ if (Math.abs(ratios[i] - avg) > avg * 0.5) {
2999
+ issues.push({
3000
+ severity: "info",
3001
+ category: "spacing",
3002
+ message: `Spacing ratio between ${values[i].name} and ${values[i + 1].name} (${ratios[i].toFixed(2)}x) differs significantly from the average (${avg.toFixed(2)}x).`,
3003
+ affectedTokens: [values[i].name, values[i + 1].name],
3004
+ suggestion: `Consider using a consistent multiplier across your spacing scale for visual rhythm.`
3005
+ });
3006
+ }
3007
+ }
3008
+ }
3009
+ }
3010
+ return issues;
3011
+ }
3012
+ function checkConsistency2(overrides) {
3013
+ return [
3014
+ ...checkRadiusConsistency(overrides),
3015
+ ...checkBorderWidthConsistency(overrides),
3016
+ ...checkSpacingConsistency(overrides)
3017
+ ];
3018
+ }
3019
+
3020
+ // src/quality-score.ts
3021
+ function scoreColourHarmony(theme) {
3022
+ let score = 100;
3023
+ const primary = theme.primaryColor;
3024
+ const accent = theme.accentColor;
3025
+ const success = theme.successColor;
3026
+ const warning = theme.warningColor;
3027
+ const error = theme.errorColor;
3028
+ if (!primary || !accent) return 50;
3029
+ const colours = [primary, accent, success, warning, error].filter(
3030
+ (c) => c && c.startsWith("#") && c.length >= 7
3031
+ );
3032
+ if (colours.length < 2) return 50;
3033
+ const semanticHues = [success, warning, error].filter((c) => !!c && c.startsWith("#") && c.length >= 7).map((c) => hexToHSL(c).h);
3034
+ for (let i = 0; i < semanticHues.length; i++) {
3035
+ for (let j = i + 1; j < semanticHues.length; j++) {
3036
+ const diff = Math.abs(semanticHues[i] - semanticHues[j]);
3037
+ const hueDist = Math.min(diff, 360 - diff);
3038
+ if (hueDist < 15) {
3039
+ score -= 15;
3040
+ }
3041
+ }
3042
+ }
3043
+ const primaryHue = hexToHSL(primary).h;
3044
+ const accentHue = hexToHSL(accent).h;
3045
+ const primaryAccentDist = Math.min(
3046
+ Math.abs(primaryHue - accentHue),
3047
+ 360 - Math.abs(primaryHue - accentHue)
3048
+ );
3049
+ if (primaryAccentDist < 20) {
3050
+ score -= 20;
3051
+ }
3052
+ for (const c of colours) {
3053
+ const hsl = hexToHSL(c);
3054
+ if (hsl.s < 20) score -= 5;
3055
+ if (hsl.s > 95) score -= 5;
3056
+ }
3057
+ return Math.max(0, Math.min(100, score));
3058
+ }
3059
+ function scoreContrastCompliance(theme) {
3060
+ const issues = findContrastIssues2(theme);
3061
+ const totalPairs = 17;
3062
+ const failingPairs = issues.length;
3063
+ const passingPairs = totalPairs - failingPairs;
3064
+ return Math.round(passingPairs / totalPairs * 100);
3065
+ }
3066
+ function scoreTypographyConsistency() {
3067
+ let score = 100;
3068
+ const typographyTokens = TOKEN_REGISTRY.flatMap((c) => c.tokens).filter((t) => t.category === "typography");
3069
+ const sizeTokens = typographyTokens.filter((t) => t.cssVar.startsWith("--text-"));
3070
+ const sizes = [];
3071
+ for (const token of sizeTokens) {
3072
+ const remMatch = token.defaultValue.match(/^([\d.]+)rem$/);
3073
+ if (remMatch) {
3074
+ sizes.push(parseFloat(remMatch[1]));
3075
+ }
3076
+ }
3077
+ if (sizes.length > 0) {
3078
+ const min = Math.min(...sizes);
3079
+ const max = Math.max(...sizes);
3080
+ const ratio = max / min;
3081
+ if (ratio > 5) score -= 10;
3082
+ if (ratio < 1.5) score -= 10;
3083
+ }
3084
+ const lineHeightTokens = typographyTokens.filter((t) => t.cssVar.startsWith("--leading-"));
3085
+ if (lineHeightTokens.length < 3) {
3086
+ score -= 15;
3087
+ }
3088
+ const weightTokens = typographyTokens.filter((t) => t.cssVar.startsWith("--weight-"));
3089
+ if (weightTokens.length < 3) {
3090
+ score -= 15;
3091
+ }
3092
+ return Math.max(0, Math.min(100, score));
3093
+ }
3094
+ function scoreSpacingRegularity() {
3095
+ let score = 100;
3096
+ const consistencyIssues = checkConsistency2();
3097
+ const spacingIssues = consistencyIssues.filter((i) => i.category === "spacing");
3098
+ for (const issue of spacingIssues) {
3099
+ score -= issue.severity === "warning" ? 15 : 5;
3100
+ }
3101
+ const otherIssues = consistencyIssues.filter((i) => i.category !== "spacing");
3102
+ for (const issue of otherIssues) {
3103
+ score -= issue.severity === "warning" ? 10 : 3;
3104
+ }
3105
+ return Math.max(0, Math.min(100, score));
3106
+ }
3107
+ function calculateQualityScore2(overrides) {
3108
+ const theme = { ...THEME_DEFAULTS, ...overrides };
3109
+ const colourHarmony = scoreColourHarmony(theme);
3110
+ const contrastCompliance = scoreContrastCompliance(theme);
3111
+ const typographyConsistency = scoreTypographyConsistency();
3112
+ const spacingRegularity = scoreSpacingRegularity();
3113
+ const overall = Math.round(
3114
+ colourHarmony * 0.2 + contrastCompliance * 0.4 + typographyConsistency * 0.2 + spacingRegularity * 0.2
3115
+ );
3116
+ return {
3117
+ overall,
3118
+ colourHarmony,
3119
+ contrastCompliance,
3120
+ typographyConsistency,
3121
+ spacingRegularity
3122
+ };
3123
+ }
3124
+ // Annotate the CommonJS export names for ESM import in node:
3125
+ 0 && (module.exports = {
3126
+ BUILT_IN_PRESETS,
3127
+ COLOUR_BLINDNESS_TYPES,
3128
+ DENSITY_CONFIGS,
3129
+ EASING_PRESETS,
3130
+ FONT_PAIRINGS,
3131
+ SCALE_RATIOS,
3132
+ SURFACE_DEFINITIONS,
3133
+ THEME_CATEGORIES,
3134
+ THEME_DEFAULTS,
3135
+ THEME_KEY_TO_CSS,
3136
+ TOKEN_REGISTRY,
3137
+ addCustomCategory,
3138
+ addExtractedToken,
3139
+ adjustSemanticForDark,
3140
+ applyAddCategory,
3141
+ applyBulkChange,
3142
+ applyBulkMove,
3143
+ applyChange,
3144
+ applyDarkModeToDOM,
3145
+ applyDensity,
3146
+ applyDensityToDOM,
3147
+ applyExtract,
3148
+ applyMove,
3149
+ applySurface,
3150
+ applySurfaceToDOM,
3151
+ applyThemeToDOM,
3152
+ auditReportToMarkdown,
3153
+ bulkMoveTokens,
3154
+ calculateQualityScore,
3155
+ captureSnapshot,
3156
+ checkConsistency,
3157
+ clampControlPoint,
3158
+ clearSnapshots,
3159
+ clearSurface,
3160
+ clearThemeFromDOM,
3161
+ clearUsageCache,
3162
+ contrastRatio,
3163
+ countTokenUsage,
3164
+ createOverlay,
3165
+ createReassignmentState,
3166
+ createThemeEditorState,
3167
+ darken,
3168
+ downloadAuditReport,
3169
+ downloadStyleGuide,
3170
+ downloadThemeAsJSON,
3171
+ downloadW3CTokens,
3172
+ ensureContrastOnWhite,
3173
+ exportTheme,
3174
+ exportToW3CFormat,
3175
+ extractToken,
3176
+ findContrastIssues,
3177
+ findPreset,
3178
+ findPresetByValue,
3179
+ findToken,
3180
+ findUnusedTokens,
3181
+ formatCubicBezier,
3182
+ generateAuditReport,
3183
+ generateDarkTheme,
3184
+ generateScale,
3185
+ generateStyleGuide,
3186
+ generateTypeScale,
3187
+ getActiveDensity,
3188
+ getActiveSurface,
3189
+ getAllCategoryLabels,
3190
+ getDensityCSS,
3191
+ getDependents,
3192
+ getEffectiveCategories,
3193
+ getOpeningSnapshot,
3194
+ getPresetsByCategory,
3195
+ getSnapshot,
3196
+ getSurfaceDefinition,
3197
+ getSurfaceOverrides,
3198
+ getTokenCategory,
3199
+ getTokenChain,
3200
+ getTokenUsageCount,
3201
+ getTokenUsageStats,
3202
+ hasExtractedToken,
3203
+ hexToHSL,
3204
+ hexToHsl,
3205
+ hslToHex,
3206
+ hslToHexDark,
3207
+ invertLightness,
3208
+ isDark,
3209
+ isDarkMode,
3210
+ lighten,
3211
+ luminance,
3212
+ makePreset,
3213
+ markSaved,
3214
+ moveToken,
3215
+ parseCubicBezier,
3216
+ readSnapshots,
3217
+ readThemeFromFile,
3218
+ redo,
3219
+ redoReassignment,
3220
+ resetToDefaults,
3221
+ resolveMode,
3222
+ rotateHue,
3223
+ sampleBezierCurve,
3224
+ setDarkMode,
3225
+ simulateColourBlindness,
3226
+ simulateThemeColourBlindness,
3227
+ snapToGrid,
3228
+ suggestFix,
3229
+ suggestPairings,
3230
+ suggestPalettes,
3231
+ suggestTokenName,
3232
+ systemPrefersDark,
3233
+ themesAreEqual,
3234
+ typeScaleToCSSVars,
3235
+ undo,
3236
+ undoReassignment,
3237
+ validateImport,
3238
+ validateTokenName
3239
+ });