@xenterprises/nuxt-x-marketing 1.3.0 → 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 CHANGED
@@ -5,6 +5,36 @@ All notable changes to `@xenterprises/nuxt-x-marketing` are documented in this f
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
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
+
8
38
  ## [1.3.0] - 2026-07-24
9
39
 
10
40
  ### BREAKING
package/README.md CHANGED
@@ -811,186 +811,14 @@ Chat widget trigger.
811
811
 
812
812
  ### Affiliate & Review Components
813
813
 
814
- For affiliate marketing, product review sites, and "best X" roundup posts.
815
-
816
- #### XMarkAffiliateDisclosure
817
-
818
- FTC/Amazon-required affiliate disclosure banner. Three variants for different placements.
819
-
820
- ```vue
821
- <!-- Subtle bar (default) -->
822
- <XMarkAffiliateDisclosure />
823
-
824
- <!-- Full-width banner at top of page -->
825
- <XMarkAffiliateDisclosure variant="banner" :dismissible="true" />
826
-
827
- <!-- Inline inside blog post prose -->
828
- <XMarkAffiliateDisclosure variant="inline" />
829
- ```
830
-
831
- | Prop | Type | Default | Description |
832
- |------|------|---------|-------------|
833
- | `text` | `String` | Amazon associate disclosure | Disclosure text |
834
- | `variant` | `String` | `'subtle'` | `subtle`, `banner`, `inline` |
835
- | `icon` | `String` | `'i-lucide-info'` | Icon name |
836
- | `dismissible` | `Boolean` | `false` | Show dismiss button |
837
- | `storageKey` | `String` | `'affiliate-disclosure-dismissed'` | localStorage key |
838
-
839
- #### XMarkAffiliateProductCard
840
-
841
- Product card with affiliate buy button, star rating, price, pros/cons. Three layout variants.
842
-
843
- ```vue
844
- <XMarkAffiliateProductCard
845
- :product="{
846
- name: 'Sony WH-1000XM5',
847
- image: '/products/sony-wh1000xm5.jpg',
848
- price: '$279.99',
849
- originalPrice: '$349.99',
850
- rating: 4.8,
851
- reviewCount: 12400,
852
- pros: ['Best-in-class ANC', '30hr battery', 'Foldable design'],
853
- cons: ['No IP rating', 'Expensive'],
854
- affiliateUrl: 'https://amazon.com/dp/B09XS7JWHH',
855
- affiliateTag: 'mysite-20',
856
- badge: '#1 Pick',
857
- }"
858
- layout="card"
859
- button-label="Check Price on Amazon"
860
- />
861
- ```
862
-
863
- | Prop | Type | Default | Description |
864
- |------|------|---------|-------------|
865
- | `product` | `Object` | sample product | Product data `{ name, image?, price?, originalPrice?, rating?, reviewCount?, pros?, cons?, affiliateUrl?, affiliateTag?, badge? }` |
866
- | `layout` | `String` | `'card'` | `card` (vertical), `row` (horizontal), `compact` (one-line) |
867
- | `showPros` | `Boolean` | `true` | Show pros list |
868
- | `showCons` | `Boolean` | `false` | Show cons list |
869
- | `showRating` | `Boolean` | `true` | Show star rating |
870
- | `showPrice` | `Boolean` | `true` | Show price |
871
- | `buttonLabel` | `String` | `'Check Price on Amazon'` | CTA button text |
872
- | `buttonIcon` | `String` | `'i-lucide-external-link'` | CTA button icon |
873
- | `disclosure` | `String` | `''` | Small disclosure text below button |
874
-
875
- #### XMarkAffiliateProductGrid
876
-
877
- Grid of product cards for "best X" roundup posts. Automatically injects rank badges.
878
-
879
- ```vue
880
- <XMarkAffiliateProductGrid
881
- :products="topHeadphones"
882
- layout="grid"
883
- :columns="3"
884
- :show-ranking="true"
885
- disclosure="This page contains affiliate links."
886
- button-label="Check Price on Amazon"
887
- />
888
- ```
889
-
890
- | Prop | Type | Default | Description |
891
- |------|------|---------|-------------|
892
- | `products` | `Array` | `[]` | Array of product objects |
893
- | `layout` | `String` | `'grid'` | `grid` or `list` |
894
- | `columns` | `Number` | `3` | Grid columns: `2` or `3` |
895
- | `showRanking` | `Boolean` | `true` | Inject #1/#2/#3 badges on first 3 products |
896
- | `disclosure` | `String` | `''` | Disclosure shown once at top |
897
- | `buttonLabel` | `String` | `'Check Price on Amazon'` | CTA label for all cards |
898
- | `cardLayout` | `String` | `'card'` | Card layout variant |
899
-
900
- #### XMarkAffiliateProductDetail
901
-
902
- Full product review page layout — image gallery, editorial score, pros/cons table, specs, sticky buy box.
903
-
904
- ```vue
905
- <XMarkAffiliateProductDetail
906
- :product="{
907
- name: 'Sony WH-1000XM5 Review',
908
- images: ['/img/sony-1.jpg', '/img/sony-2.jpg'],
909
- price: '$279.99',
910
- originalPrice: '$349.99',
911
- rating: 4.8,
912
- reviewCount: 12400,
913
- pros: ['Best-in-class ANC', '30hr battery'],
914
- cons: ['No IP rating', 'Expensive'],
915
- specs: { 'Driver Size': '30mm', 'Battery': '30 hours', 'Weight': '250g' },
916
- description: 'The WH-1000XM5 sets the bar for noise-cancelling headphones.',
917
- verdict: 'The best ANC headphones money can buy in 2024.',
918
- affiliateUrl: 'https://amazon.com/dp/B09XS7JWHH',
919
- affiliateTag: 'mysite-20',
920
- }"
921
- :score="9.2"
922
- :has-buy-box="true"
923
- :has-specs="true"
924
- :has-pros-cons-table="true"
925
- disclosure="This page contains affiliate links."
926
- >
927
- <template #after-verdict>
928
- <!-- Additional content after verdict -->
929
- </template>
930
- <template #sidebar>
931
- <!-- Additional sidebar widgets -->
932
- </template>
933
- </XMarkAffiliateProductDetail>
934
- ```
935
-
936
- | Prop | Type | Default | Description |
937
- |------|------|---------|-------------|
938
- | `product` | `Object` | sample product | Full product data including `images[]`, `specs{}`, `verdict` |
939
- | `score` | `Number` | `null` | Editorial score 0–10 (shown as circular badge) |
940
- | `hasBuyBox` | `Boolean` | `true` | Show sticky buy box sidebar |
941
- | `hasSpecs` | `Boolean` | `true` | Show specs table |
942
- | `hasProsConsTable` | `Boolean` | `true` | Show pros/cons two-column table |
943
- | `disclosure` | `String` | `''` | Affiliate disclosure text |
944
-
945
- **Slots:** `buy-box`, `after-verdict`, `sidebar`
946
-
947
- #### XMarkAffiliateComparisonTable
948
-
949
- Side-by-side product comparison table for "X vs Y" posts. Highlights best values per row.
950
-
951
- ```vue
952
- <XMarkAffiliateComparisonTable
953
- :products="[
954
- {
955
- name: 'Sony WH-1000XM5',
956
- image: '/img/sony.jpg',
957
- price: '$279.99',
958
- affiliateUrl: 'https://amazon.com/dp/B09XS7JWHH',
959
- affiliateTag: 'mysite-20',
960
- specs: { price: '$279.99', rating: '4.8/5', battery: '30 hours', weight: '250g' },
961
- },
962
- {
963
- name: 'Bose QC45',
964
- image: '/img/bose.jpg',
965
- price: '$249.99',
966
- affiliateUrl: 'https://amazon.com/dp/B098FKXT8L',
967
- affiliateTag: 'mysite-20',
968
- specs: { price: '$249.99', rating: '4.6/5', battery: '24 hours', weight: '238g' },
969
- },
970
- ]"
971
- :specs="[
972
- { label: 'Price', key: 'price' },
973
- { label: 'Rating', key: 'rating' },
974
- { label: 'Battery Life', key: 'battery' },
975
- { label: 'Weight', key: 'weight' },
976
- ]"
977
- :show-buy-buttons="true"
978
- :highlight-best="true"
979
- button-label="Buy on Amazon"
980
- disclosure="This page contains affiliate links."
981
- />
982
- ```
983
-
984
- | Prop | Type | Default | Description |
985
- |------|------|---------|-------------|
986
- | `products` | `Array` | 2 sample products | 2–4 products with `specs` object |
987
- | `specs` | `Array` | price/rating/battery | `[{ label, key }]` spec rows |
988
- | `showBuyButtons` | `Boolean` | `true` | Show buy buttons in last row |
989
- | `highlightBest` | `Boolean` | `true` | Highlight best value per row (lowest price, highest rating) |
990
- | `buttonLabel` | `String` | `'Buy on Amazon'` | Buy button label |
991
- | `disclosure` | `String` | `''` | Disclosure shown above table |
992
-
993
- ---
814
+ Removed in favour of [`@xenterprises/nuxt-x-affiliate`](../nuxt-x-affiliate).
815
+
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.
994
822
 
