lumina-slides 9.0.5 → 9.0.7

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 (38) hide show
  1. package/README.md +63 -0
  2. package/dist/lumina-slides.js +21750 -19334
  3. package/dist/lumina-slides.umd.cjs +223 -223
  4. package/dist/style.css +1 -1
  5. package/package.json +1 -1
  6. package/src/components/LandingPage.vue +1 -1
  7. package/src/components/LuminaDeck.vue +237 -232
  8. package/src/components/base/LuminaElement.vue +2 -0
  9. package/src/components/layouts/LayoutFeatures.vue +125 -123
  10. package/src/components/layouts/LayoutFlex.vue +212 -212
  11. package/src/components/layouts/LayoutStatement.vue +5 -2
  12. package/src/components/layouts/LayoutSteps.vue +110 -108
  13. package/src/components/parts/FlexHtml.vue +65 -65
  14. package/src/components/parts/FlexImage.vue +81 -81
  15. package/src/components/site/SiteDocs.vue +3313 -3314
  16. package/src/components/site/SiteExamples.vue +66 -66
  17. package/src/components/studio/EditorLayoutChart.vue +18 -0
  18. package/src/components/studio/EditorLayoutCustom.vue +18 -0
  19. package/src/components/studio/EditorLayoutVideo.vue +18 -0
  20. package/src/components/studio/LuminaStudioEmbed.vue +68 -0
  21. package/src/components/studio/StudioEmbedRoot.vue +19 -0
  22. package/src/components/studio/StudioInspector.vue +1113 -7
  23. package/src/components/studio/StudioJsonEditor.vue +10 -3
  24. package/src/components/studio/StudioSettings.vue +658 -7
  25. package/src/components/studio/StudioToolbar.vue +26 -7
  26. package/src/composables/useElementState.ts +12 -1
  27. package/src/composables/useFlexLayout.ts +128 -128
  28. package/src/core/Lumina.ts +174 -113
  29. package/src/core/animationConfig.ts +10 -0
  30. package/src/core/elementController.ts +18 -0
  31. package/src/core/elementResolver.ts +4 -2
  32. package/src/core/schema.ts +503 -503
  33. package/src/core/store.ts +465 -465
  34. package/src/core/types.ts +26 -11
  35. package/src/index.ts +2 -2
  36. package/src/utils/prepareDeckForExport.ts +47 -0
  37. package/src/utils/templateInterpolation.ts +52 -52
  38. package/src/views/DeckView.vue +313 -313
@@ -24,6 +24,157 @@
24
24
  placeholder="Author Name"
25
25
  class="w-full bg-[#1a1a1a] border border-[#333] rounded px-2 py-1.5 text-white focus:border-blue-500 focus:outline-none" />
26
26
  </div>
