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.
- package/LUMINA_LLM_EXAMPLES.json +234 -0
- package/README.md +18 -18
- package/dist/lumina-slides.js +13207 -12659
- package/dist/lumina-slides.umd.cjs +215 -215
- package/dist/style.css +1 -1
- package/package.json +5 -4
- package/src/App.vue +16 -0
- package/src/animation/index.ts +11 -0
- package/src/animation/registry.ts +126 -0
- package/src/animation/stagger.ts +95 -0
- package/src/animation/types.ts +53 -0
- package/src/components/LandingPage.vue +229 -0
- package/src/components/LuminaDeck.vue +224 -0
- package/src/components/LuminaSpeakerNotes.vue +701 -0
- package/src/components/base/BaseSlide.vue +122 -0
- package/src/components/base/LuminaElement.vue +67 -0
- package/src/components/base/VideoPlayer.vue +204 -0
- package/src/components/layouts/LayoutAuto.vue +71 -0
- package/src/components/layouts/LayoutChart.vue +287 -0
- package/src/components/layouts/LayoutCustom.vue +92 -0
- package/src/components/layouts/LayoutDiagram.vue +253 -0
- package/src/components/layouts/LayoutFeatures.vue +121 -0
- package/src/components/layouts/LayoutFlex.vue +172 -0
- package/src/components/layouts/LayoutFree.vue +62 -0
- package/src/components/layouts/LayoutHalf.vue +127 -0
- package/src/components/layouts/LayoutStatement.vue +74 -0
- package/src/components/layouts/LayoutSteps.vue +106 -0
- package/src/components/layouts/LayoutTimeline.vue +104 -0
- package/src/components/layouts/LayoutVideo.vue +41 -0
- package/src/components/parts/FlexBullets.vue +45 -0
- package/src/components/parts/FlexButton.vue +132 -0
- package/src/components/parts/FlexImage.vue +54 -0
- package/src/components/parts/FlexOrdered.vue +44 -0
- package/src/components/parts/FlexSpacer.vue +13 -0
- package/src/components/parts/FlexStepper.vue +59 -0
- package/src/components/parts/FlexText.vue +29 -0
- package/src/components/parts/FlexTimeline.vue +67 -0
- package/src/components/parts/FlexTitle.vue +39 -0
- package/src/components/parts/LuminaBackground.vue +100 -0
- package/src/components/site/LivePreview.vue +101 -0
- package/src/components/site/SiteApi.vue +301 -0
- package/src/components/site/SiteDashboard.vue +604 -0
- package/src/components/site/SiteDocs.vue +3267 -0
- package/src/components/site/SiteExamples.vue +65 -0
- package/src/components/site/SiteFooter.vue +6 -0
- package/src/components/site/SiteHome.vue +362 -0
- package/src/components/site/SiteNavBar.vue +122 -0
- package/src/components/site/SitePlayground.vue +389 -0
- package/src/components/site/SitePromptBuilder.vue +266 -0
- package/src/components/site/SiteUserMenu.vue +90 -0
- package/src/components/studio/ActionEditor.vue +108 -0
- package/src/components/studio/ArrayEditor.vue +124 -0
- package/src/components/studio/CollapsibleSection.vue +33 -0
- package/src/components/studio/ColorField.vue +22 -0
- package/src/components/studio/EditorCanvas.vue +326 -0
- package/src/components/studio/EditorLayoutFeatures.vue +18 -0
- package/src/components/studio/EditorLayoutFixed.vue +46 -0
- package/src/components/studio/EditorLayoutFlex.vue +133 -0
- package/src/components/studio/EditorLayoutHalf.vue +18 -0
- package/src/components/studio/EditorLayoutStatement.vue +18 -0
- package/src/components/studio/EditorLayoutSteps.vue +18 -0
- package/src/components/studio/EditorLayoutTimeline.vue +18 -0
- package/src/components/studio/EditorNode.vue +89 -0
- package/src/components/studio/FieldEditor.vue +133 -0
- package/src/components/studio/IconPicker.vue +109 -0
- package/src/components/studio/LayerItem.vue +117 -0
- package/src/components/studio/LuminaStudio.vue +30 -0
- package/src/components/studio/SaveSuccessModal.vue +138 -0
- package/src/components/studio/SlideNavigator.vue +373 -0
- package/src/components/studio/SliderField.vue +44 -0
- package/src/components/studio/StudioInspector.vue +595 -0
- package/src/components/studio/StudioJsonEditor.vue +191 -0
- package/src/components/studio/StudioLayers.vue +145 -0
- package/src/components/studio/StudioSettings.vue +514 -0
- package/src/components/studio/StudioSidebar.vue +29 -0
- package/src/components/studio/StudioToolbar.vue +222 -0
- package/src/components/studio/fieldLabels.ts +224 -0
- package/src/components/studio/inspectors/DiagramEdgeEditor.vue +77 -0
- package/src/components/studio/inspectors/DiagramNodeEditor.vue +117 -0
- package/src/components/studio/nodes/StudioDiagramNode.vue +138 -0
- package/src/composables/useAuth.ts +87 -0
- package/src/composables/useEditor.ts +224 -0
- package/src/composables/useElementState.ts +81 -0
- package/src/composables/useFlexLayout.ts +122 -0
- package/src/composables/useKeyboard.ts +45 -0
- package/src/composables/useLumina.ts +32 -0
- package/src/composables/useStudio.ts +87 -0
- package/src/composables/useSwipeNav.ts +53 -0
- package/src/composables/useTransition.ts +373 -0
- package/src/core/Lumina.ts +819 -0
- package/src/core/animationConfig.ts +251 -0
- package/src/core/compression.ts +34 -0
- package/src/core/elementController.ts +170 -0
- package/src/core/elementId.ts +27 -0
- package/src/core/elementResolver.ts +207 -0
- package/src/core/events.ts +53 -0
- package/src/core/fonts.ts +100 -0
- package/src/core/presets.ts +231 -0
- package/src/core/prompts.ts +272 -0
- package/src/core/schema.ts +478 -0
- package/src/core/speaker-channel.ts +250 -0
- package/src/core/store.ts +461 -0
- package/src/core/theme.ts +666 -0
- package/src/core/types.ts +1611 -0
- package/src/directives/vStudio.ts +45 -0
- package/src/index.ts +175 -0
- package/src/main.ts +17 -0
- package/src/router/index.ts +92 -0
- package/src/style/main.css +462 -0
- package/src/utils/deep.ts +127 -0
- package/src/utils/firebase.ts +184 -0
- package/src/utils/streaming.ts +134 -0
- package/src/views/DashboardView.vue +32 -0
- package/src/views/DeckView.vue +289 -0
- package/src/views/HomeView.vue +17 -0
- package/src/views/SiteLayout.vue +21 -0
- package/src/views/StudioView.vue +61 -0
- package/src/vite-env.d.ts +6 -0
- package/IMPLEMENTATION.md +0 -418
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { LuminaEventMap } from './types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* TYPE-SAFE EVENT BUS
|
|
5
|
+
* A tiny emitter ensuring strict payload types.
|
|
6
|
+
*
|
|
7
|
+
* IMPROVEMENTS:
|
|
8
|
+
* 1. Strict Typing: Enforces payload matching usage.
|
|
9
|
+
* 2. Set-based Storage: Efficient add/remove.
|
|
10
|
+
* 3. One-time listeners: via `once` (implementation optional but good).
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Generic handler function type for event callbacks.
|
|
15
|
+
*/
|
|
16
|
+
export type Handler<T> = (payload: T) => void;
|
|
17
|
+
|
|
18
|
+
export class EventBus {
|
|
19
|
+
private handlers: Map<keyof LuminaEventMap, Set<Handler<any>>> = new Map();
|
|
20
|
+
|
|
21
|
+
public on<K extends keyof LuminaEventMap>(event: K, handler: Handler<LuminaEventMap[K]>): void {
|
|
22
|
+
if (!this.handlers.has(event)) {
|
|
23
|
+
this.handlers.set(event, new Set());
|
|
24
|
+
}
|
|
25
|
+
this.handlers.get(event)!.add(handler);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
public off<K extends keyof LuminaEventMap>(event: K, handler: Handler<LuminaEventMap[K]>): void {
|
|
29
|
+
const set = this.handlers.get(event);
|
|
30
|
+
if (set) {
|
|
31
|
+
set.delete(handler);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
public emit<K extends keyof LuminaEventMap>(event: K, payload: LuminaEventMap[K]): void {
|
|
36
|
+
const set = this.handlers.get(event);
|
|
37
|
+
if (set) {
|
|
38
|
+
set.forEach((handler) => {
|
|
39
|
+
try {
|
|
40
|
+
handler(payload);
|
|
41
|
+
} catch (err) {
|
|
42
|
+
console.error(`[LuminaBus] Error in handler for event "${event}":`, err);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
public clear() {
|
|
49
|
+
this.handlers.clear();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export const bus = new EventBus();
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
export interface GoogleFont {
|
|
2
|
+
family: string;
|
|
3
|
+
category: 'sans-serif' | 'serif' | 'display' | 'handwriting' | 'monospace';
|
|
4
|
+
label: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export const GOOGLE_FONTS: GoogleFont[] = [
|
|
8
|
+
// Sans Serif
|
|
9
|
+
{ family: 'Inter', category: 'sans-serif', label: 'Inter' },
|
|
10
|
+
{ family: 'Roboto', category: 'sans-serif', label: 'Roboto' },
|
|
11
|
+
{ family: 'Open Sans', category: 'sans-serif', label: 'Open Sans' },
|
|
12
|
+
{ family: 'Montserrat', category: 'sans-serif', label: 'Montserrat' },
|
|
13
|
+
{ family: 'Lato', category: 'sans-serif', label: 'Lato' },
|
|
14
|
+
{ family: 'Poppins', category: 'sans-serif', label: 'Poppins' },
|
|
15
|
+
{ family: 'Raleway', category: 'sans-serif', label: 'Raleway' },
|
|
16
|
+
{ family: 'Nunito', category: 'sans-serif', label: 'Nunito' },
|
|
17
|
+
{ family: 'Ubuntu', category: 'sans-serif', label: 'Ubuntu' },
|
|
18
|
+
{ family: 'Rubik', category: 'sans-serif', label: 'Rubik' },
|
|
19
|
+
{ family: 'Work Sans', category: 'sans-serif', label: 'Work Sans' },
|
|
20
|
+
{ family: 'Quicksand', category: 'sans-serif', label: 'Quicksand' },
|
|
21
|
+
{ family: 'Outfit', category: 'sans-serif', label: 'Outfit' },
|
|
22
|
+
{ family: 'Plus Jakarta Sans', category: 'sans-serif', label: 'Plus Jakarta Sans' },
|
|
23
|
+
|
|
24
|
+
// Serif
|
|
25
|
+
{ family: 'Merriweather', category: 'serif', label: 'Merriweather' },
|
|
26
|
+
{ family: 'Playfair Display', category: 'serif', label: 'Playfair Display' },
|
|
27
|
+
{ family: 'Lora', category: 'serif', label: 'Lora' },
|
|
28
|
+
{ family: 'PT Serif', category: 'serif', label: 'PT Serif' },
|
|
29
|
+
{ family: 'Noto Serif', category: 'serif', label: 'Noto Serif' },
|
|
30
|
+
{ family: 'Crimson Text', category: 'serif', label: 'Crimson Text' },
|
|
31
|
+
{ family: 'Libre Baskerville', category: 'serif', label: 'Libre Baskerville' },
|
|
32
|
+
{ family: 'Cinzel', category: 'serif', label: 'Cinzel' },
|
|
33
|
+
{ family: 'Cormorant Garamond', category: 'serif', label: 'Cormorant Garamond' },
|
|
34
|
+
{ family: 'DM Serif Display', category: 'serif', label: 'DM Serif Display' },
|
|
35
|
+
|
|
36
|
+
// Display
|
|
37
|
+
{ family: 'Bebas Neue', category: 'display', label: 'Bebas Neue' },
|
|
38
|
+
{ family: 'Lobster', category: 'display', label: 'Lobster' },
|
|
39
|
+
{ family: 'Comfortaa', category: 'display', label: 'Comfortaa' },
|
|
40
|
+
{ family: 'Abril Fatface', category: 'display', label: 'Abril Fatface' },
|
|
41
|
+
{ family: 'Righteous', category: 'display', label: 'Righteous' },
|
|
42
|
+
{ family: 'Alfa Slab One', category: 'display', label: 'Alfa Slab One' },
|
|
43
|
+
{ family: 'Bangers', category: 'display', label: 'Bangers' },
|
|
44
|
+
{ family: 'Orbitron', category: 'display', label: 'Orbitron' },
|
|
45
|
+
{ family: 'Rajdhani', category: 'sans-serif', label: 'Rajdhani' },
|
|
46
|
+
{ family: 'Audiowide', category: 'display', label: 'Audiowide' },
|
|
47
|
+
{ family: 'Press Start 2P', category: 'display', label: 'Press Start 2P' },
|
|
48
|
+
{ family: 'Unbounded', category: 'display', label: 'Unbounded' },
|
|
49
|
+
{ family: 'Syne', category: 'display', label: 'Syne' },
|
|
50
|
+
|
|
51
|
+
// Handwriting
|
|
52
|
+
{ family: 'Pacifico', category: 'handwriting', label: 'Pacifico' },
|
|
53
|
+
{ family: 'Dancing Script', category: 'handwriting', label: 'Dancing Script' },
|
|
54
|
+
{ family: 'Shadows Into Light', category: 'handwriting', label: 'Shadows Into Light' },
|
|
55
|
+
{ family: 'Caveat', category: 'handwriting', label: 'Caveat' },
|
|
56
|
+
{ family: 'Satisfy', category: 'handwriting', label: 'Satisfy' },
|
|
57
|
+
{ family: 'Great Vibes', category: 'handwriting', label: 'Great Vibes' },
|
|
58
|
+
{ family: 'Sacramento', category: 'handwriting', label: 'Sacramento' },
|
|
59
|
+
{ family: 'Gloria Hallelujah', category: 'handwriting', label: 'Gloria Hallelujah' },
|
|
60
|
+
{ family: 'Permanent Marker', category: 'handwriting', label: 'Permanent Marker' },
|
|
61
|
+
|
|
62
|
+
// Monospace
|
|
63
|
+
{ family: 'Roboto Mono', category: 'monospace', label: 'Roboto Mono' },
|
|
64
|
+
{ family: 'Inconsolata', category: 'monospace', label: 'Inconsolata' },
|
|
65
|
+
{ family: 'Source Code Pro', category: 'monospace', label: 'Source Code Pro' },
|
|
66
|
+
{ family: 'Space Mono', category: 'monospace', label: 'Space Mono' },
|
|
67
|
+
{ family: 'JetBrains Mono', category: 'monospace', label: 'JetBrains Mono' },
|
|
68
|
+
{ family: 'Fira Code', category: 'monospace', label: 'Fira Code' },
|
|
69
|
+
];
|
|
70
|
+
|
|
71
|
+
const loadedFonts = new Set<string>();
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Dynamically loads a Google Font by adding a <link> tag to the document head.
|
|
75
|
+
* @param family The font family name (e.g., 'Inter', 'Playfair Display')
|
|
76
|
+
*/
|
|
77
|
+
export function loadGoogleFont(family: string) {
|
|
78
|
+
// Clean family name (remove quotes, fallbacks)
|
|
79
|
+
const cleanFamily = family.split(',')[0].replace(/['"]/g, '').trim();
|
|
80
|
+
|
|
81
|
+
// Check if it's a known Google Font
|
|
82
|
+
if (!GOOGLE_FONTS.some(f => f.family === cleanFamily)) {
|
|
83
|
+
return; // Skip system fonts or unknown fonts
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (loadedFonts.has(cleanFamily)) {
|
|
87
|
+
return; // Already loaded
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Mark as loaded immediately to prevent duplicate requests
|
|
91
|
+
loadedFonts.add(cleanFamily);
|
|
92
|
+
|
|
93
|
+
const link = document.createElement('link');
|
|
94
|
+
link.rel = 'stylesheet';
|
|
95
|
+
// Request a wide range of weights to be safe
|
|
96
|
+
const familyQuery = cleanFamily.replace(/ /g, '+');
|
|
97
|
+
link.href = `https://fonts.googleapis.com/css2?family=${familyQuery}:wght@300;400;500;600;700;800&display=swap`;
|
|
98
|
+
|
|
99
|
+
document.head.appendChild(link);
|
|
100
|
+
}
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { ThemeConfig } from './types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* THEME PRESETS
|
|
5
|
+
* Pre-configured themes that users can select by name.
|
|
6
|
+
* Each preset only defines values that differ from DEFAULT_THEME.
|
|
7
|
+
*
|
|
8
|
+
* Available presets: 'default', 'ocean', 'midnight', 'forest', 'cyber', 'latte'
|
|
9
|
+
*/
|
|
10
|
+
export const THEME_PRESETS: Record<string, ThemeConfig> = {
|
|
11
|
+
default: {
|
|
12
|
+
colors: {
|
|
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
|
+
gradientFrom: '#3b82f6',
|
|
22
|
+
gradientTo: '#8b5cf6',
|
|
23
|
+
},
|
|
24
|
+
typography: {
|
|
25
|
+
fontFamily: {
|
|
26
|
+
heading: 'Inter, system-ui, sans-serif',
|
|
27
|
+
body: 'Inter, system-ui, sans-serif',
|
|
28
|
+
mono: 'ui-monospace, SFMono-Regular, monospace',
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
effects: {
|
|
32
|
+
useGradients: true,
|
|
33
|
+
useGlass: true,
|
|
34
|
+
useOrb: true,
|
|
35
|
+
useShadows: true,
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
ocean: {
|
|
40
|
+
colors: {
|
|
41
|
+
primary: '#06b6d4',
|
|
42
|
+
secondary: '#0ea5e9',
|
|
43
|
+
accent: '#22d3ee',
|
|
44
|
+
background: '#0f172a',
|
|
45
|
+
surface: '#1e293b',
|
|
46
|
+
text: '#f0f9ff',
|
|
47
|
+
textSecondary: '#e0f2fe',
|
|
48
|
+
muted: '#94a3b8',
|
|
49
|
+
success: '#14b8a6',
|
|
50
|
+
warning: '#fb923c',
|
|
51
|
+
danger: '#f43f5e',
|
|
52
|
+
info: '#38bdf8',
|
|
53
|
+
border: 'rgba(148, 163, 184, 0.2)',
|
|
54
|
+
gradientFrom: '#06b6d4',
|
|
55
|
+
gradientTo: '#0ea5e9',
|
|
56
|
+
},
|
|
57
|
+
typography: {
|
|
58
|
+
fontFamily: {
|
|
59
|
+
heading: 'Outfit, Inter, sans-serif',
|
|
60
|
+
body: 'Inter, system-ui, sans-serif',
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
effects: {
|
|
64
|
+
orbOpacity: 0.3,
|
|
65
|
+
glassOpacity: 0.05,
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
midnight: {
|
|
70
|
+
colors: {
|
|
71
|
+
primary: '#6366f1',
|
|
72
|
+
secondary: '#8b5cf6',
|
|
73
|
+
accent: '#a78bfa',
|
|
74
|
+
background: '#000000',
|
|
75
|
+
surface: '#0a0a0f',
|
|
76
|
+
text: '#e0e7ff',
|
|
77
|
+
textSecondary: '#c7d2fe',
|
|
78
|
+
muted: '#6b7280',
|
|
79
|
+
success: '#10b981',
|
|
80
|
+
warning: '#fbbf24',
|
|
81
|
+
danger: '#f87171',
|
|
82
|
+
info: '#60a5fa',
|
|
83
|
+
border: 'rgba(99, 102, 241, 0.2)',
|
|
84
|
+
gradientFrom: '#6366f1',
|
|
85
|
+
gradientTo: '#a855f7',
|
|
86
|
+
},
|
|
87
|
+
effects: {
|
|
88
|
+
orbOpacity: 0.5,
|
|
89
|
+
shadowIntensity: 'lg',
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
forest: {
|
|
94
|
+
colors: {
|
|
95
|
+
primary: '#10b981',
|
|
96
|
+
secondary: '#059669',
|
|
97
|
+
accent: '#34d399',
|
|
98
|
+
background: '#022c22',
|
|
99
|
+
surface: '#064e3b',
|
|
100
|
+
text: '#ecfdf5',
|
|
101
|
+
textSecondary: '#d1fae5',
|
|
102
|
+
muted: '#6ee7b7',
|
|
103
|
+
success: '#22c55e',
|
|
104
|
+
warning: '#fbbf24',
|
|
105
|
+
danger: '#ef4444',
|
|
106
|
+
info: '#38bdf8',
|
|
107
|
+
border: 'rgba(52, 211, 153, 0.2)',
|
|
108
|
+
gradientFrom: '#10b981',
|
|
109
|
+
gradientTo: '#059669',
|
|
110
|
+
},
|
|
111
|
+
effects: {
|
|
112
|
+
orbOpacity: 0.35,
|
|
113
|
+
orbBlur: '150px',
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
|
|
117
|
+
cyber: {
|
|
118
|
+
colors: {
|
|
119
|
+
primary: '#f472b6',
|
|
120
|
+
secondary: '#e879f9',
|
|
121
|
+
accent: '#22d3ee',
|
|
122
|
+
background: '#18181b',
|
|
123
|
+
surface: '#27272a',
|
|
124
|
+
text: '#ffffff',
|
|
125
|
+
textSecondary: '#fafafa',
|
|
126
|
+
muted: '#a1a1aa',
|
|
127
|
+
success: '#4ade80',
|
|
128
|
+
warning: '#facc15',
|
|
129
|
+
danger: '#f87171',
|
|
130
|
+
info: '#38bdf8',
|
|
131
|
+
border: 'rgba(244, 114, 182, 0.3)',
|
|
132
|
+
gradientFrom: '#f472b6',
|
|
133
|
+
gradientTo: '#e879f9',
|
|
134
|
+
},
|
|
135
|
+
typography: {
|
|
136
|
+
fontFamily: {
|
|
137
|
+
heading: 'Orbitron, sans-serif',
|
|
138
|
+
body: 'Rajdhani, sans-serif',
|
|
139
|
+
},
|
|
140
|
+
letterSpacing: {
|
|
141
|
+
widest: '0.15em',
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
effects: {
|
|
145
|
+
orbOpacity: 0.6,
|
|
146
|
+
shadowIntensity: 'xl',
|
|
147
|
+
shadowColor: 'rgba(244, 114, 182, 0.3)',
|
|
148
|
+
},
|
|
149
|
+
components: {
|
|
150
|
+
cardRadius: '0.5rem',
|
|
151
|
+
buttonRadius: '0.5rem',
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
|
|
155
|
+
latte: {
|
|
156
|
+
colors: {
|
|
157
|
+
primary: '#d97706',
|
|
158
|
+
secondary: '#ea580c',
|
|
159
|
+
accent: '#f59e0b',
|
|
160
|
+
background: '#fffbeb',
|
|
161
|
+
surface: '#fef3c7',
|
|
162
|
+
text: '#451a03',
|
|
163
|
+
textSecondary: '#78350f',
|
|
164
|
+
muted: '#92400e',
|
|
165
|
+
success: '#16a34a',
|
|
166
|
+
warning: '#ca8a04',
|
|
167
|
+
danger: '#dc2626',
|
|
168
|
+
info: '#2563eb',
|
|
169
|
+
border: 'rgba(146, 64, 14, 0.2)',
|
|
170
|
+
shadow: 'rgba(120, 53, 15, 0.1)',
|
|
171
|
+
gradientFrom: '#d97706',
|
|
172
|
+
gradientTo: '#ea580c',
|
|
173
|
+
},
|
|
174
|
+
typography: {
|
|
175
|
+
fontFamily: {
|
|
176
|
+
heading: 'Playfair Display, Georgia, serif',
|
|
177
|
+
body: 'Lato, system-ui, sans-serif',
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
effects: {
|
|
181
|
+
useOrb: false, // Light mode, no orb
|
|
182
|
+
glassOpacity: 0.5,
|
|
183
|
+
glassBorderOpacity: 0.15,
|
|
184
|
+
shadowColor: 'rgba(120, 53, 15, 0.15)',
|
|
185
|
+
},
|
|
186
|
+
components: {
|
|
187
|
+
cardBackground: 'rgba(255, 255, 255, 0.7)',
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
|
|
191
|
+
// Additional presets for more variety
|
|
192
|
+
|
|
193
|
+
sunset: {
|
|
194
|
+
colors: {
|
|
195
|
+
primary: '#f97316',
|
|
196
|
+
secondary: '#f43f5e',
|
|
197
|
+
accent: '#fb7185',
|
|
198
|
+
background: '#1c1917',
|
|
199
|
+
surface: '#292524',
|
|
200
|
+
text: '#fafaf9',
|
|
201
|
+
textSecondary: '#e7e5e4',
|
|
202
|
+
muted: '#a8a29e',
|
|
203
|
+
gradientFrom: '#f97316',
|
|
204
|
+
gradientTo: '#f43f5e',
|
|
205
|
+
},
|
|
206
|
+
effects: {
|
|
207
|
+
orbOpacity: 0.45,
|
|
208
|
+
gradientDirection: 'to-br',
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
|
|
212
|
+
monochrome: {
|
|
213
|
+
colors: {
|
|
214
|
+
primary: '#ffffff',
|
|
215
|
+
secondary: '#a1a1aa',
|
|
216
|
+
accent: '#d4d4d8',
|
|
217
|
+
background: '#09090b',
|
|
218
|
+
surface: '#18181b',
|
|
219
|
+
text: '#fafafa',
|
|
220
|
+
textSecondary: '#e4e4e7',
|
|
221
|
+
muted: '#71717a',
|
|
222
|
+
border: 'rgba(255, 255, 255, 0.1)',
|
|
223
|
+
gradientFrom: '#ffffff',
|
|
224
|
+
gradientTo: '#a1a1aa',
|
|
225
|
+
},
|
|
226
|
+
effects: {
|
|
227
|
+
useGradients: false,
|
|
228
|
+
orbOpacity: 0.2,
|
|
229
|
+
},
|
|
230
|
+
},
|
|
231
|
+
};
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
|
|
2
|
+
import { getLuminaJsonSchema, getThemeJsonSchema } from './schema';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Options for {@link generateSystemPrompt}. Tune prompt length and content for your LLM.
|
|
6
|
+
*/
|
|
7
|
+
export interface SystemPromptOptions {
|
|
8
|
+
/**
|
|
9
|
+
* The target model family.
|
|
10
|
+
* - 'reasoning': Optimizes for CoT (Chain of Thought) before JSON. Great for complex decks.
|
|
11
|
+
* - 'fast': Optimizes for direct JSON output. Great for real-time.
|
|
12
|
+
*/
|
|
13
|
+
mode?: 'reasoning' | 'fast';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Whether to include the full strict schema in the prompt.
|
|
17
|
+
* If false, a shorter alias-based description is used (saves tokens).
|
|
18
|
+
*/
|
|
19
|
+
includeSchema?: boolean;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Whether to include theming documentation.
|
|
23
|
+
* Enables the LLM to generate custom themes.
|
|
24
|
+
*/
|
|
25
|
+
includeTheming?: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const BASE_INSTRUCTION = `
|
|
29
|
+
You are LuminaAI, a specialized presentation design agent.
|
|
30
|
+
Your goal is to transform user requests into "Lumina Deck" JSON structures.
|
|
31
|
+
The rendering engine is deterministic, so your JSON must be syntactically perfect.
|
|
32
|
+
`.trim();
|
|
33
|
+
|
|
34
|
+
const LAYOUT_GUIDE = `
|
|
35
|
+
## Available Layouts
|
|
36
|
+
1. **statement**: Big bold text. Use for titles, quotes, or single big ideas.
|
|
37
|
+
2. **features**: Grid of cards. Use for "Benefits", "Stats", "Team", "Services".
|
|
38
|
+
3. **timeline**: Vertical list of events. Use for "History", "Roadmap", "Process".
|
|
39
|
+
4. **steps**: Numbered list. Use for "How to", "Tutorials".
|
|
40
|
+
5. **half**: Split screen (Image + Text). Use for high-context slides.
|
|
41
|
+
6. **flex**: Declarative auto-layout. Use for complex compositions.
|
|
42
|
+
7. **chart**: Data visualization. Use for bar, line, pie, doughnut charts.
|
|
43
|
+
`.trim();
|
|
44
|
+
|
|
45
|
+
const THEMING_GUIDE = `
|
|
46
|
+
## Theming System
|
|
47
|
+
|
|
48
|
+
Lumina has a powerful theming system. You can customize the entire presentation look.
|
|
49
|
+
|
|
50
|
+
### Theme Presets
|
|
51
|
+
Use a built-in preset by setting \`meta.theme\`:
|
|
52
|
+
- **default**: Clean blue on dark
|
|
53
|
+
- **ocean**: Cyan on slate
|
|
54
|
+
- **midnight**: Indigo on black
|
|
55
|
+
- **forest**: Emerald greens
|
|
56
|
+
- **cyber**: Neon pink on zinc
|
|
57
|
+
- **latte**: Light mode, warm amber
|
|
58
|
+
- **sunset**: Orange to rose gradient
|
|
59
|
+
- **monochrome**: Black and white
|
|
60
|
+
|
|
61
|
+
### Custom Theme Configuration
|
|
62
|
+
Use \`meta.themeConfig\` to customize any aspect:
|
|
63
|
+
|
|
64
|
+
\`\`\`json
|
|
65
|
+
{
|
|
66
|
+
"meta": {
|
|
67
|
+
"title": "My Deck",
|
|
68
|
+
"theme": "default",
|
|
69
|
+
"themeConfig": {
|
|
70
|
+
"colors": {
|
|
71
|
+
"primary": "#8b5cf6",
|
|
72
|
+
"secondary": "#ec4899",
|
|
73
|
+
"background": "#0a0a0a",
|
|
74
|
+
"text": "#ffffff",
|
|
75
|
+
"muted": "#6b7280",
|
|
76
|
+
"gradientFrom": "#8b5cf6",
|
|
77
|
+
"gradientTo": "#ec4899"
|
|
78
|
+
},
|
|
79
|
+
"effects": {
|
|
80
|
+
"useGradients": true,
|
|
81
|
+
"useGlass": true,
|
|
82
|
+
"glassOpacity": 0.05,
|
|
83
|
+
"useOrb": true,
|
|
84
|
+
"orbOpacity": 0.4
|
|
85
|
+
},
|
|
86
|
+
"components": {
|
|
87
|
+
"cardRadius": "1.5rem",
|
|
88
|
+
"buttonRadius": "9999px"
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
"slides": [...]
|
|
93
|
+
}
|
|
94
|
+
\`\`\`
|
|
95
|
+
|
|
96
|
+
### Available Theme Options
|
|
97
|
+
|
|
98
|
+
**Colors (25+ options)**:
|
|
99
|
+
- Core: \`primary\`, \`secondary\`, \`accent\`, \`background\`, \`surface\`, \`text\`, \`textSecondary\`, \`muted\`
|
|
100
|
+
- Semantic: \`success\`, \`warning\`, \`danger\`, \`info\`
|
|
101
|
+
- UI: \`border\`, \`borderSubtle\`, \`shadow\`, \`overlay\`, \`highlight\`
|
|
102
|
+
- Interactive: \`buttonPrimary\`, \`buttonPrimaryText\`, \`buttonSecondary\`, \`link\`, \`linkHover\`
|
|
103
|
+
- Gradients: \`gradientFrom\`, \`gradientVia\`, \`gradientTo\`
|
|
104
|
+
|
|
105
|
+
**Effects**:
|
|
106
|
+
- \`useGradients\`: boolean - Enable/disable gradients
|
|
107
|
+
- \`gradientDirection\`: 'to-t' | 'to-b' | 'to-l' | 'to-r' | 'to-tl' | 'to-tr' | 'to-bl' | 'to-br'
|
|
108
|
+
- \`useShadows\`: boolean - Enable/disable shadows
|
|
109
|
+
- \`shadowIntensity\`: 'none' | 'sm' | 'md' | 'lg' | 'xl' | '2xl'
|
|
110
|
+
- \`useGlass\`: boolean - Enable/disable glassmorphism
|
|
111
|
+
- \`glassOpacity\`: number (0-1)
|
|
112
|
+
- \`glassBlur\`: string (e.g., "20px")
|
|
113
|
+
- \`useOrb\`: boolean - Enable/disable ambient orb glow
|
|
114
|
+
- \`orbOpacity\`: number (0-1)
|
|
115
|
+
- \`animationsEnabled\`: boolean
|
|
116
|
+
- \`transitionDuration\`: string (e.g., "0.3s")
|
|
117
|
+
- \`hoverScale\`: number (e.g., 1.05)
|
|
118
|
+
|
|
119
|
+
**Components**:
|
|
120
|
+
- Buttons: \`buttonRadius\`, \`buttonPadding\`, \`buttonFontWeight\`
|
|
121
|
+
- Cards: \`cardRadius\`, \`cardPadding\`, \`cardBorderWidth\`
|
|
122
|
+
- Timeline: \`timelineNodeSize\`, \`timelineLineWidth\`, \`timelineNodeColor\`
|
|
123
|
+
- Progress: \`progressHeight\`, \`progressRadius\`
|
|
124
|
+
- Tags: \`tagPadding\`, \`tagRadius\`, \`tagFontSize\`
|
|
125
|
+
`.trim();
|
|
126
|
+
|
|
127
|
+
const EXAMPLES = `
|
|
128
|
+
## Examples
|
|
129
|
+
|
|
130
|
+
User: "Create a slide about our growth."
|
|
131
|
+
Output:
|
|
132
|
+
{
|
|
133
|
+
"type": "statement",
|
|
134
|
+
"title": "Exponential Growth",
|
|
135
|
+
"subtitle": "Q4 Revenue up 200%",
|
|
136
|
+
"meta": { "variant": "gradient" }
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
User: "Show the roadmap for 2024."
|
|
140
|
+
Output:
|
|
141
|
+
{
|
|
142
|
+
"type": "timeline",
|
|
143
|
+
"title": "2024 Roadmap",
|
|
144
|
+
"timeline": [
|
|
145
|
+
{ "date": "Q1", "title": "Alpha", "description": "Internal testing." },
|
|
146
|
+
{ "date": "Q2", "title": "Beta", "description": "Public waitlist." }
|
|
147
|
+
]
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
User: "Create a dark purple themed deck."
|
|
151
|
+
Output:
|
|
152
|
+
{
|
|
153
|
+
"meta": {
|
|
154
|
+
"title": "Purple Presentation",
|
|
155
|
+
"themeConfig": {
|
|
156
|
+
"colors": {
|
|
157
|
+
"primary": "#a855f7",
|
|
158
|
+
"secondary": "#c084fc",
|
|
159
|
+
"background": "#0c0a1d",
|
|
160
|
+
"gradientFrom": "#a855f7",
|
|
161
|
+
"gradientTo": "#6366f1"
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
"slides": [
|
|
166
|
+
{ "type": "statement", "title": "Welcome", "subtitle": "Custom purple theme" }
|
|
167
|
+
]
|
|
168
|
+
}
|
|
169
|
+
`.trim();
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Builds a system prompt string for an LLM that will output Lumina JSON.
|
|
173
|
+
* Use as the system message when integrating with OpenAI, Anthropic, etc.
|
|
174
|
+
*
|
|
175
|
+
* @param options - `mode`: 'reasoning' (CoT) or 'fast' (direct JSON); `includeSchema`, `includeTheming`.
|
|
176
|
+
* @returns Full system prompt including schema (if enabled), layout guide, theming, and examples.
|
|
177
|
+
* @example
|
|
178
|
+
* const sys = generateSystemPrompt({ mode: "fast", includeSchema: true });
|
|
179
|
+
* await openai.chat.completions.create({ messages: [{ role: "system", content: sys }, { role: "user", content }] });
|
|
180
|
+
*/
|
|
181
|
+
export function generateSystemPrompt(options: SystemPromptOptions = {}) {
|
|
182
|
+
const { mode = 'fast', includeSchema = true, includeTheming = true } = options;
|
|
183
|
+
|
|
184
|
+
const sections = [BASE_INSTRUCTION];
|
|
185
|
+
|
|
186
|
+
// 1. Mode Specific Instructions
|
|
187
|
+
if (mode === 'reasoning') {
|
|
188
|
+
sections.push(`
|
|
189
|
+
**CRITICAL INSTRUCTION**:
|
|
190
|
+
Before outputting JSON, you must THINK about the design.
|
|
191
|
+
Wrap your thought process in <thinking> tags.
|
|
192
|
+
Analyze the content density and choose the best layout for each point.
|
|
193
|
+
`.trim());
|
|
194
|
+
} else {
|
|
195
|
+
sections.push(`
|
|
196
|
+
**CRITICAL INSTRUCTION**:
|
|
197
|
+
Output ONLY valid JSON. Do not output markdown code blocks.
|
|
198
|
+
Do not explain your reasoning.
|
|
199
|
+
`.trim());
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// 2. Layout Guide
|
|
203
|
+
sections.push(LAYOUT_GUIDE);
|
|
204
|
+
|
|
205
|
+
// 3. Theming Guide (if enabled)
|
|
206
|
+
if (includeTheming) {
|
|
207
|
+
sections.push(THEMING_GUIDE);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// 4. Schema Injection
|
|
211
|
+
if (includeSchema) {
|
|
212
|
+
const schema = JSON.stringify(getLuminaJsonSchema(), null, 2);
|
|
213
|
+
sections.push(`
|
|
214
|
+
## Strict JSON Schema
|
|
215
|
+
You must respect this schema for every object:
|
|
216
|
+
${schema}
|
|
217
|
+
`.trim());
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// 5. Examples (Few-Shot)
|
|
221
|
+
sections.push(EXAMPLES);
|
|
222
|
+
|
|
223
|
+
return sections.join("\n\n---\n\n");
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Builds a system prompt for an LLM that outputs only `ThemeConfig` JSON.
|
|
228
|
+
* Use when the user asks to change colors/theme without editing slides.
|
|
229
|
+
*
|
|
230
|
+
* @returns System prompt with theming guide and theme JSON schema.
|
|
231
|
+
* @example
|
|
232
|
+
* const sys = generateThemePrompt();
|
|
233
|
+
* // LLM responds with { colors: {...}, effects: {...} } to merge into meta.themeConfig.
|
|
234
|
+
*/
|
|
235
|
+
export function generateThemePrompt() {
|
|
236
|
+
const schemaJson = JSON.stringify(getThemeJsonSchema(), null, 2);
|
|
237
|
+
|
|
238
|
+
return `
|
|
239
|
+
You are a theme designer for Lumina presentation engine.
|
|
240
|
+
Your job is to generate ThemeConfig JSON based on user requests.
|
|
241
|
+
|
|
242
|
+
${THEMING_GUIDE}
|
|
243
|
+
|
|
244
|
+
## Theme Schema
|
|
245
|
+
${schemaJson}
|
|
246
|
+
|
|
247
|
+
## Example Request & Response
|
|
248
|
+
|
|
249
|
+
User: "Create a warm sunset theme"
|
|
250
|
+
Response:
|
|
251
|
+
{
|
|
252
|
+
"colors": {
|
|
253
|
+
"primary": "#f97316",
|
|
254
|
+
"secondary": "#f43f5e",
|
|
255
|
+
"accent": "#fb7185",
|
|
256
|
+
"background": "#1c1917",
|
|
257
|
+
"surface": "#292524",
|
|
258
|
+
"text": "#fafaf9",
|
|
259
|
+
"gradientFrom": "#f97316",
|
|
260
|
+
"gradientTo": "#f43f5e"
|
|
261
|
+
},
|
|
262
|
+
"effects": {
|
|
263
|
+
"useGradients": true,
|
|
264
|
+
"gradientDirection": "to-br",
|
|
265
|
+
"useOrb": true,
|
|
266
|
+
"orbOpacity": 0.45
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
Now generate a theme based on the user's request. Output ONLY valid JSON.
|
|
271
|
+
`.trim();
|
|
272
|
+
}
|