@xenterprises/nuxt-x-marketing 1.2.2 → 1.3.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 +34 -0
- package/README.md +100 -105
- package/app/app.config.ts +29 -123
- package/app/app.vue +31 -100
- package/app/components/X/Footer/index.vue +1 -1
- package/app/components/X/Mark/Button/BackToTop.vue +2 -2
- 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 -4
- package/app/types/config.ts +134 -0
- package/content.config.ts +35 -0
- package/nuxt.config.ts +18 -5
- package/package.json +31 -10
- package/playwright-report-smoke/index.html +90 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
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.3.0] - 2026-07-24
|
|
9
|
+
|
|
10
|
+
### BREAKING
|
|
11
|
+
|
|
12
|
+
- **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.
|
|
13
|
+
- **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.
|
|
14
|
+
- **`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`.
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- Config-driven default shell with `xMarketing` opt-out switches for header, footer, and consent banner.
|
|
19
|
+
- `xMarketing.blog.active` opt-out on the shipped blog pages.
|
|
20
|
+
- `xMarketing.name`/`url` wired into shell SEO defaults (title template, `og:site_name`, `og:url`).
|
|
21
|
+
- Playground e2e smoke suite (`test:e2e`, plan 17) and lint quality gate.
|
|
22
|
+
- Consumer-minimal quickstart docs and plan-18 batteries-included audit.
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
|
|
26
|
+
- Exported named `XMarketingConfig` interface for typed `app.config.ts` (plan-21 config-typing convention).
|
|
27
|
+
- Peer dependency ranges tightened to caret bounds.
|
|
28
|
+
|
|
29
|
+
### Fixed
|
|
30
|
+
|
|
31
|
+
- Widened `better-sqlite3` peer range to `^11.0.0 || ^12.0.0`.
|
|
32
|
+
- Default home page references real component names; `UToggle` replaced with `USwitch`.
|
|
33
|
+
- Repaired red gates: added `test:run`/`test:coverage` aliases and pinned `vue`.
|
|
34
|
+
- 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
|
```
|
|
@@ -1072,7 +1037,7 @@ npm run test:e2e
|
|
|
1072
1037
|
| `useStaggerReveal(selector, delay?)` | Adds incremental `transition-delay` to `[data-reveal]` children inside a container. |
|
|
1073
1038
|
| `useParallax(options?)` | Parallax scroll effect for `.xParallax[data-parallax-speed]` elements. Uses `requestAnimationFrame` for performance. |
|
|
1074
1039
|
| `useElementParallax(speed?)` | Individual element parallax via template ref. Returns `{ elementRef }`. |
|
|
1075
|
-
| `useXBlog()` |
|
|
1040
|
+
| `useXBlog()` | Nuxt Content blog helpers (`getPosts`, `getPostByPath`, `getSurround`, `normalizeBlogPost`). Expects a `blog` collection and markdown under `content/blog/`. |
|
|
1076
1041
|
|
|
1077
1042
|
All composables are SSR-safe — they guard lifecycle hooks with `getCurrentInstance()` and `import.meta.client` checks.
|
|
1078
1043
|
|
|
@@ -1082,11 +1047,10 @@ All composables are SSR-safe — they guard lifecycle hooks with `getCurrentInst
|
|
|
1082
1047
|
|
|
1083
1048
|
| Name | Required | Description |
|
|
1084
1049
|
|------|----------|-------------|
|
|
1085
|
-
| `NUXT_PUBLIC_API_URL` | No | Base API URL for blog composable (used by `useXBlog`) |
|
|
1086
1050
|
| Marker.io Project ID | No | Set via `appConfig.xMarketing.config.markerProjectId` (not env var) |
|
|
1087
1051
|
| Newsletter Org ID | No | Set via `appConfig.xMarketing.config.emailMarketingNewsletters.organizationId` |
|
|
1088
1052
|
|
|
1089
|
-
The layer itself has no required environment variables. All
|
|
1053
|
+
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
1054
|
|
|
1091
1055
|
---
|
|
1092
1056
|
|
|
@@ -1179,7 +1143,8 @@ The layer provides a complete marketing website toolkit built on Nuxt UI v4:
|
|
|
1179
1143
|
|
|
1180
1144
|
| Path | Purpose |
|
|
1181
1145
|
|------|---------|
|
|
1182
|
-
| `nuxt.config.ts` | Registers `@nuxt/ui`, loads CSS, enables SSR
|
|
1146
|
+
| `nuxt.config.ts` | Registers `@nuxt/ui` + `@nuxt/content`, loads CSS, enables SSR |
|
|
1147
|
+
| `content.config.ts` | Default Nuxt Content `blog` collection schema |
|
|
1183
1148
|
| `app/app.config.ts` | Default xMarketing config + Nuxt UI theme overrides + type augmentation |
|
|
1184
1149
|
| `app/app.vue` | Default app shell with navbar, newsletter, footer (override in consumer app) |
|
|
1185
1150
|
| `app/assets/css/x-marketing.css` | Full design system: colors, typography, animations, effects |
|
|
@@ -1190,7 +1155,36 @@ The layer provides a complete marketing website toolkit built on Nuxt UI v4:
|
|
|
1190
1155
|
| `app/composables/` | Composables: useScrollReveal, useParallax, useXBlog |
|
|
1191
1156
|
| `app/plugins/marketing.client.ts` | Client plugin: auto-initializes scroll/parallax globally |
|
|
1192
1157
|
| `app/types/marketing.d.ts` | TypeScript interfaces for all data structures |
|
|
1193
|
-
| `app/pages/` | Default blog
|
|
1158
|
+
| `app/pages/blog/` | Default blog index + slug detail (Nuxt Content, not Builder.io) |
|
|
1159
|
+
|
|
1160
|
+
### Blog (Nuxt Content)
|
|
1161
|
+
|
|
1162
|
+
**Breaking (Builder.io removed):** default `/blog` pages no longer call the Builder CDN.
|
|
1163
|
+
Posts come from markdown in the consumer app.
|
|
1164
|
+
|
|
1165
|
+
1. Peer-install `@nuxt/content` and `better-sqlite3` (already required by Content v3).
|
|
1166
|
+
2. Ensure the layer is extended so `content.config.ts` defines the `blog` collection (or copy/adapt it).
|
|
1167
|
+
3. Add posts under `content/blog/*.md`:
|
|
1168
|
+
|
|
1169
|
+
```md
|
|
1170
|
+
---
|
|
1171
|
+
title: Getting Started
|
|
1172
|
+
description: First post on the marketing site.
|
|
1173
|
+
date: 2025-01-15
|
|
1174
|
+
author: Tim
|
|
1175
|
+
image: /blog/cover.jpg
|
|
1176
|
+
category: Tutorial
|
|
1177
|
+
tags: [nuxt, marketing]
|
|
1178
|
+
published: true
|
|
1179
|
+
readingTime: 5
|
|
1180
|
+
---
|
|
1181
|
+
|
|
1182
|
+
## Hello
|
|
1183
|
+
|
|
1184
|
+
Markdown body rendered via `ContentRenderer`.
|
|
1185
|
+
```
|
|
1186
|
+
|
|
1187
|
+
4. `useXBlog().getPosts()` / `getPostByPath()` feed `XMarkBlogList` and `XMarkBlogDetail`.
|
|
1194
1188
|
|
|
1195
1189
|
### Overriding in Consumer Apps
|
|
1196
1190
|
|
|
@@ -1198,6 +1192,7 @@ The layer provides a complete marketing website toolkit built on Nuxt UI v4:
|
|
|
1198
1192
|
- **app.config.ts**: Deep-merges with layer defaults. Set `xMarketing.header`, `xMarketing.footer`, etc.
|
|
1199
1193
|
- **app.vue**: Override entirely by creating your own `app.vue` in the consumer app.
|
|
1200
1194
|
- **Pages**: Consumer pages take precedence. Override `/blog` by creating `pages/blog/index.vue`.
|
|
1195
|
+
- **Content**: Own the markdown under `content/blog/`; do not put secrets in frontmatter.
|
|
1201
1196
|
|
|
1202
1197
|
---
|
|
1203
1198
|
|
package/app/app.config.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { XMarketingConfig } from "./types/config";
|
|
2
|
+
|
|
1
3
|
export default defineAppConfig({
|
|
2
4
|
ui: {
|
|
3
5
|
primary: "brand",
|
|
@@ -25,19 +27,24 @@ export default defineAppConfig({
|
|
|
25
27
|
},
|
|
26
28
|
},
|
|
27
29
|
xMarketing: {
|
|
28
|
-
name
|
|
29
|
-
|
|
30
|
+
/** Site name — used by the shipped shell for the SEO title template
|
|
31
|
+
* ("%s | name") and og:site_name. Leave undefined to keep bare titles. */
|
|
32
|
+
name: undefined,
|
|
33
|
+
/** Canonical site URL — used as og:url on the shipped home page. */
|
|
34
|
+
url: undefined,
|
|
30
35
|
config: {
|
|
31
|
-
markerProjectId:
|
|
36
|
+
markerProjectId: undefined,
|
|
32
37
|
emailMarketingNewsletters: {
|
|
33
|
-
organizationId:
|
|
38
|
+
organizationId: undefined,
|
|
34
39
|
},
|
|
35
40
|
},
|
|
36
41
|
header: {
|
|
42
|
+
/** Set false to opt out of the default navbar in the shipped shell. */
|
|
43
|
+
active: true as boolean,
|
|
37
44
|
logo: {
|
|
38
|
-
src:
|
|
39
|
-
srcDark:
|
|
40
|
-
alt:
|
|
45
|
+
src: undefined,
|
|
46
|
+
srcDark: undefined,
|
|
47
|
+
alt: undefined,
|
|
41
48
|
},
|
|
42
49
|
nav: {
|
|
43
50
|
buttons: [],
|
|
@@ -45,23 +52,29 @@ export default defineAppConfig({
|
|
|
45
52
|
},
|
|
46
53
|
},
|
|
47
54
|
footer: {
|
|
55
|
+
/** Set false to opt out of the default footer in the shipped shell. */
|
|
56
|
+
active: true as boolean,
|
|
48
57
|
logo: {
|
|
49
|
-
src:
|
|
50
|
-
alt:
|
|
58
|
+
src: undefined,
|
|
59
|
+
alt: undefined,
|
|
51
60
|
},
|
|
52
61
|
bg: {
|
|
53
|
-
color:
|
|
62
|
+
color: undefined,
|
|
54
63
|
img: {
|
|
55
|
-
src:
|
|
56
|
-
alt:
|
|
64
|
+
src: undefined,
|
|
65
|
+
alt: undefined,
|
|
57
66
|
},
|
|
58
67
|
},
|
|
59
|
-
body:
|
|
68
|
+
body: undefined,
|
|
60
69
|
socials: [],
|
|
61
70
|
columns: [],
|
|
62
71
|
},
|
|
72
|
+
/** Cookie-consent banner in the shipped shell; gates tracking injection. */
|
|
73
|
+
consent: {
|
|
74
|
+
active: true as boolean,
|
|
75
|
+
},
|
|
63
76
|
blog: {
|
|
64
|
-
active: true,
|
|
77
|
+
active: true as boolean,
|
|
65
78
|
title: "Blog",
|
|
66
79
|
description:
|
|
67
80
|
"Welcome to our blog. Here you can find the latest news, updates, and articles.",
|
|
@@ -94,121 +107,14 @@ export default defineAppConfig({
|
|
|
94
107
|
scripts: [],
|
|
95
108
|
// ---- Behavior ----
|
|
96
109
|
/** Auto-inject configured scripts on consent. Default: true. */
|
|
97
|
-
autoInject: true,
|
|
110
|
+
autoInject: true as boolean,
|
|
98
111
|
},
|
|
99
112
|
},
|
|
100
113
|
});
|
|
101
114
|
|
|
102
115
|
declare module "@nuxt/schema" {
|
|
103
116
|
interface AppConfigInput {
|
|
104
|
-
xMarketing?:
|
|
105
|
-
/** Project name */
|
|
106
|
-
name?: string;
|
|
107
|
-
/** Project URL */
|
|
108
|
-
url?: string;
|
|
109
|
-
config?: {
|
|
110
|
-
markerProjectId?: string;
|
|
111
|
-
emailMarketingNewsletters?: {
|
|
112
|
-
organizationId?: string;
|
|
113
|
-
};
|
|
114
|
-
};
|
|
115
|
-
header?: {
|
|
116
|
-
logo?: {
|
|
117
|
-
src?: string;
|
|
118
|
-
srcDark?: string;
|
|
119
|
-
alt?: string;
|
|
120
|
-
};
|
|
121
|
-
nav?: {
|
|
122
|
-
buttons?: {
|
|
123
|
-
label: string;
|
|
124
|
-
to: string;
|
|
125
|
-
icon?: string;
|
|
126
|
-
target?: string;
|
|
127
|
-
variant?: string;
|
|
128
|
-
square?: boolean;
|
|
129
|
-
color?: string;
|
|
130
|
-
}[];
|
|
131
|
-
links?: NavLink[];
|
|
132
|
-
};
|
|
133
|
-
};
|
|
134
|
-
footer?: {
|
|
135
|
-
body?: string;
|
|
136
|
-
bg?: {
|
|
137
|
-
color?: string;
|
|
138
|
-
img?: {
|
|
139
|
-
src?: string;
|
|
140
|
-
alt?: string;
|
|
141
|
-
};
|
|
142
|
-
};
|
|
143
|
-
logo?: {
|
|
144
|
-
src?: string;
|
|
145
|
-
alt?: string;
|
|
146
|
-
};
|
|
147
|
-
socials?: {
|
|
148
|
-
name: string;
|
|
149
|
-
url: string;
|
|
150
|
-
icon: string;
|
|
151
|
-
}[];
|
|
152
|
-
columns?: {
|
|
153
|
-
headerLabel: string;
|
|
154
|
-
links: {
|
|
155
|
-
label: string;
|
|
156
|
-
to: string;
|
|
157
|
-
target?: string;
|
|
158
|
-
}[];
|
|
159
|
-
}[];
|
|
160
|
-
/**
|
|
161
|
-
* Tracking + analytics config. Read by
|
|
162
|
-
* `<XMarkPrivacyCookieConsent>` and `useConsentTracking()`.
|
|
163
|
-
* Only fires after the matching consent category is granted.
|
|
164
|
-
*/
|
|
165
|
-
tracking?: XMarketingTracking;
|
|
166
|
-
};
|
|
117
|
+
xMarketing?: XMarketingConfig;
|
|
167
118
|
}
|
|
168
119
|
}
|
|
169
120
|
|
|
170
|
-
/**
|
|
171
|
-
* Tracking entry — use the convenience IDs for the common tools
|
|
172
|
-
* (GTM, GA4, Clarity) or `scripts[]` for everything else. Scripts
|
|
173
|
-
* are injected only after the visitor grants consent for the
|
|
174
|
-
* matching `category` (default: "analytics").
|
|
175
|
-
*/
|
|
176
|
-
interface XMarketingTracking {
|
|
177
|
-
/** Google Tag Manager container ID, e.g. "GTM-XXXXXXX". */
|
|
178
|
-
gtmId?: string;
|
|
179
|
-
/** Google Analytics 4 measurement ID, e.g. "G-XXXXXXXX". */
|
|
180
|
-
ga4Id?: string;
|
|
181
|
-
/** Microsoft Clarity project ID, e.g. "abc123def4". */
|
|
182
|
-
clarityId?: string;
|
|
183
|
-
/**
|
|
184
|
-
* Arbitrary scripts (Meta Pixel, Hotjar, Segment, LinkedIn Insight,
|
|
185
|
-
* Pinterest, TikTok, etc.). Each fires once the matching consent
|
|
186
|
-
* category is granted. Use `inline` for hand-written snippets like
|
|
187
|
-
* `gtag('js', new Date())`.
|
|
188
|
-
*/
|
|
189
|
-
scripts?: XMarketingTrackingScript[];
|
|
190
|
-
/** Auto-inject configured scripts on consent. Default: true. */
|
|
191
|
-
autoInject?: boolean;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
interface XMarketingTrackingScript {
|
|
195
|
-
/** Stable ID — used to dedupe re-injection. */
|
|
196
|
-
id: string;
|
|
197
|
-
/** External script URL (e.g. "https://www.googletagmanager.com/gtag/js?id=G-XXX"). */
|
|
198
|
-
src?: string;
|
|
199
|
-
/** Inline script body (no `src` required). */
|
|
200
|
-
inline?: string;
|
|
201
|
-
/** Consent category gate. "necessary" fires immediately; "analytics" / "marketing" gated. */
|
|
202
|
-
category?: "necessary" | "analytics" | "marketing";
|
|
203
|
-
/** Extra attributes applied to the injected <script>. */
|
|
204
|
-
attrs?: Record<string, string>;
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
interface NavLink {
|
|
209
|
-
label: string;
|
|
210
|
-
to?: string;
|
|
211
|
-
target?: string;
|
|
212
|
-
children?: NavLink[];
|
|
213
|
-
}
|
|
214
|
-
|