27
+ <div class="space-y-1">
28
+ <label class="text-white/50 uppercase tracking-wider text-[10px]">Date</label>
29
+ <input type="text" :value="deckMeta?.date || ''"
30
+ @input="updateMeta('date', ($event.target as HTMLInputElement).value)"
31
+ placeholder="e.g. 2024-01-15"
32
+ class="w-full bg-[#1a1a1a] border border-[#333] rounded px-2 py-1.5 text-white focus:border-blue-500 focus:outline-none" />
33
+ </div>
34
+ </div>
35
+ </CollapsibleSection>
36
+
37
+ <!-- Element Control Section -->
38
+ <CollapsibleSection title="Element Control" icon="ph-thin ph-eye">
39
+ <div class="space-y-3">
40
+ <ToggleField label="Default Visible" :value="elementControlSettings.defaultVisible"
41
+ @update="v => updateMeta('elementControl.defaultVisible', v)" />
42
+ <p class="text-[9px] text-white/30">When on, all elements visible at start (legacy). When off, elements hidden, cascade on enter.</p>
43
+
44
+ <div class="pt-2 border-t border-[#333]">
45
+ <div class="text-white/30 text-[10px] uppercase tracking-widest mb-2">Initial Element State</div>
46
+ <div v-if="Object.keys(initialElementState).length === 0" class="text-white/30 text-[10px] italic">
47
+ No initial states defined
48
+ </div>
49
+ <div v-else class="space-y-2">
50
+ <div v-for="(state, id) in initialElementState" :key="id"
51
+ class="bg-[#1a1a1a] border border-[#333] rounded p-2">
52
+ <div class="flex items-center justify-between mb-1">
53
+ <span class="text-white/70 font-mono text-[10px]">{{ id }}</span>
54
+ <button @click="removeInitialElementState(id as string)"
55
+ class="text-red-400 hover:text-red-300 text-[10px]">
56
+ <i class="ph-thin ph-trash"></i>
57
+ </button>
58
+ </div>
59
+ <div class="flex items-center gap-2">
60
+ <label class="text-white/50 text-[9px]">visible:</label>
61
+ <select :value="state.visible !== false"
62
+ @change="updateInitialElementState(id as string, 'visible', ($event.target as HTMLSelectElement).value === 'true')"
63
+ class="flex-1 bg-[#252525] border border-[#444] rounded px-1 py-0.5 text-[10px] text-white">
64
+ <option value="true">true</option>
65
+ <option value="false">false</option>
66
+ </select>
67
+ </div>
68
+ </div>
69
+ </div>
70
+ <div class="mt-2 flex gap-2">
71
+ <input type="text" v-model="newElementStateId" placeholder="Element ID"
72
+ class="flex-1 bg-[#1a1a1a] border border-[#333] rounded px-2 py-1 text-[10px] text-white" />
73
+ <button @click="addInitialElementState"
74
+ class="px-2 py-1 bg-blue-600 hover:bg-blue-500 rounded text-[10px] text-white">
75
+ Add
76
+ </button>
77
+ </div>
78
+ </div>
79
+ </div>
80
+ </CollapsibleSection>
81
+
82
+ <!-- Reveal Options Section -->
83
+ <CollapsibleSection title="Default Reveal" icon="ph-thin ph-sparkle">
84
+ <div class="space-y-3">
85
+ <div class="space-y-1">
86
+ <label class="text-white/50 text-[10px] uppercase">Delay (ms)</label>
87
+ <input type="number" :value="revealSettings.delayMs"
88
+ @input="updateReveal('delayMs', parseInt(($event.target as HTMLInputElement).value))"
89
+ class="w-full bg-[#1a1a1a] border border-[#333] rounded px-2 py-1 text-xs text-white font-mono" />
90
+ </div>
91
+ <div class="space-y-1">
92
+ <label class="text-white/50 text-[10px] uppercase">Stagger Mode</label>
93
+ <select :value="revealSettings.staggerMode"
94
+ @change="updateReveal('staggerMode', ($event.target as HTMLSelectElement).value)"
95
+ class="w-full bg-[#1a1a1a] border border-[#333] rounded px-2 py-1 text-xs text-white">
96
+ <option value="sequential">Sequential</option>
97
+ <option value="center-out">Center Out</option>
98
+ <option value="ends-in">Ends In</option>
99
+ <option value="wave">Wave</option>
100
+ <option value="random">Random</option>
101
+ </select>
102
+ </div>
103
+ <div class="space-y-1">
104
+ <label class="text-white/50 text-[10px] uppercase">Preset</label>
105
+ <select :value="revealSettings.preset || ''"
106
+ @change="updateReveal('preset', ($event.target as HTMLSelectElement).value || undefined)"
107
+ class="w-full bg-[#1a1a1a] border border-[#333] rounded px-2 py-1 text-xs text-white">
108
+ <option value="">Default</option>
109
+ <option value="fadeUp">Fade Up</option>
110
+ <option value="fadeDown">Fade Down</option>
111
+ <option value="scaleIn">Scale In</option>
112
+ <option value="slideLeft">Slide Left</option>
113
+ <option value="slideRight">Slide Right</option>
114
+ </select>
115
+ </div>
116
+ <div class="grid grid-cols-2 gap-2">
117
+ <div class="space-y-1">
118
+ <label class="text-white/50 text-[10px] uppercase">Duration</label>
119
+ <input type="number" step="0.05" :value="revealSettings.duration"
120
+ @input="updateReveal('duration', parseFloat(($event.target as HTMLInputElement).value))"
121
+ class="w-full bg-[#1a1a1a] border border-[#333] rounded px-2 py-1 text-xs text-white font-mono" />
122
+ </div>
123
+ <div class="space-y-1">
124
+ <label class="text-white/50 text-[10px] uppercase">Ease</label>
125
+ <input type="text" :value="revealSettings.ease"
126
+ @input="updateReveal('ease', ($event.target as HTMLInputElement).value)"
127
+ placeholder="power2.out"
128
+ class="w-full bg-[#1a1a1a] border border-[#333] rounded px-2 py-1 text-xs text-white font-mono" />
129
+ </div>
130
+ </div>
131
+ <ToggleField label="Animate" :value="revealSettings.animate"
132
+ @update="v => updateReveal('animate', v)" />
133
+ <ToggleField label="Hide First" :value="revealSettings.hideFirst"
134
+ @update="v => updateReveal('hideFirst', v)" />
135
+ </div>
136
+ </CollapsibleSection>
137
+
138
+ <!-- Motions Section -->
139
+ <CollapsibleSection title="Motion Definitions" icon="ph-thin ph-path">
140
+ <div class="space-y-3">
141
+ <div v-if="Object.keys(motions).length === 0" class="text-white/30 text-[10px] italic">
142
+ No custom motions defined
143
+ </div>
144
+ <div v-else class="space-y-2">
145
+ <div v-for="(motion, id) in motions" :key="id"
146
+ class="bg-[#1a1a1a] border border-[#333] rounded p-2">
147
+ <div class="flex items-center justify-between mb-2">
148
+ <span class="text-white/70 font-mono text-[10px]">{{ id }}</span>
149
+ <button @click="removeMotion(id as string)"
150
+ class="text-red-400 hover:text-red-300 text-[10px]">
151
+ <i class="ph-thin ph-trash"></i>
152
+ </button>
153
+ </div>
154
+ <div class="space-y-1 text-[9px]">
155
+ <div class="flex gap-2">
156
+ <span class="text-white/50 w-12">ease:</span>
157
+ <input type="text" :value="motion.ease || ''"
158
+ @input="updateMotion(id as string, 'ease', ($event.target as HTMLInputElement).value)"
159
+ class="flex-1 bg-[#252525] border border-[#444] rounded px-1 py-0.5 text-white font-mono" />
160
+ </div>
161
+ <div class="flex gap-2">
162
+ <span class="text-white/50 w-12">duration:</span>
163
+ <input type="number" step="0.1" :value="motion.duration"
164
+ @input="updateMotion(id as string, 'duration', parseFloat(($event.target as HTMLInputElement).value))"
165
+ class="flex-1 bg-[#252525] border border-[#444] rounded px-1 py-0.5 text-white font-mono" />
166
+ </div>
167
+ </div>
168
+ </div>
169
+ </div>
170
+ <div class="flex gap-2">
171
+ <input type="text" v-model="newMotionId" placeholder="Motion ID"
172
+ class="flex-1 bg-[#1a1a1a] border border-[#333] rounded px-2 py-1 text-[10px] text-white" />
173
+ <button @click="addMotion"
174
+ class="px-2 py-1 bg-blue-600 hover:bg-blue-500 rounded text-[10px] text-white">
175
+ Add
176
+ </button>
177
+ </div>
27
178
  </div>
28
179
  </CollapsibleSection>
29
180
 
@@ -65,6 +216,8 @@
65
216
  <div class="text-white/30 text-[10px] uppercase tracking-widest pt-2 border-t border-[#333]">Text
66
217
  </div>
67
218
  <ColorField label="Text Main" field="text" :value="themeColors.text" @update="updateThemeColor" />
219
+ <ColorField label="Text Secondary" field="textSecondary" :value="themeColors.textSecondary"
220
+ @update="updateThemeColor" />
68
221
  <ColorField label="Text Muted" field="muted" :value="themeColors.muted"
69
222
  @update="updateThemeColor" />
70
223
 
@@ -75,12 +228,48 @@
75
228
  <ColorField label="Warning" field="warning" :value="themeColors.warning"
76
229
  @update="updateThemeColor" />
77
230
  <ColorField label="Danger" field="danger" :value="themeColors.danger" @update="updateThemeColor" />
