@snowcone-app/ui 0.1.43 → 0.2.1

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.
Files changed (196) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/README.md +18 -4
  3. package/dist/index.cjs +5 -2
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.js +5 -2
  6. package/dist/index.js.map +1 -1
  7. package/package.json +9 -5
  8. package/src/components/CanvasIsolationBoundary.tsx +202 -0
  9. package/src/components/LoadingOverlayPrism.tsx +251 -0
  10. package/src/composed/AddToCart.tsx +229 -0
  11. package/src/composed/ArtAlignment.tsx +703 -0
  12. package/src/composed/ArtSelector.tsx +290 -0
  13. package/src/composed/ArtworkCustomizer.tsx +212 -0
  14. package/src/composed/CanvasEditor.tsx +79 -0
  15. package/src/composed/ColorPicker.tsx +111 -0
  16. package/src/composed/CurrentSelectionDisplay.tsx +86 -0
  17. package/src/composed/HeroProductImage.tsx +1079 -0
  18. package/src/composed/Lightbox.index.ts +2 -0
  19. package/src/composed/Lightbox.tsx +230 -0
  20. package/src/composed/PlacementClipShapeSelector.tsx +88 -0
  21. package/src/composed/PlacementTabs.tsx +179 -0
  22. package/src/composed/ProductCard.tsx +298 -0
  23. package/src/composed/ProductGallery.tsx +54 -0
  24. package/src/composed/ProductImage.tsx +129 -0
  25. package/src/composed/ProductList.tsx +147 -0
  26. package/src/composed/ProductOptions.tsx +305 -0
  27. package/src/composed/RealtimeMockup.tsx +121 -0
  28. package/src/composed/TileCount.tsx +348 -0
  29. package/src/composed/carousels/HeroCarousel.tsx +240 -0
  30. package/src/composed/carousels/MobileProductCarousel.tsx +1002 -0
  31. package/src/composed/carousels/index.ts +11 -0
  32. package/src/composed/carousels/types.ts +58 -0
  33. package/src/composed/grids/MasonryGrid.tsx +238 -0
  34. package/src/composed/grids/index.ts +9 -0
  35. package/src/composed/search/CurrentRefinements.tsx +80 -0
  36. package/src/composed/search/Filters.tsx +49 -0
  37. package/src/composed/search/FiltersButton.tsx +57 -0
  38. package/src/composed/search/FiltersDrawer.tsx +375 -0
  39. package/src/composed/search/ProductGrid.tsx +118 -0
  40. package/src/composed/search/ProductHit.tsx +56 -0
  41. package/src/composed/search/SearchBox.tsx +109 -0
  42. package/src/composed/search/SearchProvider.tsx +136 -0
  43. package/src/composed/search/facetConfig.ts +16 -0
  44. package/src/composed/search/index.ts +22 -0
  45. package/src/composed/search/meilisearchAdapter.ts +20 -0
  46. package/src/composed/search/types.ts +22 -0
  47. package/src/composed/zoom/EnhancedImageViewer.tsx +505 -0
  48. package/src/composed/zoom/ResponsiveZoom.tsx +134 -0
  49. package/src/composed/zoom/ZoomOverlay.tsx +194 -0
  50. package/src/composed/zoom/index.ts +12 -0
  51. package/src/composed/zoom/types.ts +12 -0
  52. package/src/design-system/ColorPalette.tsx +126 -0
  53. package/src/design-system/ColorSwatch.tsx +49 -0
  54. package/src/design-system/DesignSystemPage.tsx +130 -0
  55. package/src/design-system/ThemeSwitcher.tsx +181 -0
  56. package/src/design-system/TypographyScale.tsx +106 -0
  57. package/src/design-system/index.ts +5 -0
  58. package/src/ecommerce/stories/HeroProductImage.stories.tsx +66 -0
  59. package/src/ecommerce/stories/PDPHeroGallery.stories.tsx +105 -0
  60. package/src/ecommerce/stories/PDPInfoPanel.stories.tsx +472 -0
  61. package/src/ecommerce/stories/PDPLayout.stories.tsx +365 -0
  62. package/src/hooks/useBrand.ts +41 -0
  63. package/src/hooks/useCanvasContext.ts +127 -0
  64. package/src/hooks/useDeviceDetection.ts +64 -0
  65. package/src/hooks/useFocusTrap.ts +70 -0
  66. package/src/hooks/useImagePreloader.ts +268 -0
  67. package/src/hooks/useImageTransition.ts +608 -0
  68. package/src/hooks/usePlacementsProcessor.ts +74 -0
  69. package/src/hooks/useProductGallery.ts +193 -0
  70. package/src/hooks/useProductPage.ts +467 -0
  71. package/src/hooks/useRenderGuard.ts +96 -0
  72. package/src/hooks/useScrollDirection.ts +196 -0
  73. package/src/hooks/viewport/index.ts +25 -0
  74. package/src/hooks/viewport/useContainerWidth.ts +59 -0
  75. package/src/hooks/viewport/useMediaQuery.ts +52 -0
  76. package/src/hooks/viewport/useResponsiveImageCap.ts +149 -0
  77. package/src/hooks/viewport/useViewportDimensions.ts +135 -0
  78. package/src/hooks/viewport/useWideMonitorMode.ts +150 -0
  79. package/src/hooks/visibility/index.ts +15 -0
  80. package/src/hooks/visibility/observerPool.ts +150 -0
  81. package/src/index.ts +240 -0
  82. package/src/layouts/hero-zoom/HeroShrinkLayout.tsx +209 -0
  83. package/src/layouts/hero-zoom/HeroZoomLayout.tsx +351 -0
  84. package/src/layouts/hero-zoom/index.ts +30 -0
  85. package/src/layouts/hero-zoom/stories/HeroZoomLayout.stories.tsx +350 -0
  86. package/src/layouts/hero-zoom/types.ts +113 -0
  87. package/src/layouts/hero-zoom/useHeroZoomScales.ts +156 -0
  88. package/src/layouts/index.ts +9 -0
  89. package/src/layouts/pdp/EdgeBlurBox.tsx +210 -0
  90. package/src/layouts/pdp/ImageBlurExtension.tsx +215 -0
  91. package/src/layouts/pdp/ImageEdgeBlur.tsx +215 -0
  92. package/src/layouts/pdp/PDPLayout.tsx +246 -0
  93. package/src/layouts/pdp/SimpleImageBlur.tsx +140 -0
  94. package/src/layouts/pdp/index.ts +40 -0
  95. package/src/lib/env.ts +15 -0
  96. package/src/lib/locale.ts +167 -0
  97. package/src/lib/router.tsx +46 -0
  98. package/src/lib/utils.ts +6 -0
  99. package/src/lightbox/README.md +77 -0
  100. package/src/next/index.tsx +26 -0
  101. package/src/patterns/MockupPriorityProvider.tsx +1014 -0
  102. package/src/patterns/Product.tsx +850 -0
  103. package/src/patterns/ProductPageProvider.tsx +224 -0
  104. package/src/patterns/RealtimeProvider.tsx +1162 -0
  105. package/src/patterns/ShopProvider.tsx +603 -0
  106. package/src/personalization/PersonalizationBridge.tsx +235 -0
  107. package/src/personalization/PersonalizationContext.ts +29 -0
  108. package/src/personalization/PersonalizationInputs.tsx +110 -0
  109. package/src/personalization/PersonalizationProvider.tsx +407 -0
  110. package/src/personalization/canvas-stub.d.ts +22 -0
  111. package/src/personalization/index.ts +43 -0
  112. package/src/personalization/types.ts +48 -0
  113. package/src/personalization/usePersonalization.ts +32 -0
  114. package/src/personalization/usePersonalizationShimmer.ts +159 -0
  115. package/src/personalization/utils.ts +59 -0
  116. package/src/primitives/BrandLogo.tsx +65 -0
  117. package/src/primitives/BrandName.tsx +51 -0
  118. package/src/primitives/Button.tsx +123 -0
  119. package/src/primitives/ColorSwatch.tsx +221 -0
  120. package/src/primitives/DragHintAnimation.tsx +190 -0
  121. package/src/primitives/EdgeSwipeGuards.tsx +60 -0
  122. package/src/primitives/FloatingActionGroup.tsx +176 -0
  123. package/src/primitives/ProductPrice.tsx +171 -0
  124. package/src/primitives/ProgressiveBlur.tsx +295 -0
  125. package/src/primitives/ThemeToggle.tsx +125 -0
  126. package/src/primitives/__tests__/story-coverage.test.ts +98 -0
  127. package/src/primitives/accordion.tsx +280 -0
  128. package/src/primitives/badge.tsx +137 -0
  129. package/src/primitives/card.tsx +61 -0
  130. package/src/primitives/checkbox.tsx +56 -0
  131. package/src/primitives/collapsible.tsx +51 -0
  132. package/src/primitives/drawer.tsx +828 -0
  133. package/src/primitives/dropdown-menu.tsx +197 -0
  134. package/src/primitives/fieldset.tsx +73 -0
  135. package/src/primitives/index.ts +138 -0
  136. package/src/primitives/input.tsx +91 -0
  137. package/src/primitives/kbd.tsx +130 -0
  138. package/src/primitives/label.tsx +20 -0
  139. package/src/primitives/link.tsx +182 -0
  140. package/src/primitives/popover.tsx +80 -0
  141. package/src/primitives/radio-group.tsx +79 -0
  142. package/src/primitives/scroll-fade.tsx +159 -0
  143. package/src/primitives/select.tsx +170 -0
  144. package/src/primitives/separator.tsx +25 -0
  145. package/src/primitives/slider.tsx +221 -0
  146. package/src/primitives/spinner.tsx +72 -0
  147. package/src/primitives/stories/Accordion.stories.tsx +121 -0
  148. package/src/primitives/stories/Badge.stories.tsx +221 -0
  149. package/src/primitives/stories/Button.stories.tsx +185 -0
  150. package/src/primitives/stories/Card.stories.tsx +171 -0
  151. package/src/primitives/stories/Checkbox.stories.tsx +214 -0
  152. package/src/primitives/stories/Collapsible.stories.tsx +230 -0
  153. package/src/primitives/stories/Drawer.stories.tsx +378 -0
  154. package/src/primitives/stories/DropdownMenu.stories.tsx +182 -0
  155. package/src/primitives/stories/Fieldset.stories.tsx +212 -0
  156. package/src/primitives/stories/Input.stories.tsx +172 -0
  157. package/src/primitives/stories/Kbd.stories.tsx +183 -0
  158. package/src/primitives/stories/Label.stories.tsx +98 -0
  159. package/src/primitives/stories/Link.stories.tsx +260 -0
  160. package/src/primitives/stories/Popover.stories.tsx +178 -0
  161. package/src/primitives/stories/RadioGroup.stories.tsx +205 -0
  162. package/src/primitives/stories/Select.stories.tsx +222 -0
  163. package/src/primitives/stories/Separator.stories.tsx +134 -0
  164. package/src/primitives/stories/Slider.stories.tsx +203 -0
  165. package/src/primitives/stories/Spinner.stories.tsx +142 -0
  166. package/src/primitives/stories/Surface.stories.tsx +257 -0
  167. package/src/primitives/stories/Switch.stories.tsx +131 -0
  168. package/src/primitives/stories/Tabs.stories.tsx +275 -0
  169. package/src/primitives/stories/TextField.stories.tsx +139 -0
  170. package/src/primitives/stories/Textarea.stories.tsx +148 -0
  171. package/src/primitives/stories/Tooltip.stories.tsx +119 -0
  172. package/src/primitives/surface.tsx +86 -0
  173. package/src/primitives/switch.tsx +35 -0
  174. package/src/primitives/tabs.tsx +206 -0
  175. package/src/primitives/text-field.tsx +84 -0
  176. package/src/primitives/textarea.tsx +50 -0
  177. package/src/primitives/tooltip.tsx +58 -0
  178. package/src/services/CanvasExportService.ts +518 -0
  179. package/src/styles/base.css +380 -0
  180. package/src/styles/defaults.css +280 -0
  181. package/src/styles/globals.css +1242 -0
  182. package/src/styles/index.css +17 -0
  183. package/src/styles/ne-themes.css +4740 -0
  184. package/src/styles/tailwind.css +11 -0
  185. package/src/styles/tokens.css +117 -0
  186. package/src/styles/utilities.css +188 -0
  187. package/src/themes/apply-theme.ts +449 -0
  188. package/src/themes/getThemeStyles.ts +454 -0
  189. package/src/themes/index.ts +48 -0
  190. package/src/themes/oklch-theme.ts +283 -0
  191. package/src/themes/presets.ts +989 -0
  192. package/src/themes/types.ts +386 -0
  193. package/src/themes/useTheme.tsx +450 -0
  194. package/src/utils/dev-warnings.ts +161 -0
  195. package/src/utils/devWarnings.ts +153 -0
  196. package/dist/styles.css +0 -1
