@xenterprises/nuxt-x-marketing 1.0.1 → 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 (45) hide show
  1. package/.claude/settings.local.json +23 -0
  2. package/.playground/app.vue +12 -6
  3. package/.playground/package-lock.json +6 -0
  4. package/.playground/pages/index.vue +2 -2
  5. package/.playground/pages/layout/x-legal.vue +12 -12
  6. package/LICENSE +60 -0
  7. package/README.md +210 -64
  8. package/app/app.config.ts +2 -72
  9. package/app/components/X/Footer/index.vue +3 -4
  10. package/app/components/X/Mark/Affiliate/ComparisonTable.vue +382 -0
  11. package/app/components/X/Mark/Affiliate/Disclosure.vue +138 -0
  12. package/app/components/X/Mark/Affiliate/ProductCard.vue +431 -0
  13. package/app/components/X/Mark/Affiliate/ProductDetail.vue +410 -0
  14. package/app/components/X/Mark/Affiliate/ProductGrid.vue +101 -0
  15. package/app/components/X/Mark/Blog/Author.vue +26 -20
  16. package/app/components/X/Mark/Blog/Card.vue +28 -15
  17. package/app/components/X/Mark/Blog/Detail.vue +177 -169
  18. package/app/components/X/Mark/Blog/List.vue +17 -16
  19. package/app/components/X/Mark/CTA/index.vue +74 -70
  20. package/app/components/X/Mark/Card/Promo.vue +149 -139
  21. package/app/components/X/Mark/Directory/CategoryHero.vue +124 -0
  22. package/app/components/X/Mark/Directory/ListingCard.vue +336 -0
  23. package/app/components/X/Mark/Directory/ListingDetail.vue +306 -0
  24. package/app/components/X/Mark/Directory/ListingGrid.vue +190 -0
  25. package/app/components/X/Mark/Directory/SubmitCTA.vue +201 -0
  26. package/app/components/X/Mark/FAQ/index.vue +191 -0
  27. package/app/components/X/Mark/Features/index.vue +87 -35
  28. package/app/components/X/Mark/Hero/index.vue +41 -38
  29. package/app/components/X/Mark/Layout/AnnouncementBar.vue +20 -28
  30. package/app/components/X/Mark/Modal/Chat.vue +124 -124
  31. package/app/components/X/Mark/Modal/Video.vue +46 -36
  32. package/app/components/X/Mark/Pricing/Comparison.vue +76 -21
  33. package/app/components/X/Mark/Pricing/Plans.vue +75 -25
  34. package/app/components/X/Mark/SocialProof/Testimonials.vue +89 -21
  35. package/app/components/X/Mark/SocialProof/Toast.vue +146 -138
  36. package/app/components/X/X/Footer/index.vue +101 -0
  37. package/app/components/X/X/Header/Nav/index.vue +62 -0
  38. package/app/components/X/X/Header/index.vue +72 -0
  39. package/app/components/X/X/Legal/index.vue +94 -0
  40. package/app/composables/useParallax.ts +32 -19
  41. package/app/composables/useScrollReveal.ts +20 -13
  42. package/app/pages/index.vue +230 -28
  43. package/componentList.json +1856 -238
  44. package/package.json +2 -1
  45. package/screenshots/playground-home.png +0 -0
@@ -1,187 +1,195 @@
1
1
  <template>
