lumina-slides 9.0.5 → 9.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/README.md +63 -0
  2. package/dist/lumina-slides.js +21750 -19334
  3. package/dist/lumina-slides.umd.cjs +223 -223
  4. package/dist/style.css +1 -1
  5. package/package.json +1 -1
  6. package/src/components/LandingPage.vue +1 -1
  7. package/src/components/LuminaDeck.vue +237 -232
  8. package/src/components/base/LuminaElement.vue +2 -0
  9. package/src/components/layouts/LayoutFeatures.vue +125 -123
  10. package/src/components/layouts/LayoutFlex.vue +212 -212
  11. package/src/components/layouts/LayoutStatement.vue +5 -2
  12. package/src/components/layouts/LayoutSteps.vue +110 -108
  13. package/src/components/parts/FlexHtml.vue +65 -65
  14. package/src/components/parts/FlexImage.vue +81 -81
  15. package/src/components/site/SiteDocs.vue +3313 -3314
  16. package/src/components/site/SiteExamples.vue +66 -66
  17. package/src/components/studio/EditorLayoutChart.vue +18 -0
  18. package/src/components/studio/EditorLayoutCustom.vue +18 -0
  19. package/src/components/studio/EditorLayoutVideo.vue +18 -0
  20. package/src/components/studio/LuminaStudioEmbed.vue +68 -0
  21. package/src/components/studio/StudioEmbedRoot.vue +19 -0
  22. package/src/components/studio/StudioInspector.vue +1113 -7
  23. package/src/components/studio/StudioJsonEditor.vue +10 -3
  24. package/src/components/studio/StudioSettings.vue +658 -7
  25. package/src/components/studio/StudioToolbar.vue +26 -7
  26. package/src/composables/useElementState.ts +12 -1
  27. package/src/composables/useFlexLayout.ts +128 -128
  28. package/src/core/Lumina.ts +174 -113
  29. package/src/core/animationConfig.ts +10 -0
  30. package/src/core/elementController.ts +18 -0
  31. package/src/core/elementResolver.ts +4 -2
  32. package/src/core/schema.ts +503 -503
  33. package/src/core/store.ts +465 -465
  34. package/src/core/types.ts +26 -11
  35. package/src/index.ts +2 -2
  36. package/src/utils/prepareDeckForExport.ts +47 -0
  37. package/src/utils/templateInterpolation.ts +52 -52
  38. package/src/views/DeckView.vue +313 -313
