@xenterprises/nuxt-x-marketing 0.0.23 → 1.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 (115) hide show
  1. package/.claude/settings.local.json +22 -0
  2. package/.playground/app.config.ts +44 -45
  3. package/.playground/app.vue +263 -0
  4. package/.playground/components/AppFooter.vue +103 -0
  5. package/.playground/components/AppHeader.vue +41 -0
  6. package/.playground/components/CodeExample.vue +70 -0
  7. package/.playground/components/ComponentPlayground.vue +117 -0
  8. package/.playground/components/DocsHeader.vue +19 -0
  9. package/.playground/layouts/default.vue +13 -3
  10. package/.playground/layouts/docs-layout.vue +161 -0
  11. package/.playground/layouts/docs.vue +11 -0
  12. package/.playground/package-lock.json +6 -0
  13. package/.playground/pages/blog/author.vue +145 -0
  14. package/.playground/pages/blog/card.vue +149 -0
  15. package/.playground/pages/blog/cta.vue +131 -0
  16. package/.playground/pages/blog/detail.vue +379 -0
  17. package/.playground/pages/blog/list.vue +261 -0
  18. package/.playground/pages/blog/navigation.vue +228 -0
  19. package/.playground/pages/blog/sidebar.vue +279 -0
  20. package/.playground/pages/config.vue +289 -0
  21. package/.playground/pages/content/comparison.vue +253 -0
  22. package/.playground/pages/content/cta.vue +245 -0
  23. package/.playground/pages/content/features.vue +217 -0
  24. package/.playground/pages/content/newsletter-section.vue +260 -0
  25. package/.playground/pages/content/newsletter.vue +200 -0
  26. package/.playground/pages/content/pricing.vue +335 -0
  27. package/.playground/pages/content/testimonials.vue +188 -0
  28. package/.playground/pages/hero.vue +257 -0
  29. package/.playground/pages/index.vue +379 -2
  30. package/.playground/pages/layout/footer.vue +210 -0
  31. package/.playground/pages/layout/legal-footer.vue +121 -0
  32. package/.playground/pages/layout/navbar.vue +285 -0
  33. package/.playground/pages/layout/section.vue +266 -0
  34. package/.playground/pages/layout/x-legal.vue +199 -0
  35. package/.playground/pages/modals/feature-modal.vue +320 -0
  36. package/.playground/pages/modals/image-lightbox.vue +277 -0
  37. package/.playground/pages/modals/video-modal.vue +222 -0
  38. package/.playground/pages/notifications/announcement-bar.vue +164 -0
  39. package/.playground/pages/notifications/cookie-banner.vue +210 -0
  40. package/.playground/pages/notifications/cookie-toast.vue +143 -0
  41. package/.playground/pages/notifications/gdpr.vue +276 -0
  42. package/.playground/pages/notifications/social-proof-toast.vue +255 -0
  43. package/.playground/pages/ui/badge.vue +161 -0
  44. package/.playground/pages/ui/bg-pattern.vue +177 -0
  45. package/.playground/pages/ui/glass-card.vue +179 -0
  46. package/.playground/pages/ui/glow-divider.vue +179 -0
  47. package/.playground/pages/ui/promo-card.vue +259 -0
  48. package/.playground/pages/ui/section-stitch.vue +411 -0
  49. package/.playground/pages/utilities/back-to-top.vue +153 -0
  50. package/.playground/pages/utilities/popup-chat.vue +224 -0
  51. package/README.md +799 -164
  52. package/app/app.config.ts +88 -2
  53. package/app/app.vue +106 -11
  54. package/app/assets/css/x-marketing.css +668 -0
  55. package/app/components/X/Footer/XLegal/index.vue +63 -26
  56. package/app/components/X/Footer/index.vue +89 -89
  57. package/app/components/X/Header/App.vue +63 -0
  58. package/app/components/X/Mark/Blog/Author.vue +170 -0
  59. package/app/components/X/Mark/Blog/CTANewsletter.vue +197 -0
  60. package/app/components/X/Mark/Blog/Card.vue +117 -0
  61. package/app/components/X/Mark/Blog/Detail.vue +240 -0
  62. package/app/components/X/Mark/Blog/List.vue +156 -0
  63. package/app/components/X/Mark/Blog/Navigation.vue +100 -0
  64. package/app/components/X/Mark/Blog/Sidebar.vue +160 -0
  65. package/app/components/X/Mark/Button/BackToTop.vue +99 -0
  66. package/app/components/X/Mark/CTA/index.vue +294 -0
  67. package/app/components/X/Mark/Card/Glass.vue +64 -0
  68. package/app/components/X/Mark/Card/Promo.vue +164 -0
  69. package/app/components/X/Mark/Divider/Glow.vue +113 -0
  70. package/app/components/X/Mark/Features/index.vue +194 -0
  71. package/app/components/X/Mark/Hero/index.vue +217 -0
  72. package/app/components/X/Mark/Layout/AnnouncementBar.vue +139 -0
  73. package/app/components/X/Mark/Layout/Footer.vue +160 -0
  74. package/app/components/X/Mark/Layout/FooterLegal.vue +42 -0
  75. package/app/components/X/Mark/Layout/Navbar.vue +267 -0
  76. package/app/components/X/Mark/Modal/Chat.vue +151 -0
  77. package/app/components/X/Mark/Modal/Feature.vue +197 -0
  78. package/app/components/X/Mark/Modal/Image.vue +226 -0
  79. package/app/components/X/Mark/Modal/Video.vue +203 -0
  80. package/app/components/X/Mark/Modal/index.vue +133 -0
  81. package/app/components/X/Mark/Newsletter/Form.vue +174 -0
  82. package/app/components/X/Mark/Pattern/Bg.vue +86 -0
  83. package/app/components/X/Mark/Pricing/Comparison.vue +126 -0
  84. package/app/components/X/Mark/Pricing/Plans.vue +181 -0
  85. package/app/components/X/Mark/Privacy/CookieBanner.vue +112 -0
  86. package/app/components/X/Mark/Privacy/CookieToast.vue +43 -0
  87. package/app/components/X/Mark/Privacy/GDPR.vue +245 -0
  88. package/app/components/X/Mark/Section/Newsletter.vue +183 -0
  89. package/app/components/X/Mark/Section/Stitch.vue +156 -0
  90. package/app/components/X/Mark/Section/index.vue +151 -0
  91. package/app/components/X/Mark/SocialProof/Badge.vue +102 -0
  92. package/app/components/X/Mark/SocialProof/Testimonials.vue +326 -0
  93. package/app/components/X/Mark/SocialProof/Toast.vue +136 -0
  94. package/app/components/X/Mark/_archive/Pattern/Background.vue +83 -0
  95. package/app/components/X/Mark/_archive/Pattern/CheckBg.vue +35 -0
  96. package/app/components/X/Mark/_archive/Pattern/CircuitBg.vue +41 -0
  97. package/app/components/X/Mark/_archive/Pattern/DotsBg.vue +35 -0
  98. package/app/components/X/Mark/_archive/Pattern/GlowDivider.vue +86 -0
  99. package/app/components/X/Mark/_archive/Pattern/HexBg.vue +35 -0
  100. package/app/components/X/Mark/_archive/Pattern/PlaidBg.vue +47 -0
  101. package/app/components/X/Mark/_archive/Pattern/SectionStitch.vue +168 -0
  102. package/app/components/X/Mark/_archive/Pattern/WaveBg.vue +41 -0
  103. package/app/components/X/_archive/Newsletter/Form.vue +123 -0
  104. package/app/components/X/_archive/Newsletter/Section.vue +131 -0
  105. package/app/composables/useParallax.ts +124 -0
  106. package/app/composables/useScrollReveal.ts +92 -0
  107. package/app/pages/index.vue +210 -0
  108. package/app/plugins/marketing.client.ts +13 -0
  109. package/app/types/marketing.d.ts +268 -0
  110. package/componentList.json +511 -0
  111. package/nuxt.config.ts +6 -13
  112. package/package.json +21 -9
  113. package/.editorconfig +0 -12
  114. package/app/assets/css/main.css +0 -2
  115. package/app/components/X/Marketing/Sections/EmailSubscription/index.vue +0 -129