231
+ <ColorField label="Info" field="info" :value="themeColors.info" @update="updateThemeColor" />
232
+
233
+ <div class="text-white/30 text-[10px] uppercase tracking-widest pt-2 border-t border-[#333]">
234
+ UI Elements</div>
235
+ <ColorField label="Border" field="border" :value="themeColors.border" @update="updateThemeColor" />
236
+ <ColorField label="Border Subtle" field="borderSubtle" :value="themeColors.borderSubtle"
237
+ @update="updateThemeColor" />
238
+ <ColorField label="Shadow" field="shadow" :value="themeColors.shadow" @update="updateThemeColor" />
239
+ <ColorField label="Overlay" field="overlay" :value="themeColors.overlay" @update="updateThemeColor" />
240
+ <ColorField label="Highlight" field="highlight" :value="themeColors.highlight" @update="updateThemeColor" />
241
+
242
+ <div class="text-white/30 text-[10px] uppercase tracking-widest pt-2 border-t border-[#333]">
243
+ Buttons</div>
244
+ <ColorField label="Btn Primary" field="buttonPrimary" :value="themeColors.buttonPrimary"
245
+ @update="updateThemeColor" />
246
+ <ColorField label="Btn Primary Text" field="buttonPrimaryText" :value="themeColors.buttonPrimaryText"
247
+ @update="updateThemeColor" />
248
+ <ColorField label="Btn Secondary" field="buttonSecondary" :value="themeColors.buttonSecondary"
249
+ @update="updateThemeColor" />
250
+ <ColorField label="Btn Secondary Text" field="buttonSecondaryText" :value="themeColors.buttonSecondaryText"
251
+ @update="updateThemeColor" />
252
+
253
+ <div class="text-white/30 text-[10px] uppercase tracking-widest pt-2 border-t border-[#333]">
254
+ Links</div>
255
+ <ColorField label="Link" field="link" :value="themeColors.link" @update="updateThemeColor" />
256
+ <ColorField label="Link Hover" field="linkHover" :value="themeColors.linkHover" @update="updateThemeColor" />
257
+
258
+ <div class="text-white/30 text-[10px] uppercase tracking-widest pt-2 border-t border-[#333]">
259
+ Gradients</div>
260
+ <ColorField label="Gradient From" field="gradientFrom" :value="themeColors.gradientFrom"
261
+ @update="updateThemeColor" />
262
+ <ColorField label="Gradient Via" field="gradientVia" :value="themeColors.gradientVia"
263
+ @update="updateThemeColor" />
264
+ <ColorField label="Gradient To" field="gradientTo" :value="themeColors.gradientTo"
265
+ @update="updateThemeColor" />
78
266
  </div>
79
267
  </CollapsibleSection>
80
268
 
81
269
  <!-- Typography Section -->
82
270
  <CollapsibleSection title="Typography" icon="ph-thin ph-text-t">
83
271
  <div class="space-y-3">
272
+ <div class="text-white/30 text-[10px] uppercase tracking-widest">Font Families</div>
84
273
  <div class="space-y-1">
85
274
  <label class="text-white/50 uppercase tracking-wider text-[10px]">Heading Font</label>
86
275
  <select :value="typographySettings.heading"
@@ -105,6 +294,57 @@
105
294
  </optgroup>
106
295
  </select>
107
296
  </div>
297
+ <div class="space-y-1">
298
+ <label class="text-white/50 uppercase tracking-wider text-[10px]">Mono Font</label>
299
+ <select :value="typographySettings.mono"
300
+ @change="updateTypography('mono', ($event.target as HTMLSelectElement).value)"
301
+ class="w-full bg-[#1a1a1a] border border-[#333] rounded px-2 py-1.5 text-white focus:border-blue-500 focus:outline-none">
302
+ <option value="'Fira Code', monospace">Fira Code</option>
303
+ <option value="'JetBrains Mono', monospace">JetBrains Mono</option>
304
+ <option value="'Source Code Pro', monospace">Source Code Pro</option>
305
+ <option value="monospace">System Mono</option>
306
+ </select>
307
+ </div>
308
+
309
+ <div class="text-white/30 text-[10px] uppercase tracking-widest pt-2 border-t border-[#333]">Font Sizes</div>
310
+ <div class="grid grid-cols-2 gap-2">
311
+ <div v-for="token in fontSizeTokens" :key="token" class="space-y-1">
312
+ <label class="text-white/50 uppercase tracking-wider text-[10px]">{{ token }}</label>
313
+ <input type="text" :value="themeFontSize[token]"
314
+ @input="updateFontSize(token, ($event.target as HTMLInputElement).value)"
315
+ class="w-full bg-[#1a1a1a] border border-[#333] rounded px-2 py-1.5 text-white focus:border-blue-500 focus:outline-none font-mono text-xs" />
316
+ </div>
317
+ </div>
318
+
319
+ <div class="text-white/30 text-[10px] uppercase tracking-widest pt-2 border-t border-[#333]">Font Weights</div>
320
+ <div class="grid grid-cols-2 gap-2">
321
+ <div v-for="token in fontWeightTokens" :key="token" class="space-y-1">
322
+ <label class="text-white/50 uppercase tracking-wider text-[10px]">{{ token }}</label>
323
+ <input type="number" :value="themeFontWeight[token]"
324
+ @input="updateFontWeight(token, parseInt(($event.target as HTMLInputElement).value))"
325
+ class="w-full bg-[#1a1a1a] border border-[#333] rounded px-2 py-1.5 text-white focus:border-blue-500 focus:outline-none font-mono text-xs" />
326
+ </div>
327
+ </div>
328
+
329
+ <div class="text-white/30 text-[10px] uppercase tracking-widest pt-2 border-t border-[#333]">Line Heights</div>
330
+ <div class="grid grid-cols-2 gap-2">
331
+ <div v-for="token in lineHeightTokens" :key="token" class="space-y-1">
332
+ <label class="text-white/50 uppercase tracking-wider text-[10px]">{{ token }}</label>
333
+ <input type="text" :value="themeLineHeight[token]"
334
+ @input="updateLineHeight(token, ($event.target as HTMLInputElement).value)"
335
+ class="w-full bg-[#1a1a1a] border border-[#333] rounded px-2 py-1.5 text-white focus:border-blue-500 focus:outline-none font-mono text-xs" />
336
+ </div>
337
+ </div>
338
+
339
+ <div class="text-white/30 text-[10px] uppercase tracking-widest pt-2 border-t border-[#333]">Letter Spacing</div>
340
+ <div class="grid grid-cols-2 gap-2">
341
+ <div v-for="token in letterSpacingTokens" :key="token" class="space-y-1">
342
+ <label class="text-white/50 uppercase tracking-wider text-[10px]">{{ token }}</label>
343
+ <input type="text" :value="themeLetterSpacing[token]"
344
+ @input="updateLetterSpacing(token, ($event.target as HTMLInputElement).value)"
345
+ class="w-full bg-[#1a1a1a] border border-[#333] rounded px-2 py-1.5 text-white focus:border-blue-500 focus:outline-none font-mono text-xs" />
346
+ </div>
347
+ </div>
108
348
  </div>
109
349
  </CollapsibleSection>
110
350
 
@@ -174,6 +414,51 @@
174
414
  class="w-full bg-[#1a1a1a] border border-[#333] rounded px-2 py-1 text-xs text-white font-mono" />
175
415
  </div>
176
416
  </div>