@@ -1,108 +1,110 @@
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: wrapper (header) + title and subtitle as separate controllable elements -->
6
- <LuminaElement :id="headerId" :class="['text-center max-w-4xl mx-auto', data.class || '']"
7
- :style="{ marginBottom: 'var(--lumina-space-2xl)' }">
8
- <LuminaElement :id="titleId" tag="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
- </LuminaElement>
17
- <LuminaElement v-if="data.subtitle" :id="subtitleId" tag="p" :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
- </LuminaElement>
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 titleId = computed(() => resolveId(props.data, slideIndex.value, ['title']));
106
- const subtitleId = computed(() => resolveId(props.data, slideIndex.value, ['subtitle']));
107
- const stepId = (i: number) => resolveId(props.data, slideIndex.value, ['steps', i]);
108
- </script>
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: wrapper (header) + title and subtitle as separate controllable elements -->
6
+ <LuminaElement :id="headerId" :class="['flex flex-col items-center text-center max-w-4xl mx-auto', data.class || '']"
7
+ :style="{ marginBottom: 'var(--lumina-space-2xl)' }">
8
+ <LuminaElement :id="titleId" tag="h2" class="font-bold" :style="{
9
+ display: 'block',
10
+ fontFamily: 'var(--lumina-font-heading)',
11
+ fontSize: 'var(--lumina-text-5xl)',
12
+ marginBottom: 'var(--lumina-space-md)',
13
+ letterSpacing: 'var(--lumina-tracking-tighter)',
14
+ color: 'var(--lumina-color-text-safe, var(--lumina-color-text))'
15
+ }">
16
+ {{ data.title }}
17
+ </LuminaElement>
18
+ <LuminaElement v-if="data.subtitle" :id="subtitleId" tag="p" :style="{
19
+ display: 'block',
20
+ color: 'var(--lumina-color-text-safe, var(--lumina-color-text))',
21
+ opacity: 0.9,
22
+ fontSize: 'var(--lumina-text-xl)'
23
+ }">
24
+ {{ data.subtitle }}
25
+ </LuminaElement>
26
+ </LuminaElement>
27
+
28
+ <!-- Steps Grid -->
29
+ <div :style="{
30
+ display: 'grid',
31
+ gridTemplateColumns: 'repeat(auto-fit, minmax(280px, 1fr))',
32
+ gap: 'var(--lumina-space-xl)',
33
+ maxWidth: '72rem',
34
+ margin: '0 auto',
35
+ width: '100%'
36
+ }">
37
+ <LuminaElement v-for="(step, i) in data.steps" :key="i" :id="stepId(i)"
38
+ :class="['glass-panel group relative overflow-hidden', step.class || '']" :style="{
39
+ borderRadius: 'var(--lumina-card-radius)',
40
+ padding: 'var(--lumina-card-padding)'
41
+ }">
42
+ <!-- Step Number -->
43
+ <div :style="{
44
+ display: 'flex',
45
+ alignItems: 'center',
46
+ gap: 'var(--lumina-space-md)',
47
+ marginBottom: 'var(--lumina-space-md)'
48
+ }">
49
+ <div class="flex items-center justify-center font-bold" :style="{
50
+ width: 'var(--lumina-step-badge-size, 2.5rem)',
51
+ height: 'var(--lumina-step-badge-size, 2.5rem)',
52
+ fontSize: 'var(--lumina-step-font-size, 1rem)',
53
+ background: 'linear-gradient(135deg, var(--lumina-color-primary), var(--lumina-color-secondary))',
54
+ color: 'white',
55
+ borderRadius: 'var(--lumina-radius-xl)',
56
+ boxShadow: '0 4px 12px rgba(var(--lumina-color-primary-rgb), 0.4)'
57
+ }">
58
+ {{ step.step }}
59
+ </div>
60
+ <h3 class="font-bold" :style="{
61
+ fontSize: 'var(--lumina-text-xl)',
62
+ color: 'var(--lumina-surface-text, var(--lumina-color-text))'
63
+ }">
64
+ {{ step.title }}
65
+ </h3>
66
+ </div>
67
+
68
+ <!-- Description -->
69
+ <p v-if="step.description" :style="{
70
+ color: 'var(--lumina-color-text-secondary)',
71
+ lineHeight: 'var(--lumina-leading-relaxed)',
72
+ fontSize: 'var(--lumina-text-base)',
73
+ opacity: 0.8
74
+ }">
75
+ {{ step.description }}
76
+ </p>
77
+
78
+ <!-- Icon (optional) -->
79
+ <div v-if="step.icon" class="absolute top-4 right-4" :style="{
80
+ color: 'var(--lumina-color-primary)',
81
+ opacity: 0.1,
82
+ fontSize: 'var(--lumina-text-4xl)'
83
+ }">
84
+ <i :class="['ph-thin', step.icon.startsWith('ph-') ? step.icon : `ph-${step.icon}`]"></i>
85
+ </div>
86
+ </LuminaElement>
87
+ </div>
88
+ </div>
89
+ </BaseSlide>
90
+ </template>
91
+
92
+ <script setup lang="ts">
93
+ import { computed, inject } from 'vue';
94
+ import BaseSlide from '../base/BaseSlide.vue';
95
+ import { resolveId } from '../../core/elementResolver';
96
+ import { StoreKey } from '../../core/store';
97
+ import type { SlideSteps } from '../../core/types';
98
+
99
+ const props = defineProps<{
100
+ data: SlideSteps;
101
+ slideIndex?: number;
102
+ }>();
103
+
104
+ const store = inject(StoreKey);
105
+ const slideIndex = computed(() => props.slideIndex ?? store?.state.currentIndex ?? 0);
106
+ const headerId = computed(() => resolveId(props.data, slideIndex.value, ['header']));
107
+ const titleId = computed(() => resolveId(props.data, slideIndex.value, ['title']));
108
+ const subtitleId = computed(() => resolveId(props.data, slideIndex.value, ['subtitle']));
109
+ const stepId = (i: number) => resolveId(props.data, slideIndex.value, ['steps', i]);
110
+ </script>
@@ -1,65 +1,65 @@
1
- <template>
2
- <div
3
- v-html="html"
4
- :class="customClass"
5
- :style="computedStyle"
6
- ></div>
7
- </template>
8
-
9
- <script setup lang="ts">
10
- import { computed } from 'vue';
11
-
12
- const props = defineProps<{
13
- html: string;
14
- class?: string;
15
- style?: Record<string, string>;
16
- }>();
17
-
18
- const customClass = computed(() => props.class || '');
19
-
20
- const computedStyle = computed(() => {
21
- return {
22
- width: '100%',
23
- ...props.style,
24
- };
25
- });
26
- </script>
27
-
28
- <style scoped>
29
- /* Ensure HTML content respects container boundaries */
30
- :deep(*) {
31
- max-width: 100%;
32
- }
33
-
34
- /* Style common HTML elements to match Lumina theme */
35
- :deep(h1), :deep(h2), :deep(h3), :deep(h4), :deep(h5), :deep(h6) {
36
- color: var(--lumina-color-text);
37
- font-family: var(--lumina-font-heading);
38
- margin-top: var(--lumina-space-md);
39
- margin-bottom: var(--lumina-space-sm);
40
- }
41
-
42
- :deep(p) {
43
- color: var(--lumina-color-text);
44
- line-height: var(--lumina-leading-relaxed);
45
- margin-bottom: var(--lumina-space-md);
46
- }
47
-
48
- :deep(a) {
49
- color: var(--lumina-color-primary);
50
- text-decoration: none;
51
- }
52
-
53
- :deep(a:hover) {
54
- text-decoration: underline;
55
- }
56
-
57
- :deep(ul), :deep(ol) {
58
- margin-left: var(--lumina-space-lg);
59
- margin-bottom: var(--lumina-space-md);
60
- }
61
-
62
- :deep(li) {
63
- margin-bottom: var(--lumina-space-xs);
64
- }
65
- </style>
1
+ <template>
2
+ <div
3
+ v-html="html"
4
+ :class="customClass"
5
+ :style="computedStyle"
6
+ ></div>
7
+ </template>
8
+
9
+ <script setup lang="ts">
10
+ import { computed } from 'vue';
11
+
12
+ const props = defineProps<{
13
+ html: string;
14
+ class?: string;
15
+ style?: Record<string, string>;
16
+ }>();
17
+
18
+ const customClass = computed(() => props.class || '');
19
+
20
+ const computedStyle = computed(() => {
21
+ return {
22
+ width: '100%',
23
+ ...props.style,
24
+ };
25
+ });
26
+ </script>
27
+
28
+ <style scoped>
29
+ /* Ensure HTML content respects container boundaries */
30
+ :deep(*) {
31
+ max-width: 100%;
32
+ }
33
+
34
+ /* Style common HTML elements to match Lumina theme */
35
+ :deep(h1), :deep(h2), :deep(h3), :deep(h4), :deep(h5), :deep(h6) {
36
+ color: var(--lumina-color-text);
37
+ font-family: var(--lumina-font-heading);
38
+ margin-top: var(--lumina-space-md);
39
+ margin-bottom: var(--lumina-space-sm);
40
+ }
41
+
42
+ :deep(p) {
43
+ color: var(--lumina-color-text);
44
+ line-height: var(--lumina-leading-relaxed);
45
+ margin-bottom: var(--lumina-space-md);
46
+ }
47
+
48
+ :deep(a) {
49
+ color: var(--lumina-color-primary);
50
+ text-decoration: none;
51
+ }
52
+
53
+ :deep(a:hover) {
54
+ text-decoration: underline;
55
+ }
56
+
57
+ :deep(ul), :deep(ol) {
58
+ margin-left: var(--lumina-space-lg);
59
+ margin-bottom: var(--lumina-space-md);
60
+ }
61
+
62
+ :deep(li) {
63
+ margin-bottom: var(--lumina-space-xs);
64
+ }
65
+ </style>
@@ -1,81 +1,81 @@
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
- objectFitClass,
9
- roundedClass,
10
- isLoaded ? 'opacity-100' : 'opacity-0'
11
- ]" :style="imageStyle" @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
- fit?: 'cover' | 'contain' | 'fill' | 'none' | 'scale-down';
23
- position?: string;
24
- rounded?: string;
25
- containerClass?: any;
26
- containerStyle?: any;
27
- href?: string;
28
- target?: '_blank' | '_self';
29
- class?: any;
30
- style?: Record<string, string>;
31
- }>();
32
-
33
- const isLoaded = ref(false);
34
- const customClass = computed(() => props.class || '');
35
-
36
- const onLoad = () => {
37
- isLoaded.value = true;
38
- };
39
-
40
- const onError = (e: any) => {
41
- isLoaded.value = true;
42
- e.target.src = 'https://placehold.co/800x600/1a1a1a/666?text=Image+Not+Found';
43
- };
44
-
45
- const objectFitClass = computed(() => {
46
- if (props.fit) {
47
- const fitMap: Record<string, string> = {
48
- 'cover': 'object-cover',
49
- 'contain': 'object-contain',
50
- 'fill': 'object-fill',
51
- 'none': 'object-none',
52
- 'scale-down': 'object-scale-down',
53
- };
54
- return fitMap[props.fit] || 'object-cover';
55
- }
56
- return props.fill !== false ? 'object-cover' : 'object-contain';
57
- });
58
-
59
- const imageStyle = computed(() => {
60
- const style: Record<string, string> = {
61
- ...props.style,
62
- };
63
- if (props.position) {
64
- style.objectPosition = props.position;
65
- }
66
- return style;
67
- });
68
-
69
- const roundedClass = computed(() => {
70
- if (props.fill !== false && !props.rounded) return '';
71
- const map: Record<string, string> = {
72
- 'none': 'rounded-none',
73
- 'sm': 'rounded-sm',
74
- 'md': 'rounded-md',
75
- 'lg': 'rounded-lg',
76
- 'xl': 'rounded-xl',
77
- 'full': 'rounded-full',
78
- };
79
- return map[props.rounded || 'lg'] || 'rounded-lg';
80
- });
81
- </script>
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
+ objectFitClass,
9
+ roundedClass,
10
+ isLoaded ? 'opacity-100' : 'opacity-0'
11
+ ]" :style="imageStyle" @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
+ fit?: 'cover' | 'contain' | 'fill' | 'none' | 'scale-down';
23
+ position?: string;
24
+ rounded?: string;
25
+ containerClass?: any;
26
+ containerStyle?: any;
27
+ href?: string;
28
+ target?: '_blank' | '_self';
29
+ class?: any;
30
+ style?: Record<string, string>;
31
+ }>();
32
+
33
+ const isLoaded = ref(false);
34
+ const customClass = computed(() => props.class || '');
35
+
36
+ const onLoad = () => {
37
+ isLoaded.value = true;
38
+ };
39
+
40
+ const onError = (e: any) => {
41
+ isLoaded.value = true;
42
+ e.target.src = 'https://placehold.co/800x600/1a1a1a/666?text=Image+Not+Found';
43
+ };
44
+
45
+ const objectFitClass = computed(() => {
46
+ if (props.fit) {
47
+ const fitMap: Record<string, string> = {
48
+ 'cover': 'object-cover',
49
+ 'contain': 'object-contain',
50
+ 'fill': 'object-fill',
51
+ 'none': 'object-none',
52
+ 'scale-down': 'object-scale-down',
53
+ };
54
+ return fitMap[props.fit] || 'object-cover';
55
+ }
56
+ return props.fill !== false ? 'object-cover' : 'object-contain';
57
+ });
58
+
59
+ const imageStyle = computed(() => {
60
+ const style: Record<string, string> = {
61
+ ...props.style,
62
+ };
63
+ if (props.position) {
64
+ style.objectPosition = props.position;
65
+ }
66
+ return style;
67
+ });
68
+
69
+ const roundedClass = computed(() => {
70
+ if (props.fill !== false && !props.rounded) return '';
71
+ const map: Record<string, string> = {
72
+ 'none': 'rounded-none',
73
+ 'sm': 'rounded-sm',
74
+ 'md': 'rounded-md',
75
+ 'lg': 'rounded-lg',
76
+ 'xl': 'rounded-xl',
77
+ 'full': 'rounded-full',
78
+ };
79
+ return map[props.rounded || 'lg'] || 'rounded-lg';
80
+ });
81
+ </script>