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,104 @@
|
|
|
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
|
+
<div :class="['text-center mb-16', data.class || '']">
|
|
7
|
+
<LuminaElement :id="titleId" tag="h2" class="font-bold mb-4" :style="{
|
|
8
|
+
fontFamily: 'var(--lumina-font-heading)',
|
|
9
|
+
fontSize: 'var(--lumina-text-5xl)',
|
|
10
|
+
lineHeight: 'var(--lumina-leading-tight)',
|
|
11
|
+
letterSpacing: 'var(--lumina-tracking-tighter)',
|
|
12
|
+
color: 'var(--lumina-color-text-safe, var(--lumina-color-text))',
|
|
13
|
+
textShadow: '0 2px 10px rgba(0,0,0,0.1)'
|
|
14
|
+
}">
|
|
15
|
+
{{ data.title }}
|
|
16
|
+
</LuminaElement>
|
|
17
|
+
<LuminaElement v-if="data.subtitle" :id="subtitleId" tag="p" :style="{
|
|
18
|
+
color: 'var(--lumina-color-muted-safe, var(--lumina-color-muted))',
|
|
19
|
+
fontSize: 'var(--lumina-text-xl)',
|
|
20
|
+
opacity: 0.8
|
|
21
|
+
}">
|
|
22
|
+
{{ data.subtitle }}
|
|
23
|
+
</LuminaElement>
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<!-- Timeline Container -->
|
|
27
|
+
<div class="relative max-w-6xl mx-auto w-full">
|
|
28
|
+
<!-- Vertical Line -->
|
|
29
|
+
<div :class="['absolute left-4 md:left-1/2 top-0 bottom-0 -translate-x-1/2 origin-top', data.lineClass || '']"
|
|
30
|
+
:style="{
|
|
31
|
+
width: 'var(--lumina-timeline-line-width, 2px)',
|
|
32
|
+
backgroundColor: 'var(--lumina-timeline-line-color, var(--lumina-color-border))'
|
|
33
|
+
}">
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
<!-- Items -->
|
|
37
|
+
<div :style="{ display: 'flex', flexDirection: 'column', gap: 'var(--lumina-space-2xl)' }">
|
|
38
|
+
<LuminaElement v-for="(item, i) in data.timeline" :key="i" :id="itemId(i)"
|
|
39
|
+
:class="['relative flex flex-col md:flex-row gap-8 md:gap-0 items-start md:items-center', item.class || '', i % 2 === 0 ? '' : 'md:flex-row-reverse']">
|
|
40
|
+
<!-- Date/Label Side -->
|
|
41
|
+
<div
|
|
42
|
+
:class="['w-full md:w-1/2 pl-12 md:pl-0 flex flex-col justify-center', i % 2 === 0 ? 'md:pr-12 md:text-right md:items-end' : 'md:pl-12 md:text-left md:items-start']">
|
|
43
|
+
<span class="font-bold uppercase mb-1" :style="{
|
|
44
|
+
color: 'var(--lumina-color-primary)',
|
|
45
|
+
letterSpacing: 'var(--lumina-tracking-widest)',
|
|
46
|
+
fontSize: 'var(--lumina-text-sm)'
|
|
47
|
+
}">
|
|
48
|
+
{{ item.date }}
|
|
49
|
+
</span>
|
|
50
|
+
<h3 class="font-bold" :style="{
|
|
51
|
+
fontSize: 'var(--lumina-text-2xl)',
|
|
52
|
+
color: 'var(--lumina-color-text-safe, var(--lumina-color-text))'
|
|
53
|
+
}">
|
|
54
|
+
{{ item.title || item.t }}
|
|
55
|
+
</h3>
|
|
56
|
+
</div>
|
|
57
|
+
|
|
58
|
+
<!-- Center Node -->
|
|
59
|
+
<div class="absolute left-4 md:left-1/2 -translate-x-1/2 rounded-full z-10 animate-pulse-glow"
|
|
60
|
+
:style="{
|
|
61
|
+
width: 'var(--lumina-timeline-node-size, 0.875rem)',
|
|
62
|
+
height: 'var(--lumina-timeline-node-size, 0.875rem)',
|
|
63
|
+
background: 'linear-gradient(135deg, var(--lumina-color-primary), var(--lumina-color-secondary))',
|
|
64
|
+
border: '3px solid var(--lumina-color-background)'
|
|
65
|
+
}">
|
|
66
|
+
</div>
|
|
67
|
+
|
|
68
|
+
<!-- Description Side -->
|
|
69
|
+
<div
|
|
70
|
+
:class="['w-full md:w-1/2 pl-12 md:pl-0', i % 2 === 0 ? 'md:pl-12' : 'md:pr-12 md:text-right']">
|
|
71
|
+
<p class="leading-relaxed" :style="{
|
|
72
|
+
color: 'var(--lumina-color-text-safe, var(--lumina-color-text))',
|
|
73
|
+
opacity: 0.9,
|
|
74
|
+
fontSize: 'var(--lumina-text-lg)',
|
|
75
|
+
lineHeight: 'var(--lumina-leading-relaxed)'
|
|
76
|
+
}">
|
|
77
|
+
{{ item.description || item.desc }}
|
|
78
|
+
</p>
|
|
79
|
+
</div>
|
|
80
|
+
</LuminaElement>
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
</BaseSlide>
|
|
85
|
+
</template>
|
|
86
|
+
|
|
87
|
+
<script setup lang="ts">
|
|
88
|
+
import { computed, inject } from 'vue';
|
|
89
|
+
import BaseSlide from '../base/BaseSlide.vue';
|
|
90
|
+
import { resolveId } from '../../core/elementResolver';
|
|
91
|
+
import { StoreKey } from '../../core/store';
|
|
92
|
+
import type { SlideTimeline } from '../../core/types';
|
|
93
|
+
|
|
94
|
+
const props = defineProps<{
|
|
95
|
+
data: SlideTimeline;
|
|
96
|
+
slideIndex?: number;
|
|
97
|
+
}>();
|
|
98
|
+
|
|
99
|
+
const store = inject(StoreKey);
|
|
100
|
+
const slideIndex = computed(() => props.slideIndex ?? store?.state.currentIndex ?? 0);
|
|
101
|
+
const titleId = computed(() => resolveId(props.data, slideIndex.value, ['title']));
|
|
102
|
+
const subtitleId = computed(() => resolveId(props.data, slideIndex.value, ['subtitle']));
|
|
103
|
+
const itemId = (i: number) => resolveId(props.data, slideIndex.value, ['timeline', i]);
|
|
104
|
+
</script>
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<BaseSlide :data="data" :slide-index="slideIndex">
|
|
3
|
+
<div
|
|
4
|
+
class="h-full w-full flex items-center justify-center bg-black relative overflow-hidden group">
|
|
5
|
+
<!-- Video Player -->
|
|
6
|
+
<LuminaElement :id="videoId" class="absolute inset-0 w-full h-full">
|
|
7
|
+
<VideoPlayer class="w-full h-full" :src="data.video.src" :poster="data.video.poster"
|
|
8
|
+
:autoplay="data.video.autoplay" :loop="data.video.loop" :muted="data.video.muted"
|
|
9
|
+
:controls="data.video.controls" object-fit="cover" />
|
|
10
|
+
</LuminaElement>
|
|
11
|
+
|
|
12
|
+
<!-- Optional Overlay Title -->
|
|
13
|
+
<LuminaElement v-if="data.title" :id="titleId"
|
|
14
|
+
:class="['absolute bottom-12 left-12 z-10 px-6 py-4 bg-black/50 backdrop-blur-md rounded-2xl border border-white/10 opacity-0 group-hover:opacity-100 transition-opacity duration-500', data.class || '']">
|
|
15
|
+
<h2 class="text-white font-bold text-xl tracking-tighter">{{ data.title }}</h2>
|
|
16
|
+
</LuminaElement>
|
|
17
|
+
</div>
|
|
18
|
+
</BaseSlide>
|
|
19
|
+
</template>
|
|
20
|
+
|
|
21
|
+
<script setup lang="ts">
|
|
22
|
+
import { computed, inject, PropType } from 'vue';
|
|
23
|
+
import { SlideVideo } from '../../core/types';
|
|
24
|
+
import VideoPlayer from '../base/VideoPlayer.vue';
|
|
25
|
+
import BaseSlide from '../base/BaseSlide.vue';
|
|
26
|
+
import { resolveId } from '../../core/elementResolver';
|
|
27
|
+
import { StoreKey } from '../../core/store';
|
|
28
|
+
|
|
29
|
+
const props = defineProps({
|
|
30
|
+
data: {
|
|
31
|
+
type: Object as PropType<SlideVideo>,
|
|
32
|
+
required: true
|
|
33
|
+
},
|
|
34
|
+
slideIndex: { type: Number, default: undefined }
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const store = inject(StoreKey);
|
|
38
|
+
const slideIndex = computed(() => props.slideIndex ?? store?.state.currentIndex ?? 0);
|
|
39
|
+
const videoId = computed(() => resolveId(props.data, slideIndex.value, ['video']));
|
|
40
|
+
const titleId = computed(() => resolveId(props.data, slideIndex.value, ['title']));
|
|
41
|
+
</script>
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<ul :style="listStyle" :class="customClass">
|
|
3
|
+
<li v-for="(item, i) in items" :key="i" :style="itemStyle">
|
|
4
|
+
<span :style="bulletStyle"></span>
|
|
5
|
+
<span>{{ item }}</span>
|
|
6
|
+
</li>
|
|
7
|
+
</ul>
|
|
8
|
+
</template>
|
|
9
|
+
|
|
10
|
+
<script setup lang="ts">
|
|
11
|
+
import { computed } from 'vue';
|
|
12
|
+
|
|
13
|
+
const props = defineProps<{
|
|
14
|
+
items: string[];
|
|
15
|
+
class?: string;
|
|
16
|
+
}>();
|
|
17
|
+
|
|
18
|
+
const customClass = computed(() => props.class || '');
|
|
19
|
+
|
|
20
|
+
const listStyle = {
|
|
21
|
+
display: 'flex',
|
|
22
|
+
flexDirection: 'column' as const,
|
|
23
|
+
gap: 'var(--lumina-space-sm)',
|
|
24
|
+
width: '100%'
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const itemStyle = {
|
|
28
|
+
display: 'flex',
|
|
29
|
+
alignItems: 'flex-start' as const,
|
|
30
|
+
gap: 'var(--lumina-space-sm)',
|
|
31
|
+
fontSize: 'var(--lumina-text-lg)',
|
|
32
|
+
color: 'var(--lumina-color-text-safe, var(--lumina-color-text))',
|
|
33
|
+
opacity: 0.9,
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const bulletStyle = {
|
|
37
|
+
width: 'var(--lumina-space-sm)',
|
|
38
|
+
height: 'var(--lumina-space-sm)',
|
|
39
|
+
borderRadius: 'var(--lumina-radius-full)',
|
|
40
|
+
backgroundColor: 'var(--lumina-color-primary)',
|
|
41
|
+
marginTop: 'var(--lumina-space-sm)',
|
|
42
|
+
flexShrink: 0,
|
|
43
|
+
boxShadow: '0 0 var(--lumina-space-sm) rgba(var(--lumina-color-primary-rgb), 0.5)',
|
|
44
|
+
};
|
|
45
|
+
</script>
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<button ref="btnRef" :class="['flex-button', variant, customClass]" :style="computedStyle" @click="handleClick"
|
|
3
|
+
@mousemove="onMouseMove" @mouseleave="onMouseLeave">
|
|
4
|
+
<span class="relative z-10">{{ label }}</span>
|
|
5
|
+
</button>
|
|
6
|
+
</template>
|
|
7
|
+
|
|
8
|
+
<script setup lang="ts">
|
|
9
|
+
import { computed, inject, ref } from 'vue';
|
|
10
|
+
import { StoreKey } from '../../core/store';
|
|
11
|
+
import gsap from 'gsap';
|
|
12
|
+
|
|
13
|
+
const props = defineProps<{
|
|
14
|
+
label: string;
|
|
15
|
+
action?: string;
|
|
16
|
+
actionType?: 'url' | 'slide' | 'download' | 'event';
|
|
17
|
+
href?: string;
|
|
18
|
+
gotoSlide?: number;
|
|
19
|
+
target?: string;
|
|
20
|
+
variant?: 'primary' | 'secondary' | 'outline' | 'ghost';
|
|
21
|
+
fullWidth?: boolean;
|
|
22
|
+
class?: string;
|
|
23
|
+
}>();
|
|
24
|
+
|
|
25
|
+
const emit = defineEmits(['action']);
|
|
26
|
+
const store = inject(StoreKey, null);
|
|
27
|
+
const btnRef = ref<HTMLElement | null>(null);
|
|
28
|
+
|
|
29
|
+
const customClass = computed(() => props.class || '');
|
|
30
|
+
|
|
31
|
+
// Styles
|
|
32
|
+
const computedStyle = computed(() => {
|
|
33
|
+
const base = {
|
|
34
|
+
padding: 'var(--lumina-button-padding)',
|
|
35
|
+
borderRadius: 'var(--lumina-button-radius)',
|
|
36
|
+
fontWeight: 'var(--lumina-button-font-weight)',
|
|
37
|
+
fontSize: 'var(--lumina-text-lg)',
|
|
38
|
+
width: props.fullWidth ? '100%' : 'auto',
|
|
39
|
+
position: 'relative' as const,
|
|
40
|
+
cursor: 'pointer',
|
|
41
|
+
transition: 'background-color 0.3s ease, border-color 0.3s ease, color 0.3s ease, box-shadow 0.3s ease'
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const variants: Record<string, any> = {
|
|
45
|
+
'primary': {
|
|
46
|
+
background: 'linear-gradient(to right, var(--lumina-color-primary), var(--lumina-color-secondary))',
|
|
47
|
+
color: 'var(--lumina-color-text)',
|
|
48
|
+
boxShadow: '0 var(--lumina-space-xs) var(--lumina-space-lg) var(--lumina-shadow-color)',
|
|
49
|
+
border: 'none'
|
|
50
|
+
},
|
|
51
|
+
'secondary': {
|
|
52
|
+
backgroundColor: 'rgba(var(--lumina-color-text-rgb), 0.1)',
|
|
53
|
+
color: 'var(--lumina-color-text)',
|
|
54
|
+
border: '1px solid var(--lumina-color-border)',
|
|
55
|
+
},
|
|
56
|
+
'outline': {
|
|
57
|
+
backgroundColor: 'transparent',
|
|
58
|
+
color: 'var(--lumina-color-text)',
|
|
59
|
+
border: '2px solid var(--lumina-color-border)',
|
|
60
|
+
},
|
|
61
|
+
'ghost': {
|
|
62
|
+
backgroundColor: 'transparent',
|
|
63
|
+
color: 'var(--lumina-color-muted)',
|
|
64
|
+
border: 'none',
|
|
65
|
+
boxShadow: 'none'
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
return { ...base, ...(variants[props.variant || 'primary']) };
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Actions
|
|
73
|
+
const handleClick = () => {
|
|
74
|
+
// Click Ripple / Scale Bump
|
|
75
|
+
if (btnRef.value) {
|
|
76
|
+
gsap.fromTo(btnRef.value, { scale: 0.95 }, { scale: 1, duration: 0.2, ease: 'back.out(2)' });
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const actionType = props.actionType || 'event';
|
|
80
|
+
|
|
81
|
+
switch (actionType) {
|
|
82
|
+
case 'url':
|
|
83
|
+
if (props.href) window.open(props.href, props.target || '_blank');
|
|
84
|
+
break;
|
|
85
|
+
case 'slide':
|
|
86
|
+
if (props.gotoSlide !== undefined && store) store.goto(props.gotoSlide);
|
|
87
|
+
emit('action', { type: 'goto', value: props.gotoSlide, origin: 'flex-button' });
|
|
88
|
+
break;
|
|
89
|
+
case 'download':
|
|
90
|
+
if (props.href) {
|
|
91
|
+
const link = document.createElement('a');
|
|
92
|
+
link.href = props.href;
|
|
93
|
+
link.download = '';
|
|
94
|
+
link.click();
|
|
95
|
+
}
|
|
96
|
+
break;
|
|
97
|
+
default:
|
|
98
|
+
emit('action', { type: 'click', label: props.label, value: props.action || props.label, origin: 'flex-button' });
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
// Magnetic Effect
|
|
103
|
+
const onMouseMove = (e: MouseEvent) => {
|
|
104
|
+
const el = btnRef.value;
|
|
105
|
+
if (!el) return;
|
|
106
|
+
|
|
107
|
+
const rect = el.getBoundingClientRect();
|
|
108
|
+
const x = e.clientX - rect.left - rect.width / 2;
|
|
109
|
+
const y = e.clientY - rect.top - rect.height / 2;
|
|
110
|
+
|
|
111
|
+
// Move button 30% towards cursor
|
|
112
|
+
gsap.to(el, {
|
|
113
|
+
x: x * 0.3,
|
|
114
|
+
y: y * 0.3,
|
|
115
|
+
duration: 0.3,
|
|
116
|
+
ease: 'power2.out'
|
|
117
|
+
});
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const onMouseLeave = () => {
|
|
121
|
+
if (!btnRef.value) return;
|
|
122
|
+
// Spring back
|
|
123
|
+
gsap.to(btnRef.value, {
|
|
124
|
+
x: 0,
|
|
125
|
+
y: 0,
|
|
126
|
+
duration: 0.8,
|
|
127
|
+
ease: 'elastic.out(1, 0.4)'
|
|
128
|
+
});
|
|
129
|
+
};
|
|
130
|
+
</script>
|
|
131
|
+
|
|
132
|
+
<style scoped></style>
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<component :is="href ? 'a' : 'div'" :href="href || undefined" :target="href ? (target || '_blank') : undefined"
|
|
3
|
+
:rel="href ? 'noopener noreferrer' : undefined"
|
|
4
|
+
:class="[containerClass, customClass, href && 'cursor-pointer hover:opacity-90 transition-opacity']"
|
|
5
|
+
:style="containerStyle">
|
|
6
|
+
<img :src="src" :alt="alt || ''" :class="[
|
|
7
|
+
'w-full h-full transition-opacity duration-700',
|
|
8
|
+
fill !== false ? 'object-cover' : 'object-contain',
|
|
9
|
+
roundedClass,
|
|
10
|
+
isLoaded ? 'opacity-100' : 'opacity-0'
|
|
11
|
+
]" @load="onLoad" @error="onError" />
|
|
12
|
+
</component>
|
|
13
|
+
</template>
|
|
14
|
+
|
|
15
|
+
<script setup lang="ts">
|
|
16
|
+
import { ref, computed } from 'vue';
|
|
17
|
+
|
|
18
|
+
const props = defineProps<{
|
|
19
|
+
src: string;
|
|
20
|
+
alt?: string;
|
|
21
|
+
fill?: boolean;
|
|
22
|
+
rounded?: string;
|
|
23
|
+
containerClass?: any;
|
|
24
|
+
containerStyle?: any;
|
|
25
|
+
href?: string;
|
|
26
|
+
target?: '_blank' | '_self';
|
|
27
|
+
class?: any;
|
|
28
|
+
}>();
|
|
29
|
+
|
|
30
|
+
const isLoaded = ref(false);
|
|
31
|
+
const customClass = computed(() => props.class || '');
|
|
32
|
+
|
|
33
|
+
const onLoad = () => {
|
|
34
|
+
isLoaded.value = true;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const onError = (e: any) => {
|
|
38
|
+
isLoaded.value = true;
|
|
39
|
+
e.target.src = 'https://placehold.co/800x600/1a1a1a/666?text=Image+Not+Found';
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const roundedClass = computed(() => {
|
|
43
|
+
if (props.fill !== false && !props.rounded) return '';
|
|
44
|
+
const map: Record<string, string> = {
|
|
45
|
+
'none': 'rounded-none',
|
|
46
|
+
'sm': 'rounded-sm',
|
|
47
|
+
'md': 'rounded-md',
|
|
48
|
+
'lg': 'rounded-lg',
|
|
49
|
+
'xl': 'rounded-xl',
|
|
50
|
+
'full': 'rounded-full',
|
|
51
|
+
};
|
|
52
|
+
return map[props.rounded || 'lg'] || 'rounded-lg';
|
|
53
|
+
});
|
|
54
|
+
</script>
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<ol :style="listStyle" :class="customClass">
|
|
3
|
+
<li v-for="(item, i) in items" :key="i" :style="itemStyle">
|
|
4
|
+
<span :style="numberStyle">{{ i + 1 }}</span>
|
|
5
|
+
<span>{{ item }}</span>
|
|
6
|
+
</li>
|
|
7
|
+
</ol>
|
|
8
|
+
</template>
|
|
9
|
+
|
|
10
|
+
<script setup lang="ts">
|
|
11
|
+
import { computed } from 'vue';
|
|
12
|
+
|
|
13
|
+
const props = defineProps<{
|
|
14
|
+
items: string[];
|
|
15
|
+
class?: string;
|
|
16
|
+
}>();
|
|
17
|
+
|
|
18
|
+
const customClass = computed(() => props.class || '');
|
|
19
|
+
|
|
20
|
+
const listStyle = {
|
|
21
|
+
display: 'flex',
|
|
22
|
+
flexDirection: 'column' as const,
|
|
23
|
+
gap: 'var(--lumina-space-sm)',
|
|
24
|
+
width: '100%'
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const itemStyle = {
|
|
28
|
+
display: 'flex',
|
|
29
|
+
alignItems: 'flex-start' as const,
|
|
30
|
+
gap: 'var(--lumina-space-sm)',
|
|
31
|
+
fontSize: 'var(--lumina-text-lg)',
|
|
32
|
+
color: 'var(--lumina-color-text-safe, var(--lumina-color-text))',
|
|
33
|
+
opacity: 0.9,
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const numberStyle = {
|
|
37
|
+
fontSize: 'var(--lumina-text-sm)',
|
|
38
|
+
fontWeight: 'var(--lumina-font-weight-bold)',
|
|
39
|
+
color: 'var(--lumina-color-primary)',
|
|
40
|
+
marginTop: '2px', // Slight adjustment for alignment
|
|
41
|
+
minWidth: '1.2em',
|
|
42
|
+
textAlign: 'right' as const,
|
|
43
|
+
};
|
|
44
|
+
</script>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { h, defineComponent } from 'vue';
|
|
3
|
+
import { spacingVarMap } from '../../composables/useFlexLayout';
|
|
4
|
+
|
|
5
|
+
export default defineComponent({
|
|
6
|
+
name: 'FlexSpacer',
|
|
7
|
+
props: ['size'],
|
|
8
|
+
setup(props) {
|
|
9
|
+
const sizeVar = spacingVarMap[(props.size || 'md')] || spacingVarMap['md'];
|
|
10
|
+
return () => h('div', { style: { height: sizeVar, width: '100%' } });
|
|
11
|
+
}
|
|
12
|
+
});
|
|
13
|
+
</script>
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { h, defineComponent } from 'vue';
|
|
3
|
+
|
|
4
|
+
export default defineComponent({
|
|
5
|
+
name: 'FlexStepper',
|
|
6
|
+
props: ['items', 'compact'],
|
|
7
|
+
setup(props) {
|
|
8
|
+
return () => h('div', {
|
|
9
|
+
style: {
|
|
10
|
+
display: 'flex',
|
|
11
|
+
flexDirection: 'column',
|
|
12
|
+
gap: 'var(--lumina-space-md)',
|
|
13
|
+
width: '100%'
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
(props.items || []).map((item: any) =>
|
|
17
|
+
h('div', {
|
|
18
|
+
style: {
|
|
19
|
+
display: 'flex',
|
|
20
|
+
gap: 'var(--lumina-space-md)',
|
|
21
|
+
alignItems: 'flex-start',
|
|
22
|
+
padding: 'var(--lumina-space-md)',
|
|
23
|
+
borderRadius: 'var(--lumina-radius-lg)',
|
|
24
|
+
backgroundColor: 'rgba(var(--lumina-color-text-rgb), 0.05)',
|
|
25
|
+
border: '1px solid var(--lumina-color-border)',
|
|
26
|
+
}
|
|
27
|
+
}, [
|
|
28
|
+
h('div', {
|
|
29
|
+
style: {
|
|
30
|
+
flexShrink: 0,
|
|
31
|
+
width: 'var(--lumina-step-badge-size)',
|
|
32
|
+
height: 'var(--lumina-step-badge-size)',
|
|
33
|
+
borderRadius: 'var(--lumina-radius-lg)',
|
|
34
|
+
background: 'linear-gradient(135deg, rgba(var(--lumina-color-primary-rgb), 0.2), rgba(var(--lumina-color-secondary-rgb), 0.2))',
|
|
35
|
+
display: 'flex',
|
|
36
|
+
alignItems: 'center',
|
|
37
|
+
justifyContent: 'center',
|
|
38
|
+
fontWeight: 'var(--lumina-font-weight-bold)',
|
|
39
|
+
color: 'var(--lumina-color-primary)',
|
|
40
|
+
}
|
|
41
|
+
}, item.step),
|
|
42
|
+
h('div', {}, [
|
|
43
|
+
h('h4', {
|
|
44
|
+
style: { fontWeight: 'var(--lumina-font-weight-bold)' }
|
|
45
|
+
}, item.title || item.t),
|
|
46
|
+
h('p', {
|
|
47
|
+
style: {
|
|
48
|
+
fontSize: 'var(--lumina-text-sm)',
|
|
49
|
+
color: 'var(--lumina-color-muted)',
|
|
50
|
+
opacity: 0.6,
|
|
51
|
+
}
|
|
52
|
+
}, item.description || item.desc)
|
|
53
|
+
])
|
|
54
|
+
])
|
|
55
|
+
)
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
</script>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<p :style="computedStyle" :class="customClass">
|
|
3
|
+
{{ text }}
|
|
4
|
+
</p>
|
|
5
|
+
</template>
|
|
6
|
+
|
|
7
|
+
<script setup lang="ts">
|
|
8
|
+
import { computed } from 'vue';
|
|
9
|
+
|
|
10
|
+
const props = defineProps<{
|
|
11
|
+
text: string;
|
|
12
|
+
align?: 'left' | 'center' | 'right';
|
|
13
|
+
muted?: boolean;
|
|
14
|
+
class?: string;
|
|
15
|
+
}>();
|
|
16
|
+
|
|
17
|
+
const customClass = computed(() => props.class || '');
|
|
18
|
+
|
|
19
|
+
const computedStyle = computed(() => {
|
|
20
|
+
return {
|
|
21
|
+
fontSize: 'var(--lumina-text-lg)',
|
|
22
|
+
lineHeight: 'var(--lumina-leading-relaxed)',
|
|
23
|
+
color: props.muted ? 'var(--lumina-color-text-secondary)' : 'var(--lumina-color-text)',
|
|
24
|
+
opacity: props.muted ? 0.8 : 1,
|
|
25
|
+
textAlign: props.align || 'left',
|
|
26
|
+
width: '100%',
|
|
27
|
+
};
|
|
28
|
+
});
|
|
29
|
+
</script>
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { h, defineComponent } from 'vue';
|
|
3
|
+
|
|
4
|
+
export default defineComponent({
|
|
5
|
+
name: 'FlexTimeline',
|
|
6
|
+
props: ['items', 'compact'],
|
|
7
|
+
setup(props) {
|
|
8
|
+
return () => h('div', {
|
|
9
|
+
style: {
|
|
10
|
+
display: 'flex',
|
|
11
|
+
flexDirection: 'column',
|
|
12
|
+
gap: 'var(--lumina-space-md)',
|
|
13
|
+
width: '100%'
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
(props.items || []).map((item: any) =>
|
|
17
|
+
h('div', {
|
|
18
|
+
style: {
|
|
19
|
+
display: 'flex',
|
|
20
|
+
gap: 'var(--lumina-space-md)',
|
|
21
|
+
alignItems: 'flex-start',
|
|
22
|
+
borderLeft: 'var(--lumina-timeline-line-width) solid rgba(var(--lumina-color-primary-rgb), 0.3)',
|
|
23
|
+
paddingLeft: 'var(--lumina-space-md)',
|
|
24
|
+
}
|
|
25
|
+
}, [
|
|
26
|
+
h('div', {
|
|
27
|
+
style: {
|
|
28
|
+
flexShrink: 0,
|
|
29
|
+
width: 'calc(var(--lumina-timeline-node-size) * 0.75)',
|
|
30
|
+
height: 'calc(var(--lumina-timeline-node-size) * 0.75)',
|
|
31
|
+
borderRadius: 'var(--lumina-radius-full)',
|
|
32
|
+
backgroundColor: 'var(--lumina-color-primary)',
|
|
33
|
+
marginLeft: 'calc(-1 * var(--lumina-space-md) - var(--lumina-timeline-line-width) / 2 - var(--lumina-timeline-node-size) * 0.375)',
|
|
34
|
+
marginTop: 'var(--lumina-space-xs)',
|
|
35
|
+
boxShadow: '0 0 var(--lumina-space-sm) rgba(var(--lumina-color-primary-rgb), 0.5)',
|
|
36
|
+
}
|
|
37
|
+
}),
|
|
38
|
+
h('div', {}, [
|
|
39
|
+
h('span', {
|
|
40
|
+
style: {
|
|
41
|
+
fontSize: 'var(--lumina-text-xs)',
|
|
42
|
+
color: 'var(--lumina-color-primary)',
|
|
43
|
+
fontWeight: 'var(--lumina-font-weight-bold)',
|
|
44
|
+
textTransform: 'uppercase',
|
|
45
|
+
letterSpacing: 'var(--lumina-tracking-wider)',
|
|
46
|
+
}
|
|
47
|
+
}, item.date),
|
|
48
|
+
h('h4', {
|
|
49
|
+
style: {
|
|
50
|
+
fontWeight: 'var(--lumina-font-weight-bold)',
|
|
51
|
+
fontSize: 'var(--lumina-text-lg)',
|
|
52
|
+
}
|
|
53
|
+
}, item.title || item.t),
|
|
54
|
+
h('p', {
|
|
55
|
+
style: {
|
|
56
|
+
fontSize: 'var(--lumina-text-sm)',
|
|
57
|
+
color: 'var(--lumina-color-muted)',
|
|
58
|
+
opacity: 0.6,
|
|
59
|
+
}
|
|
60
|
+
}, item.description || item.desc)
|
|
61
|
+
])
|
|
62
|
+
])
|
|
63
|
+
)
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
</script>
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<h2 :style="computedStyle" :class="['flex-title', customClass]"
|
|
3
|
+
:data-split-text="customClass.includes('char') || customClass.includes('word') ? '' : null">
|
|
4
|
+
{{ text }}
|
|
5
|
+
</h2>
|
|
6
|
+
</template>
|
|
7
|
+
|
|
8
|
+
<script setup lang="ts">
|
|
9
|
+
import { computed } from 'vue';
|
|
10
|
+
|
|
11
|
+
const props = defineProps<{
|
|
12
|
+
text: string;
|
|
13
|
+
size?: 'lg' | 'xl' | '2xl' | '3xl';
|
|
14
|
+
align?: 'left' | 'center' | 'right';
|
|
15
|
+
class?: string;
|
|
16
|
+
}>();
|
|
17
|
+
|
|
18
|
+
const customClass = computed(() => props.class || '');
|
|
19
|
+
|
|
20
|
+
const computedStyle = computed(() => {
|
|
21
|
+
const sizeMap: Record<string, string> = {
|
|
22
|
+
'lg': 'var(--lumina-text-3xl)',
|
|
23
|
+
'xl': 'var(--lumina-text-4xl)',
|
|
24
|
+
'2xl': 'var(--lumina-text-5xl)',
|
|
25
|
+
'3xl': 'var(--lumina-text-6xl)',
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
fontFamily: 'var(--lumina-font-heading)',
|
|
30
|
+
fontWeight: 'var(--lumina-font-weight-bold)',
|
|
31
|
+
lineHeight: 'var(--lumina-leading-tight)',
|
|
32
|
+
fontSize: sizeMap[props.size || 'xl'] || 'var(--lumina-text-4xl)',
|
|
33
|
+
textAlign: props.align || 'left',
|
|
34
|
+
width: '100%',
|
|
35
|
+
letterSpacing: 'var(--lumina-tracking-tighter)',
|
|
36
|
+
color: 'var(--lumina-color-text-safe, var(--lumina-color-text))',
|
|
37
|
+
};
|
|
38
|
+
});
|
|
39
|
+
</script>
|