2
- <article class="blog-detail">
3
- <!-- Hero -->
4
- <header class="mb-16 lg:mb-20">
5
- <div class="max-w-3xl mx-auto text-center mb-10">
6
- <UBadge
7
- v-if="post.category"
8
- :label="post.category"
9
- color="primary"
10
- variant="subtle"
11
- class="mb-6"
12
- />
13
- <h1
14
- class="text-3xl sm:text-4xl lg:text-5xl font-bold leading-tight tracking-tight text-neutral-900 dark:text-white"
15
- >
16
- {{ post.title }}
17
- </h1>
18
- <p
19
- v-if="post.description"
20
- class="mt-6 text-lg sm:text-xl text-neutral-600 dark:text-neutral-400 leading-relaxed max-w-2xl mx-auto"
21
- >
22
- {{ post.description }}
23
- </p>
2
+ <article class="blog-detail">
3
+ <!-- Hero -->
4
+ <header class="mb-16 lg:mb-20">
5
+ <div class="max-w-3xl mx-auto text-center mb-10">
6
+ <UBadge
7
+ v-if="post.category"
8
+ :label="post.category"
9
+ color="primary"
10
+ variant="subtle"
11
+ class="mb-6"
12
+ />
13
+ <h1
14
+ class="text-3xl sm:text-4xl lg:text-5xl font-bold leading-tight tracking-tight text-neutral-900 dark:text-white"
15
+ >
16
+ {{ post.title }}
17
+ </h1>
18
+ <p
19
+ v-if="post.description"
20
+ class="mt-6 text-lg sm:text-xl text-neutral-600 dark:text-neutral-400 leading-relaxed max-w-2xl mx-auto"
21
+ >
22
+ {{ post.description }}
23
+ </p>
24
24
 
25
- <!-- Meta -->
26
- <div
27
- class="mt-8 flex items-center justify-center gap-3 sm:gap-4 flex-wrap text-sm"
28
- >
29
- <!-- Author -->
30
- <div v-if="post.author" class="flex items-center gap-2.5">
31
- <img
32
- v-if="post.author.avatar"
33
- :src="post.author.avatar"
34
- :alt="post.author.name"
35
- class="w-10 h-10 rounded-full object-cover ring-2 ring-white dark:ring-neutral-800"
36
- />
37
- <span class="font-medium text-neutral-900 dark:text-white">
38
- {{ post.author.name }}
39
- </span>
40
- </div>
41
- <span class="text-neutral-300 dark:text-neutral-600">•</span>
42
- <time
43
- class="text-neutral-500 dark:text-neutral-400"
44
- :datetime="post.date"
45
- >
46
- {{ formattedDate }}
47
- </time>
48
- <span class="text-neutral-300 dark:text-neutral-600">•</span>
49
- <span class="text-neutral-500 dark:text-neutral-400">{{
50
- post.readingTime
51
- }}</span>
52
- </div>
53
- </div>
25
+ <!-- Meta -->
26
+ <div
27
+ class="mt-8 flex items-center justify-center gap-3 sm:gap-4 flex-wrap text-sm"
28
+ >
29
+ <!-- Author -->
30
+ <div v-if="post.author" class="flex items-center gap-2.5">
31
+ <img
32
+ v-if="post.author.avatar"
33
+ :src="post.author.avatar"
34
+ :alt="post.author.name"
35
+ class="w-10 h-10 rounded-full object-cover ring-2 ring-white dark:ring-neutral-800"
36
+ />
37
+ <span class="font-medium text-neutral-900 dark:text-white">
38
+ {{ post.author.name }}
39
+ </span>
40
+ </div>
41
+ <span class="text-neutral-300 dark:text-neutral-600">•</span>
42
+ <time
43
+ class="text-neutral-500 dark:text-neutral-400"
44
+ :datetime="post.date"
45
+ >
46
+ {{ formattedDate }}
47
+ </time>
48
+ <span class="text-neutral-300 dark:text-neutral-600">•</span>
49
+ <span class="text-neutral-500 dark:text-neutral-400">{{
50
+ post.readingTime
51
+ }}</span>
52
+ </div>
53
+ </div>
54
54
 
55
- <!-- Featured Image -->
56
- <figure
57
- v-if="post.img?.src"
58
- class="aspect-2/1 sm:aspect-21/9 rounded-2xl lg:rounded-3xl overflow-hidden shadow-xl"
59
- >
60
- <img
61
- :src="post.img.src"
62
- :alt="post.img.alt || post.title"
63
- class="w-full h-full object-cover"
64
- />
65
- </figure>
66
- </header>
55
+ <!-- Featured Image -->
56
+ <figure
57
+ v-if="post.img?.src"
58
+ class="aspect-2/1 sm:aspect-21/9 rounded-2xl lg:rounded-3xl overflow-hidden shadow-xl"
59
+ >
60
+ <img
61
+ :src="post.img.src"
62
+ :alt="post.img.alt || post.title"
63
+ class="w-full h-full object-cover"
64
+ />
65
+ </figure>
66
+ </header>
67
67
 