417
+ <div class="grid grid-cols-2 gap-2">
418
+ <div class="space-y-1">
419
+ <label class="text-white/50 text-[10px] uppercase">Transition Duration</label>
420
+ <input type="text" :value="effectsSettings.transitionDuration"
421
+ @input="updateEffect('transitionDuration', ($event.target as HTMLInputElement).value)"
422
+ placeholder="0.3s"
423
+ class="w-full bg-[#1a1a1a] border border-[#333] rounded px-2 py-1 text-xs text-white font-mono" />
424
+ </div>
425
+ <div class="space-y-1">
426
+ <label class="text-white/50 text-[10px] uppercase">Transition Ease</label>
427
+ <input type="text" :value="effectsSettings.transitionEasing"
428
+ @input="updateEffect('transitionEasing', ($event.target as HTMLInputElement).value)"
429
+ placeholder="ease-out"
430
+ class="w-full bg-[#1a1a1a] border border-[#333] rounded px-2 py-1 text-xs text-white font-mono" />
431
+ </div>
432
+ </div>
433
+ <div class="space-y-1">
434
+ <label class="text-white/50 text-[10px] uppercase">Hover Scale</label>
435
+ <input type="number" step="0.01" :value="effectsSettings.hoverScale"
436
+ @input="updateEffect('hoverScale', parseFloat(($event.target as HTMLInputElement).value))"
437
+ placeholder="1.05"
438
+ class="w-full bg-[#1a1a1a] border border-[#333] rounded px-2 py-1 text-xs text-white font-mono" />
439
+ </div>
440
+ </div>
441
+
442
+ <div class="pt-2 border-t border-[#333]">
443
+ <ToggleField label="Gradients" :value="effectsSettings.gradients"
444
+ @update="v => updateEffect('useGradients', v)" />
445
+ <div v-if="effectsSettings.gradients" class="mt-2 pl-2 border-l-2 border-[#333] space-y-2">
446
+ <div class="space-y-1">
447
+ <label class="text-white/50 text-[10px] uppercase">Direction</label>
448
+ <select :value="effectsSettings.gradientDirection"
449
+ @change="updateEffect('gradientDirection', ($event.target as HTMLSelectElement).value)"
450
+ class="w-full bg-[#1a1a1a] border border-[#333] rounded px-2 py-1 text-xs text-white">
451
+ <option value="to-t">To Top</option>
452
+ <option value="to-b">To Bottom</option>
453
+ <option value="to-l">To Left</option>
454
+ <option value="to-r">To Right</option>
455
+ <option value="to-tl">To Top Left</option>
456
+ <option value="to-tr">To Top Right</option>
457
+ <option value="to-bl">To Bottom Left</option>
458
+ <option value="to-br">To Bottom Right</option>
459
+ </select>
460
+ </div>
461
+ </div>
177
462
  </div>
178
463
 
179
464
  <div class="pt-2 border-t border-[#333]">
@@ -184,6 +469,13 @@
184
469
  :step="0.05" @update="v => updateEffect('orbOpacity', v)" />
185
470
  <SliderField label="Blur (px)" :value="parseInt(effectsSettings.orbBlur as string)" :min="0"
186
471
  :max="200" :step="10" unit="px" @update="v => updateEffect('orbBlur', v + 'px')" />
472
+ <div class="space-y-1">
473
+ <label class="text-white/50 text-[10px] uppercase">Orb Size</label>
474
+ <input type="text" :value="effectsSettings.orbSize"
475
+ @input="updateEffect('orbSize', ($event.target as HTMLInputElement).value)"
476
+ placeholder="60vw"
477
+ class="w-full bg-[#1a1a1a] border border-[#333] rounded px-2 py-1 text-xs text-white font-mono" />
478
+ </div>
187
479
  </div>
188
480
  </div>
189
481
 
@@ -196,12 +488,40 @@
196
488
  <SliderField label="Blur (px)" :value="parseInt(effectsSettings.glassBlur as string)"
197
489
  :min="0" :max="50" :step="2" unit="px"
198
490
  @update="v => updateEffect('glassBlur', v + 'px')" />
491
+ <SliderField label="Border Opacity" :value="effectsSettings.glassBorderOpacity" :min="0" :max="1"
492
+ :step="0.05" @update="v => updateEffect('glassBorderOpacity', v)" />
199
493
  </div>
200
494
  </div>
201
495
 
202
496
  <div class="pt-2 border-t border-[#333]">
203
497
  <ToggleField label="Shadows" :value="effectsSettings.shadows"
204
498
  @update="v => updateEffect('useShadows', v)" />
499
+ <div v-if="effectsSettings.shadows" class="mt-2 pl-2 border-l-2 border-[#333] space-y-2">
500
+ <div class="space-y-1">
501
+ <label class="text-white/50 text-[10px] uppercase">Intensity</label>
502
+ <select :value="effectsSettings.shadowIntensity"
503
+ @change="updateEffect('shadowIntensity', ($event.target as HTMLSelectElement).value)"
504
+ class="w-full bg-[#1a1a1a] border border-[#333] rounded px-2 py-1 text-xs text-white">
505
+ <option value="none">None</option>
506
+ <option value="sm">Small</option>
507
+ <option value="md">Medium</option>
508
+ <option value="lg">Large</option>
509
+ <option value="xl">X-Large</option>
510
+ <option value="2xl">2X-Large</option>
511
+ </select>
512
+ </div>
513
+ <div class="space-y-1">
514
+ <label class="text-white/50 text-[10px] uppercase">Shadow Color</label>
515
+ <div class="flex gap-2">
516
+ <input type="color" :value="effectsSettings.shadowColor"
517
+ @input="updateEffect('shadowColor', ($event.target as HTMLInputElement).value)"
518
+ class="w-8 h-8 rounded border border-[#333] cursor-pointer bg-transparent" />
519
+ <input type="text" :value="effectsSettings.shadowColor"
520
+ @input="updateEffect('shadowColor', ($event.target as HTMLInputElement).value)"
521
+ class="flex-1 bg-[#1a1a1a] border border-[#333] rounded px-2 py-1 text-xs text-white font-mono" />
522
+ </div>
523
+ </div>
524
+ </div>
205
525
  </div>
206
526
  </div>
207
527
  </CollapsibleSection>
@@ -221,9 +541,31 @@
221
541
  <option value="9999px">Pill (Full)</option>
222
542
  </select>
223
543
  </div>
