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,121 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<BaseSlide :data="data" :slide-index="slideIndex">
|
|
3
|
+
<div :class="[
|
|
4
|
+
'features-root w-full max-w-full flex flex-col justify-center overflow-x-hidden',
|
|
5
|
+
data.sizing === 'container' ? 'min-h-full' : 'min-h-screen'
|
|
6
|
+
]">
|
|
7
|
+
<!-- Header -->
|
|
8
|
+
<LuminaElement :id="headerId" :class="[data.class || '', 'text-center lg:text-left max-w-6xl min-w-0']"
|
|
9
|
+
:style="{ marginBottom: 'var(--lumina-space-2xl)' }">
|
|
10
|
+
<h2 class="font-bold break-words" :style="{
|
|
11
|
+
fontFamily: 'var(--lumina-font-heading)',
|
|
12
|
+
fontSize: data.sizing === 'container' ? 'var(--lumina-text-3xl)' : 'var(--lumina-text-5xl)',
|
|
13
|
+
marginBottom: 'var(--lumina-space-md)',
|
|
14
|
+
letterSpacing: 'var(--lumina-tracking-tighter)',
|
|
15
|
+
color: 'var(--lumina-color-text-safe, var(--lumina-color-text))',
|
|
16
|
+
textShadow: '0 2px 10px rgba(0,0,0,0.1)'
|
|
17
|
+
}">
|
|
18
|
+
{{ data.title }}
|
|
19
|
+
</h2>
|
|
20
|
+
<p v-if="data.description" class="break-words" :style="{
|
|
21
|
+
color: 'var(--lumina-color-text-safe, var(--lumina-color-text))',
|
|
22
|
+
opacity: 0.9,
|
|
23
|
+
fontSize: data.sizing === 'container' ? 'var(--lumina-text-sm)' : 'var(--lumina-text-xl)'
|
|
24
|
+
}">
|
|
25
|
+
{{ data.description }}
|
|
26
|
+
</p>
|
|
27
|
+
</LuminaElement>
|
|
28
|
+
|
|
29
|
+
<!-- Grid -->
|
|
30
|
+
<div :class="['features-grid', data.sizing !== 'container' ? 'features-grid--multi' : '']">
|
|
31
|
+
<LuminaElement v-for="(feature, i) in data.features" :key="i" :id="featureId(i)"
|
|
32
|
+
:class="['glass-panel group min-w-0', feature.class || '']" :style="{
|
|
33
|
+
borderRadius: 'var(--lumina-card-radius)',
|
|
34
|
+
padding: data.sizing === 'container' ? 'var(--lumina-space-lg)' : 'var(--lumina-card-padding)',
|
|
35
|
+
borderTop: '1px solid var(--lumina-color-border-subtle)',
|
|
36
|
+
transition: 'all var(--lumina-transition-duration) var(--lumina-transition-easing)'
|
|
37
|
+
}">
|
|
38
|
+
<!-- Icon -->
|
|
39
|
+
<div class="flex items-center justify-center group-hover:scale-110 shrink-0" :style="{
|
|
40
|
+
color: 'white',
|
|
41
|
+
background: 'linear-gradient(135deg, var(--lumina-color-primary) 0%, var(--lumina-color-secondary) 100%)',
|
|
42
|
+
width: data.sizing === 'container' ? 'var(--lumina-space-xl)' : 'var(--lumina-space-2xl)',
|
|
43
|
+
height: data.sizing === 'container' ? 'var(--lumina-space-xl)' : 'var(--lumina-space-2xl)',
|
|
44
|
+
borderRadius: 'var(--lumina-radius-xl)',
|
|
45
|
+
marginBottom: data.sizing === 'container' ? 'var(--lumina-space-md)' : 'var(--lumina-space-lg)',
|
|
46
|
+
fontSize: 'var(--lumina-text-xl)',
|
|
47
|
+
boxShadow: '0 4px 12px rgba(var(--lumina-color-primary-rgb), 0.3)',
|
|
48
|
+
transition: 'all var(--lumina-transition-duration) var(--lumina-transition-easing)'
|
|
49
|
+
}">
|
|
50
|
+
<i
|
|
51
|
+
:class="['ph-thin', (feature.icon || 'star').startsWith('ph-') ? feature.icon : `ph-${feature.icon || 'star'}`]"></i>
|
|
52
|
+
</div>
|
|
53
|
+
<!-- Title -->
|
|
54
|
+
<h3 class="font-bold break-words" :style="{
|
|
55
|
+
color: 'var(--lumina-surface-text, var(--lumina-color-text))',
|
|
56
|
+
fontSize: data.sizing === 'container' ? 'var(--lumina-text-lg)' : 'var(--lumina-text-2xl)',
|
|
57
|
+
marginBottom: 'var(--lumina-space-sm)'
|
|
58
|
+
}">
|
|
59
|
+
{{ feature.title }}
|
|
60
|
+
</h3>
|
|
61
|
+
<!-- Description (support both desc and description) -->
|
|
62
|
+
<p class="break-words" :style="{
|
|
63
|
+
color: 'var(--lumina-color-text-secondary)',
|
|
64
|
+
fontSize: data.sizing === 'container' ? 'var(--lumina-text-sm)' : 'var(--lumina-text-base)',
|
|
65
|
+
lineHeight: 'var(--lumina-leading-relaxed)',
|
|
66
|
+
opacity: 0.8
|
|
67
|
+
}">
|
|
68
|
+
{{ feature.desc || feature.description || '' }}
|
|
69
|
+
</p>
|
|
70
|
+
</LuminaElement>
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
73
|
+
</BaseSlide>
|
|
74
|
+
</template>
|
|
75
|
+
|
|
76
|
+
<script setup lang="ts">
|
|
77
|
+
import { computed, inject } from 'vue';
|
|
78
|
+
import BaseSlide from '../base/BaseSlide.vue';
|
|
79
|
+
import { resolveId } from '../../core/elementResolver';
|
|
80
|
+
import { StoreKey } from '../../core/store';
|
|
81
|
+
import type { SlideFeatures } from '../../core/types';
|
|
82
|
+
|
|
83
|
+
const props = defineProps<{
|
|
84
|
+
data: SlideFeatures;
|
|
85
|
+
slideIndex?: number;
|
|
86
|
+
}>();
|
|
87
|
+
|
|
88
|
+
const store = inject(StoreKey);
|
|
89
|
+
const slideIndex = computed(() => props.slideIndex ?? store?.state.currentIndex ?? 0);
|
|
90
|
+
|
|
91
|
+
const headerId = computed(() => resolveId(props.data, slideIndex.value, ['header']));
|
|
92
|
+
const featureId = (i: number) => resolveId(props.data, slideIndex.value, ['features', i]);
|
|
93
|
+
</script>
|
|
94
|
+
|
|
95
|
+
<style scoped>
|
|
96
|
+
.features-root {
|
|
97
|
+
padding: var(--lumina-space-xl) var(--lumina-space-3xl);
|
|
98
|
+
}
|
|
99
|
+
@media (max-width: 1023px) {
|
|
100
|
+
.features-root { padding-left: var(--lumina-space-xl); padding-right: var(--lumina-space-xl); }
|
|
101
|
+
}
|
|
102
|
+
@media (max-width: 639px) {
|
|
103
|
+
.features-root { padding: var(--lumina-space-lg) var(--lumina-space-md); }
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.features-grid {
|
|
107
|
+
display: grid;
|
|
108
|
+
grid-template-columns: 1fr;
|
|
109
|
+
gap: var(--lumina-space-md);
|
|
110
|
+
}
|
|
111
|
+
.features-grid--multi {
|
|
112
|
+
grid-template-columns: repeat(auto-fit, minmax(min(100%, 300px), 1fr));
|
|
113
|
+
gap: var(--lumina-space-lg);
|
|
114
|
+
}
|
|
115
|
+
@media (max-width: 639px) {
|
|
116
|
+
.features-grid--multi {
|
|
117
|
+
grid-template-columns: 1fr;
|
|
118
|
+
gap: var(--lumina-space-md);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
</style>
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<BaseSlide :data="data" :slide-index="slideIndex">
|
|
3
|
+
<div class="flex-layout w-full h-screen flex flex-col overflow-y-auto lg:overflow-hidden"
|
|
4
|
+
:style="containerStyle">
|
|
5
|
+
<div :class="['flex-1 flex w-full min-h-full lg:h-full', directionClass]"
|
|
6
|
+
:style="mainFlexStyle">
|
|
7
|
+
<template v-for="(element, i) in data.elements" :key="i">
|
|
8
|
+
<!-- Image Element -->
|
|
9
|
+
<LuminaElement v-if="element.type === 'image'" :id="elemId(i)" :class="['flex-item', getSizeClass(element.size)]" :style="getElementStyle(element)">
|
|
10
|
+
<FlexImage :src="element.src" :alt="element.alt"
|
|
11
|
+
:fill="element.fill" :rounded="element.rounded"
|
|
12
|
+
container-class="w-full h-full min-h-0" :container-style="{}" :class="element.class" />
|
|
13
|
+
</LuminaElement>
|
|
14
|
+
|
|
15
|
+
<!-- Video Element -->
|
|
16
|
+
<LuminaElement v-else-if="element.type === 'video'" :id="elemId(i)"
|
|
17
|
+
:class="['flex-item', getSizeClass(element.size), element.class]"
|
|
18
|
+
:style="getElementStyle(element)">
|
|
19
|
+
<VideoPlayer :class="getRoundedClass(element.rounded, element.fill)" :src="element.src"
|
|
20
|
+
:poster="element.poster" :autoplay="element.autoplay ?? false" :loop="element.loop ?? false"
|
|
21
|
+
:muted="element.muted ?? false" :controls="element.controls ?? true"
|
|
22
|
+
:object-fit="element.fill !== false ? 'cover' : 'contain'" />
|
|
23
|
+
</LuminaElement>
|
|
24
|
+
|
|
25
|
+
<!-- Content Container -->
|
|
26
|
+
<LuminaElement v-else-if="element.type === 'content'" :id="elemId(i)"
|
|
27
|
+
:class="['flex-item flex flex-col h-full', getSizeClass(element.size)]"
|
|
28
|
+
:style="getContentStyle(element)">
|
|
29
|
+
<template v-for="(child, j) in element.elements" :key="j">
|
|
30
|
+
<LuminaElement :id="childId(i, j)">
|
|
31
|
+
<component :is="getChildComponent(child.type)" v-bind="child" @action="handleAction" />
|
|
32
|
+
</LuminaElement>
|
|
33
|
+
</template>
|
|
34
|
+
</LuminaElement>
|
|
35
|
+
|
|
36
|
+
<!-- Top-level elements (title, text, bullets, etc.) -->
|
|
37
|
+
<LuminaElement v-else :id="elemId(i)" :class="['flex-item', getSizeClass(element.size)]"
|
|
38
|
+
:style="{ ...getTopLevelStyle(), width: data.direction === 'vertical' ? '100%' : 'auto' }">
|
|
39
|
+
<component :is="getChildComponent(element.type)" v-bind="element" @action="handleAction" />
|
|
40
|
+
</LuminaElement>
|
|
41
|
+
</template>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
</BaseSlide>
|
|
45
|
+
</template>
|
|
46
|
+
|
|
47
|
+
<script setup lang="ts">
|
|
48
|
+
import { computed, inject } from 'vue';
|
|
49
|
+
import BaseSlide from '../base/BaseSlide.vue';
|
|
50
|
+
import VideoPlayer from '../base/VideoPlayer.vue';
|
|
51
|
+
import { resolveId } from '../../core/elementResolver';
|
|
52
|
+
import { StoreKey } from '../../core/store';
|
|
53
|
+
import type { SlideFlex, FlexElement, FlexSize, FlexElementContent } from '../../core/types';
|
|
54
|
+
import FlexImage from '../parts/FlexImage.vue';
|
|
55
|
+
import FlexTitle from '../parts/FlexTitle.vue';
|
|
56
|
+
import FlexText from '../parts/FlexText.vue';
|
|
57
|
+
import FlexButton from '../parts/FlexButton.vue';
|
|
58
|
+
import FlexBullets from '../parts/FlexBullets.vue';
|
|
59
|
+
import FlexOrdered from '../parts/FlexOrdered.vue';
|
|
60
|
+
import FlexTimeline from '../parts/FlexTimeline.vue';
|
|
61
|
+
import FlexStepper from '../parts/FlexStepper.vue';
|
|
62
|
+
import FlexSpacer from '../parts/FlexSpacer.vue';
|
|
63
|
+
import { bus } from '../../core/events';
|
|
64
|
+
import {
|
|
65
|
+
spacingVarMap,
|
|
66
|
+
getSizeClass,
|
|
67
|
+
getFlexItemStyle,
|
|
68
|
+
getFlexContainerStyle,
|
|
69
|
+
getTopLevelStyle,
|
|
70
|
+
getFlexSlideMainStyle,
|
|
71
|
+
getRoundedClass
|
|
72
|
+
} from '../../composables/useFlexLayout';
|
|
73
|
+
|
|
74
|
+
const props = defineProps<{
|
|
75
|
+
data: SlideFlex;
|
|
76
|
+
slideIndex?: number;
|
|
77
|
+
}>();
|
|
78
|
+
|
|
79
|
+
const emit = defineEmits(['action']);
|
|
80
|
+
const store = inject(StoreKey);
|
|
81
|
+
const slideIndex = computed(() => props.slideIndex ?? store?.state.currentIndex ?? 0);
|
|
82
|
+
const elemId = (i: number) => resolveId(props.data, slideIndex.value, ['elements', i]);
|
|
83
|
+
const childId = (i: number, j: number) => resolveId(props.data, slideIndex.value, ['elements', i, 'elements', j]);
|
|
84
|
+
|
|
85
|
+
const containerStyle = computed(() => ({
|
|
86
|
+
padding: spacingVarMap[props.data.padding || 'none'],
|
|
87
|
+
}));
|
|
88
|
+
|
|
89
|
+
const directionClass = computed(() =>
|
|
90
|
+
props.data.direction === 'vertical' ? 'flex-col' : 'flex-col lg:flex-row'
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
const mainFlexStyle = computed(() => ({
|
|
94
|
+
gap: spacingVarMap[props.data.gap || 'none'],
|
|
95
|
+
...getFlexSlideMainStyle(props.data.direction, props.data.halign, props.data.valign),
|
|
96
|
+
}));
|
|
97
|
+
|
|
98
|
+
// Wrapper for shared logic requiring vertical flag
|
|
99
|
+
const getElementStyle = (element: FlexElement & { size?: FlexSize }) => {
|
|
100
|
+
return getFlexItemStyle(element, props.data.direction === 'vertical');
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const getContentStyle = (element: FlexElementContent & { size?: FlexSize }) => {
|
|
104
|
+
const isVertical = props.data.direction === 'vertical';
|
|
105
|
+
return {
|
|
106
|
+
...getFlexItemStyle(element, isVertical),
|
|
107
|
+
...getFlexContainerStyle(element),
|
|
108
|
+
// Force full height/width for alignment space
|
|
109
|
+
height: isVertical ? 'auto' : '100%',
|
|
110
|
+
width: isVertical ? '100%' : 'auto',
|
|
111
|
+
minHeight: isVertical ? 'auto' : '100%'
|
|
112
|
+
};
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const handleAction = (payload: any) => {
|
|
116
|
+
emit('action', payload);
|
|
117
|
+
bus.emit('action', payload);
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
// ============================================================================
|
|
121
|
+
// CHILD ELEMENT COMPONENTS - All using CSS variables
|
|
122
|
+
// ============================================================================
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
// Child components extracted to ../parts/
|
|
126
|
+
|
|
127
|
+
const getChildComponent = (type: string) => {
|
|
128
|
+
const components: Record<string, any> = {
|
|
129
|
+
'title': FlexTitle,
|
|
130
|
+
'text': FlexText,
|
|
131
|
+
'image': FlexImage,
|
|
132
|
+
'video': VideoPlayer,
|
|
133
|
+
'bullets': FlexBullets,
|
|
134
|
+
'ordered': FlexOrdered,
|
|
135
|
+
'button': FlexButton,
|
|
136
|
+
'timeline': FlexTimeline,
|
|
137
|
+
'stepper': FlexStepper,
|
|
138
|
+
'spacer': FlexSpacer,
|
|
139
|
+
};
|
|
140
|
+
return components[type] || 'div';
|
|
141
|
+
};
|
|
142
|
+
</script>
|
|
143
|
+
|
|
144
|
+
<style scoped>
|
|
145
|
+
.flex-layout {
|
|
146
|
+
min-height: 100vh;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
@media (max-width: 1023px) {
|
|
150
|
+
.flex-item {
|
|
151
|
+
flex: 0 0 auto !important;
|
|
152
|
+
width: 100% !important;
|
|
153
|
+
max-width: 100% !important;
|
|
154
|
+
max-height: none !important;
|
|
155
|
+
min-height: auto !important;
|
|
156
|
+
height: auto !important;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/* Images in stacked flex should have a reasonable height */
|
|
160
|
+
.flex-item:has(img),
|
|
161
|
+
.flex-item img {
|
|
162
|
+
height: 40vh !important;
|
|
163
|
+
min-height: 300px !important;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
.flex-layout {
|
|
167
|
+
overflow-y: auto !important;
|
|
168
|
+
height: auto !important;
|
|
169
|
+
min-height: 100vh;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
</style>
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<BaseSlide :data="data" :slide-index="slideIndex">
|
|
3
|
+
<div class="layout-free-stage absolute inset-0 min-h-[100vh] overflow-hidden">
|
|
4
|
+
<template v-for="(el, i) in (data.elements || [])" :key="i">
|
|
5
|
+
<LuminaElement
|
|
6
|
+
:id="elementId(i)"
|
|
7
|
+
:tag="el.type === 'image' ? 'div' : 'div'"
|
|
8
|
+
:style="baseStyle(el)"
|
|
9
|
+
class="layout-free-element">
|
|
10
|
+
<template v-if="el.type === 'text'">{{ el.text }}</template>
|
|
11
|
+
<img v-else-if="el.type === 'image' && el.src" :src="el.src" :alt="''" class="max-w-full max-h-full object-contain" :style="imgStyle(el)" />
|
|
12
|
+
<span v-else-if="el.type === 'box'"> </span>
|
|
13
|
+
</LuminaElement>
|
|
14
|
+
</template>
|
|
15
|
+
</div>
|
|
16
|
+
</BaseSlide>
|
|
17
|
+
</template>
|
|
18
|
+
|
|
19
|
+
<script setup lang="ts">
|
|
20
|
+
import { computed, inject } from 'vue';
|
|
21
|
+
import BaseSlide from '../base/BaseSlide.vue';
|
|
22
|
+
import LuminaElement from '../base/LuminaElement.vue';
|
|
23
|
+
import { resolveId } from '../../core/elementResolver';
|
|
24
|
+
import { StoreKey } from '../../core/store';
|
|
25
|
+
import type { SlideFree, FreeElement } from '../../core/types';
|
|
26
|
+
|
|
27
|
+
const props = defineProps<{
|
|
28
|
+
data: SlideFree;
|
|
29
|
+
slideIndex?: number;
|
|
30
|
+
}>();
|
|
31
|
+
|
|
32
|
+
const store = inject(StoreKey);
|
|
33
|
+
const slideIndex = computed(() => props.slideIndex ?? store?.state.currentIndex ?? 0);
|
|
34
|
+
|
|
35
|
+
function elementId(i: number) {
|
|
36
|
+
return resolveId(props.data, slideIndex.value, ['elements', i]);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function baseStyle(el: FreeElement): Record<string, string | number> {
|
|
40
|
+
const s: Record<string, string | number> = {
|
|
41
|
+
position: 'absolute',
|
|
42
|
+
left: 0,
|
|
43
|
+
top: 0,
|
|
44
|
+
fontFamily: 'var(--lumina-font-heading, inherit)',
|
|
45
|
+
color: 'var(--lumina-color-text-safe, #fff)',
|
|
46
|
+
};
|
|
47
|
+
if (el.width != null) s.width = typeof el.width === 'number' ? `${el.width}px` : el.width;
|
|
48
|
+
if (el.height != null) s.height = typeof el.height === 'number' ? `${el.height}px` : el.height;
|
|
49
|
+
if (el.fontSize != null) s.fontSize = typeof el.fontSize === 'number' ? `${el.fontSize}px` : el.fontSize;
|
|
50
|
+
if (el.color) s.color = el.color;
|
|
51
|
+
if (el.fontWeight) s.fontWeight = el.fontWeight;
|
|
52
|
+
if (el.type === 'box' && el.backgroundColor) s.backgroundColor = el.backgroundColor;
|
|
53
|
+
return s;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function imgStyle(el: FreeElement): Record<string, string | number> {
|
|
57
|
+
const s: Record<string, string | number> = { display: 'block' };
|
|
58
|
+
if (el.width != null) s.width = typeof el.width === 'number' ? `${el.width}px` : el.width;
|
|
59
|
+
if (el.height != null) s.height = typeof el.height === 'number' ? `${el.height}px` : el.height;
|
|
60
|
+
return s;
|
|
61
|
+
}
|
|
62
|
+
</script>
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<BaseSlide :data="data" :slide-index="si">
|
|
3
|
+
<div
|
|
4
|
+
:class="['w-full flex flex-col lg:block relative', data.sizing === 'container' ? 'min-h-full' : 'min-h-screen']">
|
|
5
|
+
<!-- Image Section -->
|
|
6
|
+
<LuminaElement :id="mediaId"
|
|
7
|
+
:class="['relative h-[40vh] lg:h-full lg:w-1/2 overflow-hidden z-0', data.sizing === 'container' ? 'lg:absolute lg:top-0' : 'lg:fixed lg:top-0', data.imageSide === 'right' ? 'lg:right-0 order-1' : 'lg:left-0 order-1']">
|
|
8
|
+
<div class="absolute inset-0 z-10" :style="{ backgroundColor: 'var(--lumina-color-overlay, rgba(0,0,0,0.2))' }">
|
|
9
|
+
</div>
|
|
10
|
+
|
|
11
|
+
<!-- Video Handling -->
|
|
12
|
+
<div v-if="data.video" class="w-full h-full relative">
|
|
13
|
+
<VideoPlayer class="w-full h-full" :src="data.video.src" :poster="data.video.poster"
|
|
14
|
+
:autoplay="data.video.autoplay ?? true" :loop="data.video.loop ?? true" :muted="data.video.muted ?? true"
|
|
15
|
+
:controls="data.video.controls" object-fit="cover" />
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
<!-- Image Handling -->
|
|
19
|
+
<img v-else-if="data.image" :src="data.image"
|
|
20
|
+
:class="['w-full h-full object-cover scale-110 origin-center', data.imageClass || '', isLoaded ? 'opacity-100' : 'opacity-0']"
|
|
21
|
+
:style="{ transition: 'opacity var(--lumina-transition-duration) var(--lumina-transition-easing)' }"
|
|
22
|
+
alt="Slide Image" @load="isLoaded = true"
|
|
23
|
+
@error="(e: any) => { isLoaded = true; e.target.src = 'https://placehold.co/1920x1080/1a1a1a/666?text=Image+Not+Found'; }">
|
|
24
|
+
</LuminaElement>
|
|
25
|
+
|
|
26
|
+
<!-- Content Section - Uses background color for proper theme integration -->
|
|
27
|
+
<div
|
|
28
|
+
:class="['relative z-10 w-full lg:w-1/2 min-h-[60vh] flex flex-col justify-center order-2', data.imageSide === 'right' ? 'lg:mr-[50%]' : 'lg:ml-[50%]', data.sizing === 'container' ? 'lg:min-h-full' : 'lg:min-h-screen']"
|
|
29
|
+
:style="contentPanelStyle">
|
|
30
|
+
<div :class="data.class || ''" :style="{ maxWidth: '36rem' }">
|
|
31
|
+
<!-- Tag -->
|
|
32
|
+
<LuminaElement v-if="data.tag" :id="tagId" tag="span" class="font-bold uppercase block" :style="{
|
|
33
|
+
color: 'var(--lumina-color-primary)',
|
|
34
|
+
letterSpacing: 'var(--lumina-tracking-widest)',
|
|
35
|
+
fontSize: 'var(--lumina-text-xs)',
|
|
36
|
+
marginBottom: 'var(--lumina-space-lg)'
|
|
37
|
+
}">
|
|
38
|
+
{{ data.tag }}
|
|
39
|
+
</LuminaElement>
|
|
40
|
+
|
|
41
|
+
<!-- Title -->
|
|
42
|
+
<LuminaElement :id="titleId" tag="h1" class="font-bold" :style="{
|
|
43
|
+
fontFamily: 'var(--lumina-font-heading)',
|
|
44
|
+
color: 'var(--lumina-color-text-safe, var(--lumina-color-text))',
|
|
45
|
+
fontSize: 'var(--lumina-text-6xl)',
|
|
46
|
+
letterSpacing: 'var(--lumina-tracking-tighter)',
|
|
47
|
+
lineHeight: 'var(--lumina-leading-tight)',
|
|
48
|
+
marginBottom: 'var(--lumina-space-xl)',
|
|
49
|
+
textShadow: '0 2px 10px rgba(0,0,0,0.1)'
|
|
50
|
+
}">
|
|
51
|
+
{{ data.title }}
|
|
52
|
+
</LuminaElement>
|
|
53
|
+
|
|
54
|
+
<!-- Paragraphs -->
|
|
55
|
+
<LuminaElement :id="paragraphsId" :style="{
|
|
56
|
+
color: 'var(--lumina-color-text-safe, var(--lumina-color-text))',
|
|
57
|
+
opacity: 0.9,
|
|
58
|
+
fontSize: 'var(--lumina-text-lg)',
|
|
59
|
+
lineHeight: 'var(--lumina-leading-relaxed)',
|
|
60
|
+
marginBottom: 'var(--lumina-space-xl)',
|
|
61
|
+
display: 'flex',
|
|
62
|
+
flexDirection: 'column',
|
|
63
|
+
gap: 'var(--lumina-space-lg)'
|
|
64
|
+
}">
|
|
65
|
+
<p v-for="(p, i) in data.paragraphs" :key="i">{{ p }}</p>
|
|
66
|
+
</LuminaElement>
|
|
67
|
+
|
|
68
|
+
<!-- CTA -->
|
|
69
|
+
<LuminaElement v-if="data.cta" :id="ctaId" :style="{
|
|
70
|
+
paddingTop: 'var(--lumina-space-lg)',
|
|
71
|
+
borderTop: '1px solid var(--lumina-color-border)'
|
|
72
|
+
}">
|
|
73
|
+
<button @click="$emit('action', { type: 'cta', label: data.cta })" class="font-bold flex items-center"
|
|
74
|
+
:style="{
|
|
75
|
+
backgroundColor: 'var(--lumina-color-primary)',
|
|
76
|
+
color: 'var(--lumina-color-background)',
|
|
77
|
+
borderRadius: 'var(--lumina-button-radius)',
|
|
78
|
+
padding: 'var(--lumina-button-padding)',
|
|
79
|
+
fontWeight: 'var(--lumina-button-font-weight)',
|
|
80
|
+
gap: 'var(--lumina-space-sm)',
|
|
81
|
+
transition: 'all var(--lumina-transition-duration) var(--lumina-transition-easing)'
|
|
82
|
+
}">
|
|
83
|
+
{{ data.cta }} <i class="ph-thin ph-arrow-right"></i>
|
|
84
|
+
</button>
|
|
85
|
+
</LuminaElement>
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
89
|
+
</BaseSlide>
|
|
90
|
+
</template>
|
|
91
|
+
|
|
92
|
+
<script setup lang="ts">
|
|
93
|
+
import { ref, computed, inject } from 'vue';
|
|
94
|
+
import BaseSlide from '../base/BaseSlide.vue';
|
|
95
|
+
import VideoPlayer from '../base/VideoPlayer.vue';
|
|
96
|
+
import { resolveId } from '../../core/elementResolver';
|
|
97
|
+
import { StoreKey } from '../../core/store';
|
|
98
|
+
import type { SlideHalf } from '../../core/types';
|
|
99
|
+
|
|
100
|
+
const props = defineProps<{
|
|
101
|
+
data: SlideHalf;
|
|
102
|
+
slideIndex?: number;
|
|
103
|
+
}>();
|
|
104
|
+
|
|
105
|
+
const store = inject(StoreKey);
|
|
106
|
+
const isLoaded = ref(false);
|
|
107
|
+
const si = computed(() => props.slideIndex ?? store?.state.currentIndex ?? 0);
|
|
108
|
+
const mediaId = computed(() => resolveId(props.data, si.value, ['media']));
|
|
109
|
+
const tagId = computed(() => resolveId(props.data, si.value, ['tag']));
|
|
110
|
+
const titleId = computed(() => resolveId(props.data, si.value, ['title']));
|
|
111
|
+
const paragraphsId = computed(() => resolveId(props.data, si.value, ['paragraphs']));
|
|
112
|
+
const ctaId = computed(() => resolveId(props.data, si.value, ['cta']));
|
|
113
|
+
|
|
114
|
+
// Content panel style - uses background color for proper theme integration
|
|
115
|
+
// This ensures text is always readable against the background
|
|
116
|
+
const contentPanelStyle = computed(() => ({
|
|
117
|
+
backgroundColor: 'var(--lumina-color-background)',
|
|
118
|
+
paddingTop: 'var(--lumina-space-xl)',
|
|
119
|
+
paddingLeft: 'var(--lumina-space-3xl)',
|
|
120
|
+
paddingRight: 'var(--lumina-space-3xl)',
|
|
121
|
+
paddingBottom: 'calc(var(--lumina-space-xl) + var(--lumina-safe-area-bottom))'
|
|
122
|
+
}));
|
|
123
|
+
|
|
124
|
+
defineEmits<{
|
|
125
|
+
(e: 'action', payload: any): void
|
|
126
|
+
}>();
|
|
127
|
+
</script>
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<BaseSlide :data="data" :slide-index="slideIndex">
|
|
3
|
+
<div
|
|
4
|
+
:class="['flex flex-col justify-center items-center text-center relative', data.sizing === 'container' ? 'h-full' : 'min-h-screen']"
|
|
5
|
+
:style="{ paddingTop: 'var(--lumina-space-xl)', paddingLeft: 'var(--lumina-space-xl)', paddingRight: 'var(--lumina-space-xl)', paddingBottom: 'calc(var(--lumina-space-xl) + var(--lumina-safe-area-bottom))' }">
|
|
6
|
+
<div :class="[data.class || '', 'max-w-5xl z-10']">
|
|
7
|
+
<!-- Tag -->
|
|
8
|
+
<LuminaElement v-if="data.tag" :id="tagId" tag="span" class="inline-block rounded-full font-medium uppercase" :style="{
|
|
9
|
+
color: 'var(--lumina-color-primary)',
|
|
10
|
+
border: '1px solid var(--lumina-color-border)',
|
|
11
|
+
backgroundColor: 'rgba(var(--lumina-color-text-rgb), 0.05)',
|
|
12
|
+
padding: 'var(--lumina-tag-padding)',
|
|
13
|
+
fontSize: 'var(--lumina-tag-font-size)',
|
|
14
|
+
letterSpacing: 'var(--lumina-tracking-widest)',
|
|
15
|
+
marginBottom: 'var(--lumina-space-xl)'
|
|
16
|
+
}">
|
|
17
|
+
{{ data.tag }}
|
|
18
|
+
</LuminaElement>
|
|
19
|
+
|
|
20
|
+
<!-- Title -->
|
|
21
|
+
<LuminaElement :id="titleId" tag="h1" :style="titleStyle">
|
|
22
|
+
{{ data.title }}
|
|
23
|
+
</LuminaElement>
|
|
24
|
+
|
|
25
|
+
<!-- Subtitle -->
|
|
26
|
+
<LuminaElement v-if="data.subtitle" :id="subtitleId" tag="p" class="max-w-3xl mx-auto" :style="{
|
|
27
|
+
color: 'var(--lumina-color-text-safe, var(--lumina-color-text))',
|
|
28
|
+
opacity: 0.8,
|
|
29
|
+
fontWeight: 'normal',
|
|
30
|
+
fontSize: 'clamp(var(--lumina-text-xl), 3vw, var(--lumina-text-3xl))',
|
|
31
|
+
lineHeight: 'var(--lumina-leading-normal)'
|
|
32
|
+
}">
|
|
33
|
+
{{ data.subtitle }}
|
|
34
|
+
</LuminaElement>
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
</BaseSlide>
|
|
38
|
+
</template>
|
|
39
|
+
|
|
40
|
+
<script setup lang="ts">
|
|
41
|
+
import { computed, inject } from 'vue';
|
|
42
|
+
import BaseSlide from '../base/BaseSlide.vue';
|
|
43
|
+
import { resolveId } from '../../core/elementResolver';
|
|
44
|
+
import { StoreKey } from '../../core/store';
|
|
45
|
+
import type { SlideStatement } from '../../core/types';
|
|
46
|
+
|
|
47
|
+
const props = defineProps<{
|
|
48
|
+
data: SlideStatement;
|
|
49
|
+
slideIndex?: number;
|
|
50
|
+
}>();
|
|
51
|
+
|
|
52
|
+
const store = inject(StoreKey);
|
|
53
|
+
const slideIndex = computed(() => props.slideIndex ?? store?.state.currentIndex ?? 0);
|
|
54
|
+
|
|
55
|
+
const tagId = computed(() => resolveId(props.data, slideIndex.value, ['tag']));
|
|
56
|
+
const titleId = computed(() => resolveId(props.data, slideIndex.value, ['title']));
|
|
57
|
+
const subtitleId = computed(() => resolveId(props.data, slideIndex.value, ['subtitle']));
|
|
58
|
+
|
|
59
|
+
const titleStyle = computed(() => ({
|
|
60
|
+
fontSize: props.data.sizing === 'container'
|
|
61
|
+
? 'var(--lumina-text-5xl)'
|
|
62
|
+
: 'clamp(var(--lumina-text-4xl), 10vw, var(--lumina-text-7xl))',
|
|
63
|
+
fontFamily: 'var(--lumina-font-heading)',
|
|
64
|
+
fontWeight: 'var(--lumina-font-weight-bold)',
|
|
65
|
+
lineHeight: 'var(--lumina-leading-tight)',
|
|
66
|
+
letterSpacing: 'var(--lumina-tracking-tighter)',
|
|
67
|
+
marginBottom: 'var(--lumina-space-xl)',
|
|
68
|
+
backgroundImage: 'linear-gradient(135deg, var(--lumina-color-text-safe, var(--lumina-color-text)) 30%, #525252 100%)',
|
|
69
|
+
WebkitBackgroundClip: 'text',
|
|
70
|
+
WebkitTextFillColor: 'transparent',
|
|
71
|
+
backgroundClip: 'text',
|
|
72
|
+
color: 'var(--lumina-color-text-safe, var(--lumina-color-text))', // Fallback
|
|
73
|
+
}));
|
|
74
|
+
</script>
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<BaseSlide :data="data" :slide-index="slideIndex">
|
|
3
|
+
<div :class="['w-full flex flex-col justify-center', data.sizing === 'container' ? 'min-h-full py-12 lg:py-16' : 'min-h-screen']"
|
|
4
|
+
:style="{ padding: 'var(--lumina-space-xl) var(--lumina-space-3xl)' }">
|
|
5
|
+
<!-- Header -->
|
|
6
|
+
<LuminaElement :id="headerId" :class="['text-center max-w-4xl mx-auto', data.class || '']"
|
|
7
|
+
:style="{ marginBottom: 'var(--lumina-space-2xl)' }">
|
|
8
|
+
<h2 class="font-bold" :style="{
|
|
9
|
+
fontFamily: 'var(--lumina-font-heading)',
|
|
10
|
+
fontSize: 'var(--lumina-text-5xl)',
|
|
11
|
+
marginBottom: 'var(--lumina-space-md)',
|
|
12
|
+
letterSpacing: 'var(--lumina-tracking-tighter)',
|
|
13
|
+
color: 'var(--lumina-color-text-safe, var(--lumina-color-text))'
|
|
14
|
+
}">
|
|
15
|
+
{{ data.title }}
|
|
16
|
+
</h2>
|
|
17
|
+
<p v-if="data.subtitle" :style="{
|
|
18
|
+
color: 'var(--lumina-color-text-safe, var(--lumina-color-text))',
|
|
19
|
+
opacity: 0.9,
|
|
20
|
+
fontSize: 'var(--lumina-text-xl)'
|
|
21
|
+
}">
|
|
22
|
+
{{ data.subtitle }}
|
|
23
|
+
</p>
|
|
24
|
+
</LuminaElement>
|
|
25
|
+
|
|
26
|
+
<!-- Steps Grid -->
|
|
27
|
+
<div :style="{
|
|
28
|
+
display: 'grid',
|
|
29
|
+
gridTemplateColumns: 'repeat(auto-fit, minmax(280px, 1fr))',
|
|
30
|
+
gap: 'var(--lumina-space-xl)',
|
|
31
|
+
maxWidth: '72rem',
|
|
32
|
+
margin: '0 auto',
|
|
33
|
+
width: '100%'
|
|
34
|
+
}">
|
|
35
|
+
<LuminaElement v-for="(step, i) in data.steps" :key="i" :id="stepId(i)"
|
|
36
|
+
:class="['glass-panel group relative overflow-hidden', step.class || '']" :style="{
|
|
37
|
+
borderRadius: 'var(--lumina-card-radius)',
|
|
38
|
+
padding: 'var(--lumina-card-padding)'
|
|
39
|
+
}">
|
|
40
|
+
<!-- Step Number -->
|
|
41
|
+
<div :style="{
|
|
42
|
+
display: 'flex',
|
|
43
|
+
alignItems: 'center',
|
|
44
|
+
gap: 'var(--lumina-space-md)',
|
|
45
|
+
marginBottom: 'var(--lumina-space-md)'
|
|
46
|
+
}">
|
|
47
|
+
<div class="flex items-center justify-center font-bold" :style="{
|
|
48
|
+
width: 'var(--lumina-step-badge-size, 2.5rem)',
|
|
49
|
+
height: 'var(--lumina-step-badge-size, 2.5rem)',
|
|
50
|
+
fontSize: 'var(--lumina-step-font-size, 1rem)',
|
|
51
|
+
background: 'linear-gradient(135deg, var(--lumina-color-primary), var(--lumina-color-secondary))',
|
|
52
|
+
color: 'white',
|
|
53
|
+
borderRadius: 'var(--lumina-radius-xl)',
|
|
54
|
+
boxShadow: '0 4px 12px rgba(var(--lumina-color-primary-rgb), 0.4)'
|
|
55
|
+
}">
|
|
56
|
+
{{ step.step }}
|
|
57
|
+
</div>
|
|
58
|
+
<h3 class="font-bold" :style="{
|
|
59
|
+
fontSize: 'var(--lumina-text-xl)',
|
|
60
|
+
color: 'var(--lumina-surface-text, var(--lumina-color-text))'
|
|
61
|
+
}">
|
|
62
|
+
{{ step.title }}
|
|
63
|
+
</h3>
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
<!-- Description -->
|
|
67
|
+
<p v-if="step.description" :style="{
|
|
68
|
+
color: 'var(--lumina-color-text-secondary)',
|
|
69
|
+
lineHeight: 'var(--lumina-leading-relaxed)',
|
|
70
|
+
fontSize: 'var(--lumina-text-base)',
|
|
71
|
+
opacity: 0.8
|
|
72
|
+
}">
|
|
73
|
+
{{ step.description }}
|
|
74
|
+
</p>
|
|
75
|
+
|
|
76
|
+
<!-- Icon (optional) -->
|
|
77
|
+
<div v-if="step.icon" class="absolute top-4 right-4" :style="{
|
|
78
|
+
color: 'var(--lumina-color-primary)',
|
|
79
|
+
opacity: 0.1,
|
|
80
|
+
fontSize: 'var(--lumina-text-4xl)'
|
|
81
|
+
}">
|
|
82
|
+
<i :class="['ph-thin', step.icon.startsWith('ph-') ? step.icon : `ph-${step.icon}`]"></i>
|
|
83
|
+
</div>
|
|
84
|
+
</LuminaElement>
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
</BaseSlide>
|
|
88
|
+
</template>
|
|
89
|
+
|
|
90
|
+
<script setup lang="ts">
|
|
91
|
+
import { computed, inject } from 'vue';
|
|
92
|
+
import BaseSlide from '../base/BaseSlide.vue';
|
|
93
|
+
import { resolveId } from '../../core/elementResolver';
|
|
94
|
+
import { StoreKey } from '../../core/store';
|
|
95
|
+
import type { SlideSteps } from '../../core/types';
|
|
96
|
+
|
|
97
|
+
const props = defineProps<{
|
|
98
|
+
data: SlideSteps;
|
|
99
|
+
slideIndex?: number;
|
|
100
|
+
}>();
|
|
101
|
+
|
|
102
|
+
const store = inject(StoreKey);
|
|
103
|
+
const slideIndex = computed(() => props.slideIndex ?? store?.state.currentIndex ?? 0);
|
|
104
|
+
const headerId = computed(() => resolveId(props.data, slideIndex.value, ['header']));
|
|
105
|
+
const stepId = (i: number) => resolveId(props.data, slideIndex.value, ['steps', i]);
|
|
106
|
+
</script>
|