68
- <!-- Content Grid -->
69
- <div class="grid lg:grid-cols-[1fr_300px] gap-12 lg:gap-16">
70
- <!-- Main Content -->
71
- <div class="min-w-0">
72
- <!-- Prose Content -->
73
- <div
74
- class="prose prose-lg prose-neutral dark:prose-invert max-w-none prose-headings:scroll-mt-20 prose-a:text-primary-600 dark:prose-a:text-primary-400 prose-a:no-underline hover:prose-a:underline prose-pre:bg-neutral-900 dark:prose-pre:bg-neutral-950 prose-pre:border prose-pre:border-neutral-800 prose-img:rounded-xl"
75
- >
76
- <slot />
77
- </div>
68
+ <!-- Content Grid -->
69
+ <div class="grid lg:grid-cols-[1fr_300px] gap-12 lg:gap-16">
70
+ <!-- Main Content -->
71
+ <div class="min-w-0">
72
+ <!-- Prose Content -->
73
+ <div
74
+ class="prose prose-lg prose-neutral dark:prose-invert max-w-none prose-headings:scroll-mt-20 prose-a:text-primary-600 dark:prose-a:text-primary-400 prose-a:no-underline hover:prose-a:underline prose-pre:bg-neutral-900 dark:prose-pre:bg-neutral-950 prose-pre:border prose-pre:border-neutral-800 prose-img:rounded-xl"
75
+ >
76
+ <slot />
77
+ </div>
78
78
 
79
- <!-- Tags -->
80
- <div
81
- v-if="post.tags?.length"
82
- class="mt-16 pt-10 border-t border-neutral-200 dark:border-neutral-800"
83
- >
84
- <h4
85
- class="text-xs font-semibold uppercase tracking-wider text-neutral-500 mb-4"
86
- >
87
- Tags
88
- </h4>
89
- <div class="flex flex-wrap gap-2">
90
- <UBadge
91
- v-for="tag in post.tags"
92
- :key="tag"
93
- :label="tag"
94
- variant="subtle"
95
- color="neutral"
96
- size="md"
97
- />
98
- </div>
99
- </div>
79
+ <!-- Tags -->
80
+ <div
81
+ v-if="post.tags?.length"
82
+ class="mt-16 pt-10 border-t border-neutral-200 dark:border-neutral-800"
83
+ >
84
+ <h4
85
+ class="text-xs font-semibold uppercase tracking-wider text-neutral-500 mb-4"
86
+ >
87
+ Tags
88
+ </h4>
89
+ <div class="flex flex-wrap gap-2">
90
+ <UBadge
91
+ v-for="tag in post.tags"
92
+ :key="tag"
93
+ :label="tag"
94
+ variant="subtle"
95
+ color="neutral"
96
+ size="md"
97
+ />
98
+ </div>
99
+ </div>
100
100
 
101
- <!-- Author Bio -->
102
- <XMarkBlogAuthor
103
- v-if="post.author"
104
- :author="post.author"
105
- variant="full"
106
- class="mt-12"
107
- />
101
+ <!-- Author Bio -->
102
+ <XMarkBlogAuthor
103
+ v-if="post.author"
104
+ :author="post.author"
105
+ variant="full"
106
+ class="mt-12"
107
+ />
108
108
 
109
- <!-- Navigation -->
110
- <XMarkBlogNavigation
111
- v-if="previousPost || nextPost"
112
- :previous="previousPost"
113
- :next="nextPost"
114
- class="mt-12"
115
- />
116
- </div>
109
+ <!-- Navigation -->
110
+ <XMarkBlogNavigation
111
+ v-if="previousPost || nextPost"
112
+ :previous="previousPost"
113
+ :next="nextPost"
114
+ class="mt-12"
115
+ />
116
+ </div>
117
117
 