224
-
225
- <div class="text-white/30 text-[10px] uppercase tracking-widest pt-2 border-t border-[#333]">Cards
544
+ <div class="space-y-1">
545
+ <label class="text-white/50 uppercase tracking-wider text-[10px]">Padding</label>
546
+ <input type="text" :value="componentsSettings.buttonPadding"
547
+ @input="updateComponent('buttonPadding', ($event.target as HTMLInputElement).value)"
548
+ placeholder="0.75rem 1.5rem"
549
+ class="w-full bg-[#1a1a1a] border border-[#333] rounded px-2 py-1.5 text-white focus:border-blue-500 focus:outline-none font-mono text-xs" />
550
+ </div>
551
+ <div class="space-y-1">
552
+ <label class="text-white/50 uppercase tracking-wider text-[10px]">Font Weight</label>
553
+ <input type="number" :value="componentsSettings.buttonFontWeight"
554
+ @input="updateComponent('buttonFontWeight', parseInt(($event.target as HTMLInputElement).value))"
555
+ class="w-full bg-[#1a1a1a] border border-[#333] rounded px-2 py-1.5 text-white focus:border-blue-500 focus:outline-none font-mono text-xs" />
226
556
  </div>
557
+ <div class="space-y-1">
558
+ <label class="text-white/50 uppercase tracking-wider text-[10px]">Text Transform</label>
559
+ <select :value="componentsSettings.buttonTextTransform"
560
+ @change="updateComponent('buttonTextTransform', ($event.target as HTMLSelectElement).value)"
561
+ class="w-full bg-[#1a1a1a] border border-[#333] rounded px-2 py-1.5 text-white focus:border-blue-500 focus:outline-none">
562
+ <option value="none">None</option>
563
+ <option value="uppercase">Uppercase</option>
564
+ <option value="capitalize">Capitalize</option>
565
+ </select>
566
+ </div>
567
+
568
+ <div class="text-white/30 text-[10px] uppercase tracking-widest pt-2 border-t border-[#333]">Cards</div>
227
569
  <div class="space-y-1">
228
570
  <label class="text-white/50 uppercase tracking-wider text-[10px]">Radius</label>
229
571
  <select :value="componentsSettings.cardRadius"
@@ -236,6 +578,110 @@
236
578
  <option value="2rem">X-Large</option>
237
579
  </select>
238
580
  </div>
581
+ <div class="space-y-1">
582
+ <label class="text-white/50 uppercase tracking-wider text-[10px]">Padding</label>
583
+ <input type="text" :value="componentsSettings.cardPadding"
584
+ @input="updateComponent('cardPadding', ($event.target as HTMLInputElement).value)"
585
+ placeholder="1.5rem"
586
+ class="w-full bg-[#1a1a1a] border border-[#333] rounded px-2 py-1.5 text-white focus:border-blue-500 focus:outline-none font-mono text-xs" />
587
+ </div>
588
+ <div class="space-y-1">
589
+ <label class="text-white/50 uppercase tracking-wider text-[10px]">Border Width</label>
590
+ <input type="text" :value="componentsSettings.cardBorderWidth"
591
+ @input="updateComponent('cardBorderWidth', ($event.target as HTMLInputElement).value)"
592
+ placeholder="1px"
593
+ class="w-full bg-[#1a1a1a] border border-[#333] rounded px-2 py-1.5 text-white focus:border-blue-500 focus:outline-none font-mono text-xs" />
594
+ </div>
595
+
596
+ <div class="text-white/30 text-[10px] uppercase tracking-widest pt-2 border-t border-[#333]">Timeline</div>
597
+ <div class="grid grid-cols-2 gap-2">
598
+ <div class="space-y-1">
599
+ <label class="text-white/50 uppercase tracking-wider text-[10px]">Node Size</label>
600
+ <input type="text" :value="componentsSettings.timelineNodeSize"
601
+ @input="updateComponent('timelineNodeSize', ($event.target as HTMLInputElement).value)"
602
+ placeholder="1rem"
603
+ class="w-full bg-[#1a1a1a] border border-[#333] rounded px-2 py-1 text-white font-mono text-xs" />
604
+ </div>
605
+ <div class="space-y-1">
606
+ <label class="text-white/50 uppercase tracking-wider text-[10px]">Line Width</label>
607
+ <input type="text" :value="componentsSettings.timelineLineWidth"
608
+ @input="updateComponent('timelineLineWidth', ($event.target as HTMLInputElement).value)"
609
+ placeholder="2px"
610
+ class="w-full bg-[#1a1a1a] border border-[#333] rounded px-2 py-1 text-white font-mono text-xs" />
611
+ </div>
612
+ </div>
613
+
614
+ <div class="text-white/30 text-[10px] uppercase tracking-widest pt-2 border-t border-[#333]">Steps</div>
615
+ <div class="grid grid-cols-2 gap-2">
616
+ <div class="space-y-1">
617
+ <label class="text-white/50 uppercase tracking-wider text-[10px]">Badge Size</label>
618
+ <input type="text" :value="componentsSettings.stepBadgeSize"
619
+ @input="updateComponent('stepBadgeSize', ($event.target as HTMLInputElement).value)"
620
+ placeholder="2.5rem"
621
+ class="w-full bg-[#1a1a1a] border border-[#333] rounded px-2 py-1 text-white font-mono text-xs" />
622
+ </div>
623
+ <div class="space-y-1">
624
+ <label class="text-white/50 uppercase tracking-wider text-[10px]">Font Size</label>
625
+ <input type="text" :value="componentsSettings.stepFontSize"
626
+ @input="updateComponent('stepFontSize', ($event.target as HTMLInputElement).value)"
627
+ placeholder="1rem"
628
+ class="w-full bg-[#1a1a1a] border border-[#333] rounded px-2 py-1 text-white font-mono text-xs" />
629
+ </div>
630
+ </div>
631
+
632
+ <div class="text-white/30 text-[10px] uppercase tracking-widest pt-2 border-t border-[#333]">Progress Bar</div>
633
+ <div class="grid grid-cols-2 gap-2">
634
+ <div class="space-y-1">
635
+ <label class="text-white/50 uppercase tracking-wider text-[10px]">Height</label>
636
+ <input type="text" :value="componentsSettings.progressHeight"
637
+ @input="updateComponent('progressHeight', ($event.target as HTMLInputElement).value)"
638
+ placeholder="4px"
639
+ class="w-full bg-[#1a1a1a] border border-[#333] rounded px-2 py-1 text-white font-mono text-xs" />
640
+ </div>
641
+ <div class="space-y-1">
642
+ <label class="text-white/50 uppercase tracking-wider text-[10px]">Radius</label>
643
+ <input type="text" :value="componentsSettings.progressRadius"
644
+ @input="updateComponent('progressRadius', ($event.target as HTMLInputElement).value)"
645
+ placeholder="2px"
646
+ class="w-full bg-[#1a1a1a] border border-[#333] rounded px-2 py-1 text-white font-mono text-xs" />
647
+ </div>
648
+ </div>
649
+
650
+ <div class="text-white/30 text-[10px] uppercase tracking-widest pt-2 border-t border-[#333]">Tags</div>
651
+ <div class="grid grid-cols-2 gap-2">
652
+ <div class="space-y-1">
653
+ <label class="text-white/50 uppercase tracking-wider text-[10px]">Padding</label>
654
+ <input type="text" :value="componentsSettings.tagPadding"
655
+ @input="updateComponent('tagPadding', ($event.target as HTMLInputElement).value)"
656
+ placeholder="0.25rem 0.5rem"
657
+ class="w-full bg-[#1a1a1a] border border-[#333] rounded px-2 py-1 text-white font-mono text-xs" />
658
+ </div>
659
+ <div class="space-y-1">
660
+ <label class="text-white/50 uppercase tracking-wider text-[10px]">Radius</label>
661
+ <input type="text" :value="componentsSettings.tagRadius"
662
+ @input="updateComponent('tagRadius', ($event.target as HTMLInputElement).value)"
663
+ placeholder="0.25rem"
664
+ class="w-full bg-[#1a1a1a] border border-[#333] rounded px-2 py-1 text-white font-mono text-xs" />
665
+ </div>
666
+ </div>
667
+
668
+ <div class="text-white/30 text-[10px] uppercase tracking-widest pt-2 border-t border-[#333]">Inputs</div>
669
+ <div class="grid grid-cols-2 gap-2">
670
+ <div class="space-y-1">
671
+ <label class="text-white/50 uppercase tracking-wider text-[10px]">Radius</label>
672
+ <input type="text" :value="componentsSettings.inputRadius"
673
+ @input="updateComponent('inputRadius', ($event.target as HTMLInputElement).value)"
674
+ placeholder="0.5rem"
675
+ class="w-full bg-[#1a1a1a] border border-[#333] rounded px-2 py-1 text-white font-mono text-xs" />
676
+ </div>
677
+ <div class="space-y-1">
678
+ <label class="text-white/50 uppercase tracking-wider text-[10px]">Padding</label>
679
+ <input type="text" :value="componentsSettings.inputPadding"
680
+ @input="updateComponent('inputPadding', ($event.target as HTMLInputElement).value)"
681
+ placeholder="0.5rem 1rem"
682
+ class="w-full bg-[#1a1a1a] border border-[#333] rounded px-2 py-1 text-white font-mono text-xs" />
683
+ </div>
684
+ </div>
239
685
  </div>