995
823
  ## Configuration
996
824
 
@@ -1,3 +1,13 @@
1
+ /* NOTE — the `x*` utilities and `--x-*` tokens in this file are duplicated in
2
+ * `nuxt-x-affiliate` (`app/assets/css/main.css`), which was ported from here.
3
+ * 32 class names overlap with identical values. A site extending BOTH layers
4
+ * loads both stylesheets and gets whichever is injected last — harmless while
5
+ * they match, a silent divergence if either is edited alone. Keep them in sync.
6
+ *
7
+ * Only `.xGlow-divider` / `.xGlow-divider-subtle` are unique to this file;
8
+ * the affiliate layer draws that rule with `<XAFDividerGlow>` instead.
9
+ */
10
+
1
11
  @import "tailwindcss";
2
12
  @import "@nuxt/ui";
3
13
  @plugin "@tailwindcss/typography";
@@ -1,7 +1,8 @@
1
1
  <template>
2
2
  <section
3
3
  :class="[
4
- 'relative overflow-hidden rounded-2xl',
4
+ 'relative overflow-hidden',
5
+ rounded ? 'rounded-2xl' : 'rounded-none',
5
6
  paddingClasses,
6
7
  bgColorClasses,
7
8
  ]"
@@ -127,6 +128,11 @@ const props = defineProps({
127
128
  default: "md",
128
129
  validator: (v) => ["sm", "md", "lg"].includes(v),
129
130
  },
131
+ /** Card corners. Set false for full-bleed pre-footer CTAs. */
132
+ rounded: {
133
+ type: Boolean,
134
+ default: true,
135
+ },
130
136
  });
