lumina-slides 8.9.4 → 9.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/LUMINA_LLM_EXAMPLES.json +234 -0
  2. package/README.md +18 -18
  3. package/dist/lumina-slides.js +13207 -12659
  4. package/dist/lumina-slides.umd.cjs +215 -215
  5. package/dist/style.css +1 -1
  6. package/package.json +5 -4
  7. package/src/App.vue +16 -0
  8. package/src/animation/index.ts +11 -0
  9. package/src/animation/registry.ts +126 -0
  10. package/src/animation/stagger.ts +95 -0
  11. package/src/animation/types.ts +53 -0
  12. package/src/components/LandingPage.vue +229 -0
  13. package/src/components/LuminaDeck.vue +224 -0
  14. package/src/components/LuminaSpeakerNotes.vue +701 -0
  15. package/src/components/base/BaseSlide.vue +122 -0
  16. package/src/components/base/LuminaElement.vue +67 -0
  17. package/src/components/base/VideoPlayer.vue +204 -0
  18. package/src/components/layouts/LayoutAuto.vue +71 -0
  19. package/src/components/layouts/LayoutChart.vue +287 -0
  20. package/src/components/layouts/LayoutCustom.vue +92 -0
  21. package/src/components/layouts/LayoutDiagram.vue +253 -0
  22. package/src/components/layouts/LayoutFeatures.vue +121 -0
  23. package/src/components/layouts/LayoutFlex.vue +172 -0
  24. package/src/components/layouts/LayoutFree.vue +62 -0
  25. package/src/components/layouts/LayoutHalf.vue +127 -0
  26. package/src/components/layouts/LayoutStatement.vue +74 -0
  27. package/src/components/layouts/LayoutSteps.vue +106 -0
  28. package/src/components/layouts/LayoutTimeline.vue +104 -0
  29. package/src/components/layouts/LayoutVideo.vue +41 -0
  30. package/src/components/parts/FlexBullets.vue +45 -0
  31. package/src/components/parts/FlexButton.vue +132 -0
  32. package/src/components/parts/FlexImage.vue +54 -0
  33. package/src/components/parts/FlexOrdered.vue +44 -0
  34. package/src/components/parts/FlexSpacer.vue +13 -0
  35. package/src/components/parts/FlexStepper.vue +59 -0
  36. package/src/components/parts/FlexText.vue +29 -0
  37. package/src/components/parts/FlexTimeline.vue +67 -0
  38. package/src/components/parts/FlexTitle.vue +39 -0
  39. package/src/components/parts/LuminaBackground.vue +100 -0
  40. package/src/components/site/LivePreview.vue +101 -0
  41. package/src/components/site/SiteApi.vue +301 -0
  42. package/src/components/site/SiteDashboard.vue +604 -0
  43. package/src/components/site/SiteDocs.vue +3267 -0
  44. package/src/components/site/SiteExamples.vue +65 -0
  45. package/src/components/site/SiteFooter.vue +6 -0
  46. package/src/components/site/SiteHome.vue +362 -0
  47. package/src/components/site/SiteNavBar.vue +122 -0
  48. package/src/components/site/SitePlayground.vue +389 -0
  49. package/src/components/site/SitePromptBuilder.vue +266 -0
  50. package/src/components/site/SiteUserMenu.vue +90 -0
  51. package/src/components/studio/ActionEditor.vue +108 -0
  52. package/src/components/studio/ArrayEditor.vue +124 -0
  53. package/src/components/studio/CollapsibleSection.vue +33 -0
  54. package/src/components/studio/ColorField.vue +22 -0
  55. package/src/components/studio/EditorCanvas.vue +326 -0
  56. package/src/components/studio/EditorLayoutFeatures.vue +18 -0
  57. package/src/components/studio/EditorLayoutFixed.vue +46 -0
  58. package/src/components/studio/EditorLayoutFlex.vue +133 -0
  59. package/src/components/studio/EditorLayoutHalf.vue +18 -0
  60. package/src/components/studio/EditorLayoutStatement.vue +18 -0
  61. package/src/components/studio/EditorLayoutSteps.vue +18 -0
  62. package/src/components/studio/EditorLayoutTimeline.vue +18 -0
  63. package/src/components/studio/EditorNode.vue +89 -0
  64. package/src/components/studio/FieldEditor.vue +133 -0
  65. package/src/components/studio/IconPicker.vue +109 -0
  66. package/src/components/studio/LayerItem.vue +117 -0
  67. package/src/components/studio/LuminaStudio.vue +30 -0
  68. package/src/components/studio/SaveSuccessModal.vue +138 -0
  69. package/src/components/studio/SlideNavigator.vue +373 -0
  70. package/src/components/studio/SliderField.vue +44 -0
  71. package/src/components/studio/StudioInspector.vue +595 -0
  72. package/src/components/studio/StudioJsonEditor.vue +191 -0
  73. package/src/components/studio/StudioLayers.vue +145 -0
  74. package/src/components/studio/StudioSettings.vue +514 -0
  75. package/src/components/studio/StudioSidebar.vue +29 -0
  76. package/src/components/studio/StudioToolbar.vue +222 -0
  77. package/src/components/studio/fieldLabels.ts +224 -0
  78. package/src/components/studio/inspectors/DiagramEdgeEditor.vue +77 -0
  79. package/src/components/studio/inspectors/DiagramNodeEditor.vue +117 -0
  80. package/src/components/studio/nodes/StudioDiagramNode.vue +138 -0
  81. package/src/composables/useAuth.ts +87 -0
  82. package/src/composables/useEditor.ts +224 -0
  83. package/src/composables/useElementState.ts +81 -0
  84. package/src/composables/useFlexLayout.ts +122 -0
  85. package/src/composables/useKeyboard.ts +45 -0
  86. package/src/composables/useLumina.ts +32 -0
  87. package/src/composables/useStudio.ts +87 -0
  88. package/src/composables/useSwipeNav.ts +53 -0
  89. package/src/composables/useTransition.ts +373 -0
  90. package/src/core/Lumina.ts +819 -0
  91. package/src/core/animationConfig.ts +251 -0
  92. package/src/core/compression.ts +34 -0
  93. package/src/core/elementController.ts +170 -0
  94. package/src/core/elementId.ts +27 -0
  95. package/src/core/elementResolver.ts +207 -0
  96. package/src/core/events.ts +53 -0
  97. package/src/core/fonts.ts +100 -0
  98. package/src/core/presets.ts +231 -0
  99. package/src/core/prompts.ts +272 -0
  100. package/src/core/schema.ts +478 -0
  101. package/src/core/speaker-channel.ts +250 -0
  102. package/src/core/store.ts +461 -0
  103. package/src/core/theme.ts +666 -0
  104. package/src/core/types.ts +1611 -0
  105. package/src/directives/vStudio.ts +45 -0
  106. package/src/index.ts +175 -0
  107. package/src/main.ts +17 -0
  108. package/src/router/index.ts +92 -0
  109. package/src/style/main.css +462 -0
  110. package/src/utils/deep.ts +127 -0
  111. package/src/utils/firebase.ts +184 -0
  112. package/src/utils/streaming.ts +134 -0
  113. package/src/views/DashboardView.vue +32 -0
  114. package/src/views/DeckView.vue +289 -0
  115. package/src/views/HomeView.vue +17 -0
  116. package/src/views/SiteLayout.vue +21 -0
  117. package/src/views/StudioView.vue +61 -0
  118. package/src/vite-env.d.ts +6 -0
  119. package/IMPLEMENTATION.md +0 -418
@@ -0,0 +1,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>