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,65 @@
1
+ <template>
2
+ <div class="min-h-screen pt-32 px-8 max-w-7xl mx-auto pb-24">
3
+ <!-- Unified Header -->
4
+ <div class="text-center mb-16 max-w-3xl mx-auto">
5
+ <h1 class="text-4xl md:text-5xl font-black mb-4 tracking-tight text-white">Interactive Examples</h1>
6
+ <p class="text-lg text-white/50 leading-relaxed">Collection of templates and semantic layouts built with the
7
+ Lumina Engine.</p>
8
+ </div>
9
+
10
+ <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
11
+ <div v-for="deck in decks" :key="deck.id"
12
+ class="group relative rounded-2xl overflow-hidden border border-white/10 bg-white/5 hover:border-blue-500/50 transition duration-500 cursor-pointer block"
13
+ @click="router.push({ name: 'deck', params: { id: deck.id } })">
14
+ <div
15
+ class="aspect-video bg-gradient-to-br from-gray-800 to-black group-hover:scale-105 transition duration-700 flex items-center justify-center">
16
+ <span class="text-4xl">{{ deck.icon }}</span>
17
+ </div>
18
+ <div class="p-6 relative z-10 bg-black/80 backdrop-blur-sm -mt-2">
19
+ <h3 class="text-xl font-bold mb-2">{{ deck.title }}</h3>
20
+ <p class="text-sm text-white/60 mb-4 h-10">{{ deck.description }}</p>
21
+ <div
22
+ class="flex items-center text-blue-400 text-sm font-bold uppercase tracking-wider group-hover:translate-x-2 transition">
23
+ View Demo <span class="ml-2">→</span>
24
+ </div>
25
+ </div>
26
+ </div>
27
+ </div>
28
+ </div>
29
+ </template>
30
+
31
+ <script setup lang="ts">
32
+ import { useRouter } from 'vue-router';
33
+
34
+ const router = useRouter();
35
+
36
+ const decks = [
37
+ { id: 'deck', title: 'Feature Showcase', description: 'A comprehensive tour of all major features.', icon: '🚀' },
38
+ { id: 'layout-element-control', title: 'Element Control', description: 'Reveal-on-demand: elements start hidden and appear automatically.', icon: '🎭' },
39
+ { id: 'animation-presets', title: 'Animation: Presets', description: 'Built-in presets: fadeUp, scaleIn, spring. Use with animate() or revealInSequence.', icon: '✨' },
40
+ { id: 'animation-stagger', title: 'Animation: Stagger', description: 'Stagger modes: center-out, wave, random. Control order and timing.', icon: '〰️' },
41
+ { id: 'animation-timeline', title: 'Animation: Timeline', description: 'Remotion-style: scrub 0–1, keyframes per element, seekTo and playTimeline.', icon: '⏯️' },
42
+ { id: 'layout-statement', title: 'Statement Layout', description: 'Bold typography for impactful messages.', icon: '📢' },
43
+ { id: 'layout-half', title: 'Split Layout', description: 'Classic text and image pairing.', icon: '🌗' },
44
+ { id: 'layout-features', title: 'Feature Grid', description: 'Responsive grids for feature lists.', icon: '🍱' },
45
+ { id: 'layout-timeline', title: 'Timeline', description: 'Vertical chronological sequence.', icon: '⏱️' },
46
+ { id: 'layout-steps', title: 'Process Steps', description: 'Horizontal sequential flow.', icon: '👣' },
47
+ { id: 'layout-flex-demo', title: 'Flex Layout', description: 'Flow-based composition for LLMs.', icon: '🧬' },
48
+ { id: 'layout-video', title: 'Video & Media', description: 'Cinematic backgrounds and video elements.', icon: '🎬' },
49
+ { id: 'layout-chart', title: 'Chart Layout', description: 'Data visualization with Chart.js.', icon: '📊' },
50
+ { id: 'layout-custom', title: 'Custom HTML', description: 'Full control with raw HTML/CSS.', icon: '🎨' },
51
+ { id: 'layout-embedded', title: 'Embedded Widget', description: 'Container-sized, perfect for embedding.', icon: '🧩' },
52
+ { id: 'theme-default', title: 'Theme: Default', description: 'Clean blue on dark. Professional.', icon: '💎' },
53
+ { id: 'theme-ocean', title: 'Theme: Ocean', description: 'Cyan on slate. Calm and professional.', icon: '🌊' },
54
+ { id: 'theme-midnight', title: 'Theme: Midnight', description: 'Indigo on black. Bold and dramatic.', icon: '🌙' },
55
+ { id: 'theme-forest', title: 'Theme: Forest', description: 'Emerald greens for eco-friendly vibes.', icon: '🌲' },
56
+ { id: 'theme-cyber', title: 'Theme: Cyber', description: 'Neon pink futuristic with sci-fi fonts.', icon: '🤖' },
57
+ { id: 'theme-latte', title: 'Theme: Latte', description: 'Light mode with warm amber accents.', icon: '☕' },
58
+ { id: 'theme-sunset', title: 'Theme: Sunset', description: 'Orange to rose gradient warmth.', icon: '🌅' },
59
+ { id: 'theme-monochrome', title: 'Theme: Monochrome', description: 'Pure black and white elegance.', icon: '⚫' },
60
+ { id: 'theme-custom-example', title: 'Custom Theme', description: 'Full ThemeConfig with 80+ options.', icon: '🔧' },
61
+ ];
62
+
63
+
64
+ defineEmits(['select-deck']);
65
+ </script>
@@ -0,0 +1,6 @@
1
+ <template>
2
+ <footer class="py-12 text-center text-white/20 border-t border-white/5 mt-auto">
3
+ <p>&copy; {{ new Date().getFullYear() }} Lumina Engine. Created by <a href="https://pailletjp.com" target="_blank"
4
+ class="hover:text-white transition-colors">Juan Pablo Paillet</a>.</p>
5
+ </footer>
6
+ </template>
@@ -0,0 +1,362 @@
1
+ <template>
2
+ <!-- VIEWING MODE (When ?id=xxx is present) -->
3
+ <div v-if="loadingDeck || viewingDeck" class="lumina-deck-root w-full bg-black relative z-[9999]">
4
+ <div id="deck-viewer" class="w-full h-full min-h-0 overflow-hidden"></div>
5
+
6
+ <!-- Loading State -->
7
+ <div v-if="loadingDeck" class="absolute inset-0 flex items-center justify-center bg-black z-50">
8
+ <div class="text-center">
9
+ <i class="ph-thin ph-spinner ph-spin text-4xl text-blue-500 mb-4"></i>
10
+ <p class="text-white/50 animate-pulse">Loading presentation...</p>
11
+ </div>
12
+ </div>
13
+
14
+ <!-- Error State -->
15
+ <div v-if="deckError" class="absolute inset-0 flex items-center justify-center bg-black z-50">
16
+ <div class="text-center max-w-md px-6">
17
+ <i class="ph-thin ph-warning text-4xl text-red-500 mb-4"></i>
18
+ <h3 class="text-xl font-bold text-white mb-2">Presentation Not Found</h3>
19
+ <p class="text-white/50 mb-6">{{ deckError }}</p>
20
+ <a href="./" class="px-6 py-2 rounded-full bg-white/10 hover:bg-white/20 text-white transition">
21
+ Go Home
22
+ </a>
23
+ </div>
24
+ </div>
25
+ </div>
26
+
27
+ <!-- NORMAL HOME PAGE -->
28
+ <div v-else
29
+ class="min-h-screen pt-24 px-8 flex flex-col items-center justify-center text-center relative overflow-hidden">
30
+ <!-- Background Gradients -->
31
+ <div class="absolute top-0 left-1/4 w-96 h-96 bg-blue-500/20 rounded-full blur-[100px] pointer-events-none">
32
+ </div>
33
+ <div
34
+ class="absolute bottom-0 right-1/4 w-96 h-96 bg-purple-500/20 rounded-full blur-[100px] pointer-events-none">
35
+ </div>
36
+
37
+ <!-- Hero Content -->
38
+ <h1 class="text-6xl md:text-8xl font-heading font-black mb-6 tracking-tight relative z-10">
39
+ The Interface Layer<br>
40
+ <span class="text-transparent bg-clip-text bg-gradient-to-r from-blue-400 to-purple-400">for the Agentic
41
+ Era.</span>
42
+ </h1>
43
+
44
+ <p class="text-xl md:text-2xl text-white/60 max-w-2xl mb-12 leading-relaxed relative z-10">
45
+ Turn text into high-performance UI. A deterministic, declarative engine specifically designed for LLM
46
+ output.
47
+ </p>
48
+
49
+ <div class="flex flex-col md:flex-row gap-6 mb-24 relative z-10">
50
+ <button @click="$emit('navigate', 'docs')"
51
+ class="px-8 py-4 rounded-full bg-white text-black text-lg font-bold hover:scale-105 transition duration-300 shadow-xl shadow-white/10">
52
+ Read the Protocol
53
+ </button>
54
+ <button @click="scrollToDemo"
55
+ class="px-8 py-4 rounded-full bg-white/10 border border-white/20 hover:bg-white/20 text-white text-lg font-bold backdrop-blur-md transition duration-300">
56
+ Try the Demo
57
+ </button>
58
+ </div>
59
+
60
+ <!-- STREAMING DEMO SECTION -->
61
+ <div id="home-demo" class="w-full max-w-[1400px] mx-auto relative z-10 mb-32 text-left px-8">
62
+ <div
63
+ class="bg-[#050505] border border-white/10 rounded-3xl overflow-hidden shadow-2xl flex flex-col h-[700px] relative group ring-1 ring-white/5">
64
+
65
+ <!-- Mac-style Window Header -->
66
+ <div class="h-12 bg-[#0A0A0A] border-b border-white/5 flex items-center px-4 justify-between shrink-0">
67
+ <div class="flex gap-2">
68
+ <div class="w-3 h-3 rounded-full bg-red-500/20"></div>
69
+ <div class="w-3 h-3 rounded-full bg-yellow-500/20"></div>
70
+ <div class="w-3 h-3 rounded-full bg-green-500/20"></div>
71
+ </div>
72
+ <div class="text-xs font-mono text-white/20 uppercase tracking-widest hidden md:block">Agent Preview
73
+ Environment</div>
74
+
75
+ <!-- Sim Button in Header -->
76
+ <button @click="runDemo"
77
+ class="px-4 py-1.5 rounded-full bg-blue-500 hover:bg-blue-400 text-white font-bold text-[10px] uppercase tracking-wide shadow-lg shadow-blue-500/20 transition-all flex items-center gap-2">
78
+ <i class="ph-thin ph-play"></i> Simulate Stream
79
+ </button>
80
+ </div>
81
+
82
+ <!-- Main Body: Split View -->
83
+ <div class="flex-1 flex flex-col md:flex-row overflow-hidden relative">
84
+
85
+ <!-- Preview (Left, 3/4) -->
86
+ <div class="w-full md:w-3/4 h-full relative bg-black border-r border-white/10 order-2 md:order-1">
87
+ <div id="home-demo-container" class="w-full h-full"></div>
88
+
89
+ <!-- Empty State -->
90
+ <div v-if="!demoStarted"
91
+ class="absolute inset-0 flex items-center justify-center text-white/10 pointer-events-none">
92
+ <div class="text-center">
93
+ <i class="ph-thin ph-stack text-6xl mb-6 opacity-50"></i>
94
+ <p class="text-lg font-light">Waiting for Agent...</p>
95
+ </div>
96
+ </div>
97
+ </div>
98
+
99
+ <!-- Editor (Right, 1/4) -->
100
+ <div class="w-full md:w-1/4 h-48 md:h-full flex flex-col bg-[#080808] order-1 md:order-2">
101
+ <div
102
+ class="px-4 py-3 text-[10px] font-bold text-blue-400/50 flex justify-between items-center border-b border-white/5 bg-blue-500/5 shrink-0">
103
+ <span>INCOMING JSON STREAM</span>
104
+ <div class="w-2 h-2 rounded-full bg-blue-500 animate-pulse" v-if="demoStarted"></div>
105
+ </div>
106
+ <textarea v-model="demoInput"
107
+ class="flex-1 bg-transparent p-4 font-mono text-xs text-green-400 resize-none focus:outline-none leading-relaxed"
108
+ placeholder='// Waiting for LLM output...' spellcheck="false"></textarea>
109
+ </div>
110
+
111
+ </div>
112
+ </div>
113
+ </div>
114
+
115
+ <!-- Feature Grid (Updated Copy) -->
116
+ <div class="grid grid-cols-1 md:grid-cols-3 gap-8 max-w-6xl w-full text-left pb-24 relative z-10">
117
+ <div
118
+ class="p-8 rounded-3xl bg-white/5 border border-white/10 hover:border-blue-500/30 transition duration-500 group">
119
+ <div
120
+ class="w-12 h-12 rounded-xl bg-blue-500/10 flex items-center justify-center mb-6 text-blue-400 group-hover:scale-110 transition">
121
+ <i class="ph-thin ph-lightning text-xl"></i>
122
+ </div>
123
+ <h3 class="text-xl font-bold mb-3 text-white">Zero Hallucinations</h3>
124
+ <p class="text-white/50 leading-relaxed">Stop hoping the LLM generates valid HTML/CSS. Give it a strict
125
+ schema and get pixel-perfect results every time.</p>
126
+ </div>
127
+ <div
128
+ class="p-8 rounded-3xl bg-white/5 border border-white/10 hover:border-purple-500/30 transition duration-500 group">
129
+ <div
130
+ class="w-12 h-12 rounded-xl bg-purple-500/10 flex items-center justify-center mb-6 text-purple-400 group-hover:scale-110 transition">
131
+ <i class="ph-thin ph-brain text-xl"></i>
132
+ </div>
133
+ <h3 class="text-xl font-bold mb-3">Semantic Layouts</h3>
134
+ <p class="text-white/50 leading-relaxed">Agents don't need to be designers. They just choose "Timeline"
135
+ or "Features", and the engine handles the aesthetics.</p>
136
+ </div>
137
+ <div
138
+ class="p-8 rounded-3xl bg-white/5 border border-white/10 hover:border-green-500/30 transition duration-500 group">
139
+ <div
140
+ class="w-12 h-12 rounded-xl bg-green-500/10 flex items-center justify-center mb-6 text-green-400 group-hover:scale-110 transition">
141
+ <i class="ph-thin ph-broadcast text-xl"></i>
142
+ </div>
143
+ <h3 class="text-xl font-bold mb-3">Realtime Streaming</h3>
144
+ <p class="text-white/50 leading-relaxed">Our partial JSON parser renders UI frames <i>while</i> the LLM
145
+ is still typing. No loading spinners.</p>
146
+ </div>
147
+ </div>
148
+ </div>
149
+ </template>
150
+
151
+ <script setup lang="ts">
152
+ import { ref, watch, onUnmounted, nextTick, onMounted } from 'vue';
153
+ import { Lumina } from '../../core/Lumina';
154
+ import { parsePartialJson } from '../../utils/streaming';
155
+ import { getDeck, initFirebase } from '../../utils/firebase';
156
+
157
+ defineEmits(['navigate']);
158
+
159
+ // Demo State
160
+ const demoInput = ref('');
161
+ const demoStarted = ref(false);
162
+ let demoEngine: Lumina | null = null;
163
+ let viewerEngine: Lumina | null = null;
164
+
165
+ // Viewer State
166
+ const loadingDeck = ref(false);
167
+ const viewingDeck = ref(false);
168
+ const deckError = ref<string | null>(null);
169
+
170
+ // Initialize Firebase (Public details are safe here, but usually use env vars)
171
+ // NOTE: You should ideally use environment variables for keys.
172
+ const firebaseConfig = {
173
+ apiKey: import.meta.env.VITE_FIREBASE_API_KEY,
174
+ authDomain: import.meta.env.VITE_FIREBASE_AUTH_DOMAIN,
175
+ projectId: import.meta.env.VITE_FIREBASE_PROJECT_ID,
176
+ storageBucket: import.meta.env.VITE_FIREBASE_STORAGE_BUCKET,
177
+ messagingSenderId: import.meta.env.VITE_FIREBASE_MESSAGING_SENDER_ID,
178
+ appId: import.meta.env.VITE_FIREBASE_APP_ID
179
+ };
180
+
181
+ const TARGET_JSON = `{
182
+ "meta": { "title": "Home Demo" },
183
+ "slides": [
184
+ {
185
+ "type": "half",
186
+ "sizing": "container",
187
+ "meta": {
188
+ "orbColor": "#8b5cf6"
189
+ },
190
+ "imageSide": "left",
191
+ "image": "https://images.unsplash.com/photo-1618005182384-a83a8bd57fbe?q=80&w=2000",
192
+ "tag": "Left Aligned",
193
+ "title": "Image on Left",
194
+ "paragraphs": [
195
+ "The classic split screen. Image on the left, content on the right.",
196
+ "Perfect for introducing a product or feature where the visual context leads the narrative."
197
+ ],
198
+ "cta": "Explore Left"
199
+ },
200
+ {
201
+ "type": "statement",
202
+ "sizing": "container",
203
+ "meta": {
204
+ "orbColor": "#ec4899"
205
+ },
206
+ "tag": "Minimal",
207
+ "title": "Impactful Headlines",
208
+ "subtitle": "The standard statement slide allows for massive typography to drive a point home."
209
+ },
210
+ {
211
+ "type": "features",
212
+ "sizing": "container",
213
+ "title": "Grid System",
214
+ "description": "The features layout automatically arranges cards into a responsive grid.",
215
+ "features": [
216
+ { "title": "Card One", "desc": "Standard card with an icon.", "icon": "lightning" },
217
+ { "title": "Card Two", "desc": "Cards scale on hover.", "icon": "star" },
218
+ { "title": "Card Three", "desc": "Fully responsive on mobile.", "icon": "device-mobile" }
219
+ ]
220
+ },
221
+ {
222
+ "type": "timeline",
223
+ "sizing": "container",
224
+ "title": "Project History",
225
+ "subtitle": "A visual journey through our milestones.",
226
+ "timeline": [
227
+ { "date": "Q1 2023", "title": "Inception", "description": "The initial concept was drafted." },
228
+ { "date": "Q3 2023", "title": "Prototype", "description": "First functional MVP released." },
229
+ { "date": "Q1 2024", "title": "Beta Launch", "description": "Public beta opened to users." }
230
+ ]
231
+ }
232
+ ]
233
+ }`;
234
+
235
+ function scrollToDemo() {
236
+ const el = document.getElementById('home-demo');
237
+ if (el) el.scrollIntoView({ behavior: 'smooth' });
238
+ }
239
+
240
+ async function runDemo() {
241
+ demoInput.value = '';
242
+ demoStarted.value = true;
243
+
244
+ // Initialize Engine if needed
245
+ if (!demoEngine) {
246
+ // Ensure container is cleared
247
+ const container = document.getElementById('home-demo-container');
248
+ if (container) container.innerHTML = '';
249
+
250
+ await nextTick();
251
+ demoEngine = new Lumina('#home-demo-container', {
252
+ ui: { visible: true, showControls: true },
253
+ keyboard: false,
254
+ animation: { enabled: true, durationIn: 0.5 }
255
+ });
256
+ }
257
+
258
+ let i = 0;
259
+ const interval = setInterval(() => {
260
+ demoInput.value += TARGET_JSON[i];
261
+ i++;
262
+ if (i >= TARGET_JSON.length) clearInterval(interval);
263
+ }, 15);
264
+ }
265
+
266
+ // Watch input and parse
267
+ watch(demoInput, (val) => {
268
+ if (!demoEngine) return;
269
+ const parsed = parsePartialJson(val);
270
+ if (parsed) {
271
+ if (parsed.slides && Array.isArray(parsed.slides)) {
272
+ parsed.slides.forEach((s: any) => s.sizing = 'container');
273
+ demoEngine.load(parsed);
274
+
275
+ // Auto-advance logic: check if we have a new slide that is ready
276
+ // We want to be on the last slide that has sufficient content (e.g., at least a title)
277
+ const availableSlides = parsed.slides.filter((s: any) => s.title || s.type);
278
+ // We subtract 1 because index is 0-based
279
+ // const targetIndex = availableSlides.length - 1;
280
+
281
+ if (availableSlides.length > 0) {
282
+ const targetIndex = availableSlides.length - 1;
283
+ // Only advance if it's forward
284
+ if (targetIndex > demoEngine.currentSlideIndex) {
285
+ demoEngine.goTo(targetIndex);
286
+ }
287
+ }
288
+
289
+ } else {
290
+ demoEngine.load({
291
+ meta: { title: "Demo" },
292
+ slides: [{ ...parsed, sizing: 'container' }]
293
+ });
294
+ }
295
+ }
296
+ });
297
+
298
+ /**
299
+ * Initialize Viewer Mode if ID is present
300
+ */
301
+ const initViewer = async (id: string) => {
302
+ loadingDeck.value = true;
303
+ try {
304
+ // Initialize Firebase
305
+ initFirebase(firebaseConfig);
306
+
307
+ const deck = await getDeck(id);
308
+ if (deck) {
309
+ viewingDeck.value = true;
310
+ await nextTick();
311
+
312
+ // Allow time for DOM to render #deck-viewer
313
+ setTimeout(() => {
314
+ viewerEngine = new Lumina('#deck-viewer', {
315
+ loop: true,
316
+ navigation: true,
317
+ keyboard: true,
318
+ ui: {
319
+ visible: true,
320
+ showControls: true,
321
+ showProgressBar: true,
322
+ showSlideCount: true
323
+ },
324
+ // Ensure the viewer takes focus for keyboard events
325
+ });
326
+ viewerEngine.load(deck);
327
+ // Emit event to notify parent App.vue to hide navbar if possible, or we just handle it via CSS/z-index
328
+ document.body.classList.add('viewing-deck');
329
+ }, 100);
330
+
331
+ } else {
332
+ deckError.value = "The presentation could not be found. It may have been deleted.";
333
+ }
334
+ } catch (e: any) {
335
+ console.error(e);
336
+ deckError.value = "Failed to load presentation. Please try again later.";
337
+ } finally {
338
+ loadingDeck.value = false;
339
+ }
340
+ };
341
+
342
+ onMounted(() => {
343
+ const params = new URLSearchParams(window.location.search);
344
+ const id = params.get('id');
345
+ if (id) {
346
+ initViewer(id);
347
+ }
348
+ });
349
+
350
+ onUnmounted(() => {
351
+ if (demoEngine) demoEngine.destroy();
352
+ if (viewerEngine) viewerEngine.destroy();
353
+ document.body.classList.remove('viewing-deck');
354
+ });
355
+ </script>
356
+
357
+ <style>
358
+ /* Global override for viewer mode to hide navbar if needed */
359
+ body.viewing-deck nav {
360
+ display: none !important;
361
+ }
362
+ </style>
@@ -0,0 +1,122 @@
1
+ <template>
2
+ <nav
3
+ class="fixed top-0 left-0 right-0 z-50 flex items-center justify-between px-4 md:px-8 py-4 bg-black/50 backdrop-blur-md border-b border-white/10">
4
+ <!-- Logo -->
5
+ <router-link :to="{ name: 'home' }"
6
+ class="text-xl font-bold tracking-tighter text-white flex items-center gap-2">
7
+ <span class="w-6 h-6 rounded-full bg-gradient-to-tr from-blue-500 to-purple-500"></span>
8
+ Lumina
9
+ </router-link>
10
+
11
+ <!-- Desktop Links -->
12
+ <div class="hidden md:flex items-center gap-8">
13
+ <router-link :to="{ name: 'examples' }"
14
+ :class="['text-sm font-bold transition', isActive('examples') ? 'text-white' : 'text-white/60 hover:text-white']">Examples</router-link>
15
+ <router-link :to="{ name: 'docs' }"
16
+ :class="['text-sm font-bold transition', isActive('docs') ? 'text-white' : 'text-white/60 hover:text-white']">Docs</router-link>
17
+ <router-link :to="{ name: 'playground' }"
18
+ :class="['text-sm font-bold transition', isActive('playground') ? 'text-white' : 'text-white/60 hover:text-white']">Playground</router-link>
19
+ <router-link :to="{ name: 'prompt-builder' }"
20
+ :class="['text-sm font-bold transition', isActive('prompt-builder') ? 'text-white' : 'text-white/60 hover:text-white']">Prompt
21
+ Builder</router-link>
22
+ <router-link :to="{ name: 'api' }"
23
+ :class="['text-sm font-bold transition', isActive('api') ? 'text-white' : 'text-white/60 hover:text-white']">API</router-link>
24
+ </div>
25
+
26
+ <!-- Right Side -->
27
+ <div class="flex items-center gap-4">
28
+ <!-- Auth UI -->
29
+ <SiteUserMenu v-if="user" @navigate="(page) => $emit('navigate', page)" />
30
+
31
+ <button v-else @click="loginWithGoogle"
32
+ class="text-xs font-bold text-blue-400 hover:text-blue-300 transition mr-2 uppercase tracking-wider">
33
+ LOGIN
34
+ </button>
35
+
36
+ <!-- GitHub (always visible) -->
37
+ <a href="https://github.com/PailletJuanPablo/lumina-slides" target="_blank"
38
+ class="px-4 py-2 rounded-full bg-white text-black text-sm font-bold hover:bg-gray-200 transition">
39
+ GitHub
40
+ </a>
41
+
42
+ <!-- Mobile Hamburger -->
43
+ <button @click="mobileMenuOpen = !mobileMenuOpen"
44
+ class="md:hidden w-10 h-10 flex items-center justify-center rounded-lg bg-white/5 hover:bg-white/10 transition">
45
+ <i :class="['ph-thin text-white', mobileMenuOpen ? 'ph-x' : 'ph-list']"></i>
46
+ </button>
47
+ </div>
48
+ </nav>
49
+
50
+ <!-- Mobile Menu Drawer -->
51
+ <Transition name="slide">
52
+ <div v-if="mobileMenuOpen" class="fixed inset-0 z-40 md:hidden">
53
+ <!-- Backdrop -->
54
+ <div class="absolute inset-0 bg-black/80 backdrop-blur-sm" @click="mobileMenuOpen = false"></div>
55
+
56
+ <!-- Menu Panel -->
57
+ <div class="absolute top-16 left-0 right-0 bg-[#0a0a0a] border-b border-white/10 p-6 flex flex-col gap-4">
58
+ <button @click="navigate('examples')"
59
+ :class="['text-lg font-bold py-2 border-b border-white/5 transition text-left', isActive('examples') ? 'text-white' : 'text-white/60']">
60
+ Examples
61
+ </button>
62
+ <button @click="navigate('docs')"
63
+ :class="['text-lg font-bold py-2 border-b border-white/5 transition text-left', isActive('docs') ? 'text-white' : 'text-white/60']">
64
+ Docs
65
+ </button>
66
+ <button @click="navigate('playground')"
67
+ :class="['text-lg font-bold py-2 border-b border-white/5 transition text-left', isActive('playground') ? 'text-white' : 'text-white/60']">
68
+ Playground
69
+ </button>
70
+ <button @click="navigate('prompt-builder')"
71
+ :class="['text-lg font-bold py-2 border-b border-white/5 transition text-left', isActive('prompt-builder') ? 'text-white' : 'text-white/60']">
72
+ Prompt Builder
73
+ </button>
74
+ <button @click="navigate('api')"
75
+ :class="['text-lg font-bold py-2 transition text-left', isActive('api') ? 'text-white' : 'text-white/60']">
76
+ API
77
+ </button>
78
+ </div>
79
+ </div>
80
+ </Transition>
81
+ </template>
82
+
83
+ <script setup lang="ts">
84
+ import { ref } from 'vue';
85
+ import { useAuth } from '../../composables/useAuth';
86
+ import { useRouter, useRoute } from 'vue-router';
87
+ import SiteUserMenu from './SiteUserMenu.vue';
88
+
89
+ const { user, loginWithGoogle, logout } = useAuth();
90
+ const router = useRouter();
91
+ const route = useRoute();
92
+
93
+ defineProps<{
94
+ activePage?: string
95
+ }>();
96
+
97
+ // emit is no longer used for navigation, but we keep it compatibility if needed or remove it
98
+ const emit = defineEmits(['navigate']);
99
+
100
+ const mobileMenuOpen = ref(false);
101
+
102
+ function navigate(page: string) {
103
+ mobileMenuOpen.value = false;
104
+ router.push({ name: page });
105
+ }
106
+
107
+ function isActive(page: string) {
108
+ return route.name === page;
109
+ }
110
+ </script>
111
+
112
+ <style scoped>
113
+ .slide-enter-active,
114
+ .slide-leave-active {
115
+ transition: opacity 0.2s ease;
116
+ }
117
+
118
+ .slide-enter-from,
119
+ .slide-leave-to {
120
+ opacity: 0;
121
+ }
122
+ </style>