@@ -0,0 +1,43 @@
1
+ <template>
2
+ <div />
3
+ </template>
4
+
5
+ <script setup>
6
+ defineOptions({
7
+ inheritAttrs: false
8
+ })
9
+
10
+ const toast = useToast()
11
+
12
+ onMounted(() => {
13
+ const accepted = localStorage.getItem('gdpr-cookies-accepted')
14
+ if (!accepted) {
15
+ toast.add({
16
+ title: 'We use cookies',
17
+ description: 'We use cookies to enhance your browsing experience and analyze site traffic. By clicking "Accept", you consent to our use of cookies.',
18
+ icon: 'i-lucide-cookie',
19
+ timeout: 0,
20
+ actions: [
21
+ {
22
+ label: 'Accept',
23
+ color: 'primary',
24
+ onClick: (e) => {
25
+ e?.stopPropagation()
26
+ localStorage.setItem('gdpr-cookies-accepted', 'true')
27
+ toast.clear()
28
+ }
29
+ },
30
+ {
31
+ label: 'Decline',
32
+ color: 'neutral',
33
+ variant: 'outline',
34
+ onClick: (e) => {
35
+ e?.stopPropagation()
36
+ toast.clear()
37
+ }
38
+ }
39
+ ]
40
+ })
41
+ }
42
+ })
43
+ </script>
@@ -0,0 +1,245 @@
1
+ <template>
2
+ <UModal v-model:open="isOpen">
3
+ <!-- Trigger Slot -->
4
+ <slot>
5
+ <UButton
6
+ :label="buttonProps.label"
7
+ :color="buttonProps.color"
8
+ :variant="buttonProps.variant"
9
+ :size="buttonProps.size"
10
+ :leading-icon="buttonProps.icon"
11
+ />
12
+ </slot>
13
+
14
+ <template #content>
15
+ <div class="p-6">
16
+ <!-- Header -->
17
+ <header class="mb-6">
18
+ <h3 class="text-xl font-semibold text-neutral-900 dark:text-white">
19
+ {{ title }}
20
+ </h3>
21
+ <p class="mt-2 text-sm text-neutral-600 dark:text-neutral-400">
22
+ {{ description }}
23
+ </p>
24
+ </header>
25
+
26
+ <!-- Cookie Categories -->
27
+ <div class="space-y-4">
28
+ <div
29
+ v-for="category in categories"
30
+ :key="category.id"
31
+ class="p-4 rounded-xl border border-neutral-200 dark:border-neutral-700"
32
+ >
33
+ <div class="flex items-start justify-between gap-4">
34
+ <div class="flex-1">
35
+ <div class="flex items-center gap-2">
36
+ <h4 class="font-medium text-neutral-900 dark:text-white">
37
+ {{ category.name }}
38
+ </h4>
39
+ <UBadge
40
+ v-if="category.required"
41
+ label="Required"
42
+ size="xs"
43
+ variant="subtle"
44
+ />
45
+ </div>
46
+ <p class="mt-1 text-sm text-neutral-600 dark:text-neutral-400">
47
+ {{ category.description }}
48
+ </p>
49
+ </div>
50
+ <UToggle
51
+ v-model="selections[category.id]"
52
+ :disabled="category.required"
53
+ />
54
+ </div>
55
+ </div>
56
+ </div>
57
+
58
+ <!-- Actions -->
59
+ <footer class="mt-6 flex flex-col sm:flex-row gap-3">
60
+ <UButton
61
+ label="Reject All"
62
+ variant="ghost"
63
+ color="neutral"
64
+ class="flex-1"
65
+ @click="rejectAll"
66
+ />
67
+ <UButton
68
+ label="Accept Selected"
69
+ variant="outline"
70
+ class="flex-1"
71
+ @click="acceptSelected"
72
+ />
73
+ <UButton
74
+ label="Accept All"
75
+ class="flex-1"
76
+ @click="acceptAll"
77
+ />
78
+ </footer>
79
+
80
+ <!-- Policy Links -->
81
+ <div class="mt-4 pt-4 border-t border-neutral-200 dark:border-neutral-700 text-center">
82
+ <div class="flex justify-center gap-4 text-sm">
83
+ <NuxtLink
84
+ v-if="privacyLink"
85
+ :to="privacyLink"
86
+ class="text-primary-500 hover:underline"
87
+ >
88
+ Privacy Policy
89
+ </NuxtLink>
90
+ <NuxtLink
91
+ v-if="cookieLink"
92
+ :to="cookieLink"
93
+ class="text-primary-500 hover:underline"
94
+ >
95
+ Cookie Policy
96
+ </NuxtLink>
97
+ </div>
98
+ </div>
99
+ </div>
100
+ </template>
101
+ </UModal>
102
+ </template>
103
+
104
+ <script setup>
105
+ const props = defineProps({
106
+ /** v-model for modal visibility */
107
+ modelValue: {
108
+ type: Boolean,
109
+ default: false,
110
+ },
111
+ /** Modal title */
112
+ title: {
113
+ type: String,
114
+ default: 'Privacy Preferences',
115
+ },
116
+ /** Modal description */
117
+ description: {
118
+ type: String,
119
+ default: 'We use cookies to enhance your browsing experience. You can choose which categories to allow.',
120
+ },
121
+ /** Cookie categories */
122
+ categories: {
123
+ type: Array,
124
+ default: () => [
125
+ {
126
+ id: 'necessary',
127
+ name: 'Necessary',
128
+ description: 'Essential cookies required for the website to function properly.',
129
+ required: true,
130
+ },
131
+ {
132
+ id: 'analytics',
133
+ name: 'Analytics',
134
+ description: 'Cookies that help us understand how visitors interact with our website.',
135
+ required: false,
136
+ },
137
+ {
138
+ id: 'marketing',
139
+ name: 'Marketing',
140
+ description: 'Cookies used for advertising and promotional purposes.',
141
+ required: false,
142
+ },
143
+ {
144
+ id: 'preferences',
145
+ name: 'Preferences',
146
+ description: 'Cookies that remember your preferences and settings.',
147
+ required: false,
148
+ },
149
+ ],
150
+ },
151
+ /** Privacy policy link */
152
+ privacyLink: {
153
+ type: String,
154
+ default: '/privacy',
155
+ },
156
+ /** Cookie policy link */
157
+ cookieLink: {
158
+ type: String,
159
+ default: '/cookies',
160
+ },
161
+ /** Storage key for preferences */
162
+ storageKey: {
163
+ type: String,
164
+ default: 'gdpr-preferences',
165
+ },
166
+ /** Button configuration object */
167
+ button: {
168
+ type: Object,
169
+ default: () => ({}),
170
+ },
171
+ });
172
+
173
+ const emit = defineEmits(['update:modelValue', 'accept', 'reject', 'save']);
174
+
175
+ // Support both v-model and self-managed state
176
+ const internalOpen = ref(false);
177
+ const isOpen = computed({
178
+ get: () => props.modelValue !== undefined ? props.modelValue : internalOpen.value,
179
+ set: (value) => {
180
+ internalOpen.value = value;
181
+ emit('update:modelValue', value);
182
+ },
183
+ });
184
+
185
+ // Button props with defaults
186
+ const buttonProps = computed(() => ({
187
+ label: props.button.label || 'Cookie Settings',
188
+ color: props.button.color || 'neutral',
189
+ variant: props.button.variant || 'outline',
190
+ size: props.button.size || 'md',
191
+ icon: props.button.icon || 'i-lucide-cookie',
192
+ }));
193
+
194
+ // Initialize selections
195
+ const selections = ref({});
196
+
197
+ onMounted(() => {
198
+ // Load saved preferences
199
+ const saved = localStorage.getItem(props.storageKey);
200
+ if (saved) {
201
+ selections.value = JSON.parse(saved);
202
+ } else {
203
+ // Default: required categories enabled
204
+ props.categories.forEach((cat) => {
205
+ selections.value[cat.id] = cat.required;
206
+ });
207
+ }
208
+ });
209
+
210
+ const savePreferences = (prefs) => {
211
+ localStorage.setItem(props.storageKey, JSON.stringify(prefs));
212
+ localStorage.setItem('cookie-consent', 'customized');
213
+ emit('save', prefs);
214
+ isOpen.value = false;
215
+ };
216
+
217
+ const acceptAll = () => {
218
+ const prefs = {};
219
+ props.categories.forEach((cat) => {
220
+ prefs[cat.id] = true;
221
+ });
222
+ savePreferences(prefs);
223
+ emit('accept');
224
+ };
225
+
226
+ const acceptSelected = () => {
227
+ // Ensure required categories are always true
228
+ const prefs = { ...selections.value };
229
+ props.categories.forEach((cat) => {
230
+ if (cat.required) {
231
+ prefs[cat.id] = true;
232
+ }
233
+ });
234
+ savePreferences(prefs);
235
+ };
236
+
237
+ const rejectAll = () => {
238
+ const prefs = {};
239
+ props.categories.forEach((cat) => {
240
+ prefs[cat.id] = cat.required;
241
+ });
242
+ savePreferences(prefs);
243
+ emit('reject');
244
+ };
245
+ </script>
@@ -0,0 +1,183 @@
1
+ <template>
2
+ <section
3
+ class="relative border-t overflow-hidden"
4
+ :class="[
5
+ variant === 'dark'
6
+ ? 'bg-neutral-950 border-neutral-800'
7
+ : 'bg-neutral-50 dark:bg-neutral-900 border-neutral-200 dark:border-neutral-800'
8
+ ]"
9
+ >
10
+ <!-- Background Pattern -->
11
+ <XMarkPatternBg
12
+ v-if="pattern"
13
+ :pattern="pattern"
14
+ :opacity="patternOpacity"
15
+ :fade="false"
16
+ />
17
+
18
+ <div class="relative max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12 md:py-16">
19
+ <div class="flex flex-col md:flex-row md:items-center md:justify-between gap-8">
20
+ <!-- Content -->
21
+ <div class="flex-1">
22
+ <h3
23
+ class="text-xl font-semibold"
24
+ :class="variant === 'dark' ? 'text-white' : 'text-neutral-900 dark:text-white'"
25
+ >
26
+ {{ title }}
27
+ </h3>
28
+ <p
29
+ v-if="description"
30
+ class="mt-2"
31
+ :class="variant === 'dark' ? 'text-neutral-400' : 'text-neutral-600 dark:text-neutral-400'"
32
+ >
33
+ {{ description }}
34
+ </p>
35
+ </div>
36
+
37
+ <!-- Form -->
38
+ <div class="flex-shrink-0 w-full md:w-auto">
39
+ <!-- Success State -->
40
+ <Transition
41
+ enter-active-class="transition-all duration-300"
42
+ enter-from-class="opacity-0 translate-y-2"
43
+ leave-active-class="transition-all duration-200"
44
+ leave-to-class="opacity-0"
45
+ >
46
+ <div
47
+ v-if="submitted"
48
+ class="flex items-center gap-3 px-4 py-3 rounded-lg"
49
+ :class="variant === 'dark' ? 'bg-primary-500/20 text-primary-300' : 'bg-primary-50 dark:bg-primary-950/30 text-primary-600 dark:text-primary-400'"
50
+ >
51
+ <UIcon name="i-lucide-check-circle" class="w-5 h-5" />
52
+ <span class="text-sm font-medium">{{ successMessage }}</span>
53
+ </div>
54
+ </Transition>
55
+
56
+ <!-- Form -->
57
+ <form
58
+ v-if="!submitted"
59
+ class="flex flex-col sm:flex-row gap-3"
60
+ @submit.prevent="handleSubmit"
61
+ >
62
+ <UInput
63
+ v-model="email"
64
+ type="email"
65
+ :placeholder="placeholder"
66
+ class="w-full sm:w-72"
67
+ :disabled="loading"
68
+ required
69
+ />
70
+ <UButton
71
+ type="submit"
72
+ :loading="loading"
73
+ :color="button?.color || 'primary'"
74
+ class="whitespace-nowrap"
75
+ >
76
+ {{ button?.label || 'Subscribe' }}
77
+ </UButton>
78
+ </form>
79
+
80
+ <!-- Privacy Note -->
81
+ <p
82
+ v-if="hasPrivacy && !submitted"
83
+ class="mt-3 text-xs"
84
+ :class="variant === 'dark' ? 'text-neutral-500' : 'text-neutral-500 dark:text-neutral-500'"
85
+ >
86
+ {{ privacyText }}
87
+ </p>
88
+ </div>
89
+ </div>
90
+ </div>
91
+ </section>
92
+ </template>
93
+
94
+ <script setup>
95
+ const props = defineProps({
96
+ /** Section title */
97
+ title: {
98
+ type: String,
99
+ default: 'Subscribe to our newsletter',
100
+ },
101
+ /** Section description */
102
+ description: {
103
+ type: String,
104
+ default: 'Get the latest news and updates.',
105
+ },
106
+ /** Email placeholder */
107
+ placeholder: {
108
+ type: String,
109
+ default: 'Enter your email',
110
+ },
111
+ /** Button config { label: string, color?: string } */
112
+ button: {
113
+ type: Object,
114
+ default: () => ({ label: 'Subscribe' }),
115
+ },
116
+ /** Has privacy note */
117
+ hasPrivacy: {
118
+ type: Boolean,
119
+ default: true,
120
+ },
121
+ /** Privacy note text */
122
+ privacyText: {
123
+ type: String,
124
+ default: 'No spam. Unsubscribe anytime.',
125
+ },
126
+ /** Success message after submission */
127
+ successMessage: {
128
+ type: String,
129
+ default: 'Thanks for subscribing!',
130
+ },
131
+ /** Style variant */
132
+ variant: {
133
+ type: String,
134
+ default: 'dark',
135
+ validator: (v) => ['dark', 'light'].includes(v),
136
+ },
137
+ /** Background pattern type (dots, grid, diagonal, topography, circuit, waves) or falsy to disable */
138
+ pattern: {
139
+ type: String,
140
+ default: null,
141
+ validator: (v) => !v || ['dots', 'grid', 'diagonal', 'topography', 'circuit', 'waves'].includes(v),
142
+ },
143
+ /** Pattern opacity (0-1) */
144
+ patternOpacity: {
145
+ type: Number,
146
+ default: 0.05,
147
+ },
148
+ /** Reset form after delay (ms), 0 to not reset */
149
+ resetDelay: {
150
+ type: Number,
151
+ default: 5000,
152
+ },
153
+ });
154
+
155
+ const emit = defineEmits(['submit']);
156
+
157
+ const email = ref('');
158
+ const loading = ref(false);
159
+ const submitted = ref(false);
160
+
161
+ const handleSubmit = async () => {
162
+ if (!email.value) return;
163
+
164
+ loading.value = true;
165
+
166
+ // Emit the email for parent to handle
167
+ emit('submit', email.value);
168
+
169
+ // Simulate submission delay
170
+ await new Promise((resolve) => setTimeout(resolve, 800));
171
+
172
+ loading.value = false;
173
+ submitted.value = true;
174
+
175
+ // Reset after delay if configured
176
+ if (props.resetDelay > 0) {
177
+ setTimeout(() => {
178
+ submitted.value = false;
179
+ email.value = '';
180
+ }, props.resetDelay);
181
+ }
182
+ };
183
+ </script>
@@ -0,0 +1,156 @@
1
+ <template>
2
+ <div
3
+ class="relative w-full overflow-hidden"
4
+ :style="{ height: heightValue }"
5
+ >
6
+ <svg
7
+ class="absolute inset-0 w-full h-full"
8
+ :class="{ 'scale-x-[-1]': reverse, 'scale-y-[-1]': invert }"
9
+ preserveAspectRatio="none"
10
+ :viewBox="`0 0 1200 ${viewBoxHeight}`"
11
+ xmlns="http://www.w3.org/2000/svg"
12
+ >
13
+ <!-- Full background with top color -->
14
+ <rect x="0" y="0" width="1200" :height="viewBoxHeight" :fill="topColor" />
15
+ <!-- Shape path with bottom color -->
16
+ <path :d="pathData" :fill="bottomColor" />
17
+ </svg>
18
+
19
+ <!-- Glow line -->
20
+ <div
21
+ v-if="glow"
22
+ class="absolute left-0 right-0 h-px"
23
+ :style="glowLineStyle"
24
+ />
25
+ </div>
26
+ </template>
27
+
28
+ <script setup lang="ts">
29
+ const props = withDefaults(
30
+ defineProps<{
31
+ /** Shape variant */
32
+ variant?: 'concave' | 'convex' | 'diagonal' | 'slant' | 'clouds' | 'waves' | 'tilt' | 'peaks' | 'scallop' | 'arrow'
33
+ /** Top section color */
34
+ topColor?: string
35
+ /** Bottom section color */
36
+ bottomColor?: string
37
+ /** Height size */
38
+ height?: 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl'
39
+ /** Reverse direction (flip horizontally) */
40
+ reverse?: boolean
41
+ /** Invert vertically (swap top/bottom) */
42
+ invert?: boolean
43
+ /** Show glow line */
44
+ glow?: boolean
45
+ /** Glow line color */
46
+ glowColor?: string
47
+ }>(),
48
+ {
49
+ variant: 'diagonal',
50
+ topColor: 'rgb(243 244 246)',
51
+ bottomColor: 'rgb(249 250 251)',
52
+ height: 'md',
53
+ reverse: false,
54
+ invert: false,
55
+ glow: false,
56
+ glowColor: 'rgba(99, 102, 241, 0.5)',
57
+ }
58
+ )
59
+
60
+ const heightValue = computed(() => {
61
+ const heights = {
62
+ sm: '40px',
63
+ md: '64px',
64
+ lg: '96px',
65
+ xl: '128px',
66
+ '2xl': '160px',
67
+ '3xl': '200px',
68
+ }
69
+ return heights[props.height]
70
+ })
71
+
72
+ const viewBoxHeight = computed(() => {
73
+ const heights = {
74
+ sm: 80,
75
+ md: 120,
76
+ lg: 160,
77
+ xl: 200,
78
+ '2xl': 240,
79
+ '3xl': 280,
80
+ }
81
+ return heights[props.height]
82
+ })
83
+
84
+ const pathData = computed(() => {
85
+ const h = viewBoxHeight.value
86
+ const mid = h / 2
87
+
88
+ const paths = {
89
+ // Full diagonal from top-left corner to bottom-right corner
90
+ diagonal: `M0 0 L1200 ${h} L0 ${h} Z`,
91
+
92
+ // Slant - opposite direction, from top-right to bottom-left
93
+ slant: `M1200 0 L1200 ${h} L0 ${h} Z`,
94
+
95
+ // Concave - curves inward (scoops down)
96
+ concave: `M0 ${mid} Q600 ${h} 1200 ${mid} L1200 ${h} L0 ${h} Z`,
97
+
98
+ // Convex - curves outward (bulges up)
99
+ convex: `M0 ${mid} Q600 0 1200 ${mid} L1200 ${h} L0 ${h} Z`,
100
+
101
+ // Tilt - gentle diagonal (less steep than full diagonal)
102
+ tilt: `M0 0 L1200 ${mid + 20} L1200 ${h} L0 ${h} Z`,
103
+
104
+ // Waves - multiple wave pattern
105
+ waves: `M0 ${mid}
106
+ Q150 ${mid - 30} 300 ${mid}
107
+ T600 ${mid}
108
+ T900 ${mid}
109
+ T1200 ${mid}
110
+ L1200 ${h} L0 ${h} Z`,
111
+
112
+ // Clouds - fluffy bumps
113
+ clouds: `M0 ${mid + 20}
114
+ Q100 ${mid - 40} 200 ${mid + 10}
115
+ Q300 ${mid - 50} 400 ${mid + 15}
116
+ Q500 ${mid - 45} 600 ${mid + 5}
117
+ Q700 ${mid - 55} 800 ${mid + 20}
118
+ Q900 ${mid - 40} 1000 ${mid + 10}
119
+ Q1100 ${mid - 50} 1200 ${mid + 15}
120
+ L1200 ${h} L0 ${h} Z`,
121
+
122
+ // Peaks - mountain-like
123
+ peaks: `M0 ${mid + 20}
124
+ L200 ${mid - 30}
125
+ L400 ${mid + 10}
126
+ L600 ${mid - 40}
127
+ L800 ${mid + 5}
128
+ L1000 ${mid - 25}
129
+ L1200 ${mid + 15}
130
+ L1200 ${h} L0 ${h} Z`,
131
+
132
+ // Scallop - repeating curves
133
+ scallop: `M0 ${mid}
134
+ Q100 ${mid - 40} 200 ${mid}
135
+ Q300 ${mid - 40} 400 ${mid}
136
+ Q500 ${mid - 40} 600 ${mid}
137
+ Q700 ${mid - 40} 800 ${mid}
138
+ Q900 ${mid - 40} 1000 ${mid}
139
+ Q1100 ${mid - 40} 1200 ${mid}
140
+ L1200 ${h} L0 ${h} Z`,
141
+
142
+ // Arrow - pointed center
143
+ arrow: `M0 ${mid + 20} L600 ${mid - 50} L1200 ${mid + 20} L1200 ${h} L0 ${h} Z`,
144
+ }
145
+ return paths[props.variant]
146
+ })
147
+
148
+ const glowLineStyle = computed(() => {
149
+ return {
150
+ top: '50%',
151
+ transform: 'translateY(-50%)',
152
+ background: props.glowColor,
153
+ boxShadow: `0 0 8px ${props.glowColor}, 0 0 16px ${props.glowColor}`,
154
+ }
155
+ })
156
+ </script>