@xenterprises/nuxt-x-marketing 1.0.0 → 1.0.2
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/.claude/settings.local.json +2 -1
- package/.playground/app.vue +12 -6
- package/.playground/pages/blog/detail.vue +341 -249
- package/.playground/pages/index.vue +2 -2
- package/.playground/pages/layout/x-legal.vue +12 -12
- package/.playground/pages/notifications/social-proof-toast.vue +330 -70
- package/LICENSE +60 -0
- package/README.md +210 -64
- package/app/app.config.ts +2 -72
- package/app/assets/css/x-marketing.css +1 -0
- package/app/components/X/Footer/index.vue +3 -4
- package/app/components/X/Mark/Affiliate/ComparisonTable.vue +382 -0
- package/app/components/X/Mark/Affiliate/Disclosure.vue +138 -0
- package/app/components/X/Mark/Affiliate/ProductCard.vue +431 -0
- package/app/components/X/Mark/Affiliate/ProductDetail.vue +410 -0
- package/app/components/X/Mark/Affiliate/ProductGrid.vue +101 -0
- package/app/components/X/Mark/Blog/Author.vue +26 -20
- package/app/components/X/Mark/Blog/Card.vue +28 -15
- package/app/components/X/Mark/Blog/Detail.vue +82 -127
- package/app/components/X/Mark/Blog/List.vue +17 -16
- package/app/components/X/Mark/CTA/index.vue +74 -70
- package/app/components/X/Mark/Card/Promo.vue +149 -139
- package/app/components/X/Mark/CodeBlock/index.vue +69 -0
- package/app/components/X/Mark/Directory/CategoryHero.vue +124 -0
- package/app/components/X/Mark/Directory/ListingCard.vue +336 -0
- package/app/components/X/Mark/Directory/ListingDetail.vue +306 -0
- package/app/components/X/Mark/Directory/ListingGrid.vue +190 -0
- package/app/components/X/Mark/Directory/SubmitCTA.vue +201 -0
- package/app/components/X/Mark/FAQ/index.vue +191 -0
- package/app/components/X/Mark/Features/index.vue +87 -35
- package/app/components/X/Mark/Hero/index.vue +41 -38
- package/app/components/X/Mark/Layout/AnnouncementBar.vue +20 -28
- package/app/components/X/Mark/Modal/Chat.vue +124 -124
- package/app/components/X/Mark/Modal/Video.vue +46 -36
- package/app/components/X/Mark/Pricing/Comparison.vue +76 -21
- package/app/components/X/Mark/Pricing/Plans.vue +75 -25
- package/app/components/X/Mark/SocialProof/Testimonials.vue +89 -21
- package/app/components/X/Mark/SocialProof/Toast.vue +155 -102
- package/app/components/X/X/Footer/index.vue +101 -0
- package/app/components/X/X/Header/Nav/index.vue +62 -0
- package/app/components/X/X/Header/index.vue +72 -0
- package/app/components/X/X/Legal/index.vue +94 -0
- package/app/composables/useParallax.ts +32 -19
- package/app/composables/useScrollReveal.ts +20 -13
- package/app/pages/index.vue +230 -28
- package/componentList.json +1856 -238
- package/nuxt.config.ts +12 -3
- package/package.json +5 -2
- package/screenshots/playground-home.png +0 -0
|
@@ -2,17 +2,27 @@
|
|
|
2
2
|
<div>
|
|
3
3
|
<!-- Billing Toggle -->
|
|
4
4
|
<div v-if="hasBillingToggle" class="flex justify-center mb-12">
|
|
5
|
-
<div
|
|
5
|
+
<div
|
|
6
|
+
class="inline-flex items-center gap-4 rounded-full bg-neutral-100 dark:bg-neutral-900 p-1"
|
|
7
|
+
>
|
|
6
8
|
<button
|
|
7
9
|
class="px-4 py-2 rounded-full text-sm font-medium transition-colors"
|
|
8
|
-
:class="
|
|
10
|
+
:class="
|
|
11
|
+
billingPeriod === 'monthly'
|
|
12
|
+
? 'bg-white dark:bg-neutral-800 shadow-sm'
|
|
13
|
+
: 'text-neutral-600 dark:text-neutral-400'
|
|
14
|
+
"
|
|
9
15
|
@click="billingPeriod = 'monthly'"
|
|
10
16
|
>
|
|
11
17
|
Monthly
|
|
12
18
|
</button>
|
|
13
19
|
<button
|
|
14
20
|
class="px-4 py-2 rounded-full text-sm font-medium transition-colors"
|
|
15
|
-
:class="
|
|
21
|
+
:class="
|
|
22
|
+
billingPeriod === 'yearly'
|
|
23
|
+
? 'bg-white dark:bg-neutral-800 shadow-sm'
|
|
24
|
+
: 'text-neutral-600 dark:text-neutral-400'
|
|
25
|
+
"
|
|
16
26
|
@click="billingPeriod = 'yearly'"
|
|
17
27
|
>
|
|
18
28
|
Yearly
|
|
@@ -65,15 +75,52 @@
|
|
|
65
75
|
|
|
66
76
|
<script setup>
|
|
67
77
|
const props = defineProps({
|
|
68
|
-
/** Array of pricing plans */
|
|
78
|
+
/** Array of pricing plans [{ name, price, period?, description?, features, button?, popular? }] */
|
|
69
79
|
plans: {
|
|
70
80
|
type: Array,
|
|
71
|
-
|
|
81
|
+
default: () => [
|
|
82
|
+
{
|
|
83
|
+
name: "Starter",
|
|
84
|
+
price: "$9",
|
|
85
|
+
period: "/month",
|
|
86
|
+
description: "Perfect for individuals and small projects.",
|
|
87
|
+
features: ["5 projects", "10GB storage", "Email support"],
|
|
88
|
+
button: { label: "Get Started" },
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
name: "Pro",
|
|
92
|
+
price: "$29",
|
|
93
|
+
period: "/month",
|
|
94
|
+
description: "For growing teams that need more power.",
|
|
95
|
+
features: [
|
|
96
|
+
"Unlimited projects",
|
|
97
|
+
"100GB storage",
|
|
98
|
+
"Priority support",
|
|
99
|
+
"Advanced analytics",
|
|
100
|
+
],
|
|
101
|
+
button: { label: "Get Pro" },
|
|
102
|
+
popular: true,
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
name: "Enterprise",
|
|
106
|
+
price: "$99",
|
|
107
|
+
period: "/month",
|
|
108
|
+
description: "For large organizations with custom needs.",
|
|
109
|
+
features: [
|
|
110
|
+
"Unlimited everything",
|
|
111
|
+
"1TB storage",
|
|
112
|
+
"Dedicated support",
|
|
113
|
+
"Custom integrations",
|
|
114
|
+
"SLA guarantee",
|
|
115
|
+
],
|
|
116
|
+
button: { label: "Contact Sales" },
|
|
117
|
+
},
|
|
118
|
+
],
|
|
72
119
|
},
|
|
73
120
|
/** Plan name to highlight (alternative to setting popular: true on plan) */
|
|
74
121
|
highlighted: {
|
|
75
122
|
type: String,
|
|
76
|
-
default:
|
|
123
|
+
default: "",
|
|
77
124
|
},
|
|
78
125
|
/** Has monthly/yearly billing toggle */
|
|
79
126
|
hasBillingToggle: {
|
|
@@ -99,7 +146,7 @@ const props = defineProps({
|
|
|
99
146
|
variant: {
|
|
100
147
|
type: String,
|
|
101
148
|
default: undefined,
|
|
102
|
-
validator: (v) => [
|
|
149
|
+
validator: (v) => ["soft", "solid", "outline", "subtle"].includes(v),
|
|
103
150
|
},
|
|
104
151
|
/** Social proof badge presets or custom badges */
|
|
105
152
|
badges: {
|
|
@@ -109,33 +156,36 @@ const props = defineProps({
|
|
|
109
156
|
/** Position of badges */
|
|
110
157
|
badgesPosition: {
|
|
111
158
|
type: String,
|
|
112
|
-
default:
|
|
113
|
-
validator: (v) => [
|
|
159
|
+
default: "bottom",
|
|
160
|
+
validator: (v) => ["top", "bottom"].includes(v),
|
|
114
161
|
},
|
|
115
162
|
/** Alignment of badges */
|
|
116
163
|
badgesAlign: {
|
|
117
164
|
type: String,
|
|
118
|
-
default:
|
|
119
|
-
validator: (v) => [
|
|
165
|
+
default: "center",
|
|
166
|
+
validator: (v) => ["left", "center", "right"].includes(v),
|
|
120
167
|
},
|
|
121
168
|
});
|
|
122
169
|
|
|
123
|
-
const emit = defineEmits([
|
|
170
|
+
const emit = defineEmits(["select"]);
|
|
124
171
|
|
|
125
|
-
const billingPeriod = ref(
|
|
172
|
+
const billingPeriod = ref("monthly");
|
|
126
173
|
|
|
127
174
|
// Map XMark plan format to Nuxt UI format
|
|
128
175
|
const mappedPlans = computed(() => {
|
|
129
176
|
return props.plans.map((plan) => {
|
|
130
177
|
// Get the price based on billing period
|
|
131
178
|
let price = plan.price;
|
|
132
|
-
let billingCycle = plan.period ||
|
|
179
|
+
let billingCycle = plan.period || "";
|
|
133
180
|
|
|
134
|
-
if (typeof plan.price ===
|
|
135
|
-
price =
|
|
136
|
-
|
|
181
|
+
if (typeof plan.price === "object") {
|
|
182
|
+
price =
|
|
183
|
+
billingPeriod.value === "yearly"
|
|
184
|
+
? plan.price.yearly
|
|
185
|
+
: plan.price.monthly;
|
|
186
|
+
billingCycle = billingPeriod.value === "yearly" ? "/year" : "/month";
|
|
137
187
|
} else if (props.hasBillingToggle && !plan.period) {
|
|
138
|
-
billingCycle = billingPeriod.value ===
|
|
188
|
+
billingCycle = billingPeriod.value === "yearly" ? "/year" : "/month";
|
|
139
189
|
}
|
|
140
190
|
|
|
141
191
|
// Determine if this plan should be highlighted
|
|
@@ -152,10 +202,10 @@ const mappedPlans = computed(() => {
|
|
|
152
202
|
features: plan.features,
|
|
153
203
|
highlight: isHighlighted,
|
|
154
204
|
scale: shouldScale,
|
|
155
|
-
badge: isHighlighted ?
|
|
205
|
+
badge: isHighlighted ? "Most Popular" : undefined,
|
|
156
206
|
button: {
|
|
157
|
-
label: plan.button?.label ||
|
|
158
|
-
onClick: () => emit(
|
|
207
|
+
label: plan.button?.label || "Get Started",
|
|
208
|
+
onClick: () => emit("select", plan),
|
|
159
209
|
},
|
|
160
210
|
};
|
|
161
211
|
});
|
|
@@ -164,16 +214,16 @@ const mappedPlans = computed(() => {
|
|
|
164
214
|
// Badge alignment classes
|
|
165
215
|
const badgesAlignClasses = computed(() => {
|
|
166
216
|
const alignments = {
|
|
167
|
-
left:
|
|
168
|
-
center:
|
|
169
|
-
right:
|
|
217
|
+
left: "justify-start",
|
|
218
|
+
center: "justify-center",
|
|
219
|
+
right: "justify-end",
|
|
170
220
|
};
|
|
171
221
|
return alignments[props.badgesAlign];
|
|
172
222
|
});
|
|
173
223
|
|
|
174
224
|
// Get badge props (handles string presets and object configs)
|
|
175
225
|
const getBadgeProps = (badge) => {
|
|
176
|
-
if (typeof badge ===
|
|
226
|
+
if (typeof badge === "string") {
|
|
177
227
|
return { preset: badge };
|
|
178
228
|
}
|
|
179
229
|
return badge;
|
|
@@ -9,14 +9,20 @@
|
|
|
9
9
|
class="absolute left-0 top-1/2 -translate-y-1/2 -translate-x-4 lg:-translate-x-16 w-11 h-11 flex items-center justify-center rounded-full bg-white dark:bg-neutral-800 shadow-lg border border-neutral-200 dark:border-neutral-700 hover:bg-neutral-50 dark:hover:bg-neutral-700 transition-all duration-300 opacity-60 hover:opacity-100"
|
|
10
10
|
@click="prev"
|
|
11
11
|
>
|
|
12
|
-
<UIcon
|
|
12
|
+
<UIcon
|
|
13
|
+
name="i-lucide-chevron-left"
|
|
14
|
+
class="w-5 h-5 text-neutral-700 dark:text-neutral-300"
|
|
15
|
+
/>
|
|
13
16
|
</button>
|
|
14
17
|
<button
|
|
15
18
|
v-if="testimonials.length > 1"
|
|
16
19
|
class="absolute right-0 top-1/2 -translate-y-1/2 translate-x-4 lg:translate-x-16 w-11 h-11 flex items-center justify-center rounded-full bg-white dark:bg-neutral-800 shadow-lg border border-neutral-200 dark:border-neutral-700 hover:bg-neutral-50 dark:hover:bg-neutral-700 transition-all duration-300 opacity-60 hover:opacity-100"
|
|
17
20
|
@click="next"
|
|
18
21
|
>
|
|
19
|
-
<UIcon
|
|
22
|
+
<UIcon
|
|
23
|
+
name="i-lucide-chevron-right"
|
|
24
|
+
class="w-5 h-5 text-neutral-700 dark:text-neutral-300"
|
|
25
|
+
/>
|
|
20
26
|
</button>
|
|
21
27
|
|
|
22
28
|
<!-- Quote Icon -->
|
|
@@ -26,25 +32,38 @@
|
|
|
26
32
|
|
|
27
33
|
<!-- Quote with crossfade -->
|
|
28
34
|
<Transition name="fade" mode="out-in">
|
|
29
|
-
<blockquote
|
|
35
|
+
<blockquote
|
|
36
|
+
:key="currentIndex"
|
|
37
|
+
class="xText-headline text-neutral-900 dark:text-white xText-balance"
|
|
38
|
+
>
|
|
30
39
|
"{{ currentTestimonial.quote }}"
|
|
31
40
|
</blockquote>
|
|
32
41
|
</Transition>
|
|
33
42
|
|
|
34
43
|
<!-- Stars -->
|
|
35
|
-
<div
|
|
44
|
+
<div
|
|
45
|
+
v-if="hasStars && currentTestimonial.rating"
|
|
46
|
+
class="flex justify-center gap-1 mt-6"
|
|
47
|
+
>
|
|
36
48
|
<UIcon
|
|
37
49
|
v-for="i in 5"
|
|
38
50
|
:key="i"
|
|
39
51
|
name="i-lucide-star"
|
|
40
52
|
class="w-5 h-5"
|
|
41
|
-
:class="
|
|
53
|
+
:class="
|
|
54
|
+
i <= currentTestimonial.rating
|
|
55
|
+
? 'text-yellow-400'
|
|
56
|
+
: 'text-neutral-300 dark:text-neutral-600'
|
|
57
|
+
"
|
|
42
58
|
/>
|
|
43
59
|
</div>
|
|
44
60
|
|
|
45
61
|
<!-- Attribution with crossfade -->
|
|
46
62
|
<Transition name="fade" mode="out-in">
|
|
47
|
-
<div
|
|
63
|
+
<div
|
|
64
|
+
:key="currentIndex"
|
|
65
|
+
class="mt-8 flex items-center justify-center gap-4"
|
|
66
|
+
>
|
|
48
67
|
<div class="relative">
|
|
49
68
|
<img
|
|
50
69
|
v-if="currentTestimonial.avatar"
|
|
@@ -52,7 +71,9 @@
|
|
|
52
71
|
:alt="currentTestimonial.name"
|
|
53
72
|
class="w-14 h-14 rounded-full object-cover ring-2 ring-white dark:ring-neutral-800 shadow-lg"
|
|
54
73
|
/>
|
|
55
|
-
<div
|
|
74
|
+
<div
|
|
75
|
+
class="absolute inset-0 rounded-full ring-2 ring-primary-500/20 ring-offset-2 ring-offset-white dark:ring-offset-neutral-950"
|
|
76
|
+
/>
|
|
56
77
|
</div>
|
|
57
78
|
<div class="text-left">
|
|
58
79
|
<p class="font-semibold text-neutral-900 dark:text-white">
|
|
@@ -83,7 +104,11 @@
|
|
|
83
104
|
v-for="(_, index) in testimonials"
|
|
84
105
|
:key="index"
|
|
85
106
|
class="w-2 h-2 rounded-full transition-colors duration-300"
|
|
86
|
-
:class="
|
|
107
|
+
:class="
|
|
108
|
+
index === currentIndex
|
|
109
|
+
? 'bg-primary-500'
|
|
110
|
+
: 'bg-neutral-300 dark:bg-neutral-700 hover:bg-neutral-400 dark:hover:bg-neutral-600'
|
|
111
|
+
"
|
|
87
112
|
@click="goToIndex(index)"
|
|
88
113
|
/>
|
|
89
114
|
</div>
|
|
@@ -106,7 +131,11 @@
|
|
|
106
131
|
:key="i"
|
|
107
132
|
name="i-lucide-star"
|
|
108
133
|
class="w-5 h-5"
|
|
109
|
-
:class="
|
|
134
|
+
:class="
|
|
135
|
+
i <= (testimonial.rating || 5)
|
|
136
|
+
? 'text-yellow-400'
|
|
137
|
+
: 'text-neutral-300 dark:text-neutral-600'
|
|
138
|
+
"
|
|
110
139
|
/>
|
|
111
140
|
</div>
|
|
112
141
|
|
|
@@ -137,7 +166,9 @@
|
|
|
137
166
|
</p>
|
|
138
167
|
<p class="text-xs text-neutral-500">
|
|
139
168
|
{{ testimonial.title }}
|
|
140
|
-
<span v-if="testimonial.company">
|
|
169
|
+
<span v-if="testimonial.company">
|
|
170
|
+
at {{ testimonial.company }}</span
|
|
171
|
+
>
|
|
141
172
|
</p>
|
|
142
173
|
</div>
|
|
143
174
|
</div>
|
|
@@ -150,7 +181,10 @@
|
|
|
150
181
|
<div class="relative overflow-hidden">
|
|
151
182
|
<div
|
|
152
183
|
class="flex transition-transform duration-500"
|
|
153
|
-
:style="{
|
|
184
|
+
:style="{
|
|
185
|
+
transform: `translateX(-${currentIndex * 100}%)`,
|
|
186
|
+
transitionTimingFunction: 'cubic-bezier(0.16, 1, 0.3, 1)',
|
|
187
|
+
}"
|
|
154
188
|
>
|
|
155
189
|
<article
|
|
156
190
|
v-for="testimonial in testimonials"
|
|
@@ -165,7 +199,11 @@
|
|
|
165
199
|
:key="i"
|
|
166
200
|
name="i-lucide-star"
|
|
167
201
|
class="w-5 h-5"
|
|
168
|
-
:class="
|
|
202
|
+
:class="
|
|
203
|
+
i <= (testimonial.rating || 5)
|
|
204
|
+
? 'text-yellow-400'
|
|
205
|
+
: 'text-neutral-300'
|
|
206
|
+
"
|
|
169
207
|
/>
|
|
170
208
|
</div>
|
|
171
209
|
|
|
@@ -220,7 +258,11 @@
|
|
|
220
258
|
v-for="(_, index) in testimonials"
|
|
221
259
|
:key="index"
|
|
222
260
|
class="w-2 h-2 rounded-full transition-colors"
|
|
223
|
-
:class="
|
|
261
|
+
:class="
|
|
262
|
+
index === currentIndex
|
|
263
|
+
? 'bg-primary-500'
|
|
264
|
+
: 'bg-neutral-300 dark:bg-neutral-700'
|
|
265
|
+
"
|
|
224
266
|
@click="goToIndex(index)"
|
|
225
267
|
/>
|
|
226
268
|
</div>
|
|
@@ -231,19 +273,44 @@
|
|
|
231
273
|
|
|
232
274
|
<script setup>
|
|
233
275
|
// Initialize scroll reveal animations
|
|
234
|
-
useScrollReveal()
|
|
276
|
+
useScrollReveal();
|
|
235
277
|
|
|
236
278
|
const props = defineProps({
|
|
237
|
-
/** Array of testimonials */
|
|
279
|
+
/** Array of testimonials [{ quote, name, title?, company?, avatar?, logo?, rating? }] */
|
|
238
280
|
testimonials: {
|
|
239
281
|
type: Array,
|
|
240
|
-
|
|
282
|
+
default: () => [
|
|
283
|
+
{
|
|
284
|
+
quote:
|
|
285
|
+
"This product completely transformed how our team works. Highly recommended.",
|
|
286
|
+
name: "Alex Johnson",
|
|
287
|
+
title: "CEO",
|
|
288
|
+
company: "Acme Inc",
|
|
289
|
+
rating: 5,
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
quote:
|
|
293
|
+
"The best investment we made this year. Setup was a breeze and results were immediate.",
|
|
294
|
+
name: "Sarah Chen",
|
|
295
|
+
title: "CTO",
|
|
296
|
+
company: "TechCorp",
|
|
297
|
+
rating: 5,
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
quote:
|
|
301
|
+
"Outstanding support and a product that actually delivers on its promises.",
|
|
302
|
+
name: "Marcus Williams",
|
|
303
|
+
title: "Product Manager",
|
|
304
|
+
company: "StartupXYZ",
|
|
305
|
+
rating: 5,
|
|
306
|
+
},
|
|
307
|
+
],
|
|
241
308
|
},
|
|
242
309
|
/** Display layout */
|
|
243
310
|
layout: {
|
|
244
311
|
type: String,
|
|
245
|
-
default:
|
|
246
|
-
validator: (v) => [
|
|
312
|
+
default: "featured",
|
|
313
|
+
validator: (v) => ["featured", "grid", "carousel"].includes(v),
|
|
247
314
|
},
|
|
248
315
|
/** Show star ratings */
|
|
249
316
|
hasStars: {
|
|
@@ -280,9 +347,10 @@ const next = () => {
|
|
|
280
347
|
};
|
|
281
348
|
|
|
282
349
|
const prev = () => {
|
|
283
|
-
currentIndex.value =
|
|
284
|
-
|
|
285
|
-
|
|
350
|
+
currentIndex.value =
|
|
351
|
+
currentIndex.value === 0
|
|
352
|
+
? props.testimonials.length - 1
|
|
353
|
+
: currentIndex.value - 1;
|
|
286
354
|
resetAutoplay();
|
|
287
355
|
};
|
|
288
356
|
|