lumina-slides 8.9.4 → 9.0.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.
Files changed (119) hide show
  1. package/LUMINA_LLM_EXAMPLES.json +234 -0
  2. package/README.md +18 -18
  3. package/dist/lumina-slides.js +13207 -12659
  4. package/dist/lumina-slides.umd.cjs +215 -215
  5. package/dist/style.css +1 -1
  6. package/package.json +5 -4
  7. package/src/App.vue +16 -0
  8. package/src/animation/index.ts +11 -0
  9. package/src/animation/registry.ts +126 -0
  10. package/src/animation/stagger.ts +95 -0
  11. package/src/animation/types.ts +53 -0
  12. package/src/components/LandingPage.vue +229 -0
  13. package/src/components/LuminaDeck.vue +224 -0
  14. package/src/components/LuminaSpeakerNotes.vue +701 -0
  15. package/src/components/base/BaseSlide.vue +122 -0
  16. package/src/components/base/LuminaElement.vue +67 -0
  17. package/src/components/base/VideoPlayer.vue +204 -0
  18. package/src/components/layouts/LayoutAuto.vue +71 -0
  19. package/src/components/layouts/LayoutChart.vue +287 -0
  20. package/src/components/layouts/LayoutCustom.vue +92 -0
  21. package/src/components/layouts/LayoutDiagram.vue +253 -0
  22. package/src/components/layouts/LayoutFeatures.vue +121 -0
  23. package/src/components/layouts/LayoutFlex.vue +172 -0
  24. package/src/components/layouts/LayoutFree.vue +62 -0
  25. package/src/components/layouts/LayoutHalf.vue +127 -0
  26. package/src/components/layouts/LayoutStatement.vue +74 -0
  27. package/src/components/layouts/LayoutSteps.vue +106 -0
  28. package/src/components/layouts/LayoutTimeline.vue +104 -0
  29. package/src/components/layouts/LayoutVideo.vue +41 -0
  30. package/src/components/parts/FlexBullets.vue +45 -0
  31. package/src/components/parts/FlexButton.vue +132 -0
  32. package/src/components/parts/FlexImage.vue +54 -0
  33. package/src/components/parts/FlexOrdered.vue +44 -0
  34. package/src/components/parts/FlexSpacer.vue +13 -0
  35. package/src/components/parts/FlexStepper.vue +59 -0
  36. package/src/components/parts/FlexText.vue +29 -0
  37. package/src/components/parts/FlexTimeline.vue +67 -0
  38. package/src/components/parts/FlexTitle.vue +39 -0
  39. package/src/components/parts/LuminaBackground.vue +100 -0
  40. package/src/components/site/LivePreview.vue +101 -0
  41. package/src/components/site/SiteApi.vue +301 -0
  42. package/src/components/site/SiteDashboard.vue +604 -0
  43. package/src/components/site/SiteDocs.vue +3267 -0
  44. package/src/components/site/SiteExamples.vue +65 -0
  45. package/src/components/site/SiteFooter.vue +6 -0
  46. package/src/components/site/SiteHome.vue +362 -0
  47. package/src/components/site/SiteNavBar.vue +122 -0
  48. package/src/components/site/SitePlayground.vue +389 -0
  49. package/src/components/site/SitePromptBuilder.vue +266 -0
  50. package/src/components/site/SiteUserMenu.vue +90 -0
  51. package/src/components/studio/ActionEditor.vue +108 -0
  52. package/src/components/studio/ArrayEditor.vue +124 -0
  53. package/src/components/studio/CollapsibleSection.vue +33 -0
  54. package/src/components/studio/ColorField.vue +22 -0
  55. package/src/components/studio/EditorCanvas.vue +326 -0
  56. package/src/components/studio/EditorLayoutFeatures.vue +18 -0
  57. package/src/components/studio/EditorLayoutFixed.vue +46 -0
  58. package/src/components/studio/EditorLayoutFlex.vue +133 -0
  59. package/src/components/studio/EditorLayoutHalf.vue +18 -0
  60. package/src/components/studio/EditorLayoutStatement.vue +18 -0
  61. package/src/components/studio/EditorLayoutSteps.vue +18 -0
  62. package/src/components/studio/EditorLayoutTimeline.vue +18 -0
  63. package/src/components/studio/EditorNode.vue +89 -0
  64. package/src/components/studio/FieldEditor.vue +133 -0
  65. package/src/components/studio/IconPicker.vue +109 -0
  66. package/src/components/studio/LayerItem.vue +117 -0
  67. package/src/components/studio/LuminaStudio.vue +30 -0
  68. package/src/components/studio/SaveSuccessModal.vue +138 -0
  69. package/src/components/studio/SlideNavigator.vue +373 -0
  70. package/src/components/studio/SliderField.vue +44 -0
  71. package/src/components/studio/StudioInspector.vue +595 -0
  72. package/src/components/studio/StudioJsonEditor.vue +191 -0
  73. package/src/components/studio/StudioLayers.vue +145 -0
  74. package/src/components/studio/StudioSettings.vue +514 -0
  75. package/src/components/studio/StudioSidebar.vue +29 -0
  76. package/src/components/studio/StudioToolbar.vue +222 -0
  77. package/src/components/studio/fieldLabels.ts +224 -0
  78. package/src/components/studio/inspectors/DiagramEdgeEditor.vue +77 -0
  79. package/src/components/studio/inspectors/DiagramNodeEditor.vue +117 -0
  80. package/src/components/studio/nodes/StudioDiagramNode.vue +138 -0
  81. package/src/composables/useAuth.ts +87 -0
  82. package/src/composables/useEditor.ts +224 -0
  83. package/src/composables/useElementState.ts +81 -0
  84. package/src/composables/useFlexLayout.ts +122 -0
  85. package/src/composables/useKeyboard.ts +45 -0
  86. package/src/composables/useLumina.ts +32 -0
  87. package/src/composables/useStudio.ts +87 -0
  88. package/src/composables/useSwipeNav.ts +53 -0
  89. package/src/composables/useTransition.ts +373 -0
  90. package/src/core/Lumina.ts +819 -0
  91. package/src/core/animationConfig.ts +251 -0
  92. package/src/core/compression.ts +34 -0
  93. package/src/core/elementController.ts +170 -0
  94. package/src/core/elementId.ts +27 -0
  95. package/src/core/elementResolver.ts +207 -0
  96. package/src/core/events.ts +53 -0
  97. package/src/core/fonts.ts +100 -0
  98. package/src/core/presets.ts +231 -0
  99. package/src/core/prompts.ts +272 -0
  100. package/src/core/schema.ts +478 -0
  101. package/src/core/speaker-channel.ts +250 -0
  102. package/src/core/store.ts +461 -0
  103. package/src/core/theme.ts +666 -0
  104. package/src/core/types.ts +1611 -0
  105. package/src/directives/vStudio.ts +45 -0
  106. package/src/index.ts +175 -0
  107. package/src/main.ts +17 -0
  108. package/src/router/index.ts +92 -0
  109. package/src/style/main.css +462 -0
  110. package/src/utils/deep.ts +127 -0
  111. package/src/utils/firebase.ts +184 -0
  112. package/src/utils/streaming.ts +134 -0
  113. package/src/views/DashboardView.vue +32 -0
  114. package/src/views/DeckView.vue +289 -0
  115. package/src/views/HomeView.vue +17 -0
  116. package/src/views/SiteLayout.vue +21 -0
  117. package/src/views/StudioView.vue +61 -0
  118. package/src/vite-env.d.ts +6 -0
  119. package/IMPLEMENTATION.md +0 -418
