@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.
- package/.claude/settings.local.json +23 -0
- package/.playground/app.vue +12 -6
- package/.playground/package-lock.json +6 -0
- package/.playground/pages/index.vue +2 -2
- package/.playground/pages/layout/x-legal.vue +12 -12
- package/LICENSE +60 -0
- package/README.md +210 -64
- package/app/app.config.ts +2 -72
- 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 +177 -169
- 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/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 +146 -138
- 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/package.json +2 -1
- package/screenshots/playground-home.png +0 -0
|
@@ -1,187 +1,195 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
|
56
|
+
/** Array of blog posts [{ title, slug, img?, excerpt?, date?, category?, author?, tags? }] */
|
|
57
57
|
posts: {
|
|
58
58
|
type: Array,
|
|
59
|
-
|
|
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:
|
|
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(
|
|
120
|
-
post
|
|
121
|
-
|
|
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:
|
|
146
|
-
2:
|
|
147
|
-
3:
|
|
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
|
});
|