131
137
 
132
138
  // Default bg values
@@ -201,23 +207,30 @@ const containerClasses = computed(() => {
201
207
  });
202
208
 
203
209
  const iconContainerClasses = computed(() => {
210
+ // Keep the badge square: flex parents (inline/default column layout)
211
+ // default to stretch on the cross axis and can squash fixed width/height.
212
+ const centered = ["stacked", "centered"].includes(props.variant);
204
213
  return [
205
- "flex items-center justify-center rounded-xl",
206
- props.size === "lg" ? "w-16 h-16" : "w-12 h-12",
214
+ "flex shrink-0 items-center justify-center rounded-xl aspect-square",
215
+ centered ? "self-center" : "self-start",
216
+ props.size === "lg"
217
+ ? "size-16 min-w-16 min-h-16"
218
+ : "size-12 min-w-12 min-h-12",
207
219
  isBoldBg.value ? "bg-white/20" : "bg-primary-100 dark:bg-primary-900/50",
208
220
  ];
209
221
  });
210
222
 
211
223
  const iconClasses = computed(() => {
212
224
  return [
213
- props.size === "lg" ? "w-8 h-8" : "w-6 h-6",
225
+ "shrink-0 block",
226
+ props.size === "lg" ? "size-8" : "size-6",
214
227
  isBoldBg.value ? "text-white" : "text-primary-500",
215
228
  ];
216
229
  });
217
230
 
218
231
  const contentClasses = computed(() => {
219
- if (props.variant === "default") {
220
- return "flex-1";
232
+ if (props.variant === "default" || props.variant === "inline") {
233
+ return "flex-1 min-w-0";
221
234
  }
222
235
  return "";
223
236
  });
@@ -86,114 +86,6 @@
86
86
  </div>
87
87
  </XMarkSection>
88
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>
197
89
  </div>
198
90
  </template>
199
91
 
@@ -268,103 +160,6 @@ const testimonials = [
268
160
  },
269
161
  ];
270
162
 
