@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.
Files changed (49) hide show
  1. package/.claude/settings.local.json +2 -1
  2. package/.playground/app.vue +12 -6
  3. package/.playground/pages/blog/detail.vue +341 -249
  4. package/.playground/pages/index.vue +2 -2
  5. package/.playground/pages/layout/x-legal.vue +12 -12
  6. package/.playground/pages/notifications/social-proof-toast.vue +330 -70
  7. package/LICENSE +60 -0
  8. package/README.md +210 -64
  9. package/app/app.config.ts +2 -72
  10. package/app/assets/css/x-marketing.css +1 -0
  11. package/app/components/X/Footer/index.vue +3 -4
  12. package/app/components/X/Mark/Affiliate/ComparisonTable.vue +382 -0
  13. package/app/components/X/Mark/Affiliate/Disclosure.vue +138 -0
  14. package/app/components/X/Mark/Affiliate/ProductCard.vue +431 -0
  15. package/app/components/X/Mark/Affiliate/ProductDetail.vue +410 -0
  16. package/app/components/X/Mark/Affiliate/ProductGrid.vue +101 -0
  17. package/app/components/X/Mark/Blog/Author.vue +26 -20
  18. package/app/components/X/Mark/Blog/Card.vue +28 -15
  19. package/app/components/X/Mark/Blog/Detail.vue +82 -127
  20. package/app/components/X/Mark/Blog/List.vue +17 -16
  21. package/app/components/X/Mark/CTA/index.vue +74 -70
  22. package/app/components/X/Mark/Card/Promo.vue +149 -139
  23. package/app/components/X/Mark/CodeBlock/index.vue +69 -0
  24. package/app/components/X/Mark/Directory/CategoryHero.vue +124 -0
  25. package/app/components/X/Mark/Directory/ListingCard.vue +336 -0
  26. package/app/components/X/Mark/Directory/ListingDetail.vue +306 -0
  27. package/app/components/X/Mark/Directory/ListingGrid.vue +190 -0
  28. package/app/components/X/Mark/Directory/SubmitCTA.vue +201 -0
  29. package/app/components/X/Mark/FAQ/index.vue +191 -0
  30. package/app/components/X/Mark/Features/index.vue +87 -35
  31. package/app/components/X/Mark/Hero/index.vue +41 -38
  32. package/app/components/X/Mark/Layout/AnnouncementBar.vue +20 -28
  33. package/app/components/X/Mark/Modal/Chat.vue +124 -124
  34. package/app/components/X/Mark/Modal/Video.vue +46 -36
  35. package/app/components/X/Mark/Pricing/Comparison.vue +76 -21
  36. package/app/components/X/Mark/Pricing/Plans.vue +75 -25
  37. package/app/components/X/Mark/SocialProof/Testimonials.vue +89 -21
  38. package/app/components/X/Mark/SocialProof/Toast.vue +155 -102
  39. package/app/components/X/X/Footer/index.vue +101 -0
  40. package/app/components/X/X/Header/Nav/index.vue +62 -0
  41. package/app/components/X/X/Header/index.vue +72 -0
  42. package/app/components/X/X/Legal/index.vue +94 -0
  43. package/app/composables/useParallax.ts +32 -19
  44. package/app/composables/useScrollReveal.ts +20 -13
  45. package/app/pages/index.vue +230 -28
  46. package/componentList.json +1856 -238
  47. package/nuxt.config.ts +12 -3
  48. package/package.json +5 -2
  49. package/screenshots/playground-home.png +0 -0
@@ -4,15 +4,20 @@
4
4
  * From spec-prompt.md specification
5
5
  */