240
686
  </CollapsibleSection>
241
687
 
@@ -252,10 +698,10 @@
252
698
  </template>
253
699
 
254
700
  <script setup lang="ts">
255
- import { computed, h, defineComponent } from 'vue';
701
+ import { computed, h, defineComponent, ref } from 'vue';
256
702
  import { useEditor } from '../../composables/useEditor';
257
703
  import { ThemeManager } from '../../core/theme';
258
- import type { DeckMeta } from '../../core/types';
704
+ import type { DeckMeta, ElementState, MotionDef } from '../../core/types';
259
705
  import CollapsibleSection from './CollapsibleSection.vue';
260
706
  import SliderField from './SliderField.vue';
261
707
  import { GOOGLE_FONTS } from '../../core/fonts';
@@ -266,6 +712,81 @@ const deckMeta = computed(() => editor.store.state.deck?.meta);
266
712
  const slideCount = computed(() => editor.store.state.deck?.slides?.length || 0);
267
713
  const currentTheme = computed(() => editor.store.state.deck?.meta?.theme || editor.store.state.deck?.theme || 'default');
268
714
 
715
+ // Element Control settings
716
+ const elementControlSettings = computed(() => ({
717
+ defaultVisible: editor.store.state.deck?.meta?.elementControl?.defaultVisible ?? false,
718
+ }));
719
+
720
+ const initialElementState = computed(() =>
721
+ editor.store.state.deck?.meta?.initialElementState || {} as Record<string, Partial<ElementState>>
722
+ );
723
+
724
+ const newElementStateId = ref('');
725
+
726
+ const addInitialElementState = () => {
727
+ if (!newElementStateId.value.trim()) return;
728
+ editor.store.updateNode(`meta.initialElementState.${newElementStateId.value}`, { visible: false });
729
+ commitUpdate();
730
+ newElementStateId.value = '';
731
+ };
732
+
733
+ const removeInitialElementState = (id: string) => {
734
+ const current = { ...initialElementState.value };
735
+ delete current[id];
736
+ editor.store.updateNode('meta.initialElementState', Object.keys(current).length > 0 ? current : undefined);
737
+ commitUpdate();
738
+ };
739
+
740
+ const updateInitialElementState = (id: string, key: string, value: any) => {
741
+ editor.store.updateNode(`meta.initialElementState.${id}.${key}`, value);
742
+ commitUpdate();
743
+ };
744
+
745
+ // Reveal settings
746
+ const revealSettings = computed(() => ({
747
+ delayMs: editor.store.state.deck?.meta?.reveal?.delayMs ?? 400,
748
+ staggerMode: editor.store.state.deck?.meta?.reveal?.staggerMode || 'sequential',
749
+ preset: editor.store.state.deck?.meta?.reveal?.preset,
750
+ duration: editor.store.state.deck?.meta?.reveal?.duration ?? 0.45,
751
+ ease: editor.store.state.deck?.meta?.reveal?.ease || 'power2.out',
752
+ animate: editor.store.state.deck?.meta?.reveal?.animate ?? true,
753
+ hideFirst: editor.store.state.deck?.meta?.reveal?.hideFirst ?? true,
754
+ }));
755
+
756
+ const updateReveal = (key: string, value: any) => {
757
+ editor.store.updateNode(`meta.reveal.${key}`, value);
758
+ commitUpdate();
759
+ };
760
+
761
+ // Motions
762
+ const motions = computed(() =>
763
+ editor.store.state.deck?.meta?.motions || {} as Record<string, MotionDef>
764
+ );
765
+
766
+ const newMotionId = ref('');
767
+
768
+ const addMotion = () => {
769
+ if (!newMotionId.value.trim()) return;
770
+ editor.store.updateNode(`meta.motions.${newMotionId.value}`, {
771
+ ease: 'power2.out',
772
+ duration: 0.5
773
+ });
774
+ commitUpdate();
775
+ newMotionId.value = '';
776
+ };
777
+
778
+ const removeMotion = (id: string) => {
779
+ const current = { ...motions.value };
780
+ delete current[id];
781
+ editor.store.updateNode('meta.motions', Object.keys(current).length > 0 ? current : undefined);
782
+ commitUpdate();
783
+ };
784
+
785
+ const updateMotion = (id: string, key: string, value: any) => {
786
+ editor.store.updateNode(`meta.motions.${id}.${key}`, value);
787
+ commitUpdate();
788
+ };
789
+
269
790
  // Theme presets