118
- <!-- Sidebar -->
119
- <aside class="hidden lg:block">
120
- <div class="sticky top-24 space-y-8">
121
- <!-- Table of Contents -->
122
- <div
123
- v-if="toc?.length"
124
- class="p-5 rounded-xl bg-neutral-50 dark:bg-neutral-900/50 border border-neutral-200/60 dark:border-neutral-800"
125
- >
126
- <h4
127
- class="font-semibold text-neutral-900 dark:text-white mb-4 text-sm"
128
- >
129
- On this page
130
- </h4>
131
- <nav class="space-y-1">
132
- <a
133
- v-for="item in toc"
134
- :key="item.id"
135
- :href="`#${item.id}`"
136
- class="block py-1.5 text-sm text-neutral-600 dark:text-neutral-400 hover:text-primary-600 dark:hover:text-primary-400 transition-colors leading-snug"
137
- :class="{ 'pl-4 text-[13px]': item.depth > 2 }"
138
- >
139
- {{ item.text }}
140
- </a>
141
- </nav>
142
- </div>
118
+ <!-- Sidebar -->
119
+ <aside class="hidden lg:block">
120
+ <div class="sticky top-24 space-y-8">
121
+ <!-- Table of Contents -->
122
+ <div
123
+ v-if="toc?.length"
124
+ class="p-5 rounded-xl bg-neutral-50 dark:bg-neutral-900/50 border border-neutral-200/60 dark:border-neutral-800"
125
+ >
126
+ <h4
127
+ class="font-semibold text-neutral-900 dark:text-white mb-4 text-sm"
128
+ >
129
+ On this page
130
+ </h4>
131
+ <nav class="space-y-1">
132
+ <a
133
+ v-for="item in toc"
134
+ :key="item.id"
135
+ :href="`#${item.id}`"
136
+ class="block py-1.5 text-sm text-neutral-600 dark:text-neutral-400 hover:text-primary-600 dark:hover:text-primary-400 transition-colors leading-snug"
137
+ :class="{ 'pl-4 text-[13px]': item.depth > 2 }"
138
+ >
139
+ {{ item.text }}
140
+ </a>
141
+ </nav>
142
+ </div>
143
143
 
144
- <!-- Newsletter CTA -->
145
- <slot name="sidebar">
146
- <XMarkNewsletterForm
147
- variant="compact"
148
- title="Subscribe to our newsletter"
149
- description="Get the latest posts and insights directly to your inbox."
150
- />
151
- </slot>
152
- </div>
153
- </aside>
154
- </div>
155
- </article>
144
+ <!-- Newsletter CTA -->
145
+ <slot name="sidebar">
146
+ <XMarkNewsletterForm
147
+ variant="compact"
148
+ title="Subscribe to our newsletter"
149
+ description="Get the latest posts and insights directly to your inbox."
150
+ />
151
+ </slot>
152
+ </div>
153
+ </aside>
154
+ </div>
155
+ </article>
156
156
  </template>
157
157
 
158
158
  <script setup>
159
159
  const props = defineProps({
160
- post: {
161
- type: Object,
162
- required: true,
163
- },
164
- previousPost: {
165
- type: Object,
166
- default: null,
167
- },
168
- nextPost: {
169
- type: Object,
170
- default: null,
171
- },
172
- toc: {
173
- type: Array,
174
- default: () => [],
175
- },
160
+ /** Blog post object { title, slug, img?, excerpt?, date?, category?, author?, body? } */
161
+ post: {
162
+ type: Object,
163
+ default: () => ({
164
+ title: "Blog Post Title",
165
+ slug: "#",
166
+ excerpt: "A short description of this blog post.",
167
+ date: new Date().toISOString(),
168
+ category: "General",
169
+ body: "",
170
+ }),
171
+ },
172
+ previousPost: {
173
+ type: Object,
174
+ default: null,
175
+ },
176
+ nextPost: {
177
+ type: Object,
178
+ default: null,
179
+ },
180
+ toc: {
181
+ type: Array,
182
+ default: () => [],
183
+ },
176
184
  });
