@shalomormsby/ui 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,710 @@
1
+ "use client";
2
+
3
+ // src/providers/ThemeProvider.tsx
4
+ import { useEffect, useState } from "react";
5
+
6
+ // src/lib/store/theme.ts
7
+ import { create } from "zustand";
8
+ import { persist } from "zustand/middleware";
9
+ var useThemeStore = create()(
10
+ persist(
11
+ (set, get) => ({
12
+ // Defaults
13
+ theme: "volt",
14
+ mode: "dark",
15
+ // Actions
16
+ setTheme: (theme) => set({ theme }),
17
+ setMode: (mode) => set({ mode }),
18
+ toggleMode: () => set((state) => ({ mode: state.mode === "light" ? "dark" : "light" })),
19
+ // Computed
20
+ get themeConfig() {
21
+ const state = get();
22
+ return { name: state.theme, mode: state.mode };
23
+ }
24
+ }),
25
+ {
26
+ name: "ecosystem-theme",
27
+ // Only persist theme and mode
28
+ partialize: (state) => ({
29
+ theme: state.theme,
30
+ mode: state.mode
31
+ })
32
+ }
33
+ )
34
+ );
35
+
36
+ // src/lib/store/customizer.ts
37
+ import { create as create2 } from "zustand";
38
+ import { persist as persist2 } from "zustand/middleware";
39
+ import { computeDerivedTokens } from "@sage/tokens";
40
+
41
+ // src/lib/colors.ts
42
+ var colorTokens = {
43
+ // Background colors
44
+ background: "var(--color-background)",
45
+ backgroundSecondary: "var(--color-background-secondary)",
46
+ backgroundTertiary: "var(--color-background-tertiary)",
47
+ surface: "var(--color-surface)",
48
+ // Foreground/Text colors
49
+ foreground: "var(--color-foreground)",
50
+ foregroundSecondary: "var(--color-foreground-secondary)",
51
+ foregroundTertiary: "var(--color-foreground-tertiary)",
52
+ textPrimary: "var(--color-text-primary)",
53
+ textSecondary: "var(--color-text-secondary)",
54
+ textMuted: "var(--color-text-muted)",
55
+ // Brand colors
56
+ primary: "var(--color-primary)",
57
+ primaryForeground: "var(--color-primary-foreground)",
58
+ secondary: "var(--color-secondary)",
59
+ secondaryForeground: "var(--color-secondary-foreground)",
60
+ accent: "var(--color-accent)",
61
+ accentForeground: "var(--color-accent-foreground)",
62
+ // Semantic colors
63
+ success: "var(--color-success)",
64
+ successForeground: "var(--color-success-foreground)",
65
+ warning: "var(--color-warning)",
66
+ warningForeground: "var(--color-warning-foreground)",
67
+ error: "var(--color-error)",
68
+ errorForeground: "var(--color-error-foreground)",
69
+ info: "var(--color-info)",
70
+ infoForeground: "var(--color-info-foreground)",
71
+ // Borders
72
+ border: "var(--color-border)",
73
+ borderSubtle: "var(--color-border-subtle)",
74
+ // Interactive states
75
+ hover: "var(--color-hover)",
76
+ active: "var(--color-active)",
77
+ focus: "var(--color-focus)",
78
+ // Links
79
+ link: "var(--color-link)",
80
+ linkHover: "var(--color-link-hover)",
81
+ linkHoverForeground: "var(--color-link-hover-foreground)"
82
+ };
83
+ var semanticColors = {
84
+ /**
85
+ * Status colors for indicating states
86
+ */
87
+ status: {
88
+ success: {
89
+ bg: colorTokens.success,
90
+ fg: colorTokens.successForeground
91
+ },
92
+ warning: {
93
+ bg: colorTokens.warning,
94
+ fg: colorTokens.warningForeground
95
+ },
96
+ error: {
97
+ bg: colorTokens.error,
98
+ fg: colorTokens.errorForeground
99
+ },
100
+ info: {
101
+ bg: colorTokens.info,
102
+ fg: colorTokens.infoForeground
103
+ }
104
+ },
105
+ /**
106
+ * Brand colors for primary UI elements
107
+ */
108
+ brand: {
109
+ primary: {
110
+ bg: colorTokens.primary,
111
+ fg: colorTokens.primaryForeground
112
+ },
113
+ secondary: {
114
+ bg: colorTokens.secondary,
115
+ fg: colorTokens.secondaryForeground
116
+ },
117
+ accent: {
118
+ bg: colorTokens.accent,
119
+ fg: colorTokens.accentForeground
120
+ }
121
+ },
122
+ /**
123
+ * Interactive state colors
124
+ */
125
+ interactive: {
126
+ default: {
127
+ bg: colorTokens.background,
128
+ fg: colorTokens.foreground
129
+ },
130
+ hover: {
131
+ bg: colorTokens.hover,
132
+ fg: colorTokens.foreground
133
+ },
134
+ active: {
135
+ bg: colorTokens.active,
136
+ fg: colorTokens.foreground
137
+ },
138
+ focus: {
139
+ border: colorTokens.focus
140
+ }
141
+ }
142
+ };
143
+ function hexToRgb(hex) {
144
+ const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
145
+ return result ? {
146
+ r: parseInt(result[1], 16),
147
+ g: parseInt(result[2], 16),
148
+ b: parseInt(result[3], 16)
149
+ } : null;
150
+ }
151
+ function getLuminance(r, g, b) {
152
+ const [rs, gs, bs] = [r, g, b].map((c) => {
153
+ const srgb = c / 255;
154
+ return srgb <= 0.03928 ? srgb / 12.92 : Math.pow((srgb + 0.055) / 1.055, 2.4);
155
+ });
156
+ return 0.2126 * rs + 0.7152 * gs + 0.0722 * bs;
157
+ }
158
+ function getContrastRatio(hex1, hex2) {
159
+ const rgb1 = hexToRgb(hex1);
160
+ const rgb2 = hexToRgb(hex2);
161
+ if (!rgb1 || !rgb2) return 0;
162
+ const lum1 = getLuminance(rgb1.r, rgb1.g, rgb1.b);
163
+ const lum2 = getLuminance(rgb2.r, rgb2.g, rgb2.b);
164
+ const lighter = Math.max(lum1, lum2);
165
+ const darker = Math.min(lum1, lum2);
166
+ return (lighter + 0.05) / (darker + 0.05);
167
+ }
168
+ function hexToHSL(hex) {
169
+ const rgb = hexToRgb(hex);
170
+ if (!rgb) return { h: 0, s: 0, l: 0 };
171
+ const r = rgb.r / 255;
172
+ const g = rgb.g / 255;
173
+ const b = rgb.b / 255;
174
+ const max = Math.max(r, g, b);
175
+ const min = Math.min(r, g, b);
176
+ let h = 0, s = 0, l = (max + min) / 2;
177
+ if (max !== min) {
178
+ const d = max - min;
179
+ s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
180
+ switch (max) {
181
+ case r:
182
+ h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
183
+ break;
184
+ case g:
185
+ h = ((b - r) / d + 2) / 6;
186
+ break;
187
+ case b:
188
+ h = ((r - g) / d + 4) / 6;
189
+ break;
190
+ }
191
+ }
192
+ return {
193
+ h: Math.round(h * 360),
194
+ s: Math.round(s * 100),
195
+ l: Math.round(l * 100)
196
+ };
197
+ }
198
+ function hslToHex(h, s, l) {
199
+ h = h / 360;
200
+ s = s / 100;
201
+ l = l / 100;
202
+ let r, g, b;
203
+ if (s === 0) {
204
+ r = g = b = l;
205
+ } else {
206
+ const hue2rgb = (p2, q2, t) => {
207
+ if (t < 0) t += 1;
208
+ if (t > 1) t -= 1;
209
+ if (t < 1 / 6) return p2 + (q2 - p2) * 6 * t;
210
+ if (t < 1 / 2) return q2;
211
+ if (t < 2 / 3) return p2 + (q2 - p2) * (2 / 3 - t) * 6;
212
+ return p2;
213
+ };
214
+ const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
215
+ const p = 2 * l - q;
216
+ r = hue2rgb(p, q, h + 1 / 3);
217
+ g = hue2rgb(p, q, h);
218
+ b = hue2rgb(p, q, h - 1 / 3);
219
+ }
220
+ const toHex = (x) => {
221
+ const hex = Math.round(x * 255).toString(16);
222
+ return hex.length === 1 ? "0" + hex : hex;
223
+ };
224
+ return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
225
+ }
226
+ function getOptimalForeground(bgHex, whiteHex = "#ffffff", blackHex = "#000000") {
227
+ const whiteRatio = getContrastRatio(bgHex, whiteHex);
228
+ const blackRatio = getContrastRatio(bgHex, blackHex);
229
+ return whiteRatio > blackRatio ? whiteHex : blackHex;
230
+ }
231
+ function generateColorScale(baseHex) {
232
+ const hsl = hexToHSL(baseHex);
233
+ return {
234
+ 50: hslToHex(hsl.h, Math.max(hsl.s - 10, 20), 95),
235
+ 100: hslToHex(hsl.h, Math.max(hsl.s - 5, 30), 90),
236
+ 200: hslToHex(hsl.h, hsl.s, 80),
237
+ 300: hslToHex(hsl.h, hsl.s, 70),
238
+ 400: hslToHex(hsl.h, hsl.s, 60),
239
+ 500: baseHex,
240
+ // Base color
241
+ 600: hslToHex(hsl.h, Math.min(hsl.s + 5, 100), 45),
242
+ 700: hslToHex(hsl.h, Math.min(hsl.s + 10, 100), 35),
243
+ 800: hslToHex(hsl.h, Math.min(hsl.s + 15, 100), 25),
244
+ 900: hslToHex(hsl.h, Math.min(hsl.s + 20, 100), 15)
245
+ };
246
+ }
247
+
248
+ // src/lib/store/customizer.ts
249
+ var useCustomizer = create2()(
250
+ persist2(
251
+ (set, get) => ({
252
+ motion: 5,
253
+ prefersReducedMotion: false,
254
+ customizationMode: "simple",
255
+ customColors: {},
256
+ savedPalettes: [],
257
+ customFontThemes: {},
258
+ savedFontThemes: [],
259
+ setMotion: (level) => set({ motion: level }),
260
+ setPrefersReducedMotion: (value) => set({ prefersReducedMotion: value }),
261
+ setCustomizationMode: (mode) => set({ customizationMode: mode }),
262
+ setCustomPrimaryColor: (theme, mode, hexColor) => {
263
+ const state = get();
264
+ const currentPalette = state.customColors[theme]?.[mode];
265
+ const scale = generateColorScale(hexColor);
266
+ const primaryForeground = getOptimalForeground(hexColor);
267
+ const derivedTokens = computeDerivedTokens("--color-primary", hexColor, mode);
268
+ const isSimple = state.customizationMode === "simple";
269
+ const palette = {
270
+ primary: hexColor,
271
+ primaryForeground,
272
+ secondary: isSimple ? void 0 : currentPalette?.secondary,
273
+ secondaryForeground: isSimple ? void 0 : currentPalette?.secondaryForeground,
274
+ accent: isSimple ? void 0 : currentPalette?.accent,
275
+ accentForeground: isSimple ? void 0 : currentPalette?.accentForeground,
276
+ scale,
277
+ derivedTokens
278
+ };
279
+ set((state2) => ({
280
+ customColors: {
281
+ ...state2.customColors,
282
+ [theme]: {
283
+ ...state2.customColors[theme],
284
+ [mode]: palette
285
+ }
286
+ }
287
+ }));
288
+ },
289
+ setCustomSecondaryColor: (theme, mode, hexColor) => {
290
+ const state = get();
291
+ const currentPalette = state.customColors[theme]?.[mode];
292
+ if (!currentPalette) return;
293
+ const secondaryForeground = getOptimalForeground(hexColor);
294
+ const derivedTokens = computeDerivedTokens("--color-secondary", hexColor, mode);
295
+ set((state2) => ({
296
+ customColors: {
297
+ ...state2.customColors,
298
+ [theme]: {
299
+ ...state2.customColors[theme],
300
+ [mode]: {
301
+ ...currentPalette,
302
+ secondary: hexColor,
303
+ secondaryForeground,
304
+ derivedTokens: {
305
+ ...currentPalette.derivedTokens,
306
+ ...derivedTokens
307
+ }
308
+ }
309
+ }
310
+ }
311
+ }));
312
+ },
313
+ setCustomAccentColor: (theme, mode, hexColor) => {
314
+ const state = get();
315
+ const currentPalette = state.customColors[theme]?.[mode];
316
+ if (!currentPalette) return;
317
+ const accentForeground = getOptimalForeground(hexColor);
318
+ const derivedTokens = computeDerivedTokens("--color-accent", hexColor, mode);
319
+ set((state2) => ({
320
+ customColors: {
321
+ ...state2.customColors,
322
+ [theme]: {
323
+ ...state2.customColors[theme],
324
+ [mode]: {
325
+ ...currentPalette,
326
+ accent: hexColor,
327
+ accentForeground,
328
+ derivedTokens: {
329
+ ...currentPalette.derivedTokens,
330
+ ...derivedTokens
331
+ }
332
+ }
333
+ }
334
+ }
335
+ }));
336
+ },
337
+ applyColorPalette: (theme, mode, colors) => {
338
+ const scale = generateColorScale(colors.primary);
339
+ const primaryForeground = getOptimalForeground(colors.primary);
340
+ let derivedTokens = computeDerivedTokens("--color-primary", colors.primary, mode);
341
+ let secondary = colors.secondary;
342
+ let secondaryForeground = secondary ? getOptimalForeground(secondary) : void 0;
343
+ if (secondary) {
344
+ const secondaryDerived = computeDerivedTokens("--color-secondary", secondary, mode);
345
+ derivedTokens = { ...derivedTokens, ...secondaryDerived };
346
+ }
347
+ let accent = colors.accent;
348
+ let accentForeground = accent ? getOptimalForeground(accent) : void 0;
349
+ if (accent) {
350
+ const accentDerived = computeDerivedTokens("--color-accent", accent, mode);
351
+ derivedTokens = { ...derivedTokens, ...accentDerived };
352
+ }
353
+ const palette = {
354
+ name: colors.name,
355
+ description: colors.description,
356
+ primary: colors.primary,
357
+ primaryForeground,
358
+ secondary,
359
+ secondaryForeground,
360
+ accent,
361
+ accentForeground,
362
+ scale,
363
+ derivedTokens
364
+ };
365
+ set((state) => ({
366
+ customColors: {
367
+ ...state.customColors,
368
+ [theme]: {
369
+ ...state.customColors[theme],
370
+ [mode]: palette
371
+ }
372
+ }
373
+ }));
374
+ },
375
+ resetCustomColors: (theme, mode) => {
376
+ if (mode) {
377
+ set((state) => ({
378
+ customColors: {
379
+ ...state.customColors,
380
+ [theme]: {
381
+ ...state.customColors[theme],
382
+ [mode]: void 0
383
+ }
384
+ }
385
+ }));
386
+ } else {
387
+ set((state) => {
388
+ const { [theme]: _, ...rest } = state.customColors;
389
+ return { customColors: rest };
390
+ });
391
+ }
392
+ },
393
+ getActiveColorPalette: (theme, mode) => {
394
+ return get().customColors[theme]?.[mode] || null;
395
+ },
396
+ // Saved palette management
397
+ savePalette: (paletteData) => {
398
+ const id = `custom-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
399
+ const newPalette = {
400
+ ...paletteData,
401
+ id,
402
+ category: "custom",
403
+ createdAt: Date.now()
404
+ };
405
+ set((state) => ({
406
+ savedPalettes: [...state.savedPalettes, newPalette]
407
+ }));
408
+ },
409
+ updatePalette: (id, updates) => {
410
+ set((state) => ({
411
+ savedPalettes: state.savedPalettes.map(
412
+ (p) => p.id === id ? { ...p, ...updates } : p
413
+ )
414
+ }));
415
+ },
416
+ renamePalette: (id, newName) => {
417
+ set((state) => ({
418
+ savedPalettes: state.savedPalettes.map(
419
+ (p) => p.id === id ? { ...p, name: newName } : p
420
+ )
421
+ }));
422
+ },
423
+ deletePalette: (id) => {
424
+ set((state) => ({
425
+ savedPalettes: state.savedPalettes.filter((p) => p.id !== id)
426
+ }));
427
+ },
428
+ reorderPalettes: (palettes) => {
429
+ set({ savedPalettes: palettes });
430
+ },
431
+ getSavedPalettes: () => {
432
+ return get().savedPalettes;
433
+ },
434
+ // Font theme management
435
+ applyFontTheme: (theme, mode, fontTheme) => {
436
+ set((state) => ({
437
+ customFontThemes: {
438
+ ...state.customFontThemes,
439
+ [theme]: {
440
+ ...state.customFontThemes[theme],
441
+ [mode]: fontTheme
442
+ }
443
+ }
444
+ }));
445
+ },
446
+ resetCustomFonts: (theme, mode) => {
447
+ if (mode) {
448
+ set((state) => ({
449
+ customFontThemes: {
450
+ ...state.customFontThemes,
451
+ [theme]: {
452
+ ...state.customFontThemes[theme],
453
+ [mode]: void 0
454
+ }
455
+ }
456
+ }));
457
+ } else {
458
+ set((state) => {
459
+ const { [theme]: _, ...rest } = state.customFontThemes;
460
+ return { customFontThemes: rest };
461
+ });
462
+ }
463
+ },
464
+ getActiveFontTheme: (theme, mode) => {
465
+ return get().customFontThemes[theme]?.[mode] || null;
466
+ },
467
+ // Saved font theme management
468
+ saveFontTheme: (fontThemeData) => {
469
+ const id = `font-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
470
+ const newFontTheme = {
471
+ ...fontThemeData,
472
+ id,
473
+ category: "custom",
474
+ createdAt: Date.now(),
475
+ isCustom: true
476
+ };
477
+ set((state) => ({
478
+ savedFontThemes: [...state.savedFontThemes, newFontTheme]
479
+ }));
480
+ },
481
+ updateFontTheme: (id, updates) => {
482
+ set((state) => ({
483
+ savedFontThemes: state.savedFontThemes.map(
484
+ (ft) => ft.id === id ? { ...ft, ...updates } : ft
485
+ )
486
+ }));
487
+ },
488
+ renameFontTheme: (id, newName) => {
489
+ set((state) => ({
490
+ savedFontThemes: state.savedFontThemes.map(
491
+ (ft) => ft.id === id ? { ...ft, name: newName } : ft
492
+ )
493
+ }));
494
+ },
495
+ deleteFontTheme: (id) => {
496
+ set((state) => ({
497
+ savedFontThemes: state.savedFontThemes.filter((ft) => ft.id !== id)
498
+ }));
499
+ },
500
+ reorderFontThemes: (fontThemes) => {
501
+ set({ savedFontThemes: fontThemes });
502
+ },
503
+ getSavedFontThemes: () => {
504
+ return get().savedFontThemes;
505
+ }
506
+ }),
507
+ {
508
+ name: "ecosystem-customizer",
509
+ version: 4,
510
+ partialize: (state) => ({
511
+ motion: state.motion,
512
+ prefersReducedMotion: state.prefersReducedMotion,
513
+ customizationMode: state.customizationMode,
514
+ customColors: state.customColors,
515
+ savedPalettes: state.savedPalettes,
516
+ customFontThemes: state.customFontThemes,
517
+ savedFontThemes: state.savedFontThemes
518
+ })
519
+ }
520
+ )
521
+ );
522
+
523
+ // src/providers/ThemeProvider.tsx
524
+ import { studioTokens, sageTokens, voltTokens, syntaxColors, codeColors } from "@sage/tokens";
525
+ import { Fragment, jsx } from "react/jsx-runtime";
526
+ var themeTokens = {
527
+ studio: studioTokens,
528
+ sage: sageTokens,
529
+ volt: voltTokens
530
+ };
531
+ var fontFamilies = {
532
+ studio: {
533
+ heading: "var(--font-studio-heading)",
534
+ body: "var(--font-studio-body)",
535
+ mono: "var(--font-mono)"
536
+ },
537
+ sage: {
538
+ sans: "var(--font-sage-body)",
539
+ serif: "var(--font-sage-heading)",
540
+ mono: "var(--font-mono)"
541
+ },
542
+ volt: {
543
+ sans: "var(--font-volt-heading)",
544
+ mono: "var(--font-mono)"
545
+ }
546
+ };
547
+ function getThemeVars(theme, mode) {
548
+ const tokens = themeTokens[theme];
549
+ const colors = tokens[mode]?.colors;
550
+ const effects = tokens[mode]?.effects;
551
+ const fonts = fontFamilies[theme];
552
+ return {
553
+ // Colors - Base
554
+ "--color-background": colors?.background || "#ffffff",
555
+ "--color-background-secondary": colors?.backgroundSecondary || colors?.background || "#fafafa",
556
+ "--color-background-tertiary": colors?.backgroundTertiary || colors?.backgroundSecondary || colors?.background || "#f5f5f5",
557
+ "--color-foreground": colors?.foreground || "#0a0a0a",
558
+ "--color-primary": colors?.primary || "#0a0a0a",
559
+ "--color-primary-foreground": colors?.primaryForeground || "#ffffff",
560
+ "--color-secondary": colors?.secondary || "#f5f5f5",
561
+ "--color-secondary-foreground": colors?.secondaryForeground || "#0a0a0a",
562
+ "--color-accent": colors?.accent || colors?.primary || "#0070f3",
563
+ "--color-accent-foreground": colors?.accentForeground || "#ffffff",
564
+ "--color-success": colors?.success || "#00a86b",
565
+ "--color-success-foreground": colors?.successForeground || "#ffffff",
566
+ "--color-warning": colors?.warning || "#f59e0b",
567
+ "--color-warning-foreground": colors?.warningForeground || "#ffffff",
568
+ "--color-error": colors?.error || "#ef4444",
569
+ "--color-error-foreground": colors?.errorForeground || "#ffffff",
570
+ "--color-info": colors?.info || colors?.accent || "#0070f3",
571
+ "--color-info-foreground": colors?.infoForeground || "#ffffff",
572
+ "--color-glass": colors?.glass || "rgba(255, 255, 255, 0.7)",
573
+ "--color-glass-border": colors?.glassBorder || "rgba(0, 0, 0, 0.1)",
574
+ // Semantic color aliases (matching README examples)
575
+ "--color-text-primary": colors?.foreground || "#0a0a0a",
576
+ "--color-text-secondary": colors?.foregroundSecondary || "#525252",
577
+ "--color-text-muted": colors?.foregroundTertiary || "#a3a3a3",
578
+ "--color-surface": colors?.backgroundSecondary || colors?.background || "#fafafa",
579
+ "--color-border": colors?.border || colors?.glassBorder || "rgba(0, 0, 0, 0.1)",
580
+ "--color-focus": colors?.accent || colors?.primary || "#0070f3",
581
+ // Links and focus rings (can be overridden by derived tokens)
582
+ "--color-link": colors?.link || colors?.primary || "#0a0a0a",
583
+ "--color-ring": colors?.ring || colors?.primary || "#0a0a0a",
584
+ // Interactive states
585
+ "--color-hover": colors?.hover || colors?.backgroundSecondary || "#fafafa",
586
+ "--color-active": colors?.active || colors?.backgroundTertiary || "#f0f0f0",
587
+ "--color-link-hover": colors?.linkHover || colors?.primary || "#0a0a0a",
588
+ "--color-link-hover-foreground": colors?.linkHoverForeground || colors?.background || "#ffffff",
589
+ // Effects - Blur
590
+ "--effect-blur-sm": effects?.blur?.sm || "blur(4px)",
591
+ "--effect-blur-md": effects?.blur?.md || "blur(8px)",
592
+ "--effect-blur-lg": effects?.blur?.lg || "blur(16px)",
593
+ "--effect-blur-xl": effects?.blur?.xl || effects?.blur?.lg || "blur(24px)",
594
+ // Effects - Shadow
595
+ "--effect-shadow-sm": effects?.shadow?.sm || "0 1px 2px 0 rgba(0, 0, 0, 0.05)",
596
+ "--effect-shadow-md": effects?.shadow?.md || effects?.shadow?.sm || "0 4px 6px -1px rgba(0, 0, 0, 0.1)",
597
+ "--effect-shadow-lg": effects?.shadow?.lg || effects?.shadow?.md || effects?.shadow?.sm || "0 10px 15px -3px rgba(0, 0, 0, 0.1)",
598
+ // Typography - Font Families
599
+ "--font-heading": fonts?.heading || (theme === "sage" && fonts?.serif ? fonts.serif : fonts?.sans) || "var(--font-studio-heading)",
600
+ "--font-body": fonts?.body || fonts?.sans || "var(--font-studio-body)",
601
+ "--font-mono": fonts?.mono || "var(--font-studio-mono)",
602
+ // Motion - These are accessed programmatically via tokens
603
+ // but we can set defaults for CSS animations
604
+ "--ease-default": tokens?.motion?.ease?.default || "cubic-bezier(0.4, 0, 0.2, 1)",
605
+ "--ease-spring": tokens?.motion?.ease?.spring || tokens?.motion?.ease?.default || "cubic-bezier(0.16, 1, 0.3, 1)",
606
+ // Syntax Highlighting - Based on VS Code Dark+ theme
607
+ "--syntax-comment": mode === "light" ? syntaxColors.light.comment : syntaxColors.dark.comment,
608
+ "--syntax-keyword": mode === "light" ? syntaxColors.light.keyword : syntaxColors.dark.keyword,
609
+ "--syntax-function": mode === "light" ? syntaxColors.light.function : syntaxColors.dark.function,
610
+ "--syntax-string": mode === "light" ? syntaxColors.light.string : syntaxColors.dark.string,
611
+ "--syntax-number": mode === "light" ? syntaxColors.light.number : syntaxColors.dark.number,
612
+ "--syntax-boolean": mode === "light" ? syntaxColors.light.boolean : syntaxColors.dark.boolean,
613
+ "--syntax-operator": mode === "light" ? syntaxColors.light.operator : syntaxColors.dark.operator,
614
+ "--syntax-property": mode === "light" ? syntaxColors.light.property : syntaxColors.dark.property,
615
+ "--syntax-className": mode === "light" ? syntaxColors.light.className : syntaxColors.dark.className,
616
+ "--syntax-tag": mode === "light" ? syntaxColors.light.tag : syntaxColors.dark.tag,
617
+ "--syntax-attribute": mode === "light" ? syntaxColors.light.attribute : syntaxColors.dark.attribute,
618
+ "--syntax-variable": mode === "light" ? syntaxColors.light.variable : syntaxColors.dark.variable,
619
+ "--syntax-punctuation": mode === "light" ? syntaxColors.light.punctuation : syntaxColors.dark.punctuation,
620
+ "--syntax-plain": mode === "light" ? syntaxColors.light.plain : syntaxColors.dark.plain,
621
+ // Code Block Backgrounds and Borders - Accessible contrast (WCAG AA 4.5:1)
622
+ "--code-block-bg": mode === "light" ? codeColors.light.blockBackground : codeColors.dark.blockBackground,
623
+ "--code-inline-bg": mode === "light" ? codeColors.light.inlineBackground : codeColors.dark.inlineBackground,
624
+ "--code-border": mode === "light" ? codeColors.light.border : codeColors.dark.border
625
+ };
626
+ }
627
+ function mergeCustomColorTokens(baseTokens, customPalette) {
628
+ return {
629
+ ...baseTokens,
630
+ // Override primary color
631
+ "--color-primary": customPalette.primary,
632
+ "--color-primary-foreground": customPalette.primaryForeground,
633
+ // Apply color scale (for utilities like bg-primary/50)
634
+ "--color-primary-50": customPalette.scale[50],
635
+ "--color-primary-100": customPalette.scale[100],
636
+ "--color-primary-200": customPalette.scale[200],
637
+ "--color-primary-300": customPalette.scale[300],
638
+ "--color-primary-400": customPalette.scale[400],
639
+ "--color-primary-500": customPalette.scale[500],
640
+ "--color-primary-600": customPalette.scale[600],
641
+ "--color-primary-700": customPalette.scale[700],
642
+ "--color-primary-800": customPalette.scale[800],
643
+ "--color-primary-900": customPalette.scale[900],
644
+ // Override secondary color if provided (advanced mode)
645
+ ...customPalette.secondary && {
646
+ "--color-secondary": customPalette.secondary,
647
+ "--color-secondary-foreground": customPalette.secondaryForeground || baseTokens["--color-secondary-foreground"]
648
+ },
649
+ // Override accent color if provided (advanced mode)
650
+ ...customPalette.accent && {
651
+ "--color-accent": customPalette.accent,
652
+ "--color-accent-foreground": customPalette.accentForeground || baseTokens["--color-accent-foreground"]
653
+ },
654
+ // Apply ALL derived tokens from dependency graph
655
+ // This automatically updates:
656
+ // - Links (--color-link, --color-link-hover)
657
+ // - Focus rings (--color-ring)
658
+ // - Charts (--chart-1, --chart-2, --chart-3, --chart-4, --chart-5)
659
+ // - Buttons, badges, and any other dependent tokens
660
+ ...customPalette.derivedTokens
661
+ };
662
+ }
663
+ function ThemeProvider({ children }) {
664
+ const { theme, mode } = useThemeStore();
665
+ const customPalette = useCustomizer((state) => state.customColors?.[theme]?.[mode]);
666
+ const [isTransitioning, setIsTransitioning] = useState(false);
667
+ const [mounted, setMounted] = useState(false);
668
+ useEffect(() => {
669
+ setMounted(true);
670
+ }, []);
671
+ useEffect(() => {
672
+ if (!mounted) return;
673
+ setIsTransitioning(true);
674
+ const root = document.documentElement;
675
+ const baseTokens = getThemeVars(theme, mode);
676
+ console.log("[ThemeProvider] Update:", {
677
+ theme,
678
+ mode,
679
+ hasCustomPalette: !!customPalette,
680
+ customPrimary: customPalette?.primary,
681
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
682
+ });
683
+ const finalTokens = customPalette ? mergeCustomColorTokens(baseTokens, customPalette) : baseTokens;
684
+ root.classList.add("theme-transitioning");
685
+ Object.entries(finalTokens).forEach(([key, value]) => {
686
+ root.style.setProperty(key, value);
687
+ });
688
+ root.setAttribute("data-theme", theme);
689
+ root.setAttribute("data-mode", mode);
690
+ root.setAttribute("data-custom-colors", customPalette ? "active" : "default");
691
+ if (mode === "dark") {
692
+ root.classList.add("dark");
693
+ } else {
694
+ root.classList.remove("dark");
695
+ }
696
+ const timeout = setTimeout(() => {
697
+ root.classList.remove("theme-transitioning");
698
+ setIsTransitioning(false);
699
+ }, 400);
700
+ return () => clearTimeout(timeout);
701
+ }, [theme, mode, mounted, customPalette]);
702
+ if (!mounted) {
703
+ return null;
704
+ }
705
+ return /* @__PURE__ */ jsx(Fragment, { children });
706
+ }
707
+ export {
708
+ ThemeProvider
709
+ };
710
+ //# sourceMappingURL=providers.mjs.map