@@ -0,0 +1,666 @@
1
+ import type { ThemeConfig } from './types';
2
+ import { THEME_PRESETS } from './presets';
3
+ import { loadGoogleFont } from './fonts';
4
+
5
+ /**
6
+ * Default Theme Values
7
+
8
+ * Complete set of default design tokens for the Lumina Engine.
9
+ */
10
+ export const DEFAULT_THEME: Required<ThemeConfig> = {
11
+ colors: {
12
+ // Core
13
+ primary: '#3b82f6',
14
+ secondary: '#8b5cf6',
15
+ accent: '#06b6d4',
16
+ background: '#030303',
17
+ surface: '#0a0a0a',
18
+ text: '#ffffff',
19
+ textSecondary: '#e5e7eb',
20
+ muted: '#9ca3af',
21
+
22
+ // Semantic
23
+ success: '#10b981',
24
+ warning: '#f59e0b',
25
+ danger: '#ef4444',
26
+ info: '#3b82f6',
27
+
28
+ // UI Elements
29
+ border: 'rgba(255, 255, 255, 0.1)',
30
+ borderSubtle: 'rgba(255, 255, 255, 0.05)',
31
+ shadow: '#000000',
32
+ overlay: 'rgba(0, 0, 0, 0.5)',
33
+ highlight: 'rgba(59, 130, 246, 0.2)',
34
+
35
+ // Interactive
36
+ buttonPrimary: '#3b82f6',
37
+ buttonPrimaryText: '#ffffff',
38
+ buttonSecondary: 'rgba(255, 255, 255, 0.1)',
39
+ buttonSecondaryText: '#ffffff',
40
+ link: '#3b82f6',
41
+ linkHover: '#60a5fa',
42
+
43
+ // Gradients
44
+ gradientFrom: '#3b82f6',
45
+ gradientVia: undefined,
46
+ gradientTo: '#8b5cf6',
47
+ },
48
+
49
+ typography: {
50
+ fontFamily: {
51
+ heading: 'Inter, system-ui, sans-serif',
52
+ body: 'Inter, system-ui, sans-serif',
53
+ mono: 'ui-monospace, SFMono-Regular, monospace',
54
+ },
55
+ fontSize: {
56
+ xs: '0.75rem',
57
+ sm: '0.875rem',
58
+ base: '1rem',
59
+ lg: '1.125rem',
60
+ xl: '1.25rem',
61
+ '2xl': '1.5rem',
62
+ '3xl': '1.875rem',
63
+ '4xl': '2.25rem',
64
+ '5xl': '3rem',
65
+ '6xl': '3.75rem',
66
+ '7xl': '4.5rem',
67
+ },
68
+ fontWeight: {
69
+ light: 300,
70
+ normal: 400,
71
+ medium: 500,
72
+ semibold: 600,
73
+ bold: 800,
74
+ extrabold: 900,
75
+ },
76
+ lineHeight: {
77
+ tight: '1.1',
78
+ snug: '1.25',
79
+ normal: '1.5',
80
+ relaxed: '1.625',
81
+ loose: '2',
82
+ },
83
+ letterSpacing: {
84
+ tighter: '-0.05em',
85
+ tight: '-0.025em',
86
+ normal: '0',
87
+ wide: '0.025em',
88
+ wider: '0.05em',
89
+ widest: '0.1em',
90
+ },
91
+ },
92
+
93
+ spacing: {
94
+ none: '0',
95
+ xs: '0.25rem',
96
+ sm: '0.5rem',
97
+ md: '1rem',
98
+ lg: '1.5rem',
99
+ xl: '2rem',
100
+ '2xl': '3rem',
101
+ '3xl': '4rem',
102
+ '4xl': '6rem',
103
+ },
104
+
105
+ borderRadius: {
106
+ none: '0',
107
+ sm: '0.25rem',
108
+ md: '0.5rem',
109
+ lg: '0.75rem',
110
+ xl: '1rem',
111
+ '2xl': '1.5rem',
112
+ '3xl': '2rem',
113
+ full: '9999px',
114
+ },
115
+
116
+ effects: {
117
+ // Gradients
118
+ useGradients: true,
119
+ gradientDirection: 'to-b',
120
+ gradientFrom: undefined, // Falls back to colors.gradientFrom
121
+ gradientVia: undefined,
122
+ gradientTo: undefined, // Falls back to colors.gradientTo
123
+
124
+ // Shadows
125
+ useShadows: true,
126
+ shadowIntensity: 'md',
127
+ shadowColor: 'rgba(0, 0, 0, 0.4)',
128
+
129
+ // Glass
130
+ useGlass: true,
131
+ glassOpacity: 0.12,
132
+ glassBlur: '20px',
133
+ glassBorderOpacity: 0.20,
134
+
135
+ // Orb
136
+ useOrb: true,
137
+ orbOpacity: 0.20,
138
+ orbBlur: '120px',
139
+ orbSize: '80vw',
140
+
141
+ // Animations
142
+ animationsEnabled: true,
143
+ transitionDuration: '0.3s',
144
+ transitionEasing: 'cubic-bezier(0.16, 1, 0.3, 1)',
145
+ hoverScale: 1.05,
146
+ },
147
+
148
+ components: {
149
+ // Buttons
150
+ buttonRadius: '9999px',
151
+ buttonPadding: '0.75rem 1.5rem',
152
+ buttonFontWeight: 700,
153
+ buttonTextTransform: 'none',
154
+
155
+ // Cards
156
+ cardRadius: '1.5rem',
157
+ cardPadding: '2rem',
158
+ cardBorderWidth: '1px',
159
+ cardBackground: undefined, // Uses surface color
160
+
161
+ // Timeline
162
+ timelineNodeSize: '1rem',
163
+ timelineLineWidth: '2px',
164
+ timelineNodeColor: undefined, // Uses primary
165
+ timelineLineColor: undefined, // Uses border
166
+
167
+ // Steps
168
+ stepBadgeSize: '2.5rem',
169
+ stepFontSize: '1rem',
170
+
171
+ // Progress
172
+ progressHeight: '0.5rem',
173
+ progressRadius: '9999px',
174
+ progressBackground: 'rgba(255, 255, 255, 0.1)',
175
+ progressFill: undefined, // Uses primary
176
+
177
+ // Tags
178
+ tagPadding: '0.25rem 1rem',
179
+ tagRadius: '9999px',
180
+ tagFontSize: '0.75rem',
181
+
182
+ // Inputs
183
+ inputRadius: '0.5rem',
184
+ inputPadding: '0.75rem 1rem',
185
+ inputBorder: undefined, // Uses border color
186
+ inputFocusBorder: undefined, // Uses primary
187
+ },
188
+
189
+ // Legacy (empty object - use typography.fontFamily instead)
190
+ fonts: {},
191
+ };
192
+
193
+ const DEFAULT_THEME_ID = 'lumina-theme-styles';
194
+
195
+ /**
196
+ * Deep merge utility for theme configuration.
197
+ * Exported for use when merging meta.themeConfig with direct meta overrides.
198
+ */
199
+ export function deepMerge<T extends Record<string, any>>(target: T, source: Partial<T>): T {
200
+ const result = { ...target };
201
+
202
+ for (const key in source) {
203
+ if (Object.prototype.hasOwnProperty.call(source, key)) {
204
+ const sourceValue = source[key];
205
+ const targetValue = target[key];
206
+
207
+ if (
208
+ sourceValue !== null &&
209
+ sourceValue !== undefined &&
210
+ typeof sourceValue === 'object' &&
211
+ !Array.isArray(sourceValue) &&
212
+ typeof targetValue === 'object' &&
213
+ !Array.isArray(targetValue)
214
+ ) {
215
+ result[key] = deepMerge(targetValue, sourceValue);
216
+ } else if (sourceValue !== undefined) {
217
+ result[key] = sourceValue as any;
218
+ }
219
+ }
220
+ }
221
+
222
+ return result;
223
+ }
224
+
225
+ /**
226
+ * THEME MANAGER
227
+ * Generates and injects CSS Custom Properties based on the configuration.
228
+ */
229
+ export class ThemeManager {
230
+ /**
231
+ * Injects the requested theme into the document head.
232
+ *
233
+ * @param theme - A preset name ('ocean', 'cyber') or a complete ThemeConfig object.
234
+ * @param overrides - Optional additional overrides to merge on top.
235
+ */
236
+ static inject(theme: ThemeConfig | string, overrides?: ThemeConfig) {
237
+ let config: ThemeConfig;
238
+
239
+ // 1. Resolve Base Preset or Config
240
+ if (typeof theme === 'string') {
241
+ const preset = THEME_PRESETS[theme] || THEME_PRESETS['default'];
242
+ config = deepMerge(DEFAULT_THEME, preset);
243
+ } else {
244
+ config = deepMerge(DEFAULT_THEME, theme);
245
+ }
246
+
247
+ // 2. Apply Overrides
248
+ if (overrides) {
249
+ config = deepMerge(config, overrides);
250
+ }
251
+
252
+ // 3. Handle legacy fonts -> typography migration
253
+ if (config.fonts && !config.typography?.fontFamily) {
254
+ config.typography = config.typography || {};
255
+ config.typography.fontFamily = {
256
+ heading: config.fonts.heading,
257
+ body: config.fonts.body,
258
+ mono: config.fonts.mono,
259
+ };
260
+ }
261
+
262
+ let styleEl = document.getElementById(DEFAULT_THEME_ID);
263
+
264
+ if (!styleEl) {
265
+ styleEl = document.createElement('style');
266
+ styleEl.id = DEFAULT_THEME_ID;
267
+ document.head.appendChild(styleEl);
268
+ }
269
+
270
+ const vars = this.generateVars(config);
271
+ styleEl.innerHTML = `:root { ${vars} }`;
272
+ }
273
+
274
+ /**
275
+ * Gets the current resolved theme configuration.
276
+ */
277
+ static getDefaultTheme(): Required<ThemeConfig> {
278
+ return DEFAULT_THEME;
279
+ }
280
+
281
+ /**
282
+ * Converts a ThemeConfig object into a CSS string of custom properties.
283
+ */
284
+ private static generateVars(theme: ThemeConfig): string {
285
+ const cssVars: string[] = [];
286
+
287
+ // --- Colors ---
288
+ if (theme.colors) {
289
+ for (const [key, value] of Object.entries(theme.colors)) {
290
+ if (value) {
291
+ const cssKey = this.camelToKebab(key);
292
+ cssVars.push(`--lumina-color-${cssKey}: ${value};`);
293
+
294
+ // RGB conversion for opacity usage
295
+ const rgb = this.hexToRgb(value);
296
+ if (rgb) {
297
+ cssVars.push(`--lumina-color-${cssKey}-rgb: ${rgb.r}, ${rgb.g}, ${rgb.b};`);
298
+ }
299
+ }
300
+ }
301
+ }
302
+
303
+ // --- Typography ---
304
+ if (theme.typography) {
305
+ const typo = theme.typography;
306
+
307
+ // Font Families
308
+ if (typo.fontFamily) {
309
+ for (const [key, value] of Object.entries(typo.fontFamily)) {
310
+ if (value) {
311
+ cssVars.push(`--lumina-font-${key}: ${value};`);
312
+ // Auto-load if it's a known Google Font
313
+ loadGoogleFont(value);
314
+ }
315
+ }
316
+ }
317
+
318
+ // Font Sizes
319
+ if (typo.fontSize) {
320
+ for (const [key, value] of Object.entries(typo.fontSize)) {
321
+ if (value) cssVars.push(`--lumina-text-${key}: ${value};`);
322
+ }
323
+ }
324
+
325
+ // Font Weights
326
+ if (typo.fontWeight) {
327
+ for (const [key, value] of Object.entries(typo.fontWeight)) {
328
+ if (value !== undefined) cssVars.push(`--lumina-font-weight-${key}: ${value};`);
329
+ }
330
+ }
331
+
332
+ // Line Heights
333
+ if (typo.lineHeight) {
334
+ for (const [key, value] of Object.entries(typo.lineHeight)) {
335
+ if (value) cssVars.push(`--lumina-leading-${key}: ${value};`);
336
+ }
337
+ }
338
+
339
+ // Letter Spacing
340
+ if (typo.letterSpacing) {
341
+ for (const [key, value] of Object.entries(typo.letterSpacing)) {
342
+ if (value) cssVars.push(`--lumina-tracking-${key}: ${value};`);
343
+ }
344
+ }
345
+ }
346
+
347
+ // --- Spacing ---
348
+ if (theme.spacing) {
349
+ for (const [key, value] of Object.entries(theme.spacing)) {
350
+ if (value) cssVars.push(`--lumina-space-${key}: ${value};`);
351
+ }
352
+ }
353
+
354
+ // --- Border Radius ---
355
+ if (theme.borderRadius) {
356
+ for (const [key, value] of Object.entries(theme.borderRadius)) {
357
+ if (value) cssVars.push(`--lumina-radius-${key}: ${value};`);
358
+ }
359
+ }
360
+
361
+ // --- Effects ---
362
+ if (theme.effects) {
363
+ const fx = theme.effects;
364
+
365
+ // Booleans as 0/1 for CSS calc tricks
366
+ cssVars.push(`--lumina-use-gradients: ${fx.useGradients ? 1 : 0};`);
367
+ cssVars.push(`--lumina-use-shadows: ${fx.useShadows ? 1 : 0};`);
368
+ cssVars.push(`--lumina-use-glass: ${fx.useGlass ? 1 : 0};`);
369
+ cssVars.push(`--lumina-use-orb: ${fx.useOrb ? 1 : 0};`);
370
+ cssVars.push(`--lumina-use-animations: ${fx.animationsEnabled ? 1 : 0};`);
371
+
372
+ // Gradient Logic
373
+ if (fx.useGradients) {
374
+ cssVars.push(`--lumina-gradient-text-fill: transparent;`);
375
+ cssVars.push(`--lumina-gradient-bg-clip: text;`);
376
+ } else {
377
+ cssVars.push(`--lumina-gradient-text-fill: currentColor;`);
378
+ cssVars.push(`--lumina-gradient-bg-clip: border-box;`);
379
+ }
380
+
381
+ // Gradient Colors
382
+ if (fx.gradientDirection) cssVars.push(`--lumina-gradient-direction: ${this.gradientDirectionToCSS(fx.gradientDirection)};`);
383
+ if (fx.gradientFrom) cssVars.push(`--lumina-gradient-from: ${fx.gradientFrom};`);
384
+ if (fx.gradientVia) cssVars.push(`--lumina-gradient-via: ${fx.gradientVia};`);
385
+ if (fx.gradientTo) cssVars.push(`--lumina-gradient-to: ${fx.gradientTo};`);
386
+
387
+ // Shadows
388
+ if (fx.shadowIntensity) cssVars.push(`--lumina-shadow-intensity: ${fx.shadowIntensity};`);
389
+ if (fx.shadowColor) cssVars.push(`--lumina-shadow-color: ${fx.shadowColor};`);
390
+
391
+ // Glass
392
+ if (fx.glassOpacity !== undefined) cssVars.push(`--lumina-glass-opacity: ${fx.glassOpacity};`);
393
+ if (fx.glassBlur) cssVars.push(`--lumina-glass-blur: ${fx.glassBlur};`);
394
+ if (fx.glassBorderOpacity !== undefined) cssVars.push(`--lumina-glass-border-opacity: ${fx.glassBorderOpacity};`);
395
+
396
+ // Orb
397
+ if (fx.orbOpacity !== undefined) cssVars.push(`--lumina-orb-opacity: ${fx.orbOpacity};`);
398
+ if (fx.orbBlur) cssVars.push(`--lumina-orb-blur: ${fx.orbBlur};`);
399
+ if (fx.orbSize) cssVars.push(`--lumina-orb-size: ${fx.orbSize};`);
400
+
401
+ // Animations
402
+ if (fx.transitionDuration) cssVars.push(`--lumina-transition-duration: ${fx.transitionDuration};`);
403
+ if (fx.transitionEasing) cssVars.push(`--lumina-transition-easing: ${fx.transitionEasing};`);
404
+ if (fx.hoverScale !== undefined) cssVars.push(`--lumina-hover-scale: ${fx.hoverScale};`);
405
+ }
406
+
407
+ // --- Components ---
408
+ if (theme.components) {
409
+ const comp = theme.components;
410
+
411
+ // Buttons
412
+ if (comp.buttonRadius) cssVars.push(`--lumina-button-radius: ${comp.buttonRadius};`);
413
+ if (comp.buttonPadding) cssVars.push(`--lumina-button-padding: ${comp.buttonPadding};`);
414
+ if (comp.buttonFontWeight !== undefined) cssVars.push(`--lumina-button-font-weight: ${comp.buttonFontWeight};`);
415
+ if (comp.buttonTextTransform) cssVars.push(`--lumina-button-text-transform: ${comp.buttonTextTransform};`);
416
+
417
+ // Cards
418
+ if (comp.cardRadius) cssVars.push(`--lumina-card-radius: ${comp.cardRadius};`);
419
+ if (comp.cardPadding) cssVars.push(`--lumina-card-padding: ${comp.cardPadding};`);
420
+ if (comp.cardBorderWidth) cssVars.push(`--lumina-card-border-width: ${comp.cardBorderWidth};`);
421
+ if (comp.cardBackground) cssVars.push(`--lumina-card-background: ${comp.cardBackground};`);
422
+
423
+ // Timeline
424
+ if (comp.timelineNodeSize) cssVars.push(`--lumina-timeline-node-size: ${comp.timelineNodeSize};`);
425
+ if (comp.timelineLineWidth) cssVars.push(`--lumina-timeline-line-width: ${comp.timelineLineWidth};`);
426
+ if (comp.timelineNodeColor) cssVars.push(`--lumina-timeline-node-color: ${comp.timelineNodeColor};`);
427
+ if (comp.timelineLineColor) cssVars.push(`--lumina-timeline-line-color: ${comp.timelineLineColor};`);
428
+
429
+ // Steps
430
+ if (comp.stepBadgeSize) cssVars.push(`--lumina-step-badge-size: ${comp.stepBadgeSize};`);
431
+ if (comp.stepFontSize) cssVars.push(`--lumina-step-font-size: ${comp.stepFontSize};`);
432
+
433
+ // Progress
434
+ if (comp.progressHeight) cssVars.push(`--lumina-progress-height: ${comp.progressHeight};`);
435
+ if (comp.progressRadius) cssVars.push(`--lumina-progress-radius: ${comp.progressRadius};`);
436
+ if (comp.progressBackground) cssVars.push(`--lumina-progress-background: ${comp.progressBackground};`);
437
+ if (comp.progressFill) cssVars.push(`--lumina-progress-fill: ${comp.progressFill};`);
438
+
439
+ // Tags
440
+ if (comp.tagPadding) cssVars.push(`--lumina-tag-padding: ${comp.tagPadding};`);
441
+ if (comp.tagRadius) cssVars.push(`--lumina-tag-radius: ${comp.tagRadius};`);
442
+ if (comp.tagFontSize) cssVars.push(`--lumina-tag-font-size: ${comp.tagFontSize};`);
443
+
444
+ // Inputs
445
+ if (comp.inputRadius) cssVars.push(`--lumina-input-radius: ${comp.inputRadius};`);
446
+ if (comp.inputPadding) cssVars.push(`--lumina-input-padding: ${comp.inputPadding};`);
447
+ if (comp.inputBorder) cssVars.push(`--lumina-input-border: ${comp.inputBorder};`);
448
+ if (comp.inputFocusBorder) cssVars.push(`--lumina-input-focus-border: ${comp.inputFocusBorder};`);
449
+ }
450
+
451
+ // Legacy support: also generate old variable names for backwards compatibility
452
+ if (theme.colors?.primary) cssVars.push(`--lumina-colors-primary: ${theme.colors.primary};`);
453
+ if (theme.colors?.background) cssVars.push(`--lumina-colors-background: ${theme.colors.background};`);
454
+ if (theme.colors?.text) cssVars.push(`--lumina-colors-text: ${theme.colors.text};`);
455
+ if (theme.colors?.muted) cssVars.push(`--lumina-colors-muted: ${theme.colors.muted};`);
456
+
457
+ // --- Auto-Contrast (Intelligent text color calculation) ---
458
+ const autoContrastVars = this.generateAutoContrastVars(theme);
459
+ cssVars.push(...autoContrastVars);
460
+
461
+ return cssVars.join(' ');
462
+ }
463
+
464
+ /**
465
+ * Convert camelCase to kebab-case.
466
+ */
467
+ private static camelToKebab(str: string): string {
468
+ return str.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();
469
+ }
470
+
471
+ /**
472
+ * Convert gradient direction token to CSS value.
473
+ */
474
+ private static gradientDirectionToCSS(dir: string): string {
475
+ const map: Record<string, string> = {
476
+ 'to-t': 'to top',
477
+ 'to-b': 'to bottom',
478
+ 'to-l': 'to left',
479
+ 'to-r': 'to right',
480
+ 'to-tl': 'to top left',
481
+ 'to-tr': 'to top right',
482
+ 'to-bl': 'to bottom left',
483
+ 'to-br': 'to bottom right',
484
+ };
485
+ return map[dir] || 'to bottom';
486
+ }
487
+
488
+ /**
489
+ * Helper to parse a Hex color string into RGB components.
490
+ * Supports both #RGB and #RRGGBB formats.
491
+ */
492
+ private static hexToRgb(hex: string): { r: number; g: number; b: number } | null {
493
+ // Handle shorthand hex (#RGB)
494
+ const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
495
+ hex = hex.replace(shorthandRegex, (_, r, g, b) => r + r + g + g + b + b);
496
+
497
+ const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
498
+ return result ? {
499
+ r: parseInt(result[1], 16),
500
+ g: parseInt(result[2], 16),
501
+ b: parseInt(result[3], 16)
502
+ } : null;
503
+ }
504
+
505
+ /**
506
+ * Calculate relative luminance of a color (WCAG formula).
507
+ * Returns value between 0 (black) and 1 (white).
508
+ */
509
+ private static getLuminance(hex: string): number {
510
+ const rgb = this.hexToRgb(hex);
511
+ if (!rgb) return 0;
512
+
513
+ // Convert to sRGB
514
+ const toLinear = (c: number) => {
515
+ const sRGB = c / 255;
516
+ return sRGB <= 0.03928 ? sRGB / 12.92 : Math.pow((sRGB + 0.055) / 1.055, 2.4);
517
+ };
518
+
519
+ const R = toLinear(rgb.r);
520
+ const G = toLinear(rgb.g);
521
+ const B = toLinear(rgb.b);
522
+
523
+ return 0.2126 * R + 0.7152 * G + 0.0722 * B;
524
+ }
525
+
526
+ /**
527
+ * Determine if a background color is "light" or "dark".
528
+ * Returns true if the color is light (should use dark text).
529
+ */
530
+ private static isLightColor(hex: string): boolean {
531
+ return this.getLuminance(hex) > 0.4;
532
+ }
533
+
534
+ /**
535
+ * Get optimal contrasting text color for a given background.
536
+ * Returns dark or light text color based on background luminance.
537
+ */
538
+ private static getContrastTextColor(bgHex: string, darkText = '#1a1a1a', lightText = '#ffffff'): string {
539
+ return this.isLightColor(bgHex) ? darkText : lightText;
540
+ }
541
+
542
+ /**
543
+ * Get optimal muted text color for a given background.
544
+ */
545
+ private static getContrastMutedColor(bgHex: string): string {
546
+ return this.isLightColor(bgHex) ? '#4b5563' : '#9ca3af';
547
+ }
548
+
549
+ /**
550
+ * Extract hex color from various formats (hex, rgb, rgba, gradient).
551
+ * Returns the first valid hex color found, or null.
552
+ */
553
+ private static extractHexColor(color: string): string | null {
554
+ if (!color) return null;
555
+
556
+ // Handle direct hex
557
+ if (color.startsWith('#')) {
558
+ return color.split(' ')[0];
559
+ }
560
+
561
+ // Handle rgb/rgba
562
+ if (color.startsWith('rgb')) {
563
+ const values = color.match(/\d+/g);
564
+ if (values && values.length >= 3) {
565
+ const r = parseInt(values[0]);
566
+ const g = parseInt(values[1]);
567
+ const b = parseInt(values[2]);
568
+ return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
569
+ }
570
+ }
571
+
572
+ // Try to find hex in string (for gradients)
573
+ const hexMatch = color.match(/#[a-fA-F0-9]{3,6}/);
574
+ if (hexMatch) return hexMatch[0];
575
+
576
+ return null;
577
+ }
578
+
579
+ /**
580
+ * Generate auto-contrast CSS variables based on background color.
581
+ * These variables automatically adjust text colors for readability.
582
+ * CRITICAL: Also overrides the main color variables when necessary.
583
+ */
584
+ private static generateAutoContrastVars(theme: ThemeConfig): string[] {
585
+ const cssVars: string[] = [];
586
+
587
+ // Get background color
588
+ const bgColor = theme.colors?.background;
589
+ if (!bgColor) return cssVars;
590
+
591
+ const bgHex = this.extractHexColor(bgColor);
592
+ if (!bgHex) return cssVars;
593
+
594
+ const isLight = this.isLightColor(bgHex);
595
+
596
+ // Auto text colors based on background
597
+ const autoText = isLight ? '#000000' : '#ffffff';
598
+ const autoMuted = isLight ? '#374151' : '#9ca3af'; // Darker mute for light mode (accessibility fix)
599
+ const autoTextSecondary = isLight ? '#374151' : '#d1d5db';
600
+
601
+ cssVars.push(`--lumina-auto-text: ${autoText};`);
602
+ cssVars.push(`--lumina-auto-text-secondary: ${autoTextSecondary};`);
603
+ cssVars.push(`--lumina-auto-muted: ${autoMuted};`);
604
+ cssVars.push(`--lumina-auto-is-light: ${isLight ? '1' : '0'};`);
605
+
606
+ // Check contrast and override when necessary
607
+ const userText = theme.colors?.text;
608
+ const textHex = userText ? this.extractHexColor(userText) : null;
609
+
610
+ if (textHex) {
611
+ const textLum = this.getLuminance(textHex);
612
+ const bgLum = this.getLuminance(bgHex);
613
+ const contrastRatio = (Math.max(textLum, bgLum) + 0.05) / (Math.min(textLum, bgLum) + 0.05);
614
+
615
+ // If contrast is poor, FORCE override the main variables
616
+ if (contrastRatio < 3) {
617
+ cssVars.push(`--lumina-color-text-safe: ${autoText};`);
618
+ cssVars.push(`--lumina-color-muted-safe: ${autoMuted};`);
619
+ // FORCE OVERRIDE the main variables
620
+ cssVars.push(`--lumina-color-text: ${autoText};`);
621
+ cssVars.push(`--lumina-color-muted: ${autoMuted};`);
622
+ cssVars.push(`--lumina-color-text-secondary: ${autoTextSecondary};`);
623
+ } else {
624
+ cssVars.push(`--lumina-color-text-safe: ${userText};`);
625
+ cssVars.push(`--lumina-color-muted-safe: ${theme.colors?.muted || autoMuted};`);
626
+ }
627
+ } else {
628
+ cssVars.push(`--lumina-color-text-safe: ${autoText};`);
629
+ cssVars.push(`--lumina-color-muted-safe: ${autoMuted};`);
630
+ }
631
+
632
+ // For LIGHT backgrounds, generate special glass panel colors
633
+ if (isLight) {
634
+ // Glass panels on light backgrounds need dark overlays
635
+ cssVars.push(`--lumina-color-text-rgb: 0, 0, 0;`);
636
+ cssVars.push(`--lumina-glass-opacity: 0.06;`);
637
+ cssVars.push(`--lumina-glass-border-opacity: 0.15;`);
638
+ // Surface should be white for readability
639
+ cssVars.push(`--lumina-color-surface: #ffffff;`);
640
+ cssVars.push(`--lumina-surface-text: #1a1a1a;`);
641
+ cssVars.push(`--lumina-surface-muted: #4b5563;`);
642
+ cssVars.push(`--lumina-shadow-color: rgba(0, 0, 0, 0.15);`);
643
+ // Critical Overrides for Light Mode (High Contrast for Accessibility)
644
+ cssVars.push(`--lumina-color-border: rgba(0, 0, 0, 0.12);`);
645
+ cssVars.push(`--lumina-color-border-subtle: rgba(0, 0, 0, 0.08);`);
646
+ // Muted text must be dark enough to be legible on light backgrounds (Global Fix)
647
+ cssVars.push(`--lumina-color-muted: #374151;`); // gray-700 instead of gray-500
648
+ cssVars.push(`--lumina-color-text-secondary: #1f2937;`); // gray-800
649
+ } else {
650
+ // Dark theme surface
651
+ const surfaceColor = theme.colors?.surface;
652
+ if (surfaceColor) {
653
+ const surfaceHex = this.extractHexColor(surfaceColor);
654
+ if (surfaceHex) {
655
+ const surfaceText = this.getContrastTextColor(surfaceHex);
656
+ const surfaceMuted = this.getContrastMutedColor(surfaceHex);
657
+ cssVars.push(`--lumina-surface-text: ${surfaceText};`);
658
+ cssVars.push(`--lumina-surface-muted: ${surfaceMuted};`);
659
+ }
660
+ }
661
+ }
662
+
663
+ return cssVars;
664
+ }
665
+ }
666
+