@xenterprises/nuxt-x-marketing 1.2.2 → 1.4.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.
- package/CHANGELOG.md +64 -0
- package/README.md +107 -284
- package/app/app.config.ts +29 -123
- package/app/app.vue +31 -100
- package/app/assets/css/x-marketing.css +10 -0
- package/app/components/X/Footer/index.vue +1 -1
- package/app/components/X/Mark/Button/BackToTop.vue +2 -2
- package/app/components/X/Mark/CTA/index.vue +19 -6
- package/app/components/X/Mark/Privacy/CookieConsent.vue +15 -16
- package/app/components/X/Mark/Privacy/GDPR.vue +1 -1
- package/app/composables/useXBlog.js +168 -8
- package/app/pages/blog/[...slug].vue +81 -69
- package/app/pages/blog/index.vue +46 -50
- package/app/pages/index.vue +7 -209
- package/app/types/config.ts +134 -0
- package/content.config.ts +35 -0
- package/nuxt.config.ts +18 -5
- package/package.json +33 -11
- package/playwright-report-smoke/index.html +90 -0
- package/app/components/X/Mark/Affiliate/ComparisonTable.vue +0 -382
- package/app/components/X/Mark/Affiliate/Disclosure.vue +0 -138
- package/app/components/X/Mark/Affiliate/ProductCard.vue +0 -431
- package/app/components/X/Mark/Affiliate/ProductDetail.vue +0 -410
- package/app/components/X/Mark/Affiliate/ProductGrid.vue +0 -101
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `@xenterprises/nuxt-x-marketing` are documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [1.4.0] - 2026-08-02
|
|
9
|
+
|
|
10
|
+
### Removed
|
|
11
|
+
|
|
12
|
+
> **Released as a minor despite removing public components.** Semver would call
|
|
13
|
+
> this a major. Sites on `^1.3.0` will therefore pick this up automatically —
|
|
14
|
+
> if you import any `XMarkAffiliate*` component, pin to `1.3.0` or migrate to
|
|
15
|
+
> the `XAF*` equivalents below before upgrading.
|
|
16
|
+
|
|
17
|
+
- **Removed the `XMarkAffiliate*` component family** — `XMarkAffiliateDisclosure`,
|
|
18
|
+
`XMarkAffiliateProductCard`, `XMarkAffiliateProductGrid`, `XMarkAffiliateProductDetail`,
|
|
19
|
+
and `XMarkAffiliateComparisonTable` (1,462 lines).
|
|
20
|
+
|
|
21
|
+
They duplicated components `@xenterprises/nuxt-x-affiliate` already ships, without its
|
|
22
|
+
per-merchant tagging (`xAffiliateContent.merchants`), impression tracking, or
|
|
23
|
+
Product/Offer structured data — so a site using them shipped untagged outbound links
|
|
24
|
+
and no product schema.
|
|
25
|
+
|
|
26
|
+
Migrate to `<XAFDisclosure>`, `<XAFProductCard>`, `<XAFRelatedProducts>`,
|
|
27
|
+
`<XAFComparisonTable>`, and (for the detail page) `<XAFHero>` + `<XAFProsCons>` +
|
|
28
|
+
`<XAFVerdict>` + `<XAFSpecsTable>`. Affiliate sites should extend `nuxt-x-affiliate`
|
|
29
|
+
directly — it is now self-contained and ships the site chrome (header, hero, sections,
|
|
30
|
+
cards, footer) that previously required this layer alongside it.
|
|
31
|
+
|
|
32
|
+
### Changed
|
|
33
|
+
|
|
34
|
+
- **`app/pages/index.vue`** no longer showcases the removed affiliate components; the
|
|
35
|
+
section and its sample product data were dropped with them. Every other section of the
|
|
36
|
+
demo page is unchanged.
|
|
37
|
+
|
|
38
|
+
## [1.3.0] - 2026-07-24
|
|
39
|
+
|
|
40
|
+
### BREAKING
|
|
41
|
+
|
|
42
|
+
- **Blog migrated from Builder.io to Nuxt Content.** Consumers that relied on `NUXT_PUBLIC_BUILDERIO_KEY` and Builder models must migrate posts to `content/blog/*.md` and install the Content peer dependencies.
|
|
43
|
+
- **Default app shell rewritten to a config-driven shell.** The layer's `app/app.vue` (previously a hardcoded demo referencing non-existent `XMarkNavbar`/`XMarkFooter`, rendering no navbar/footer) now renders `XHeaderNav` + `XFooter` from `xMarketing.header`/`footer` config plus the consent banner. Consumers without their own `app/app.vue` will see the rendered shell change; the hardcoded newsletter section, dead demo links, and `/logo-white.svg`/`/logo-dark.svg` fallbacks are gone.
|
|
44
|
+
- **`xMarketing.name`/`url` defaults changed from placeholders to `undefined`** and both keys are now live: `name` drives the shell title template and `og:site_name` (page titles gain a `| <name>` suffix when set), `url` drives the home page `og:url`. Consumers reading the old placeholder defaults from `appConfig` now get `undefined`.
|
|
45
|
+
|
|
46
|
+
### Added
|
|
47
|
+
|
|
48
|
+
- Config-driven default shell with `xMarketing` opt-out switches for header, footer, and consent banner.
|
|
49
|
+
- `xMarketing.blog.active` opt-out on the shipped blog pages.
|
|
50
|
+
- `xMarketing.name`/`url` wired into shell SEO defaults (title template, `og:site_name`, `og:url`).
|
|
51
|
+
- Playground e2e smoke suite (`test:e2e`, plan 17) and lint quality gate.
|
|
52
|
+
- Consumer-minimal quickstart docs and plan-18 batteries-included audit.
|
|
53
|
+
|
|
54
|
+
### Changed
|
|
55
|
+
|
|
56
|
+
- Exported named `XMarketingConfig` interface for typed `app.config.ts` (plan-21 config-typing convention).
|
|
57
|
+
- Peer dependency ranges tightened to caret bounds.
|
|
58
|
+
|
|
59
|
+
### Fixed
|
|
60
|
+
|
|
61
|
+
- Widened `better-sqlite3` peer range to `^11.0.0 || ^12.0.0`.
|
|
62
|
+
- Default home page references real component names; `UToggle` replaced with `USwitch`.
|
|
63
|
+
- Repaired red gates: added `test:run`/`test:coverage` aliases and pinned `vue`.
|
|
64
|
+
- Test suite: fixed stale hero title assertion and scoped `h1` queries to main content.
|
package/README.md
CHANGED
|
@@ -31,86 +31,56 @@ export default defineNuxtConfig({
|
|
|
31
31
|
|
|
32
32
|
## Quick Start
|
|
33
33
|
|
|
34
|
-
###
|
|
34
|
+
### What the consumer writes
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
The layer is batteries-included: it ships a default app shell (navbar + footer + cookie-consent banner) and default pages (`/`, `/blog`, `/blog/[...slug]`). You do **not** write an `app.vue` — configure the shell in `app/app.config.ts` under the `xMarketing` namespace (it must live in `app/`, not the project root):
|
|
37
37
|
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
logo-alt
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
38
|
+
```javascript
|
|
39
|
+
// app/app.config.ts
|
|
40
|
+
export default defineAppConfig({
|
|
41
|
+
xMarketing: {
|
|
42
|
+
name: "Acme Inc",
|
|
43
|
+
url: "https://acme.com",
|
|
44
|
+
header: {
|
|
45
|
+
logo: { src: "/logo.svg", srcDark: "/logo-dark.svg", alt: "Acme" },
|
|
46
|
+
nav: {
|
|
47
|
+
links: [
|
|
48
|
+
{ label: "Pricing", to: "/#pricing" },
|
|
49
|
+
{ label: "Blog", to: "/blog" },
|
|
50
|
+
],
|
|
51
|
+
buttons: [{ label: "Get Started", to: "/signup", color: "primary" }],
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
footer: {
|
|
55
|
+
logo: { src: "/logo.svg", alt: "Acme" },
|
|
56
|
+
body: "Building the future of modern software.",
|
|
57
|
+
socials: [
|
|
58
|
+
{ name: "GitHub", url: "https://github.com/acme", icon: "i-lucide-github" },
|
|
59
|
+
],
|
|
60
|
+
columns: [
|
|
61
|
+
{
|
|
62
|
+
headerLabel: "Product",
|
|
63
|
+
links: [{ label: "Blog", to: "/blog" }],
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
},
|
|
67
|
+
blog: { active: true, title: "Blog" },
|
|
68
|
+
tracking: { gtmId: "GTM-XXXXXXX" }, // optional; fires only after consent
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
```
|
|
64
72
|
|
|
65
|
-
|
|
66
|
-
:logo="'/logo-dark.svg'"
|
|
67
|
-
description="Building the future of modern software."
|
|
68
|
-
:social="socialLinks"
|
|
69
|
-
:columns="footerColumns"
|
|
70
|
-
:legal-links="legalLinks"
|
|
71
|
-
/>
|
|
72
|
-
</div>
|
|
73
|
-
</template>
|
|
73
|
+
Blog posts are markdown in `content/blog/*.md` — re-declare the `blog` collection in your own `content.config.ts` so Nuxt Content binds it to your content dir (the layer's `.playground/content.config.ts` is the reference).
|
|
74
74
|
|
|
75
|
-
|
|
76
|
-
const navLinks = [
|
|
77
|
-
{ label: "Features", to: "/#features" },
|
|
78
|
-
{ label: "Pricing", to: "/pricing" },
|
|
79
|
-
{ label: "Blog", to: "/blog" },
|
|
80
|
-
];
|
|
81
|
-
|
|
82
|
-
const socialLinks = [
|
|
83
|
-
{ name: "Twitter", href: "https://twitter.com", icon: "i-lucide-twitter" },
|
|
84
|
-
{ name: "GitHub", href: "https://github.com", icon: "i-lucide-github" },
|
|
85
|
-
];
|
|
86
|
-
|
|
87
|
-
const footerColumns = [
|
|
88
|
-
{
|
|
89
|
-
title: "Product",
|
|
90
|
-
links: [
|
|
91
|
-
{ label: "Features", to: "/#features" },
|
|
92
|
-
{ label: "Pricing", to: "/pricing" },
|
|
93
|
-
],
|
|
94
|
-
},
|
|
95
|
-
{
|
|
96
|
-
title: "Company",
|
|
97
|
-
links: [
|
|
98
|
-
{ label: "About", to: "/about" },
|
|
99
|
-
{ label: "Blog", to: "/blog" },
|
|
100
|
-
],
|
|
101
|
-
},
|
|
102
|
-
];
|
|
75
|
+
### Opting out of the defaults
|
|
103
76
|
|
|
104
|
-
|
|
105
|
-
{ label: "Privacy Policy", to: "/privacy" },
|
|
106
|
-
{ label: "Terms of Service", to: "/terms" },
|
|
107
|
-
];
|
|
77
|
+
Every shipped default has a switch or a standard Nuxt override:
|
|
108
78
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
79
|
+
- `xMarketing.header.active: false` — removes the default navbar from the shell
|
|
80
|
+
- `xMarketing.footer.active: false` — removes the default footer
|
|
81
|
+
- `xMarketing.consent.active: false` — removes the cookie-consent banner (tracking scripts then never fire)
|
|
82
|
+
- `xMarketing.blog.active: false` — the shipped `/blog` pages 404
|
|
83
|
+
- Replace the whole shell by shipping your own `app/app.vue`; replace any page by shipping the same path under `app/pages/` (standard Nuxt layer overriding)
|
|
114
84
|
|
|
115
85
|
### Example Landing Page
|
|
116
86
|
|
|
@@ -177,32 +147,28 @@ const pricingPlans = [
|
|
|
177
147
|
|
|
178
148
|
### Core Layout
|
|
179
149
|
|
|
180
|
-
####
|
|
150
|
+
#### XMarkLayoutNavbar
|
|
181
151
|
|
|
182
|
-
Fixed navigation header with transparent-to-solid scroll transition.
|
|
152
|
+
Fixed navigation header with transparent-to-solid scroll transition. Falls back to `xMarketing.header` (logo, nav links, nav buttons) when props are omitted.
|
|
183
153
|
|
|
184
154
|
```vue
|
|
185
|
-
<
|
|
155
|
+
<XMarkLayoutNavbar
|
|
186
156
|
:links="[{ label: 'Features', to: '/#features' }]"
|
|
187
|
-
logo
|
|
188
|
-
|
|
189
|
-
|
|
157
|
+
:logo="{ src: '/logo.svg', srcDark: '/logo-dark.svg', alt: 'Company' }"
|
|
158
|
+
:buttons="[
|
|
159
|
+
{ label: 'Sign In', to: '/login', variant: 'ghost' },
|
|
160
|
+
{ label: 'Get Started', to: '/signup', color: 'primary' },
|
|
161
|
+
]"
|
|
190
162
|
:transparent="true"
|
|
191
163
|
:scroll-threshold="100"
|
|
192
|
-
|
|
193
|
-
<template #actions>
|
|
194
|
-
<UButton variant="ghost">Sign In</UButton>
|
|
195
|
-
<UButton color="primary">Get Started</UButton>
|
|
196
|
-
</template>
|
|
197
|
-
</XMarkNavbar>
|
|
164
|
+
/>
|
|
198
165
|
```
|
|
199
166
|
|
|
200
167
|
| Prop | Type | Default | Description |
|
|
201
168
|
|------|------|---------|-------------|
|
|
202
|
-
| `links` | `Array` | `
|
|
203
|
-
| `
|
|
204
|
-
| `
|
|
205
|
-
| `logoAlt` | `String` | `'Logo'` | Logo alt text |
|
|
169
|
+
| `links` | `Array` | `xMarketing.header.nav.links` | Navigation links `{ label, to }` |
|
|
170
|
+
| `logo` | `Object` | `xMarketing.header.logo` | Logo `{ src, srcDark?, alt? }` |
|
|
171
|
+
| `buttons` | `Array` | `xMarketing.header.nav.buttons` | Action buttons `{ label, to, color?, variant?, icon? }` |
|
|
206
172
|
| `transparent` | `Boolean` | `true` | Start transparent over hero |
|
|
207
173
|
| `scrollThreshold` | `Number` | `100` | Pixels before transition |
|
|
208
174
|
|
|
@@ -226,40 +192,39 @@ Section wrapper with background variants and optional patterns.
|
|
|
226
192
|
| `bgImage` | `String` | `''` | Background image URL |
|
|
227
193
|
| `parallax` | `Boolean` | `false` | Enable parallax on bg image |
|
|
228
194
|
|
|
229
|
-
####
|
|
195
|
+
#### XMarkLayoutFooter
|
|
230
196
|
|
|
231
197
|
Full footer with brand, link columns, social icons, and newsletter.
|
|
232
198
|
|
|
233
199
|
```vue
|
|
234
|
-
<
|
|
235
|
-
logo="/logo.svg"
|
|
236
|
-
logo-alt="Company"
|
|
200
|
+
<XMarkLayoutFooter
|
|
201
|
+
:logo="{ src: '/logo.svg', alt: 'Company' }"
|
|
237
202
|
description="Building the future of modern software."
|
|
238
203
|
:social="[{ name: 'Twitter', href: 'https://twitter.com', icon: 'i-lucide-twitter' }]"
|
|
239
204
|
:columns="[{ title: 'Product', links: [{ label: 'Features', to: '/features' }] }]"
|
|
240
205
|
:legal-links="[{ label: 'Privacy', to: '/privacy' }]"
|
|
241
|
-
:
|
|
206
|
+
:has-newsletter="true"
|
|
242
207
|
@newsletter-submit="handleSubmit"
|
|
243
208
|
/>
|
|
244
209
|
```
|
|
245
210
|
|
|
246
211
|
| Prop | Type | Default | Description |
|
|
247
212
|
|------|------|---------|-------------|
|
|
248
|
-
| `logo` | `
|
|
213
|
+
| `logo` | `Object` | `null` | Logo `{ src, alt? }` |
|
|
249
214
|
| `description` | `String` | `''` | Brand description |
|
|
250
215
|
| `social` | `Array` | `[]` | Social links `{ name, href, icon }` |
|
|
251
216
|
| `columns` | `Array` | `[]` | Link columns `{ title, links: [{ label, to }] }` |
|
|
252
217
|
| `legalLinks` | `Array` | `[]` | Legal links `{ label, to }` |
|
|
253
218
|
| `copyright` | `String` | auto | Copyright text |
|
|
254
|
-
| `
|
|
219
|
+
| `hasNewsletter` | `Boolean` | `false` | Show newsletter section |
|
|
255
220
|
|
|
256
|
-
####
|
|
221
|
+
#### XMarkLayoutFooterLegal
|
|
257
222
|
|
|
258
223
|
Minimal footer with copyright and legal links only.
|
|
259
224
|
|
|
260
225
|
```vue
|
|
261
|
-
<
|
|
262
|
-
|
|
226
|
+
<XMarkLayoutFooterLegal
|
|
227
|
+
copyright="© 2026 Acme Inc. All rights reserved."
|
|
263
228
|
:links="[{ label: 'Privacy', to: '/privacy' }]"
|
|
264
229
|
/>
|
|
265
230
|
```
|
|
@@ -846,186 +811,14 @@ Chat widget trigger.
|
|
|
846
811
|
|
|
847
812
|
### Affiliate & Review Components
|
|
848
813
|
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
#### XMarkAffiliateDisclosure
|
|
852
|
-
|
|
853
|
-
FTC/Amazon-required affiliate disclosure banner. Three variants for different placements.
|
|
854
|
-
|
|
855
|
-
```vue
|
|
856
|
-
<!-- Subtle bar (default) -->
|
|
857
|
-
<XMarkAffiliateDisclosure />
|
|
858
|
-
|
|
859
|
-
<!-- Full-width banner at top of page -->
|
|
860
|
-
<XMarkAffiliateDisclosure variant="banner" :dismissible="true" />
|
|
861
|
-
|
|
862
|
-
<!-- Inline inside blog post prose -->
|
|
863
|
-
<XMarkAffiliateDisclosure variant="inline" />
|
|
864
|
-
```
|
|
865
|
-
|
|
866
|
-
| Prop | Type | Default | Description |
|
|
867
|
-
|------|------|---------|-------------|
|
|
868
|
-
| `text` | `String` | Amazon associate disclosure | Disclosure text |
|
|
869
|
-
| `variant` | `String` | `'subtle'` | `subtle`, `banner`, `inline` |
|
|
870
|
-
| `icon` | `String` | `'i-lucide-info'` | Icon name |
|
|
871
|
-
| `dismissible` | `Boolean` | `false` | Show dismiss button |
|
|
872
|
-
| `storageKey` | `String` | `'affiliate-disclosure-dismissed'` | localStorage key |
|
|
873
|
-
|
|
874
|
-
#### XMarkAffiliateProductCard
|
|
875
|
-
|
|
876
|
-
Product card with affiliate buy button, star rating, price, pros/cons. Three layout variants.
|
|
877
|
-
|
|
878
|
-
```vue
|
|
879
|
-
<XMarkAffiliateProductCard
|
|
880
|
-
:product="{
|
|
881
|
-
name: 'Sony WH-1000XM5',
|
|
882
|
-
image: '/products/sony-wh1000xm5.jpg',
|
|
883
|
-
price: '$279.99',
|
|
884
|
-
originalPrice: '$349.99',
|
|
885
|
-
rating: 4.8,
|
|
886
|
-
reviewCount: 12400,
|
|
887
|
-
pros: ['Best-in-class ANC', '30hr battery', 'Foldable design'],
|
|
888
|
-
cons: ['No IP rating', 'Expensive'],
|
|
889
|
-
affiliateUrl: 'https://amazon.com/dp/B09XS7JWHH',
|
|
890
|
-
affiliateTag: 'mysite-20',
|
|
891
|
-
badge: '#1 Pick',
|
|
892
|
-
}"
|
|
893
|
-
layout="card"
|
|
894
|
-
button-label="Check Price on Amazon"
|
|
895
|
-
/>
|
|
896
|
-
```
|
|
897
|
-
|
|
898
|
-
| Prop | Type | Default | Description |
|
|
899
|
-
|------|------|---------|-------------|
|
|
900
|
-
| `product` | `Object` | sample product | Product data `{ name, image?, price?, originalPrice?, rating?, reviewCount?, pros?, cons?, affiliateUrl?, affiliateTag?, badge? }` |
|
|
901
|
-
| `layout` | `String` | `'card'` | `card` (vertical), `row` (horizontal), `compact` (one-line) |
|
|
902
|
-
| `showPros` | `Boolean` | `true` | Show pros list |
|
|
903
|
-
| `showCons` | `Boolean` | `false` | Show cons list |
|
|
904
|
-
| `showRating` | `Boolean` | `true` | Show star rating |
|
|
905
|
-
| `showPrice` | `Boolean` | `true` | Show price |
|
|
906
|
-
| `buttonLabel` | `String` | `'Check Price on Amazon'` | CTA button text |
|
|
907
|
-
| `buttonIcon` | `String` | `'i-lucide-external-link'` | CTA button icon |
|
|
908
|
-
| `disclosure` | `String` | `''` | Small disclosure text below button |
|
|
909
|
-
|
|
910
|
-
#### XMarkAffiliateProductGrid
|
|
814
|
+
Removed in favour of [`@xenterprises/nuxt-x-affiliate`](../nuxt-x-affiliate).
|
|
911
815
|
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
:columns="3"
|
|
919
|
-
:show-ranking="true"
|
|
920
|
-
disclosure="This page contains affiliate links."
|
|
921
|
-
button-label="Check Price on Amazon"
|
|
922
|
-
/>
|
|
923
|
-
```
|
|
924
|
-
|
|
925
|
-
| Prop | Type | Default | Description |
|
|
926
|
-
|------|------|---------|-------------|
|
|
927
|
-
| `products` | `Array` | `[]` | Array of product objects |
|
|
928
|
-
| `layout` | `String` | `'grid'` | `grid` or `list` |
|
|
929
|
-
| `columns` | `Number` | `3` | Grid columns: `2` or `3` |
|
|
930
|
-
| `showRanking` | `Boolean` | `true` | Inject #1/#2/#3 badges on first 3 products |
|
|
931
|
-
| `disclosure` | `String` | `''` | Disclosure shown once at top |
|
|
932
|
-
| `buttonLabel` | `String` | `'Check Price on Amazon'` | CTA label for all cards |
|
|
933
|
-
| `cardLayout` | `String` | `'card'` | Card layout variant |
|
|
934
|
-
|
|
935
|
-
#### XMarkAffiliateProductDetail
|
|
936
|
-
|
|
937
|
-
Full product review page layout — image gallery, editorial score, pros/cons table, specs, sticky buy box.
|
|
938
|
-
|
|
939
|
-
```vue
|
|
940
|
-
<XMarkAffiliateProductDetail
|
|
941
|
-
:product="{
|
|
942
|
-
name: 'Sony WH-1000XM5 Review',
|
|
943
|
-
images: ['/img/sony-1.jpg', '/img/sony-2.jpg'],
|
|
944
|
-
price: '$279.99',
|
|
945
|
-
originalPrice: '$349.99',
|
|
946
|
-
rating: 4.8,
|
|
947
|
-
reviewCount: 12400,
|
|
948
|
-
pros: ['Best-in-class ANC', '30hr battery'],
|
|
949
|
-
cons: ['No IP rating', 'Expensive'],
|
|
950
|
-
specs: { 'Driver Size': '30mm', 'Battery': '30 hours', 'Weight': '250g' },
|
|
951
|
-
description: 'The WH-1000XM5 sets the bar for noise-cancelling headphones.',
|
|
952
|
-
verdict: 'The best ANC headphones money can buy in 2024.',
|
|
953
|
-
affiliateUrl: 'https://amazon.com/dp/B09XS7JWHH',
|
|
954
|
-
affiliateTag: 'mysite-20',
|
|
955
|
-
}"
|
|
956
|
-
:score="9.2"
|
|
957
|
-
:has-buy-box="true"
|
|
958
|
-
:has-specs="true"
|
|
959
|
-
:has-pros-cons-table="true"
|
|
960
|
-
disclosure="This page contains affiliate links."
|
|
961
|
-
>
|
|
962
|
-
<template #after-verdict>
|
|
963
|
-
<!-- Additional content after verdict -->
|
|
964
|
-
</template>
|
|
965
|
-
<template #sidebar>
|
|
966
|
-
<!-- Additional sidebar widgets -->
|
|
967
|
-
</template>
|
|
968
|
-
</XMarkAffiliateProductDetail>
|
|
969
|
-
```
|
|
970
|
-
|
|
971
|
-
| Prop | Type | Default | Description |
|
|
972
|
-
|------|------|---------|-------------|
|
|
973
|
-
| `product` | `Object` | sample product | Full product data including `images[]`, `specs{}`, `verdict` |
|
|
974
|
-
| `score` | `Number` | `null` | Editorial score 0–10 (shown as circular badge) |
|
|
975
|
-
| `hasBuyBox` | `Boolean` | `true` | Show sticky buy box sidebar |
|
|
976
|
-
| `hasSpecs` | `Boolean` | `true` | Show specs table |
|
|
977
|
-
| `hasProsConsTable` | `Boolean` | `true` | Show pros/cons two-column table |
|
|
978
|
-
| `disclosure` | `String` | `''` | Affiliate disclosure text |
|
|
979
|
-
|
|
980
|
-
**Slots:** `buy-box`, `after-verdict`, `sidebar`
|
|
981
|
-
|
|
982
|
-
#### XMarkAffiliateComparisonTable
|
|
983
|
-
|
|
984
|
-
Side-by-side product comparison table for "X vs Y" posts. Highlights best values per row.
|
|
985
|
-
|
|
986
|
-
```vue
|
|
987
|
-
<XMarkAffiliateComparisonTable
|
|
988
|
-
:products="[
|
|
989
|
-
{
|
|
990
|
-
name: 'Sony WH-1000XM5',
|
|
991
|
-
image: '/img/sony.jpg',
|
|
992
|
-
price: '$279.99',
|
|
993
|
-
affiliateUrl: 'https://amazon.com/dp/B09XS7JWHH',
|
|
994
|
-
affiliateTag: 'mysite-20',
|
|
995
|
-
specs: { price: '$279.99', rating: '4.8/5', battery: '30 hours', weight: '250g' },
|
|
996
|
-
},
|
|
997
|
-
{
|
|
998
|
-
name: 'Bose QC45',
|
|
999
|
-
image: '/img/bose.jpg',
|
|
1000
|
-
price: '$249.99',
|
|
1001
|
-
affiliateUrl: 'https://amazon.com/dp/B098FKXT8L',
|
|
1002
|
-
affiliateTag: 'mysite-20',
|
|
1003
|
-
specs: { price: '$249.99', rating: '4.6/5', battery: '24 hours', weight: '238g' },
|
|
1004
|
-
},
|
|
1005
|
-
]"
|
|
1006
|
-
:specs="[
|
|
1007
|
-
{ label: 'Price', key: 'price' },
|
|
1008
|
-
{ label: 'Rating', key: 'rating' },
|
|
1009
|
-
{ label: 'Battery Life', key: 'battery' },
|
|
1010
|
-
{ label: 'Weight', key: 'weight' },
|
|
1011
|
-
]"
|
|
1012
|
-
:show-buy-buttons="true"
|
|
1013
|
-
:highlight-best="true"
|
|
1014
|
-
button-label="Buy on Amazon"
|
|
1015
|
-
disclosure="This page contains affiliate links."
|
|
1016
|
-
/>
|
|
1017
|
-
```
|
|
1018
|
-
|
|
1019
|
-
| Prop | Type | Default | Description |
|
|
1020
|
-
|------|------|---------|-------------|
|
|
1021
|
-
| `products` | `Array` | 2 sample products | 2–4 products with `specs` object |
|
|
1022
|
-
| `specs` | `Array` | price/rating/battery | `[{ label, key }]` spec rows |
|
|
1023
|
-
| `showBuyButtons` | `Boolean` | `true` | Show buy buttons in last row |
|
|
1024
|
-
| `highlightBest` | `Boolean` | `true` | Highlight best value per row (lowest price, highest rating) |
|
|
1025
|
-
| `buttonLabel` | `String` | `'Buy on Amazon'` | Buy button label |
|
|
1026
|
-
| `disclosure` | `String` | `''` | Disclosure shown above table |
|
|
1027
|
-
|
|
1028
|
-
---
|
|
816
|
+
`XMarkAffiliateDisclosure`, `XMarkAffiliateProductCard`, `XMarkAffiliateProductGrid`,
|
|
817
|
+
`XMarkAffiliateProductDetail`, and `XMarkAffiliateComparisonTable` duplicated components
|
|
818
|
+
the affiliate layer already ships — without its per-merchant tagging
|
|
819
|
+
(`xAffiliateContent.merchants`), impression tracking, or Product/Offer structured data.
|
|
820
|
+
Use `<XAFDisclosure>`, `<XAFProductCard>`, `<XAFRelatedProducts>`, `<XAFBuyButton>`,
|
|
821
|
+
and `<XAFComparisonTable>` instead.
|
|
1029
822
|
|
|
1030
823
|
## Configuration
|
|
1031
824
|
|
|
@@ -1072,7 +865,7 @@ npm run test:e2e
|
|
|
1072
865
|
| `useStaggerReveal(selector, delay?)` | Adds incremental `transition-delay` to `[data-reveal]` children inside a container. |
|
|
1073
866
|
| `useParallax(options?)` | Parallax scroll effect for `.xParallax[data-parallax-speed]` elements. Uses `requestAnimationFrame` for performance. |
|
|
1074
867
|
| `useElementParallax(speed?)` | Individual element parallax via template ref. Returns `{ elementRef }`. |
|
|
1075
|
-
| `useXBlog()` |
|
|
868
|
+
| `useXBlog()` | Nuxt Content blog helpers (`getPosts`, `getPostByPath`, `getSurround`, `normalizeBlogPost`). Expects a `blog` collection and markdown under `content/blog/`. |
|
|
1076
869
|
|
|
1077
870
|
All composables are SSR-safe — they guard lifecycle hooks with `getCurrentInstance()` and `import.meta.client` checks.
|
|
1078
871
|
|
|
@@ -1082,11 +875,10 @@ All composables are SSR-safe — they guard lifecycle hooks with `getCurrentInst
|
|
|
1082
875
|
|
|
1083
876
|
| Name | Required | Description |
|
|
1084
877
|
|------|----------|-------------|
|
|
1085
|
-
| `NUXT_PUBLIC_API_URL` | No | Base API URL for blog composable (used by `useXBlog`) |
|
|
1086
878
|
| Marker.io Project ID | No | Set via `appConfig.xMarketing.config.markerProjectId` (not env var) |
|
|
1087
879
|
| Newsletter Org ID | No | Set via `appConfig.xMarketing.config.emailMarketingNewsletters.organizationId` |
|
|
1088
880
|
|
|
1089
|
-
The layer itself has no required environment variables. All
|
|
881
|
+
The layer itself has no required environment variables. Blog content is **file-based via Nuxt Content** (not Builder.io). All site config is done via `app.config.ts`.
|
|
1090
882
|
|
|
1091
883
|
---
|
|
1092
884
|
|
|
@@ -1179,7 +971,8 @@ The layer provides a complete marketing website toolkit built on Nuxt UI v4:
|
|
|
1179
971
|
|
|
1180
972
|
| Path | Purpose |
|
|
1181
973
|
|------|---------|
|
|
1182
|
-
| `nuxt.config.ts` | Registers `@nuxt/ui`, loads CSS, enables SSR
|
|
974
|
+
| `nuxt.config.ts` | Registers `@nuxt/ui` + `@nuxt/content`, loads CSS, enables SSR |
|
|
975
|
+
| `content.config.ts` | Default Nuxt Content `blog` collection schema |
|
|
1183
976
|
| `app/app.config.ts` | Default xMarketing config + Nuxt UI theme overrides + type augmentation |
|
|
1184
977
|
| `app/app.vue` | Default app shell with navbar, newsletter, footer (override in consumer app) |
|
|
1185
978
|
| `app/assets/css/x-marketing.css` | Full design system: colors, typography, animations, effects |
|
|
@@ -1190,7 +983,36 @@ The layer provides a complete marketing website toolkit built on Nuxt UI v4:
|
|
|
1190
983
|
| `app/composables/` | Composables: useScrollReveal, useParallax, useXBlog |
|
|
1191
984
|
| `app/plugins/marketing.client.ts` | Client plugin: auto-initializes scroll/parallax globally |
|
|
1192
985
|
| `app/types/marketing.d.ts` | TypeScript interfaces for all data structures |
|
|
1193
|
-
| `app/pages/` | Default blog
|
|
986
|
+
| `app/pages/blog/` | Default blog index + slug detail (Nuxt Content, not Builder.io) |
|
|
987
|
+
|
|
988
|
+
### Blog (Nuxt Content)
|
|
989
|
+
|
|
990
|
+
**Breaking (Builder.io removed):** default `/blog` pages no longer call the Builder CDN.
|
|
991
|
+
Posts come from markdown in the consumer app.
|
|
992
|
+
|
|
993
|
+
1. Peer-install `@nuxt/content` and `better-sqlite3` (already required by Content v3).
|
|
994
|
+
2. Ensure the layer is extended so `content.config.ts` defines the `blog` collection (or copy/adapt it).
|
|
995
|
+
3. Add posts under `content/blog/*.md`:
|
|
996
|
+
|
|
997
|
+
```md
|
|
998
|
+
---
|
|
999
|
+
title: Getting Started
|
|
1000
|
+
description: First post on the marketing site.
|
|
1001
|
+
date: 2025-01-15
|
|
1002
|
+
author: Tim
|
|
1003
|
+
image: /blog/cover.jpg
|
|
1004
|
+
category: Tutorial
|
|
1005
|
+
tags: [nuxt, marketing]
|
|
1006
|
+
published: true
|
|
1007
|
+
readingTime: 5
|
|
1008
|
+
---
|
|
1009
|
+
|
|
1010
|
+
## Hello
|
|
1011
|
+
|
|
1012
|
+
Markdown body rendered via `ContentRenderer`.
|
|
1013
|
+
```
|
|
1014
|
+
|
|
1015
|
+
4. `useXBlog().getPosts()` / `getPostByPath()` feed `XMarkBlogList` and `XMarkBlogDetail`.
|
|
1194
1016
|
|
|
1195
1017
|
### Overriding in Consumer Apps
|
|
1196
1018
|
|
|
@@ -1198,6 +1020,7 @@ The layer provides a complete marketing website toolkit built on Nuxt UI v4:
|
|
|
1198
1020
|
- **app.config.ts**: Deep-merges with layer defaults. Set `xMarketing.header`, `xMarketing.footer`, etc.
|
|
1199
1021
|
- **app.vue**: Override entirely by creating your own `app.vue` in the consumer app.
|
|
1200
1022
|
- **Pages**: Consumer pages take precedence. Override `/blog` by creating `pages/blog/index.vue`.
|
|
1023
|
+
- **Content**: Own the markdown under `content/blog/`; do not put secrets in frontmatter.
|
|
1201
1024
|
|
|
1202
1025
|
---
|
|
1203
1026
|
|