@@ -0,0 +1,989 @@
1
+ /**
2
+ * Theme Presets (OKLCH-Based)
3
+ *
4
+ * This is the single source of truth for all theme definitions.
5
+ * All apps (next-ecommerce, docs, canvas) use these same presets.
6
+ *
7
+ * OKLCH THEME SYSTEM:
8
+ * -------------------
9
+ * Themes are defined with just hue + chroma, and all colors are generated
10
+ * algorithmically. This ensures:
11
+ * - Consistent contrast ratios
12
+ * - Neutral foreground colors (no accidental tinting)
13
+ * - Mathematically correct color relationships
14
+ *
15
+ * THEME STRUCTURE:
16
+ * ----------------
17
+ * Themes are organized as "base themes" with light and dark variants.
18
+ * The dark mode toggle switches between variants of the same base theme.
19
+ */
20
+
21
+ import type { ThemePreset, OklchThemeConfig, RadiusPresetId } from './types';
22
+
23
+ // =============================================================================
24
+ // OKLCH THEME PRESETS (Source of Truth)
25
+ // =============================================================================
26
+
27
+ /**
28
+ * OKLCH-based theme configurations
29
+ *
30
+ * Define themes with just hue + chroma, everything else is computed.
31
+ * Hue reference: 0=red, 30=orange, 60=yellow, 120=green, 180=cyan, 240=blue, 270=purple, 300=magenta, 330=pink
32
+ */
33
+ export const oklchPresets: OklchThemeConfig[] = [
34
+ // ============================================================================
35
+ // SNOWCONE (Neutral achromatic with black primary — Snowcone brand)
36
+ // ============================================================================
37
+ {
38
+ name: 'Snowcone',
39
+ hue: 0,
40
+ chroma: 0.0,
41
+ isDark: false,
42
+ primaryLightness: 0.0, // Pure black primary
43
+ radiusPreset: 'default',
44
+ fontFamily: 'sans',
45
+ iconStrokeWidth: 'thin',
46
+ darkVariant: 'Snowcone Dark',
47
+ },
48
+ {
49
+ name: 'Snowcone Dark',
50
+ hue: 0,
51
+ chroma: 0.0,
52
+ isDark: true,
53
+ primaryLightness: 1.0, // Pure white primary
54
+ radiusPreset: 'default',
55
+ fontFamily: 'sans',
56
+ lightVariant: 'Snowcone',
57
+ },
58
+
59
+ // ============================================================================
60
+ // MINIMAL / MIDNIGHT (Neutral - achromatic)
61
+ // ============================================================================
62
+ {
63
+ name: 'Minimal',
64
+ hue: 0,
65
+ chroma: 0.0, // Pure achromatic
66
+ isDark: false,
67
+ radiusPreset: 'default',
68
+ fontFamily: 'sans',
69
+ iconStrokeWidth: 'thin',
70
+ darkVariant: 'Midnight',
71
+ },
72
+ {
73
+ name: 'Midnight',
74
+ hue: 0,
75
+ chroma: 0.0,
76
+ isDark: true,
77
+ radiusPreset: 'default',
78
+ fontFamily: 'sans',
79
+ lightVariant: 'Minimal',
80
+ },
81
+
82
+ // ============================================================================
83
+ // LINEAR (Indigo - hue ~250)
84
+ // ============================================================================
85
+ {
86
+ name: 'Linear',
87
+ hue: 250,
88
+ chroma: 0.15,
89
+ isDark: false,
90
+ radiusPreset: 'default',
91
+ fontFamily: 'sans',
92
+ darkVariant: 'Linear Dark',
93
+ },
94
+ {
95
+ name: 'Linear Dark',
96
+ hue: 250,
97
+ chroma: 0.18,
98
+ isDark: true,
99
+ radiusPreset: 'default',
100
+ fontFamily: 'sans',
101
+ iconStrokeWidth: 'thick',
102
+ lightVariant: 'Linear',
103
+ },
104
+
105
+ // ============================================================================
106
+ // RADIX (Violet - hue ~270)
107
+ // ============================================================================
108
+ {
109
+ name: 'Radix',
110
+ hue: 270,
111
+ chroma: 0.16,
112
+ isDark: false,
113
+ radiusPreset: 'default',
114
+ fontFamily: 'sans',
115
+ darkVariant: 'Radix Dark',
116
+ },
117
+ {
118
+ name: 'Radix Dark',
119
+ hue: 270,
120
+ chroma: 0.18,
121
+ isDark: true,
122
+ radiusPreset: 'default',
123
+ fontFamily: 'sans',
124
+ iconStrokeWidth: 'thick',
125
+ lightVariant: 'Radix',
126
+ },
127
+
128
+ // ============================================================================
129
+ // STRIPE (Indigo-blue - hue ~255)
130
+ // ============================================================================
131
+ {
132
+ name: 'Stripe',
133
+ hue: 255,
134
+ chroma: 0.18,
135
+ isDark: false,
136
+ radiusPreset: 'default',
137
+ fontFamily: 'sans',
138
+ darkVariant: 'Stripe Dark',
139
+ },
140
+ {
141
+ name: 'Stripe Dark',
142
+ hue: 255,
143
+ chroma: 0.18,
144
+ isDark: true,
145
+ radiusPreset: 'default',
146
+ fontFamily: 'sans',
147
+ iconStrokeWidth: 'thick',
148
+ lightVariant: 'Stripe',
149
+ },
150
+
151
+ // ============================================================================
152
+ // GITHUB (Green - hue ~145)
153
+ // ============================================================================
154
+ {
155
+ name: 'GitHub',
156
+ hue: 145,
157
+ chroma: 0.18,
158
+ isDark: false,
159
+ radiusPreset: 'default',
160
+ fontFamily: 'sans',
161
+ darkVariant: 'GitHub Dark',
162
+ },
163
+ {
164
+ name: 'GitHub Dark',
165
+ hue: 145,
166
+ chroma: 0.18,
167
+ isDark: true,
168
+ radiusPreset: 'default',
169
+ fontFamily: 'sans',
170
+ lightVariant: 'GitHub',
171
+ },
172
+
173
+ // ============================================================================
174
+ // VERCEL (Neutral - achromatic)
175
+ // ============================================================================
176
+ {
177
+ name: 'Vercel',
178
+ hue: 0,
179
+ chroma: 0.0,
180
+ isDark: false,
181
+ radiusPreset: 'default',
182
+ fontFamily: 'sans',
183
+ darkVariant: 'Vercel Dark',
184
+ },
185
+ {
186
+ name: 'Vercel Dark',
187
+ hue: 0,
188
+ chroma: 0.0,
189
+ isDark: true,
190
+ radiusPreset: 'default',
191
+ fontFamily: 'sans',
192
+ lightVariant: 'Vercel',
193
+ },
194
+
195
+ // ============================================================================
196
+ // NOTION (Neutral - sharp corners)
197
+ // ============================================================================
198
+ {
199
+ name: 'Notion',
200
+ hue: 0,
201
+ chroma: 0.0,
202
+ isDark: false,
203
+ radiusPreset: 'sharp',
204
+ fontFamily: 'sans',
205
+ darkVariant: 'Notion Dark',
206
+ },
207
+ {
208
+ name: 'Notion Dark',
209
+ hue: 0,
210
+ chroma: 0.0,
211
+ isDark: true,
212
+ radiusPreset: 'sharp',
213
+ fontFamily: 'sans',
214
+ lightVariant: 'Notion',
215
+ },
216
+
217
+ // ============================================================================
218
+ // MEDIUM (Green - editorial serif)
219
+ // ============================================================================
220
+ {
221
+ name: 'Medium',
222
+ hue: 145,
223
+ chroma: 0.20,
224
+ isDark: false,
225
+ radiusPreset: 'sharp',
226
+ fontFamily: 'serif',
227
+ darkVariant: 'Medium Dark',
228
+ fontPairing: {
229
+ id: 'medium-serif',
230
+ headingFont: 'ui-serif, Georgia, Cambria, "Times New Roman", Times, serif',
231
+ bodyFont: 'ui-serif, Georgia, Cambria, "Times New Roman", Times, serif',
232
+ labelFont: 'ui-serif, Georgia, Cambria, "Times New Roman", Times, serif',
233
+ buttonFont: 'ui-serif, Georgia, Cambria, "Times New Roman", Times, serif',
234
+ captionFont: 'ui-serif, Georgia, Cambria, "Times New Roman", Times, serif',
235
+ displayFont: 'ui-serif, Georgia, Cambria, "Times New Roman", Times, serif',
236
+ accentFont: 'ui-serif, Georgia, Cambria, "Times New Roman", Times, serif',
237
+ weights: {
238
+ bodyWeight: '400',
239
+ headingWeight: '700',
240
+ buttonWeight: '500',
241
+ labelWeight: '500',
242
+ captionWeight: '400',
243
+ displayWeight: '700',
244
+ },
245
+ },
246
+ },
247
+ {
248
+ name: 'Medium Dark',
249
+ hue: 145,
250
+ chroma: 0.20,
251
+ isDark: true,
252
+ radiusPreset: 'sharp',
253
+ fontFamily: 'serif',
254
+ lightVariant: 'Medium',
255
+ fontPairing: {
256
+ id: 'medium-serif-dark',
257
+ headingFont: 'ui-serif, Georgia, Cambria, "Times New Roman", Times, serif',
258
+ bodyFont: 'ui-serif, Georgia, Cambria, "Times New Roman", Times, serif',
259
+ labelFont: 'ui-serif, Georgia, Cambria, "Times New Roman", Times, serif',
260
+ buttonFont: 'ui-serif, Georgia, Cambria, "Times New Roman", Times, serif',
261
+ captionFont: 'ui-serif, Georgia, Cambria, "Times New Roman", Times, serif',
262
+ displayFont: 'ui-serif, Georgia, Cambria, "Times New Roman", Times, serif',
263
+ accentFont: 'ui-serif, Georgia, Cambria, "Times New Roman", Times, serif',
264
+ weights: {
265
+ bodyWeight: '400',
266
+ headingWeight: '700',
267
+ buttonWeight: '500',
268
+ labelWeight: '500',
269
+ captionWeight: '400',
270
+ displayWeight: '700',
271
+ },
272
+ },
273
+ },
274
+
275
+ // ============================================================================
276
+ // FRAMER (Cyan - playful with bold weights)
277
+ // ============================================================================
278
+ {
279
+ name: 'Framer',
280
+ hue: 200,
281
+ chroma: 0.20,
282
+ isDark: false,
283
+ radiusPreset: 'playful',
284
+ fontFamily: 'sans',
285
+ iconStrokeWidth: 'extra-thick',
286
+ darkVariant: 'Framer Dark',
287
+ fontPairing: {
288
+ id: 'framer-bold-light',
289
+ headingFont: 'ui-sans-serif, system-ui, sans-serif',
290
+ bodyFont: 'ui-sans-serif, system-ui, sans-serif',
291
+ labelFont: 'ui-sans-serif, system-ui, sans-serif',
292
+ buttonFont: 'ui-sans-serif, system-ui, sans-serif',
293
+ captionFont: 'ui-sans-serif, system-ui, sans-serif',
294
+ displayFont: 'ui-sans-serif, system-ui, sans-serif',
295
+ weights: {
296
+ bodyWeight: '400',
297
+ headingWeight: '700',
298
+ buttonWeight: '600',
299
+ labelWeight: '500',
300
+ captionWeight: '400',
301
+ displayWeight: '800',
302
+ },
303
+ },
304
+ },
305
+ {
306
+ name: 'Framer Dark',
307
+ hue: 200,
308
+ chroma: 0.20,
309
+ isDark: true,
310
+ radiusPreset: 'playful',
311
+ fontFamily: 'sans',
312
+ iconStrokeWidth: 'extra-thick',
313
+ lightVariant: 'Framer',
314
+ fontPairing: {
315
+ id: 'framer-bold',
316
+ headingFont: 'ui-sans-serif, system-ui, sans-serif',
317
+ bodyFont: 'ui-sans-serif, system-ui, sans-serif',
318
+ labelFont: 'ui-sans-serif, system-ui, sans-serif',
319
+ buttonFont: 'ui-sans-serif, system-ui, sans-serif',
320
+ captionFont: 'ui-sans-serif, system-ui, sans-serif',
321
+ displayFont: 'ui-sans-serif, system-ui, sans-serif',
322
+ weights: {
323
+ bodyWeight: '400',
324
+ headingWeight: '700',
325
+ buttonWeight: '600',
326
+ labelWeight: '500',
327
+ captionWeight: '400',
328
+ displayWeight: '800',
329
+ },
330
+ },
331
+ },
332
+
333
+ // ============================================================================
334
+ // FIGMA (Cyan - hue ~195)
335
+ // ============================================================================
336
+ {
337
+ name: 'Figma',
338
+ hue: 195,
339
+ chroma: 0.18,
340
+ isDark: false,
341
+ radiusPreset: 'sharp',
342
+ fontFamily: 'sans',
343
+ darkVariant: 'Figma Dark',
344
+ },
345
+ {
346
+ name: 'Figma Dark',
347
+ hue: 195,
348
+ chroma: 0.18,
349
+ isDark: true,
350
+ radiusPreset: 'sharp',
351
+ fontFamily: 'sans',
352
+ iconStrokeWidth: 'thick',
353
+ lightVariant: 'Figma',
354
+ },
355
+
356
+ // ============================================================================
357
+ // APPLE (Blue - hue ~220)
358
+ // ============================================================================
359
+ {
360
+ name: 'Apple',
361
+ hue: 220,
362
+ chroma: 0.18,
363
+ isDark: false,
364
+ radiusPreset: 'soft',
365
+ fontFamily: 'sans',
366
+ darkVariant: 'Apple Dark',
367
+ fontPairing: {
368
+ id: 'apple-clean',
369
+ headingFont: 'ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"',
370
+ bodyFont: 'ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"',
371
+ labelFont: 'ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"',
372
+ buttonFont: 'ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"',
373
+ captionFont: 'ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"',
374
+ displayFont: 'ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"',
375
+ weights: {
376
+ bodyWeight: '400',
377
+ headingWeight: '600',
378
+ buttonWeight: '500',
379
+ labelWeight: '500',
380
+ captionWeight: '400',
381
+ displayWeight: '700',
382
+ },
383
+ },
384
+ },
385
+ {
386
+ name: 'Apple Dark',
387
+ hue: 220,
388
+ chroma: 0.18,
389
+ isDark: true,
390
+ radiusPreset: 'soft',
391
+ fontFamily: 'sans',
392
+ iconStrokeWidth: 'thick',
393
+ lightVariant: 'Apple',
394
+ fontPairing: {
395
+ id: 'apple-clean-dark',
396
+ headingFont: 'ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"',
397
+ bodyFont: 'ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"',
398
+ labelFont: 'ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"',
399
+ buttonFont: 'ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"',
400
+ captionFont: 'ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"',
401
+ displayFont: 'ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"',
402
+ weights: {
403
+ bodyWeight: '400',
404
+ headingWeight: '600',
405
+ buttonWeight: '500',
406
+ labelWeight: '500',
407
+ captionWeight: '400',
408
+ displayWeight: '700',
409
+ },
410
+ },
411
+ },
412
+
413
+ // ============================================================================
414
+ // TAILWIND (Cyan - hue ~190)
415
+ // ============================================================================
416
+ {
417
+ name: 'Tailwind',
418
+ hue: 190,
419
+ chroma: 0.18,
420
+ isDark: false,
421
+ radiusPreset: 'default',
422
+ fontFamily: 'sans',
423
+ darkVariant: 'Tailwind Dark',
424
+ },
425
+ {
426
+ name: 'Tailwind Dark',
427
+ hue: 190,
428
+ chroma: 0.20,
429
+ isDark: true,
430
+ radiusPreset: 'default',
431
+ fontFamily: 'sans',
432
+ iconStrokeWidth: 'thick',
433
+ lightVariant: 'Tailwind',
434
+ },
435
+
436
+ // ============================================================================
437
+ // SQUIRCLE (Violet - soft/pill-shaped)
438
+ // ============================================================================
439
+ {
440
+ name: 'Squircle',
441
+ hue: 270,
442
+ chroma: 0.16,
443
+ isDark: false,
444
+ radiusPreset: 'soft',
445
+ fontFamily: 'sans',
446
+ darkVariant: 'Squircle Dark',
447
+ },
448
+ {
449
+ name: 'Squircle Dark',
450
+ hue: 270,
451
+ chroma: 0.18,
452
+ isDark: true,
453
+ radiusPreset: 'soft',
454
+ fontFamily: 'sans',
455
+ iconStrokeWidth: 'thick',
456
+ lightVariant: 'Squircle',
457
+ },
458
+
459
+ // ============================================================================
460
+ // PILL UI (Blue - pill-shaped)
461
+ // ============================================================================
462
+ {
463
+ name: 'Pill UI',
464
+ hue: 220,
465
+ chroma: 0.18,
466
+ isDark: false,
467
+ radiusPreset: 'soft',
468
+ fontFamily: 'sans',
469
+ darkVariant: 'Pill UI Dark',
470
+ },
471
+ {
472
+ name: 'Pill UI Dark',
473
+ hue: 220,
474
+ chroma: 0.18,
475
+ isDark: true,
476
+ radiusPreset: 'soft',
477
+ fontFamily: 'sans',
478
+ iconStrokeWidth: 'thick',
479
+ lightVariant: 'Pill UI',
480
+ },
481
+
482
+ // ============================================================================
483
+ // MODERN STORE (Blue - e-commerce focused)
484
+ // ============================================================================
485
+ {
486
+ name: 'Modern Store',
487
+ hue: 225,
488
+ chroma: 0.20,
489
+ isDark: false,
490
+ radiusPreset: 'default',
491
+ fontFamily: 'montserrat-combo',
492
+ iconStrokeWidth: 'thick',
493
+ darkVariant: 'Modern Store Dark',
494
+ fontPairing: {
495
+ id: 'montserrat-bold',
496
+ headingFont: 'ui-sans-serif, system-ui, sans-serif',
497
+ bodyFont: 'ui-sans-serif, system-ui, sans-serif',
498
+ labelFont: 'ui-sans-serif, system-ui, sans-serif',
499
+ buttonFont: 'ui-sans-serif, system-ui, sans-serif',
500
+ captionFont: 'ui-sans-serif, system-ui, sans-serif',
501
+ displayFont: 'ui-sans-serif, system-ui, sans-serif',
502
+ weights: {
503
+ bodyWeight: '400',
504
+ headingWeight: '700',
505
+ buttonWeight: '600',
506
+ labelWeight: '500',
507
+ captionWeight: '400',
508
+ displayWeight: '700',
509
+ },
510
+ },
511
+ },
512
+ {
513
+ name: 'Modern Store Dark',
514
+ hue: 225,
515
+ chroma: 0.18,
516
+ isDark: true,
517
+ radiusPreset: 'default',
518
+ fontFamily: 'montserrat-combo',
519
+ iconStrokeWidth: 'thick',
520
+ lightVariant: 'Modern Store',
521
+ fontPairing: {
522
+ id: 'montserrat-bold-dark',
523
+ headingFont: 'ui-sans-serif, system-ui, sans-serif',
524
+ bodyFont: 'ui-sans-serif, system-ui, sans-serif',
525
+ labelFont: 'ui-sans-serif, system-ui, sans-serif',
526
+ buttonFont: 'ui-sans-serif, system-ui, sans-serif',
527
+ captionFont: 'ui-sans-serif, system-ui, sans-serif',
528
+ displayFont: 'ui-sans-serif, system-ui, sans-serif',
529
+ weights: {
530
+ bodyWeight: '400',
531
+ headingWeight: '700',
532
+ buttonWeight: '600',
533
+ labelWeight: '500',
534
+ captionWeight: '400',
535
+ displayWeight: '700',
536
+ },
537
+ },
538
+ },
539
+
540
+ // ============================================================================
541
+ // LUXURY RETAIL (Red - elegant serif)
542
+ // ============================================================================
543
+ {
544
+ name: 'Luxury Retail',
545
+ hue: 25,
546
+ chroma: 0.22,
547
+ isDark: false,
548
+ radiusPreset: 'sharp',
549
+ fontFamily: 'playfair-combo',
550
+ iconStrokeWidth: 'thin',
551
+ darkVariant: 'Luxury Retail Dark',
552
+ fontPairing: {
553
+ id: 'playfair-elegant',
554
+ headingFont: 'ui-serif, Georgia, Cambria, serif',
555
+ bodyFont: 'ui-sans-serif, system-ui, sans-serif',
556
+ buttonFont: 'ui-serif, Georgia, Cambria, serif',
557
+ labelFont: 'ui-sans-serif, system-ui, sans-serif',
558
+ captionFont: 'ui-sans-serif, system-ui, sans-serif',
559
+ displayFont: 'ui-serif, Georgia, Cambria, serif',
560
+ accentFont: 'ui-serif, Georgia, Cambria, serif',
561
+ weights: {
562
+ bodyWeight: '400',
563
+ headingWeight: '500',
564
+ buttonWeight: '500',
565
+ labelWeight: '500',
566
+ captionWeight: '400',
567
+ displayWeight: '600',
568
+ },
569
+ },
570
+ },
571
+ {
572
+ name: 'Luxury Retail Dark',
573
+ hue: 25,
574
+ chroma: 0.20,
575
+ isDark: true,
576
+ radiusPreset: 'default',
577
+ fontFamily: 'playfair-combo',
578
+ iconStrokeWidth: 'thin',
579
+ lightVariant: 'Luxury Retail',
580
+ fontPairing: {
581
+ id: 'playfair-elegant-dark',
582
+ headingFont: 'ui-serif, Georgia, Cambria, serif',
583
+ bodyFont: 'ui-sans-serif, system-ui, sans-serif',
584
+ buttonFont: 'ui-serif, Georgia, Cambria, serif',
585
+ labelFont: 'ui-sans-serif, system-ui, sans-serif',
586
+ captionFont: 'ui-sans-serif, system-ui, sans-serif',
587
+ displayFont: 'ui-serif, Georgia, Cambria, serif',
588
+ accentFont: 'ui-serif, Georgia, Cambria, serif',
589
+ weights: {
590
+ bodyWeight: '300',
591
+ headingWeight: '400',
592
+ buttonWeight: '500',
593
+ labelWeight: '400',
594
+ captionWeight: '300',
595
+ displayWeight: '500',
596
+ },
597
+ },
598
+ },
599
+
600
+ // ============================================================================
601
+ // CORPORATE PRO (Emerald - professional)
602
+ // ============================================================================
603
+ {
604
+ name: 'Corporate Pro',
605
+ hue: 160,
606
+ chroma: 0.18,
607
+ isDark: false,
608
+ radiusPreset: 'sharp',
609
+ fontFamily: 'helvetica-combo',
610
+ darkVariant: 'Corporate Pro Dark',
611
+ fontPairing: {
612
+ id: 'helvetica-corporate',
613
+ headingFont: '"Helvetica Neue", Helvetica, Arial, ui-sans-serif, system-ui, sans-serif',
614
+ bodyFont: '"Helvetica Neue", Helvetica, Arial, ui-sans-serif, system-ui, sans-serif',
615
+ labelFont: '"Helvetica Neue", Helvetica, Arial, ui-sans-serif, system-ui, sans-serif',
616
+ buttonFont: '"Helvetica Neue", Helvetica, Arial, ui-sans-serif, system-ui, sans-serif',
617
+ captionFont: '"Helvetica Neue", Helvetica, Arial, ui-sans-serif, system-ui, sans-serif',
618
+ displayFont: '"Helvetica Neue", Helvetica, Arial, ui-sans-serif, system-ui, sans-serif',
619
+ weights: {
620
+ bodyWeight: '400',
621
+ headingWeight: '600',
622
+ buttonWeight: '500',
623
+ labelWeight: '500',
624
+ captionWeight: '400',
625
+ displayWeight: '700',
626
+ },
627
+ },
628
+ },
629
+ {
630
+ name: 'Corporate Pro Dark',
631
+ hue: 160,
632
+ chroma: 0.20,
633
+ isDark: true,
634
+ radiusPreset: 'sharp',
635
+ fontFamily: 'helvetica-combo',
636
+ lightVariant: 'Corporate Pro',
637
+ fontPairing: {
638
+ id: 'helvetica-corporate-dark',
639
+ headingFont: '"Helvetica Neue", Helvetica, Arial, ui-sans-serif, system-ui, sans-serif',
640
+ bodyFont: '"Helvetica Neue", Helvetica, Arial, ui-sans-serif, system-ui, sans-serif',
641
+ labelFont: '"Helvetica Neue", Helvetica, Arial, ui-sans-serif, system-ui, sans-serif',
642
+ buttonFont: '"Helvetica Neue", Helvetica, Arial, ui-sans-serif, system-ui, sans-serif',
643
+ captionFont: '"Helvetica Neue", Helvetica, Arial, ui-sans-serif, system-ui, sans-serif',
644
+ displayFont: '"Helvetica Neue", Helvetica, Arial, ui-sans-serif, system-ui, sans-serif',
645
+ weights: {
646
+ bodyWeight: '400',
647
+ headingWeight: '600',
648
+ buttonWeight: '500',
649
+ labelWeight: '500',
650
+ captionWeight: '400',
651
+ displayWeight: '700',
652
+ },
653
+ },
654
+ },
655
+
656
+ // ============================================================================
657
+ // NEON (Lime - electric)
658
+ // ============================================================================
659
+ {
660
+ name: 'Neon',
661
+ hue: 95,
662
+ chroma: 0.22,
663
+ isDark: false,
664
+ radiusPreset: 'default',
665
+ fontFamily: 'sans',
666
+ iconStrokeWidth: 'thick',
667
+ darkVariant: 'Neon Dark',
668
+ },
669
+ {
670
+ name: 'Neon Dark',
671
+ hue: 95,
672
+ chroma: 0.25,
673
+ isDark: true,
674
+ radiusPreset: 'playful',
675
+ fontFamily: 'sans',
676
+ iconStrokeWidth: 'extra-thick',
677
+ lightVariant: 'Neon',
678
+ },
679
+
680
+ // ============================================================================
681
+ // NEO BRUTAL (Pink/Yellow - raw brutalist)
682
+ // ============================================================================
683
+ {
684
+ name: 'Neo Brutal',
685
+ hue: 50, // Yellow-ish base
686
+ chroma: 0.25,
687
+ isDark: false,
688
+ radiusPreset: 'brutalist',
689
+ fontFamily: 'sans',
690
+ iconStrokeWidth: 'extra-thick',
691
+ darkVariant: 'Neo Brutal Dark',
692
+ fontPairing: {
693
+ id: 'brutal-bold',
694
+ headingFont: 'ui-sans-serif, system-ui, sans-serif',
695
+ bodyFont: 'ui-sans-serif, system-ui, sans-serif',
696
+ buttonFont: 'ui-sans-serif, system-ui, sans-serif',
697
+ weights: {
698
+ bodyWeight: '500',
699
+ headingWeight: '900',
700
+ buttonWeight: '700',
701
+ labelWeight: '600',
702
+ captionWeight: '500',
703
+ displayWeight: '900',
704
+ },
705
+ },
706
+ },
707
+ {
708
+ name: 'Neo Brutal Dark',
709
+ hue: 155, // Neon green on black
710
+ chroma: 0.30,
711
+ isDark: true,
712
+ radiusPreset: 'brutalist',
713
+ fontFamily: 'sans',
714
+ iconStrokeWidth: 'extra-thick',
715
+ lightVariant: 'Neo Brutal',
716
+ fontPairing: {
717
+ id: 'brutal-bold-dark',
718
+ headingFont: 'ui-sans-serif, system-ui, sans-serif',
719
+ bodyFont: 'ui-sans-serif, system-ui, sans-serif',
720
+ buttonFont: 'ui-sans-serif, system-ui, sans-serif',
721
+ weights: {
722
+ bodyWeight: '500',
723
+ headingWeight: '900',
724
+ buttonWeight: '700',
725
+ labelWeight: '600',
726
+ captionWeight: '500',
727
+ displayWeight: '900',
728
+ },
729
+ },
730
+ },
731
+
732
+ // ============================================================================
733
+ // AURORA (Indigo - ethereal glassmorphism)
734
+ // ============================================================================
735
+ {
736
+ name: 'Aurora',
737
+ hue: 250,
738
+ chroma: 0.16,
739
+ isDark: false,
740
+ radiusPreset: 'soft',
741
+ fontFamily: 'sans',
742
+ darkVariant: 'Aurora Dark',
743
+ fontPairing: {
744
+ id: 'aurora-ethereal',
745
+ headingFont: 'ui-sans-serif, system-ui, sans-serif',
746
+ bodyFont: 'ui-sans-serif, system-ui, sans-serif',
747
+ weights: {
748
+ bodyWeight: '400',
749
+ headingWeight: '600',
750
+ buttonWeight: '500',
751
+ labelWeight: '500',
752
+ captionWeight: '400',
753
+ displayWeight: '700',
754
+ },
755
+ },
756
+ },
757
+ {
758
+ name: 'Aurora Dark',
759
+ hue: 260,
760
+ chroma: 0.18,
761
+ isDark: true,
762
+ radiusPreset: 'soft',
763
+ fontFamily: 'sans',
764
+ lightVariant: 'Aurora',
765
+ fontPairing: {
766
+ id: 'aurora-ethereal-dark',
767
+ headingFont: 'ui-sans-serif, system-ui, sans-serif',
768
+ bodyFont: 'ui-sans-serif, system-ui, sans-serif',
769
+ weights: {
770
+ bodyWeight: '400',
771
+ headingWeight: '600',
772
+ buttonWeight: '500',
773
+ labelWeight: '500',
774
+ captionWeight: '400',
775
+ displayWeight: '700',
776
+ },
777
+ },
778
+ },
779
+
780
+ // ============================================================================
781
+ // CANDY (Pink - playful and bubbly)
782
+ // ============================================================================
783
+ {
784
+ name: 'Candy',
785
+ hue: 330,
786
+ chroma: 0.20,
787
+ isDark: false,
788
+ radiusPreset: 'playful',
789
+ fontFamily: 'sans',
790
+ iconStrokeWidth: 'thick',
791
+ darkVariant: 'Candy Dark',
792
+ fontPairing: {
793
+ id: 'candy-fun',
794
+ headingFont: 'ui-sans-serif, system-ui, sans-serif',
795
+ bodyFont: 'ui-sans-serif, system-ui, sans-serif',
796
+ weights: {
797
+ bodyWeight: '500',
798
+ headingWeight: '700',
799
+ buttonWeight: '600',
800
+ labelWeight: '600',
801
+ captionWeight: '500',
802
+ displayWeight: '800',
803
+ },
804
+ },
805
+ },
806
+ {
807
+ name: 'Candy Dark',
808
+ hue: 330,
809
+ chroma: 0.22,
810
+ isDark: true,
811
+ radiusPreset: 'playful',
812
+ fontFamily: 'sans',
813
+ iconStrokeWidth: 'thick',
814
+ lightVariant: 'Candy',
815
+ fontPairing: {
816
+ id: 'candy-fun-dark',
817
+ headingFont: 'ui-sans-serif, system-ui, sans-serif',
818
+ bodyFont: 'ui-sans-serif, system-ui, sans-serif',
819
+ weights: {
820
+ bodyWeight: '500',
821
+ headingWeight: '700',
822
+ buttonWeight: '600',
823
+ labelWeight: '600',
824
+ captionWeight: '500',
825
+ displayWeight: '800',
826
+ },
827
+ },
828
+ },
829
+ ];
830
+
831
+ // =============================================================================
832
+ // OKLCH TO LEGACY CONVERSION
833
+ // =============================================================================
834
+
835
+ /**
836
+ * Convert OKLCH lightness and chroma to approximate hex color
837
+ * This is a simplified conversion for backward compatibility
838
+ */
839
+ function oklchToHex(l: number, c: number, h: number): string {
840
+ // Convert OKLCH to sRGB (simplified approximation)
841
+ // For more accurate conversion, use a proper color library
842
+
843
+ // Normalize hue to radians
844
+ const hRad = (h * Math.PI) / 180;
845
+
846
+ // Convert to Lab-like intermediate (simplified)
847
+ const a = c * Math.cos(hRad);
848
+ const b = c * Math.sin(hRad);
849
+
850
+ // Approximate conversion to linear RGB
851
+ const l_ = l + 0.3963377774 * a + 0.2158037573 * b;
852
+ const m_ = l - 0.1055613458 * a - 0.0638541728 * b;
853
+ const s_ = l - 0.0894841775 * a - 1.2914855480 * b;
854
+
855
+ const l3 = l_ * l_ * l_;
856
+ const m3 = m_ * m_ * m_;
857
+ const s3 = s_ * s_ * s_;
858
+
859
+ // Linear RGB
860
+ let r = +4.0767416621 * l3 - 3.3077115913 * m3 + 0.2309699292 * s3;
861
+ let g = -1.2684380046 * l3 + 2.6097574011 * m3 - 0.3413193965 * s3;
862
+ let bl = -0.0041960863 * l3 - 0.7034186147 * m3 + 1.7076147010 * s3;
863
+
864
+ // Clamp and convert to sRGB
865
+ const toSRGB = (x: number) => {
866
+ x = Math.max(0, Math.min(1, x));
867
+ return x <= 0.0031308 ? 12.92 * x : 1.055 * Math.pow(x, 1 / 2.4) - 0.055;
868
+ };
869
+
870
+ r = Math.round(toSRGB(r) * 255);
871
+ g = Math.round(toSRGB(g) * 255);
872
+ bl = Math.round(toSRGB(bl) * 255);
873
+
874
+ // Clamp to valid range
875
+ r = Math.max(0, Math.min(255, r));
876
+ g = Math.max(0, Math.min(255, g));
877
+ bl = Math.max(0, Math.min(255, bl));
878
+
879
+ return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${bl.toString(16).padStart(2, '0')}`;
880
+ }
881
+
882
+ /**
883
+ * Convert OKLCH theme config to legacy ThemePreset format
884
+ * This provides backward compatibility with existing theme consumers
885
+ */
886
+ export function oklchToLegacyPreset(config: OklchThemeConfig): ThemePreset {
887
+ const { hue, isDark, chroma: primaryChroma = 0.18 } = config;
888
+
889
+ // Generate colors using OKLCH
890
+ const backgroundL = isDark ? 0.12 : 0.97;
891
+ const backgroundC = 0.008;
892
+ const foregroundL = isDark ? 0.96 : 0.15;
893
+ const foregroundC = 0.005;
894
+ const primaryL = isDark ? 0.70 : 0.55;
895
+
896
+ // Map radius preset to legacy borderRadius
897
+ const radiusToLegacy: Record<RadiusPresetId, ThemePreset['borderRadius']> = {
898
+ default: 'md',
899
+ sharp: 'sm',
900
+ soft: 'full',
901
+ brutalist: 'none',
902
+ playful: 'xl',
903
+ };
904
+
905
+ return {
906
+ name: config.name,
907
+ backgroundColor: oklchToHex(backgroundL, backgroundC, hue),
908
+ primaryColor: oklchToHex(primaryL, primaryChroma, hue),
909
+ textColor: oklchToHex(foregroundL, foregroundC, hue),
910
+ borderRadius: radiusToLegacy[config.radiusPreset || 'default'],
911
+ radiusPreset: config.radiusPreset,
912
+ fontFamily: config.fontFamily,
913
+ fontPairing: config.fontPairing,
914
+ iconStrokeWidth: config.iconStrokeWidth,
915
+ isDark: config.isDark,
916
+ lightVariant: config.lightVariant,
917
+ darkVariant: config.darkVariant,
918
+ };
919
+ }
920
+
921
+ // =============================================================================
922
+ // EXPORTS (Backward Compatible)
923
+ // =============================================================================
924
+
925
+ /**
926
+ * All theme presets (both light and dark variants)
927
+ * Converted from OKLCH to legacy format for backward compatibility
928
+ */
929
+ export const presetThemes: ThemePreset[] = oklchPresets.map(oklchToLegacyPreset);
930
+
931
+ /**
932
+ * Base themes - one entry per "theme family"
933
+ * Each has a light variant (the base) and a dark variant
934
+ * Used for the theme picker dropdown
935
+ */
936
+ export const baseThemes = presetThemes.filter((t) => !t.isDark);
937
+
938
+ /**
939
+ * Get the opposite variant of a theme (light <-> dark)
940
+ */
941
+ export function getThemeVariant(theme: ThemePreset, isDark: boolean): ThemePreset {
942
+ if (theme.isDark === isDark) {
943
+ return theme;
944
+ }
945
+
946
+ const variantName = isDark ? theme.darkVariant : theme.lightVariant;
947
+ if (!variantName) {
948
+ return theme;
949
+ }
950
+
951
+ const variant = presetThemes.find((t) => t.name === variantName);
952
+ return variant || theme;
953
+ }
954
+
955
+ /**
956
+ * Find a theme by name
957
+ */
958
+ export function findTheme(name: string): ThemePreset | undefined {
959
+ return presetThemes.find((t) => t.name === name);
960
+ }
961
+
962
+ /**
963
+ * Find a theme by its ID (data-theme value, e.g., "ne-github")
964
+ */
965
+ export function findThemeById(themeId: string): ThemePreset | undefined {
966
+ // Theme IDs are formatted as "ne-{name}" with lowercase and hyphens
967
+ // e.g., "ne-github", "ne-linear-dark"
968
+ return presetThemes.find((t) => {
969
+ const id = `ne-${t.name.toLowerCase().replace(/[^a-z0-9]/g, '-')}`;
970
+ return id === themeId;
971
+ });
972
+ }
973
+
974
+ /**
975
+ * Find an OKLCH theme by name
976
+ */
977
+ export function findOklchTheme(name: string): OklchThemeConfig | undefined {
978
+ return oklchPresets.find((t) => t.name === name);
979
+ }
980
+
981
+ /**
982
+ * Get the base theme name for a theme (handles both light and dark variants)
983
+ */
984
+ export function getBaseThemeName(theme: ThemePreset): string {
985
+ if (theme.isDark && theme.lightVariant) {
986
+ return theme.lightVariant;
987
+ }
988
+ return theme.name;
989
+ }