271
- // Affiliate sample products
272
- const sampleProducts = [
273
- {
274
- name: "Sony WH-1000XM5",
275
- image:
276
- "https://images.unsplash.com/photo-1505740420928-5e560c06d30e?w=400&q=80",
277
- price: "$279.99",
278
- originalPrice: "$349.99",
279
- rating: 4.8,
280
- reviewCount: 12400,
281
- pros: ["Best-in-class ANC", "30hr battery life", "Foldable design"],
282
- cons: ["No IP rating", "Expensive"],
283
- affiliateUrl: "https://amazon.com",
284
- affiliateTag: "mysite-20",
285
- },
286
- {
287
- name: "Bose QuietComfort 45",
288
- image:
289
- "https://images.unsplash.com/photo-1484704849700-f032a568e944?w=400&q=80",
290
- price: "$249.99",
291
- rating: 4.6,
292
- reviewCount: 8900,
293
- pros: ["Comfortable fit", "Great sound quality", "Easy controls"],
294
- cons: ["No multipoint", "Older design"],
295
- affiliateUrl: "https://amazon.com",
296
- affiliateTag: "mysite-20",
297
- },
298
- {
299
- name: "Apple AirPods Max",
300
- image:
301
- "https://images.unsplash.com/photo-1585386959984-a4155224a1ad?w=400&q=80",
302
- price: "$449.00",
303
- rating: 4.7,
304
- reviewCount: 6200,
305
- pros: ["Premium build quality", "Excellent ANC", "Spatial audio"],
306
- cons: ["Very expensive", "Heavy", "No USB-C charging"],
307
- affiliateUrl: "https://amazon.com",
308
- affiliateTag: "mysite-20",
309
- },
310
- ];
311
-
312
- // Comparison table data
313
- const comparisonProducts = [
314
- {
315
- name: "Sony WH-1000XM5",
316
- image:
317
- "https://images.unsplash.com/photo-1505740420928-5e560c06d30e?w=200&q=80",
318
- price: "$279.99",
319
- affiliateUrl: "https://amazon.com",
320
- affiliateTag: "mysite-20",
321
- specs: {
322
- price: "$279.99",
323
- rating: "4.8/5",
324
- battery: "30 hours",
325
- weight: "250g",
326
- anc: "Yes",
327
- },
328
- },
329
- {
330
- name: "Bose QC45",
331
- image:
332
- "https://images.unsplash.com/photo-1484704849700-f032a568e944?w=200&q=80",
333
- price: "$249.99",
334
- affiliateUrl: "https://amazon.com",
335
- affiliateTag: "mysite-20",
336
- specs: {
337
- price: "$249.99",
338
- rating: "4.6/5",
339
- battery: "24 hours",
340
- weight: "238g",
341
- anc: "Yes",
342
- },
343
- },
344
- {
345
- name: "AirPods Max",
346
- image:
347
- "https://images.unsplash.com/photo-1585386959984-a4155224a1ad?w=200&q=80",
348
- price: "$449.00",
349
- affiliateUrl: "https://amazon.com",
350
- affiliateTag: "mysite-20",
351
- specs: {
352
- price: "$449.00",
353
- rating: "4.7/5",
354
- battery: "20 hours",
355
- weight: "385g",
356
- anc: "Yes",
357
- },
358
- },
359
- ];
360
-
361
- const comparisonSpecs = [
362
- { label: "Price", key: "price" },
363
- { label: "Rating", key: "rating" },
364
- { label: "Battery Life", key: "battery" },
365
- { label: "Weight", key: "weight" },
366
- { label: "ANC", key: "anc" },
367
- ];
368
163
 
369
164
  // Pricing plans data
370
165
  const pricingPlans = [
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@xenterprises/nuxt-x-marketing",
3
3
  "license": "SEE LICENSE IN LICENSE",
4
4
  "type": "module",
5
- "version": "1.3.0",
5
+ "version": "1.4.0",
6
6
  "main": "./nuxt.config.ts",
7
7
  "scripts": {
8
8
  "dev": "nuxi dev .playground",
@@ -23,7 +23,8 @@
23
23
  "test:e2e:headed": "playwright test --config test/playwright.smoke.config.js --headed",
24
24
  "test:e2e:full": "playwright test --config test/playwright.config.js",
25
25
  "test:visual": "playwright test --config test/playwright.config.js visual-regression",
26
- "test:visual:update": "playwright test --config test/playwright.config.js visual-regression --update-snapshots"
26
+ "test:visual:update": "playwright test --config test/playwright.config.js visual-regression --update-snapshots",
27
+ "prepublishOnly": "npm run test:run && npm run typecheck && npm run lint"
27
28
  },
28
29
  "dependencies": {
29
30
  "@marker.io/browser": "^0.20.2",