270
791
  const themePresets = [
271
792
  { id: 'default', name: 'Default', primary: '#3b82f6', secondary: '#8b5cf6', bg: '#030303' },
@@ -278,30 +799,146 @@ const themePresets = [
278
799
  { id: 'minimal', name: 'Minimal', primary: '#18181b', secondary: '#3f3f46', bg: '#fafafa' },
279
800
  ];
280
801
 
281
- // Current theme colors
802
+ // Current theme colors (complete set)
282
803
  const themeColors = computed(() => ({
804
+ // Brand
283
805
  primary: editor.store.state.deck?.meta?.colors?.primary || '#3b82f6',
284
806
  secondary: editor.store.state.deck?.meta?.colors?.secondary || '#8b5cf6',
285
807
  accent: editor.store.state.deck?.meta?.colors?.accent || '#06b6d4',
808
+ // Base
286
809
  background: editor.store.state.deck?.meta?.colors?.background || '#030303',
287
810
  surface: editor.store.state.deck?.meta?.colors?.surface || '#0a0a0a',
811
+ // Text
288
812
  text: editor.store.state.deck?.meta?.colors?.text || '#ffffff',
813
+ textSecondary: editor.store.state.deck?.meta?.colors?.textSecondary || '#e5e7eb',
289
814
  muted: editor.store.state.deck?.meta?.colors?.muted || '#9ca3af',
815
+ // Semantic
290
816
  success: editor.store.state.deck?.meta?.colors?.success || '#10b981',
291
817
  warning: editor.store.state.deck?.meta?.colors?.warning || '#f59e0b',
292
818
  danger: editor.store.state.deck?.meta?.colors?.danger || '#ef4444',
819
+ info: editor.store.state.deck?.meta?.colors?.info || '#3b82f6',
820
+ // UI Elements
821
+ border: editor.store.state.deck?.meta?.colors?.border || 'rgba(255,255,255,0.1)',
822
+ borderSubtle: editor.store.state.deck?.meta?.colors?.borderSubtle || 'rgba(255,255,255,0.05)',
823
+ shadow: editor.store.state.deck?.meta?.colors?.shadow || '#000000',
824
+ overlay: editor.store.state.deck?.meta?.colors?.overlay || 'rgba(0,0,0,0.5)',
825
+ highlight: editor.store.state.deck?.meta?.colors?.highlight || '#3b82f6',
826
+ // Buttons
827
+ buttonPrimary: editor.store.state.deck?.meta?.colors?.buttonPrimary || '#3b82f6',
828
+ buttonPrimaryText: editor.store.state.deck?.meta?.colors?.buttonPrimaryText || '#ffffff',
829
+ buttonSecondary: editor.store.state.deck?.meta?.colors?.buttonSecondary || '#374151',
830
+ buttonSecondaryText: editor.store.state.deck?.meta?.colors?.buttonSecondaryText || '#ffffff',
831
+ // Links
832
+ link: editor.store.state.deck?.meta?.colors?.link || '#3b82f6',
833
+ linkHover: editor.store.state.deck?.meta?.colors?.linkHover || '#60a5fa',
834
+ // Gradients
835
+ gradientFrom: editor.store.state.deck?.meta?.colors?.gradientFrom || '#3b82f6',
836
+ gradientVia: editor.store.state.deck?.meta?.colors?.gradientVia || '',
837
+ gradientTo: editor.store.state.deck?.meta?.colors?.gradientTo || '#8b5cf6',
293
838
  }));
294
839
 
295
840
  // Typography settings
296
841
  const typographySettings = computed(() => ({
297
842
  heading: editor.store.state.deck?.meta?.typography?.fontFamily?.heading || 'Inter, system-ui, sans-serif',
298
843
  body: editor.store.state.deck?.meta?.typography?.fontFamily?.body || 'Inter, system-ui, sans-serif',
844
+ mono: editor.store.state.deck?.meta?.typography?.fontFamily?.mono || 'monospace',
299
845
  }));
300
846
 
301
- // Components settings
847
+ // Font size scale
848
+ const fontSizeTokens = ['xs', 'sm', 'base', 'lg', 'xl', '2xl', '3xl', '4xl', '5xl', '6xl', '7xl'];
849
+ const themeFontSize = computed(() => {
850
+ const defaults: Record<string, string> = {
851
+ xs: '0.75rem', sm: '0.875rem', base: '1rem', lg: '1.125rem', xl: '1.25rem',
852
+ '2xl': '1.5rem', '3xl': '1.875rem', '4xl': '2.25rem', '5xl': '3rem', '6xl': '3.75rem', '7xl': '4.5rem'
853
+ };
854
+ const saved = editor.store.state.deck?.meta?.typography?.fontSize || {};
855
+ return Object.fromEntries(fontSizeTokens.map(k => [k, (saved as any)[k] || defaults[k]]));
856
+ });
857
+
858
+ const updateFontSize = (key: string, value: string) => {
859
+ editor.store.updateNode(`meta.typography.fontSize.${key}`, value);
860
+ commitUpdate();
861
+ };
862
+
863
+ // Font weight scale
864
+ const fontWeightTokens = ['light', 'normal', 'medium', 'semibold', 'bold', 'extrabold'];
865
+ const themeFontWeight = computed(() => {
866
+ const defaults: Record<string, number> = {
867
+ light: 300, normal: 400, medium: 500, semibold: 600, bold: 700, extrabold: 800
868
+ };
869
+ const saved = editor.store.state.deck?.meta?.typography?.fontWeight || {};
870
+ return Object.fromEntries(fontWeightTokens.map(k => [k, (saved as any)[k] || defaults[k]]));
871
+ });
872
+
873
+ const updateFontWeight = (key: string, value: number) => {
874
+ editor.store.updateNode(`meta.typography.fontWeight.${key}`, value);
875
+ commitUpdate();
876
+ };
877
+
878
+ // Line height scale
879
+ const lineHeightTokens = ['tight', 'snug', 'normal', 'relaxed', 'loose'];
880
+ const themeLineHeight = computed(() => {
881
+ const defaults: Record<string, string> = {
882
+ tight: '1.1', snug: '1.25', normal: '1.5', relaxed: '1.625', loose: '2'
883
+ };
884
+ const saved = editor.store.state.deck?.meta?.typography?.lineHeight || {};
885
+ return Object.fromEntries(lineHeightTokens.map(k => [k, (saved as any)[k] || defaults[k]]));
886
+ });
887
+
888
+ const updateLineHeight = (key: string, value: string) => {
889
+ editor.store.updateNode(`meta.typography.lineHeight.${key}`, value);
890
+ commitUpdate();
891
+ };
892
+
893
+ // Letter spacing scale
894
+ const letterSpacingTokens = ['tighter', 'tight', 'normal', 'wide', 'wider', 'widest'];
895
+ const themeLetterSpacing = computed(() => {
896
+ const defaults: Record<string, string> = {
897
+ tighter: '-0.05em', tight: '-0.025em', normal: '0', wide: '0.025em', wider: '0.05em', widest: '0.1em'
898
+ };
899
+ const saved = editor.store.state.deck?.meta?.typography?.letterSpacing || {};
900
+ return Object.fromEntries(letterSpacingTokens.map(k => [k, (saved as any)[k] || defaults[k]]));
901
+ });
902
+
903
+ const updateLetterSpacing = (key: string, value: string) => {
904
+ editor.store.updateNode(`meta.typography.letterSpacing.${key}`, value);
905
+ commitUpdate();
906
+ };
907
+
908
+ // Components settings (complete set)
302
909
  const componentsSettings = computed(() => ({
910
+ // Buttons
303
911
  buttonRadius: editor.store.state.deck?.meta?.components?.buttonRadius || '9999px',
912
+ buttonPadding: editor.store.state.deck?.meta?.components?.buttonPadding || '0.75rem 1.5rem',
913
+ buttonFontWeight: editor.store.state.deck?.meta?.components?.buttonFontWeight ?? 600,
914
+ buttonTextTransform: editor.store.state.deck?.meta?.components?.buttonTextTransform || 'none',
915
+ // Cards
304
916
  cardRadius: editor.store.state.deck?.meta?.components?.cardRadius || '1.5rem',
917
+ cardPadding: editor.store.state.deck?.meta?.components?.cardPadding || '1.5rem',
918
+ cardBorderWidth: editor.store.state.deck?.meta?.components?.cardBorderWidth || '1px',
919
+ cardBackground: editor.store.state.deck?.meta?.components?.cardBackground || '',
920
+ // Timeline
921
+ timelineNodeSize: editor.store.state.deck?.meta?.components?.timelineNodeSize || '1rem',
922
+ timelineLineWidth: editor.store.state.deck?.meta?.components?.timelineLineWidth || '2px',
923
+ timelineNodeColor: editor.store.state.deck?.meta?.components?.timelineNodeColor || '',
924
+ timelineLineColor: editor.store.state.deck?.meta?.components?.timelineLineColor || '',
925
+ // Steps
926
+ stepBadgeSize: editor.store.state.deck?.meta?.components?.stepBadgeSize || '2.5rem',
927
+ stepFontSize: editor.store.state.deck?.meta?.components?.stepFontSize || '1rem',
928
+ // Progress
929
+ progressHeight: editor.store.state.deck?.meta?.components?.progressHeight || '4px',
930
+ progressRadius: editor.store.state.deck?.meta?.components?.progressRadius || '2px',
931
+ progressBackground: editor.store.state.deck?.meta?.components?.progressBackground || '',
932
+ progressFill: editor.store.state.deck?.meta?.components?.progressFill || '',
933
+ // Tags
934
+ tagPadding: editor.store.state.deck?.meta?.components?.tagPadding || '0.25rem 0.5rem',
935
+ tagRadius: editor.store.state.deck?.meta?.components?.tagRadius || '0.25rem',
936
+ tagFontSize: editor.store.state.deck?.meta?.components?.tagFontSize || '0.75rem',
937
+ // Inputs
938
+ inputRadius: editor.store.state.deck?.meta?.components?.inputRadius || '0.5rem',
939
+ inputPadding: editor.store.state.deck?.meta?.components?.inputPadding || '0.5rem 1rem',
940
+ inputBorder: editor.store.state.deck?.meta?.components?.inputBorder || '',
941
+ inputFocusBorder: editor.store.state.deck?.meta?.components?.inputFocusBorder || '',
305
942
  }));
306
943
 
307
944
  // Spacing settings
@@ -322,20 +959,34 @@ const themeRadius = computed(() => {
322
959
  return Object.fromEntries(radiusTokens.map(k => [k, saved[k] || defaults[k]]));
323
960
  });
324
961
 
325
- // Effects settings
962
+ // Effects settings (complete set)
326
963
  const effectsSettings = computed(() => ({
964
+ // Animations
327
965
  animations: editor.store.state.deck?.meta?.effects?.animationsEnabled ?? true,
328
966
  animationType: editor.store.state.deck?.meta?.effects?.animationType || 'cascade',
329
967
  animationDuration: editor.store.state.deck?.meta?.effects?.animationDuration ?? 0.8,
330
968
  animationStagger: editor.store.state.deck?.meta?.effects?.animationStagger ?? 0.1,
331
969
  animationEase: editor.store.state.deck?.meta?.effects?.animationEase || 'power3.out',
970
+ transitionDuration: editor.store.state.deck?.meta?.effects?.transitionDuration || '0.3s',
971
+ transitionEasing: editor.store.state.deck?.meta?.effects?.transitionEasing || 'ease-out',
972
+ hoverScale: editor.store.state.deck?.meta?.effects?.hoverScale ?? 1.05,
973
+ // Gradients
974
+ gradients: editor.store.state.deck?.meta?.effects?.useGradients ?? false,
975
+ gradientDirection: editor.store.state.deck?.meta?.effects?.gradientDirection || 'to-br',
976
+ // Shadows
332
977
  shadows: editor.store.state.deck?.meta?.effects?.useShadows ?? true,
978
+ shadowIntensity: editor.store.state.deck?.meta?.effects?.shadowIntensity || 'md',
979
+ shadowColor: editor.store.state.deck?.meta?.effects?.shadowColor || '#000000',
980
+ // Glass
333
981
  glass: editor.store.state.deck?.meta?.effects?.useGlass ?? true,
334
982
  glassOpacity: editor.store.state.deck?.meta?.effects?.glassOpacity ?? 0.12,
335
983
  glassBlur: editor.store.state.deck?.meta?.effects?.glassBlur ?? '20px',
984
+ glassBorderOpacity: editor.store.state.deck?.meta?.effects?.glassBorderOpacity ?? 0.1,
985
+ // Orb
336
986
  orb: editor.store.state.deck?.meta?.effects?.useOrb ?? true,
337
987
  orbOpacity: editor.store.state.deck?.meta?.effects?.orbOpacity ?? 0.2,
338
988
  orbBlur: editor.store.state.deck?.meta?.effects?.orbBlur ?? '120px',
989
+ orbSize: editor.store.state.deck?.meta?.effects?.orbSize || '60vw',
339
990
  }));
340
991
 
341
992
  // Font groupings