177
185
 
178
186
  const formattedDate = computed(() => {
179
- if (!props.post.date) return "";
180
- const date = new Date(props.post.date);
181
- return date.toLocaleDateString("en-US", {
182
- year: "numeric",
183
- month: "long",
184
- day: "numeric",
185
- });
187
+ if (!props.post.date) return "";
188
+ const date = new Date(props.post.date);
189
+ return date.toLocaleDateString("en-US", {
190
+ year: "numeric",
191
+ month: "long",
192
+ day: "numeric",
193
+ });
186
194
  });
187
195
  </script>
@@ -32,11 +32,11 @@
32
32
  </div>
33
33
 
34
34
  <!-- Empty State -->
35
- <div
36
- v-if="!paginatedPosts.length"
37
- class="text-center py-16"
38
- >
39
- <UIcon name="i-lucide-file-text" class="w-12 h-12 text-neutral-300 dark:text-neutral-700 mx-auto mb-4" />
35
+ <div v-if="!paginatedPosts.length" class="text-center py-16">
36
+ <UIcon
37
+ name="i-lucide-file-text"
38
+ class="w-12 h-12 text-neutral-300 dark:text-neutral-700 mx-auto mb-4"
39
+ />
40
40
  <p class="text-neutral-600 dark:text-neutral-400">No posts found</p>
41
41
  </div>
42
42
 
@@ -53,10 +53,10 @@
53
53
 
54
54
  <script setup>
55
55
  const props = defineProps({
56
- /** Array of blog posts with img: { src, alt? } */
56
+ /** Array of blog posts [{ title, slug, img?, excerpt?, date?, category?, author?, tags? }] */
57
57
  posts: {
58
58
  type: Array,
59
- required: true,
59
+ default: () => [],
60
60
  },
61
61
  /** Grid columns */
62
62
  columns: {
@@ -96,13 +96,13 @@ const props = defineProps({
96
96
  },
97
97
  });
98
98
 
99
- const search = ref('');
100
- const selectedCategory = ref('');
99
+ const search = ref("");
100
+ const selectedCategory = ref("");
101
101
  const currentPage = ref(1);
102
102
 
103
103
  const categoryOptions = computed(() => {
104
104
  return [
105
- { label: 'All Categories', value: '' },
105
+ { label: "All Categories", value: "" },
106
106
  ...props.categories.map((cat) => ({
107
107
  label: cat,
108
108
  value: cat,
@@ -116,9 +116,10 @@ const filteredPosts = computed(() => {
116
116
  // Filter by search
117
117
  if (search.value) {
118
118
  const searchLower = search.value.toLowerCase();
119
- result = result.filter((post) =>
120
- post.title.toLowerCase().includes(searchLower) ||
121
- post.description?.toLowerCase().includes(searchLower)
119
+ result = result.filter(
120
+ (post) =>
121
+ post.title.toLowerCase().includes(searchLower) ||
122
+ post.description?.toLowerCase().includes(searchLower)
122
123
  );
123
124
  }
124
125
 
@@ -142,9 +143,9 @@ const paginatedPosts = computed(() => {
142
143
 
143
144
  const gridClasses = computed(() => {
144
145
  const cols = {
145
- 1: 'grid gap-8',
146
- 2: 'grid md:grid-cols-2 gap-8',
147
- 3: 'grid md:grid-cols-2 lg:grid-cols-3 gap-8',
146
+ 1: "grid gap-8",
147
+ 2: "grid md:grid-cols-2 gap-8",
148
+ 3: "grid md:grid-cols-2 lg:grid-cols-3 gap-8",
148
149
  };
149
150
  return cols[props.columns];
150
151
  });