6
6
  export const useParallax = (options = {}) => {
7
+ // Only register lifecycle hooks when inside a component instance
8
+ const instance = getCurrentInstance();
9
+ if (!instance) return { initParallax: () => {}, cleanup: () => {} };
10
+
7
11
  const defaultOptions = {
8
12
  defaultSpeed: 0.5,
9
13
  ...options,
10
14
  };
11
15
 
12
16
  const elements = ref<Element[]>([]);
17
+ let handleScroll: (() => void) | null = null;
13
18
 
14
19
  const initParallax = () => {
15
- if (process.client) {
20
+ if (import.meta.client) {
16
21
  // Find all elements with xParallax class and data-parallax-speed attribute
17
22
  const parallaxElements = document.querySelectorAll('.xParallax[data-parallax-speed]');
18
23
  elements.value = Array.from(parallaxElements);
@@ -37,7 +42,7 @@ export const useParallax = (options = {}) => {
37
42
 
38
43
  // Use requestAnimationFrame for smoother updates
39
44
  let ticking = false;
40
- const handleScroll = () => {
45
+ handleScroll = () => {
41
46
  if (!ticking) {
42
47
  window.requestAnimationFrame(() => {
43
48
  updateParallax();
@@ -51,25 +56,27 @@ export const useParallax = (options = {}) => {
51
56
 
52
57
  // Initial update
53
58
  updateParallax();
54
-
55
- onUnmounted(() => {
56
- window.removeEventListener('scroll', handleScroll);
57
- });
58
59
  }
59
60
  };
60
61
 
61
62
  const cleanup = () => {
63
+ if (handleScroll) {
64
+ window.removeEventListener('scroll', handleScroll);
65
+ handleScroll = null;
66
+ }
62
67
  elements.value = [];
63
68
  };
64
69
 
65
- onMounted(() => {
66
- // Small delay to ensure DOM is ready
67
- setTimeout(initParallax, 100);
68
- });
70
+ if (import.meta.client) {
71
+ onMounted(() => {
72
+ // Small delay to ensure DOM is ready
73
+ setTimeout(initParallax, 100);
74
+ });
69
75
 
70
- onUnmounted(() => {
71
- cleanup();
72
- });
76
+ onUnmounted(() => {
77
+ cleanup();
78
+ });
79
+ }
73
80
 
74
81
  return {
75
82
  initParallax,
@@ -83,9 +90,10 @@ export const useParallax = (options = {}) => {
83
90
  */
84
91
  export const useElementParallax = (speed = 0.5) => {
85
92
  const elementRef = ref<HTMLElement | null>(null);
93
+ let handleScroll: (() => void) | null = null;
86
94
 
87
95
  onMounted(() => {
88
- if (process.client && elementRef.value) {
96
+ if (import.meta.client && elementRef.value) {
89
97
  let ticking = false;
90
98
 
91
99
  const updateParallax = () => {
@@ -99,7 +107,7 @@ export const useElementParallax = (speed = 0.5) => {
99
107
  elementRef.value.style.transform = `translateY(${offset}px)`;
100
108
  };
101
109
 
102
- const handleScroll = () => {
110
+ handleScroll = () => {
103
111
  if (!ticking) {
104
112
  window.requestAnimationFrame(() => {
105
113
  updateParallax();
@@ -111,13 +119,18 @@ export const useElementParallax = (speed = 0.5) => {
111
119
 
112
120
  window.addEventListener('scroll', handleScroll, { passive: true });
113
121
  updateParallax();
114
-
115
- onUnmounted(() => {
116
- window.removeEventListener('scroll', handleScroll);
117
- });
118
122
  }
119
123
  });
120
124
 
125
+ if (import.meta.client) {
126
+ onUnmounted(() => {
127
+ if (handleScroll) {
128
+ window.removeEventListener('scroll', handleScroll);
129
+ handleScroll = null;
130
+ }
131
+ });
132
+ }
133
+
121
134
  return {
122
135
  elementRef,
123
136
  };
@@ -8,7 +8,11 @@
8
8
  * - .xFadeUp, .xFadeIn, .xFadeLeft, .xFadeRight, .xScale classes
9
9
  * - .xFadeUp-stagger for staggered child animations
10
10
  */
11
- export const useScrollReveal = (options = {}) => {
11
+ export const useScrollReveal = (options: Record<string, unknown> = {}) => {
12
+ // Only register lifecycle hooks when inside a component instance
13
+ const instance = getCurrentInstance();
14
+ if (!instance) return { initScrollReveal: () => {}, cleanup: () => {} };
15
+
12
16
  const defaultOptions = {
13
17
  threshold: 0.15,
14
18
  rootMargin: '0px 0px -80px 0px',
@@ -29,7 +33,7 @@ export const useScrollReveal = (options = {}) => {
29
33
  ].join(', ');
30
34
 
31
35
  const initScrollReveal = () => {
32
- if (process.client) {
36
+ if (import.meta.client) {
33
37
  observer.value = new IntersectionObserver((entries) => {
34
38
  entries.forEach((entry) => {
35
39
  if (entry.isIntersecting) {
@@ -54,17 +58,20 @@ export const useScrollReveal = (options = {}) => {
54
58
  const cleanup = () => {
55
59
  if (observer.value) {
56
60
  observer.value.disconnect();
61
+ observer.value = null;
57
62
  }
58
63
  };
59
64
 
60
- onMounted(() => {
61
- // Small delay to ensure DOM is ready
62
- setTimeout(initScrollReveal, 100);
63
- });
65
+ if (import.meta.client) {
66
+ onMounted(() => {
67
+ // Small delay to ensure DOM is ready
68
+ setTimeout(initScrollReveal, 100);
69
+ });
64
70
 
65
- onUnmounted(() => {
66
- cleanup();
67
- });
71
+ onUnmounted(() => {
72
+ cleanup();
73
+ });
74
+ }
68
75
 
69
76
  return {
70
77
  initScrollReveal,
@@ -76,8 +83,8 @@ export const useScrollReveal = (options = {}) => {
76
83
  * Helper to add staggered reveal delays to child elements
77
84
  */
78
85
  export const useStaggerReveal = (containerSelector: string, staggerDelay = 100) => {
79
- onMounted(() => {
80
- if (process.client) {
86
+ if (import.meta.client) {
87
+ onMounted(() => {
81
88
  setTimeout(() => {
82
89
  const container = document.querySelector(containerSelector);
83
90
  if (container) {
@@ -87,6 +94,6 @@ export const useStaggerReveal = (containerSelector: string, staggerDelay = 100)
87
94
  });
88
95
  }
89
96
  }, 100);
90
- }
91
- });
97
+ });
98
+ }
92
99
  };
@@ -10,9 +10,7 @@
10
10
  overlay="gradient"
11
11
  >
12
12
  <template #actions>
13
- <UButton size="xl" color="white">
14
- Get Started Free
15
- </UButton>
13
+ <UButton size="xl" color="white"> Get Started Free </UButton>
16
14
  <UButton size="xl" variant="outline" color="white">
17
15
  Watch Demo
18
16
  </UButton>
@@ -23,41 +21,41 @@
23
21
  <XMarkSection id="features" bg="default" padding="xl">
24
22
  <header class="text-center max-w-3xl mx-auto mb-16">
25
23
  <p class="xText-eyebrow text-primary-500 mb-4">Features</p>
26
- <h2 class="xText-headline text-neutral-900 dark:text-white xText-balance">
24
+ <h2
25
+ class="xText-headline text-neutral-900 dark:text-white xText-balance"
26
+ >
27
27
  Everything you need to succeed
28
28
  </h2>
29
29
  <p class="mt-4 text-lg text-neutral-600 dark:text-neutral-400">
30
- Powerful tools designed to help you build, launch, and grow your product.
30
+ Powerful tools designed to help you build, launch, and grow your
31
+ product.
31
32
  </p>
32
33
  </header>
33
34
 
34
- <XMarkFeatures
35
- :features="features"
36
- layout="grid"
37
- :columns="3"
38
- />
35
+ <XMarkFeatures :features="features" layout="grid" :columns="3" />
39
36
  </XMarkSection>
40
37
 
41
38
  <!-- Testimonials Section -->
42
39
  <XMarkSection bg="subtle" padding="xl">
43
40
  <header class="text-center max-w-3xl mx-auto mb-16">
44
41
  <p class="xText-eyebrow text-primary-500 mb-4">Testimonials</p>
45
- <h2 class="xText-headline text-neutral-900 dark:text-white xText-balance">
42
+ <h2
43
+ class="xText-headline text-neutral-900 dark:text-white xText-balance"
44
+ >
46
45
  Loved by teams worldwide
47
46
  </h2>
48
47
  </header>
49
48
 
50
- <XMarkTestimonials
51
- :testimonials="testimonials"
52
- layout="grid"
53
- />
49
+ <XMarkTestimonials :testimonials="testimonials" layout="grid" />
54
50
  </XMarkSection>
55
51
 
56
52
  <!-- Pricing Section -->
57
53
  <XMarkSection bg="default" padding="xl">
58
54
  <header class="text-center max-w-3xl mx-auto mb-16">
59
55
  <p class="xText-eyebrow text-primary-500 mb-4">Pricing</p>
60
- <h2 class="xText-headline text-neutral-900 dark:text-white xText-balance">
56
+ <h2
57
+ class="xText-headline text-neutral-900 dark:text-white xText-balance"
58
+ >
61
59
  Simple, transparent pricing
62
60
  </h2>
63
61
  <p class="mt-4 text-lg text-neutral-600 dark:text-neutral-400">
@@ -65,24 +63,18 @@
65
63
  </p>
66
64
  </header>
67
65
 
68
- <XMarkPricing
69
- :plans="pricingPlans"
70
- />
66
+ <XMarkPricing :plans="pricingPlans" />
71
67
  </XMarkSection>
72
68
 
73
69
  <!-- CTA Section -->
74
70
  <XMarkSection bg="bold" padding="xl">
75
71
  <div class="text-center max-w-3xl mx-auto">
76
- <h2 class="xText-headline xText-balance">
77
- Ready to get started?
78
- </h2>
72
+ <h2 class="xText-headline xText-balance">Ready to get started?</h2>
79
73
  <p class="mt-4 text-lg opacity-80">
80
74
  Join thousands of teams already using our platform.
81
75
  </p>
82
76
  <div class="mt-8 flex flex-wrap justify-center gap-4">
83
- <UButton size="xl" color="white">
84
- Start Free Trial
85
- </UButton>
77
+ <UButton size="xl" color="white"> Start Free Trial </UButton>
86
78
  <UButton size="xl" variant="outline" color="white">
87
79
  Contact Sales
88
80
  </UButton>
@@ -93,13 +85,123 @@
93
85
  </div>
94
86
  </div>
95
87
  </XMarkSection>
88
+
89
+ <!-- ─── Affiliate Components Demo ─── -->
90
+
91
+ <!-- Affiliate Disclosure variants -->
92
+ <XMarkSection bg="subtle" padding="xl">
93
+ <header class="text-center max-w-3xl mx-auto mb-12">
94
+ <p class="xText-eyebrow text-primary-500 mb-4">Affiliate Components</p>
95
+ <h2
96
+ class="xText-headline text-neutral-900 dark:text-white xText-balance"
97
+ >
98
+ Affiliate &amp; Review Components
99
+ </h2>
100
+ <p class="mt-4 text-lg text-neutral-600 dark:text-neutral-400">
101
+ FTC-compliant disclosures, product cards, grids, detail pages, and
102
+ comparison tables.
103
+ </p>
104
+ </header>
105
+
106
+ <!-- Disclosure variants -->
107
+ <div class="max-w-3xl mx-auto space-y-4 mb-16">
108
+ <h3
109
+ class="font-semibold text-neutral-700 dark:text-neutral-300 text-sm uppercase tracking-wider mb-4"
110
+ >
111
+ Disclosure Variants
112
+ </h3>
113
+ <XMarkAffiliateDisclosure variant="subtle" />
114
+ <XMarkAffiliateDisclosure variant="banner" />
115
+ <XMarkAffiliateDisclosure variant="inline" />
116
+ </div>
117
+
118
+ <!-- Product Cards — all 3 layouts -->
119
+ <div class="mb-16">
120
+ <h3
121
+ class="font-semibold text-neutral-700 dark:text-neutral-300 text-sm uppercase tracking-wider mb-6"
122
+ >
123
+ Product Card Layouts
124
+ </h3>
125
+ <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6 mb-8">
126
+ <XMarkAffiliateProductCard
127
+ :product="sampleProducts[0]"
128
+ layout="card"
129
+ :show-pros="true"
130
+ />
131
+ <XMarkAffiliateProductCard
132
+ :product="sampleProducts[1]"
133
+ layout="card"
134
+ :show-pros="true"
135
+ :show-cons="true"
136
+ />
137
+ <XMarkAffiliateProductCard
138
+ :product="sampleProducts[2]"
139
+ layout="card"
140
+ :show-pros="true"
141
+ />
142
+ </div>
143
+ <div class="space-y-4 mb-6">
144
+ <XMarkAffiliateProductCard
145
+ :product="sampleProducts[0]"
146
+ layout="row"
147
+ :show-pros="true"
148
+ />
149
+ <XMarkAffiliateProductCard
150
+ :product="sampleProducts[1]"
151
+ layout="row"
152
+ />
153
+ </div>
154
+ <div class="space-y-2">
155
+ <XMarkAffiliateProductCard
156
+ v-for="p in sampleProducts"
157
+ :key="p.name"
158
+ :product="p"
159
+ layout="compact"
160
+ />
161
+ </div>
162
+ </div>
163
+
164
+ <!-- Product Grid -->
165
+ <div class="mb-16">
166
+ <h3
167
+ class="font-semibold text-neutral-700 dark:text-neutral-300 text-sm uppercase tracking-wider mb-6"
168
+ >
169
+ Product Grid (Roundup Post)
170
+ </h3>
171
+ <XMarkAffiliateProductGrid
172
+ :products="sampleProducts"
173
+ layout="grid"
174
+ :columns="3"
175
+ :show-ranking="true"
176
+ disclosure="This page contains affiliate links. As an Amazon Associate, we earn from qualifying purchases."
177
+ />
178
+ </div>
179
+
180
+ <!-- Comparison Table -->
181
+ <div class="mb-16">
182
+ <h3
183
+ class="font-semibold text-neutral-700 dark:text-neutral-300 text-sm uppercase tracking-wider mb-6"
184
+ >
185
+ Comparison Table (X vs Y)
186
+ </h3>
187
+ <XMarkAffiliateComparisonTable
188
+ :products="comparisonProducts"
189
+ :specs="comparisonSpecs"
190
+ :show-buy-buttons="true"
191
+ :highlight-best="true"
192
+ button-label="Buy on Amazon"
193
+ disclosure="This page contains affiliate links."
194
+ />
195
+ </div>
196
+ </XMarkSection>
96
197
  </div>
97
198
  </template>
98
199
 
99
200
  <script setup>
100
201
  useSeoMeta({
101
202
  title: "Modern Platform for Teams",
102
- description: "Build, launch, and grow your product with our powerful platform.",
203
+ description:
204
+ "Build, launch, and grow your product with our powerful platform.",
103
205
  });
104
206
 
105
207
  // Features data
@@ -139,7 +241,8 @@ const features = [
139
241
  // Testimonials data
140
242
  const testimonials = [
141
243
  {
142
- quote: "This platform transformed how our team works. We shipped 3x faster within the first month.",
244
+ quote:
245
+ "This platform transformed how our team works. We shipped 3x faster within the first month.",
143
246
  name: "Sarah Chen",
144
247
  title: "CTO",
145
248
  company: "TechStart",
@@ -153,7 +256,8 @@ const testimonials = [
153
256
  avatar: "https://randomuser.me/api/portraits/men/32.jpg",
154
257
  },
155
258
  {
156
- quote: "Finally, a platform that understands what modern teams need. Highly recommended.",
259
+ quote:
260
+ "Finally, a platform that understands what modern teams need. Highly recommended.",
157
261
  name: "Emily Park",
158
262
  title: "Product Lead",
159
263
  company: "InnovateCo",
@@ -161,6 +265,104 @@ const testimonials = [
161
265
  },
162
266
  ];
163
267
 
268
+ // Affiliate sample products
269
+ const sampleProducts = [
270
+ {
271
+ name: "Sony WH-1000XM5",
272
+ image:
273
+ "https://images.unsplash.com/photo-1505740420928-5e560c06d30e?w=400&q=80",
274
+ price: "$279.99",
275
+ originalPrice: "$349.99",
276
+ rating: 4.8,
277
+ reviewCount: 12400,
278
+ pros: ["Best-in-class ANC", "30hr battery life", "Foldable design"],
279
+ cons: ["No IP rating", "Expensive"],
280
+ affiliateUrl: "https://amazon.com",
281
+ affiliateTag: "mysite-20",
282
+ },
283
+ {
284
+ name: "Bose QuietComfort 45",
285
+ image:
286
+ "https://images.unsplash.com/photo-1484704849700-f032a568e944?w=400&q=80",
287
+ price: "$249.99",
288
+ rating: 4.6,
289
+ reviewCount: 8900,
290
+ pros: ["Comfortable fit", "Great sound quality", "Easy controls"],
291
+ cons: ["No multipoint", "Older design"],
292
+ affiliateUrl: "https://amazon.com",
293
+ affiliateTag: "mysite-20",
294
+ },
295
+ {
296
+ name: "Apple AirPods Max",
297
+ image:
298
+ "https://images.unsplash.com/photo-1585386959984-a4155224a1ad?w=400&q=80",
299
+ price: "$449.00",
300
+ rating: 4.7,
301
+ reviewCount: 6200,
302
+ pros: ["Premium build quality", "Excellent ANC", "Spatial audio"],
303
+ cons: ["Very expensive", "Heavy", "No USB-C charging"],
304
+ affiliateUrl: "https://amazon.com",
305
+ affiliateTag: "mysite-20",
306
+ },
307
+ ];
308
+
309
+ // Comparison table data
310
+ const comparisonProducts = [
311
+ {
312
+ name: "Sony WH-1000XM5",
313
+ image:
314
+ "https://images.unsplash.com/photo-1505740420928-5e560c06d30e?w=200&q=80",
315
+ price: "$279.99",
316
+ affiliateUrl: "https://amazon.com",
317
+ affiliateTag: "mysite-20",
318
+ specs: {
319
+ price: "$279.99",
320
+ rating: "4.8/5",
321
+ battery: "30 hours",
322
+ weight: "250g",
323
+ anc: "Yes",
324
+ },
325
+ },
326
+ {
327
+ name: "Bose QC45",
328
+ image:
329
+ "https://images.unsplash.com/photo-1484704849700-f032a568e944?w=200&q=80",
330
+ price: "$249.99",
331
+ affiliateUrl: "https://amazon.com",
332
+ affiliateTag: "mysite-20",
333
+ specs: {
334
+ price: "$249.99",
335
+ rating: "4.6/5",
336
+ battery: "24 hours",
337
+ weight: "238g",
338
+ anc: "Yes",
339
+ },
340
+ },
341
+ {
342
+ name: "AirPods Max",
343
+ image:
344
+ "https://images.unsplash.com/photo-1585386959984-a4155224a1ad?w=200&q=80",
345
+ price: "$449.00",
346
+ affiliateUrl: "https://amazon.com",
347
+ affiliateTag: "mysite-20",
348
+ specs: {
349
+ price: "$449.00",
350
+ rating: "4.7/5",
351
+ battery: "20 hours",
352
+ weight: "385g",
353
+ anc: "Yes",
354
+ },
355
+ },
356
+ ];
357
+
358
+ const comparisonSpecs = [
359
+ { label: "Price", key: "price" },
360
+ { label: "Rating", key: "rating" },
361
+ { label: "Battery Life", key: "battery" },
362
+ { label: "Weight", key: "weight" },
363
+ { label: "ANC", key: "anc" },
364
+ ];
365
+
164
366
  // Pricing plans data
165
367
  const pricingPlans = [
166
368
  {