srcdev-nuxt-components 9.1.28 → 9.1.30

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 (82) hide show
  1. package/.claude/settings.json +7 -2
  2. package/.claude/settings.local.json +2 -1
  3. package/.claude/skills/components/banner-video.md +178 -0
  4. package/.claude/skills/components/grid-stack.md +133 -0
  5. package/.claude/skills/components/scroll-reveal-frame.md +136 -0
  6. package/.claude/skills/components/scroll-reveal-image.md +161 -0
  7. package/.claude/skills/index.md +9 -1
  8. package/.claude/skills/new-app-scaffold.md +301 -0
  9. package/.claude/skills/setup-postinstall.md +45 -0
  10. package/.claude/skills/theming-override-default.md +6 -0
  11. package/.claude/skills/theming-partial-override.md +131 -0
  12. package/.claude/skills/vue-video-autoplay.md +74 -0
  13. package/README.md +30 -5
  14. package/app/components/01.atoms/banner-video/BannerVideo.vue +207 -0
  15. package/app/components/01.atoms/banner-video/stories/BannerVideo.stories.ts +180 -0
  16. package/app/components/01.atoms/banner-video/tests/BannerVideo.spec.ts +350 -0
  17. package/app/components/01.atoms/banner-video/tests/__snapshots__/BannerVideo.spec.ts.snap +13 -0
  18. package/app/components/01.atoms/grid-stack/GridStack.vue +34 -0
  19. package/app/components/01.atoms/grid-stack/stories/GridStack.stories.ts +162 -0
  20. package/app/components/01.atoms/grid-stack/tests/GridStack.spec.ts +66 -0
  21. package/app/components/01.atoms/scroll-reveal-frame/ScrollRevealFrame.vue +125 -0
  22. package/app/components/01.atoms/scroll-reveal-frame/stories/ScrollRevealFrame.stories.ts +166 -0
  23. package/app/components/01.atoms/scroll-reveal-frame/tests/ScrollRevealFrame.spec.ts +148 -0
  24. package/app/components/01.atoms/scroll-reveal-frame/tests/__snapshots__/ScrollRevealFrame.spec.ts.snap +13 -0
  25. package/app/components/01.atoms/scroll-reveal-image/ScrollRevealImage.vue +96 -0
  26. package/app/components/01.atoms/scroll-reveal-image/stories/ScrollRevealImage.stories.ts +181 -0
  27. package/app/components/01.atoms/scroll-reveal-image/tests/ScrollRevealImage.spec.ts +235 -0
  28. package/app/components/01.atoms/scroll-reveal-image/tests/__snapshots__/ScrollRevealImage.spec.ts.snap +13 -0
  29. package/app/components/03.organisms/services/services-card/stories/ServicesCard.stories.ts +1 -0
  30. package/app/components/03.organisms/services/services-card/tests/ServicesCard.spec.ts +1 -0
  31. package/app/components/03.organisms/services/services-grids/stories/ServicesCardGrid.stories.ts +1 -0
  32. package/app/components/03.organisms/services/services-grids/stories/ServicesSectionGrid.stories.ts +1 -0
  33. package/app/components/03.organisms/services/services-grids/tests/ServicesCardGrid.spec.ts +1 -0
  34. package/app/components/03.organisms/services/services-grids/tests/ServicesSectionGrid.spec.ts +1 -0
  35. package/app/components/03.organisms/services/services-grids/tests/__snapshots__/ServicesSectionGrid.spec.ts.snap +3 -3
  36. package/app/components/03.organisms/services/services-section/stories/ServicesSection.stories.ts +1 -0
  37. package/app/components/03.organisms/services/services-section/tests/ServicesSection.spec.ts +1 -0
  38. package/app/components/03.organisms/services/services-section/tests/__snapshots__/ServicesSection.spec.ts.snap +1 -1
  39. package/app/layouts/default.vue +2 -0
  40. package/app/pages/banner-video.vue +238 -0
  41. package/app/pages/grid-stack.vue +252 -0
  42. package/app/pages/index.vue +5 -0
  43. package/app/pages/ui/scroll-reveal-image.vue +483 -0
  44. package/app/types/types.services.ts +1 -0
  45. package/package.json +1 -1
  46. package/app/assets/styles/extends-layer/srcdev-components/components/_display-prompt-core.css +0 -94
  47. package/app/assets/styles/extends-layer/srcdev-components/components/_display-toast.css +0 -5
  48. package/app/assets/styles/extends-layer/srcdev-components/components/_expanding-panel.css +0 -37
  49. package/app/assets/styles/extends-layer/srcdev-components/components/index.css +0 -3
  50. package/app/assets/styles/extends-layer/srcdev-components/index.css +0 -1
  51. package/app/assets/styles/extends-layer/srcdev-forms/components/_form-fieldset.css +0 -38
  52. package/app/assets/styles/extends-layer/srcdev-forms/components/_input-button.css +0 -67
  53. package/app/assets/styles/extends-layer/srcdev-forms/components/_input-checkbox-radio-core.css +0 -87
  54. package/app/assets/styles/extends-layer/srcdev-forms/components/_input-checkbox-radio-options-button.css +0 -74
  55. package/app/assets/styles/extends-layer/srcdev-forms/components/_input-checkbox-radio-with-label.css +0 -14
  56. package/app/assets/styles/extends-layer/srcdev-forms/components/_input-description.css +0 -13
  57. package/app/assets/styles/extends-layer/srcdev-forms/components/_input-error.css +0 -54
  58. package/app/assets/styles/extends-layer/srcdev-forms/components/_input-label.css +0 -21
  59. package/app/assets/styles/extends-layer/srcdev-forms/components/_input-select.css +0 -143
  60. package/app/assets/styles/extends-layer/srcdev-forms/components/_input-text.css +0 -190
  61. package/app/assets/styles/extends-layer/srcdev-forms/components/_input-textarea.css +0 -96
  62. package/app/assets/styles/extends-layer/srcdev-forms/components/_input-toggle-switch.css +0 -68
  63. package/app/assets/styles/extends-layer/srcdev-forms/components/index.css +0 -12
  64. package/app/assets/styles/extends-layer/srcdev-forms/index.css +0 -2
  65. package/app/assets/styles/extends-layer/srcdev-forms/setup/_generic.css +0 -21
  66. package/app/assets/styles/extends-layer/srcdev-forms/setup/index.css +0 -3
  67. package/app/assets/styles/extends-layer/srcdev-forms/setup/sizes/_default.css +0 -13
  68. package/app/assets/styles/extends-layer/srcdev-forms/setup/sizes/_large.css +0 -12
  69. package/app/assets/styles/extends-layer/srcdev-forms/setup/sizes/_medium.css +0 -12
  70. package/app/assets/styles/extends-layer/srcdev-forms/setup/sizes/_small.css +0 -12
  71. package/app/assets/styles/extends-layer/srcdev-forms/setup/sizes/_x-small.css +0 -11
  72. package/app/assets/styles/extends-layer/srcdev-forms/setup/sizes/index.css +0 -23
  73. package/app/assets/styles/extends-layer/srcdev-forms/setup/themes/_default.css +0 -62
  74. package/app/assets/styles/extends-layer/srcdev-forms/setup/themes/_error.css +0 -63
  75. package/app/assets/styles/extends-layer/srcdev-forms/setup/themes/_ghost.css +0 -63
  76. package/app/assets/styles/extends-layer/srcdev-forms/setup/themes/_info.css +0 -63
  77. package/app/assets/styles/extends-layer/srcdev-forms/setup/themes/_primary.css +0 -66
  78. package/app/assets/styles/extends-layer/srcdev-forms/setup/themes/_secondary.css +0 -64
  79. package/app/assets/styles/extends-layer/srcdev-forms/setup/themes/_success.css +0 -63
  80. package/app/assets/styles/extends-layer/srcdev-forms/setup/themes/_tertiary.css +0 -63
  81. package/app/assets/styles/extends-layer/srcdev-forms/setup/themes/_warning.css +0 -63
  82. package/app/assets/styles/extends-layer/srcdev-forms/setup/themes/index.css +0 -7
@@ -0,0 +1,301 @@
1
+ # New App Scaffold
2
+
3
+ ## Overview
4
+
5
+ Scaffold a new Nuxt app that extends `srcdev-nuxt-components` as a layer. Use this skill when
6
+ a user asks to initialise or set up a new consumer app from scratch.
7
+
8
+ ## Prerequisites
9
+
10
+ - An empty (or near-empty) git repo exists at the target path
11
+ - The user provides: repo path, package name, production domain, fonts, and whether to include
12
+ `nuxt-security` + `@nuxtjs/robots`
13
+
14
+ ## Steps
15
+
16
+ ### 1. Gather details
17
+
18
+ Ask for (or confirm from context):
19
+
20
+ | Detail | Example |
21
+ |---|---|
22
+ | Repo path | `/Users/name/websites/my-app` |
23
+ | Package name | `my-app` (used in `package.json` and `bodyAttrs.class`) |
24
+ | Production domain | `myapp.co.uk` |
25
+ | Fonts | `Fraunces, Manrope` — assume bunny CDN unless stated otherwise |
26
+ | Include security modules? | yes / no (nuxt-security + @nuxtjs/robots) |
27
+
28
+ Check for any existing files (`.claude/`, `README.md`, design docs) before writing — read them
29
+ to inform the CLAUDE.md and CSS tokens.
30
+
31
+ ### 2. Create `package.json`
32
+
33
+ ```json
34
+ {
35
+ "name": "{name}",
36
+ "private": true,
37
+ "type": "module",
38
+ "scripts": {
39
+ "clean": "rm -rf .nuxt && rm -rf .output && rm -rf node_modules && rm ./package-lock.json",
40
+ "cleanupandprepare": "npx nuxi cleanup && npx nuxi prepare",
41
+ "reinstall": "npm install",
42
+ "cleaninstall": "npm run clean && npm run reinstall",
43
+ "build": "nuxt build",
44
+ "dev": "nuxt dev",
45
+ "generate": "nuxt generate",
46
+ "preview": "nuxt preview",
47
+ "setup:claude": "mkdir -p .claude/skills/srcdev-nuxt-components && cp -r node_modules/srcdev-nuxt-components/.claude/skills/. .claude/skills/srcdev-nuxt-components",
48
+ "postinstall": "NUXT_STANDALONE=true nuxt prepare && npm run setup:claude",
49
+ "lint": "eslint .",
50
+ "lint:fix": "eslint . --fix"
51
+ },
52
+ "dependencies": {
53
+ "@iconify-json/gravity-ui": "1.2.12",
54
+ "@iconify-json/ic": "1.2.4",
55
+ "@iconify-json/lucide": "1.2.101",
56
+ "@iconify-json/material-symbols": "1.2.65",
57
+ "@iconify-json/mdi": "1.2.3",
58
+ "modern-normalize": "3.0.1",
59
+ "nuxt-security": "2.5.1"
60
+ },
61
+ "devDependencies": {
62
+ "@nuxt/eslint": "1.15.2",
63
+ "@nuxt/scripts": "0.13.2",
64
+ "@nuxtjs/robots": "6.0.6",
65
+ "eslint": "10.2.0",
66
+ "nuxt": "4.4.2",
67
+ "srcdev-nuxt-components": "{latest version}"
68
+ }
69
+ }
70
+ ```
71
+
72
+ > **`setup:claude` script**: `mkdir -p` is required — the destination folder won't exist on a
73
+ > fresh clone. The trailing `/.` on the source path copies contents into the destination rather
74
+ > than nesting a `skills` subfolder inside it.
75
+
76
+ ### 3. Create `nuxt.config.ts`
77
+
78
+ ```ts
79
+ const PROD_HOST = "{domain}"
80
+ const canonicalHost = process.env.NUXT_PUBLIC_CANONICAL_HOST ?? "{name}.vercel.app"
81
+ const isProduction = canonicalHost === PROD_HOST
82
+
83
+ export default defineNuxtConfig({
84
+ debug: false,
85
+ devServer: { https: false },
86
+ compatibilityDate: "2025-08-04",
87
+ runtimeConfig: {
88
+ public: {
89
+ canonicalHost,
90
+ colourScheme: { enabled: false },
91
+ },
92
+ },
93
+ extends: ["srcdev-nuxt-components"],
94
+ modules: ["@nuxt/eslint", "@nuxt/scripts", "nuxt-security", "@nuxtjs/robots"],
95
+ robots: {
96
+ enabled: isProduction,
97
+ groups: [{ userAgent: ["*"], allow: ["/"] }],
98
+ sitemap: [`https://${PROD_HOST}/sitemap.xml`],
99
+ },
100
+ components: [{ path: "./components", pathPrefix: false }],
101
+ imports: { dirs: ["./stores"] },
102
+ devtools: { enabled: true },
103
+ app: {
104
+ head: {
105
+ htmlAttrs: { lang: "en" },
106
+ titleTemplate: "%s - {App Title}",
107
+ meta: [{ charset: "utf-8" }, { name: "viewport", content: "width=device-width, initial-scale=1" }],
108
+ link: [{ rel: "icon", href: "/icons/favicon.png" }],
109
+ bodyAttrs: { class: "{name}-body" },
110
+ },
111
+ pageTransition: { name: "page", mode: "out-in" },
112
+ layoutTransition: { name: "layout", mode: "out-in" },
113
+ },
114
+ css: ["srcdev-nuxt-components/app/assets/styles/main.css", "./app/assets/styles/main.css"],
115
+ fonts: {
116
+ assets: { prefix: "/_fonts" },
117
+ families: [
118
+ // Add chosen fonts here — provider: "bunny" for Google Fonts via bunny CDN
119
+ { name: "{Font}", weights: [300, 400, 500, 600, 700], styles: ["normal", "italic"], provider: "bunny", display: "optional" },
120
+ ],
121
+ },
122
+ typescript: {
123
+ includeWorkspace: true,
124
+ strict: true,
125
+ shim: true,
126
+ typeCheck: false,
127
+ tsConfig: { compilerOptions: { types: ["srcdev-nuxt-components"] } },
128
+ },
129
+ eslint: { config: {} },
130
+ security: {
131
+ headers: {
132
+ contentSecurityPolicy: {
133
+ "default-src": ["'self'"],
134
+ "script-src": ["'self'", "'unsafe-inline'", "'unsafe-eval'", "https://vercel.live"],
135
+ "style-src": ["'self'", "'unsafe-inline'"],
136
+ "script-src-attr": ["'self'", "'unsafe-inline'"],
137
+ "img-src": ["'self'", "data:"],
138
+ "connect-src": ["'self'", "https://api.iconify.design", "https://vercel.live"],
139
+ "frame-src": ["'self'", "https://vercel.live"],
140
+ },
141
+ },
142
+ },
143
+ vite: {
144
+ optimizeDeps: {
145
+ include: ["@oddbird/css-anchor-positioning", "@vue/devtools-core", "@vue/devtools-kit", "zod"],
146
+ },
147
+ },
148
+ })
149
+ ```
150
+
151
+ ### 4. Create supporting config files
152
+
153
+ **`tsconfig.json`**
154
+ ```json
155
+ {
156
+ "extends": "./.nuxt/tsconfig.json",
157
+ "compilerOptions": { "strict": true }
158
+ }
159
+ ```
160
+
161
+ **`eslint.config.mjs`**
162
+ ```js
163
+ // @ts-check
164
+ import withNuxt from "./.nuxt/eslint.config.mjs"
165
+
166
+ export default withNuxt({
167
+ files: ["**/*.vue"],
168
+ rules: {
169
+ "@stylistic/max-len": "off",
170
+ "vue/max-len": "off",
171
+ "vue/max-attributes-per-line": "off",
172
+ "vue/singleline-html-element-content-newline": "off",
173
+ "vue/html-closing-bracket-newline": "off",
174
+ "vue/html-self-closing": [
175
+ "error",
176
+ { html: { void: "always", normal: "never", component: "always" } },
177
+ ],
178
+ "@stylistic/member-delimiter-style": [
179
+ "error",
180
+ {
181
+ multiline: { delimiter: "semi", requireLast: true },
182
+ singleline: { delimiter: "semi", requireLast: false },
183
+ multilineDetection: "brackets",
184
+ },
185
+ ],
186
+ },
187
+ })
188
+ ```
189
+
190
+ **`.prettierrc`**
191
+ ```json
192
+ {
193
+ "printWidth": 120,
194
+ "singleQuote": false,
195
+ "trailingComma": "es5",
196
+ "bracketSpacing": true,
197
+ "semi": false,
198
+ "vueIndentScriptAndStyle": false,
199
+ "htmlWhitespaceSensitivity": "ignore"
200
+ }
201
+ ```
202
+
203
+ **`.nvmrc`**
204
+ ```
205
+ node 20
206
+ ```
207
+
208
+ **`.gitignore`** — include `.output`, `.nuxt`, `.nitro`, `.cache`, `node_modules`, `.env`, `.DS_Store`, `.vercel`, `storybook-static`.
209
+
210
+ ### 5. Create app directory structure
211
+
212
+ ```
213
+ app/
214
+ ├── assets/styles/
215
+ │ └── main.css ← brand tokens + font vars (loads after layer CSS)
216
+ ├── layouts/
217
+ │ └── default.vue ← minimal shell with <slot>
218
+ ├── pages/
219
+ │ └── index.vue ← placeholder home page with useSeoMeta
220
+ └── error.vue ← 404 / 500 handler with clearError redirect
221
+ ```
222
+
223
+ **`app/assets/styles/main.css`** — declare brand CSS custom properties and set font-family:
224
+
225
+ ```css
226
+ :root {
227
+ /* Add brand colour tokens */
228
+ --font-display: "{Display Font}", serif;
229
+ --font-body: "{Body Font}", sans-serif;
230
+ font-family: var(--font-body);
231
+ }
232
+ ```
233
+
234
+ **`app/layouts/default.vue`** — minimal:
235
+
236
+ ```vue
237
+ <template>
238
+ <div class="layout-default">
239
+ <main><slot></slot></main>
240
+ </div>
241
+ </template>
242
+ ```
243
+
244
+ **`app/pages/index.vue`** — placeholder with `useSeoMeta`.
245
+
246
+ **`app/error.vue`** — handle `statusCode` 404 vs 500, call `clearError({ redirect: "/" })`.
247
+
248
+ ### 6. Create `.claude/skills/.gitkeep`
249
+
250
+ Ensures `.claude/skills/` exists in the repo on a fresh clone so `setup:claude` never errors.
251
+
252
+ ```bash
253
+ mkdir -p .claude/skills && touch .claude/skills/.gitkeep
254
+ ```
255
+
256
+ ### 7. Create `CLAUDE.md`
257
+
258
+ Brief project context file at repo root:
259
+
260
+ ```md
261
+ # {App Title} — Claude Guidelines
262
+
263
+ This app extends the `srcdev-nuxt-components` Nuxt layer.
264
+
265
+ ## Layer Skills
266
+
267
+ Skills are copied into `.claude/skills/srcdev-nuxt-components/` during `npm install`.
268
+ Run `npm run setup:claude` to refresh after a layer upgrade.
269
+
270
+ ## Project Context
271
+
272
+ - **Design system**: `.claude/design.md` (if it exists)
273
+ - **Production domain**: {domain}
274
+ - **Fonts**: {fonts}
275
+
276
+ ## Key Conventions
277
+
278
+ - Follow all guidelines in the layer's `CLAUDE.md`
279
+ - CSS custom properties for all design tokens — no hardcoded values in component styles
280
+ - Layer CSS loads first; `app/assets/styles/main.css` overrides
281
+ ```
282
+
283
+ ### 8. Confirm next step for the user
284
+
285
+ Tell the user to run:
286
+
287
+ ```bash
288
+ npm install
289
+ ```
290
+
291
+ `postinstall` will run `nuxt prepare` and copy the layer skills automatically.
292
+
293
+ ## Notes
294
+
295
+ - If the repo contains a `.claude/design.md`, read it before writing `main.css` — use any
296
+ brand colours or font choices from it to seed the CSS tokens.
297
+ - If security modules are not wanted, omit `nuxt-security` from `dependencies` and remove it
298
+ from `modules` and the `security` config block in `nuxt.config.ts`. Same for `@nuxtjs/robots`.
299
+ - The `colourScheme.enabled: false` default is correct for most consumer apps — enable only if
300
+ the app needs light/dark switching.
301
+ - Check the latest `srcdev-nuxt-components` version on npm before writing `package.json`.
@@ -0,0 +1,45 @@
1
+ # Automate nuxt prepare and Claude Skills with postinstall
2
+
3
+ ## Overview
4
+
5
+ Prevent two common "forgot to run after install" problems in a consuming app:
6
+
7
+ 1. `nuxt prepare` — generates Nuxt type declarations. Skipping it causes TypeScript errors after install or package updates.
8
+ 2. `npm run setup:claude` — copies the latest `srcdev-nuxt-components` skills into `.claude/skills/srcdev-nuxt-components/`. Skipping it leaves Claude working from stale skill docs after a package update.
9
+
10
+ A `postinstall` script runs both automatically after every `npm install`.
11
+
12
+ ## Steps
13
+
14
+ ### 1. Add the scripts to package.json
15
+
16
+ ```json
17
+ "scripts": {
18
+ "setup:claude": "cp -r node_modules/srcdev-nuxt-components/.claude/skills .claude/skills/srcdev-nuxt-components",
19
+ "postinstall": "nuxt prepare && npm run setup:claude"
20
+ }
21
+ ```
22
+
23
+ ### 2. Check whether your app needs an env flag for nuxt prepare
24
+
25
+ Some apps set an env var to switch `nuxt.config.ts` behaviour when running outside the full dev server (e.g. `NUXT_STANDALONE=true`). Check your own `nuxt.config.ts` — if it gates any config behind such a variable, add it to the `postinstall` command:
26
+
27
+ ```json
28
+ "postinstall": "NUXT_STANDALONE=true nuxt prepare && npm run setup:claude"
29
+ ```
30
+
31
+ If your `nuxt.config.ts` has no such conditional, plain `nuxt prepare` is sufficient.
32
+
33
+ ### 3. Run once manually to bootstrap
34
+
35
+ ```bash
36
+ npm run setup:claude
37
+ ```
38
+
39
+ From this point on, `npm install` and `npm ci` trigger both steps automatically.
40
+
41
+ ## Notes
42
+
43
+ - Skills land in `.claude/skills/srcdev-nuxt-components/` — safe to re-run without overwriting your own project's skills.
44
+ - `postinstall` also fires on `npm ci`, so CI environments get the skills too if they have a `.claude/` directory in the project.
45
+ - If you do not want `postinstall` running in CI, guard it: `"postinstall": "[ \"$CI\" = \"true\" ] || (nuxt prepare && npm run setup:claude)"`.
@@ -241,6 +241,12 @@ The consuming app's CSS loads after the layer's, so your token overrides win via
241
241
  | 24px | 2.4rem |
242
242
  | 32px | 3.2rem |
243
243
 
244
+ ## Token reference
245
+
246
+ See `CONSUMER-STYLING.md` in the layer package root for the full list of available tokens grouped
247
+ by category. This is the authoritative reference when deciding which tokens to include in your
248
+ override files.
249
+
244
250
  ## Notes
245
251
 
246
252
  - The `--slate-*` scale comes from the layer and does not need to be redefined — keep all neutral/background tokens pointing at `--slate-*`.
@@ -0,0 +1,131 @@
1
+ # Partial Token Override in a Consuming App
2
+
3
+ ## Overview
4
+
5
+ Use this skill when you need to override a specific category of tokens (e.g. form inputs, buttons,
6
+ colours) without replacing the entire default theme. The full token reference is in
7
+ `CONSUMER-STYLING.md` at the root of the layer package.
8
+
9
+ ## How it works
10
+
11
+ All library tokens are declared inside `@layer theming`. Any CSS written outside a layer wins
12
+ automatically — no `!important` or specificity tricks required. Override files just need to be
13
+ imported **after** the layer styles.
14
+
15
+ ## Steps
16
+
17
+ ### 1. Create your override file
18
+
19
+ Create a CSS file for the category you want to override. Place it anywhere in your app's assets —
20
+ `app/assets/styles/` is conventional.
21
+
22
+ **Example: form input overrides**
23
+
24
+ ```css
25
+ /* app/assets/styles/form-overrides.css */
26
+ :root {
27
+ --theme-input-surface: oklch(0.98 0.005 250);
28
+ --theme-input-border: oklch(0.45 0.08 270);
29
+ --theme-input-border-focus: oklch(0.55 0.18 280);
30
+ --theme-input-outline-focus: oklch(0.55 0.18 280);
31
+ --theme-focus-visible-shadow: 0 0 0 2px oklch(0.80 0.12 280);
32
+ --theme-input-placeholder: oklch(0.60 0.02 255);
33
+ --theme-input-text-color-normal: oklch(0.20 0.02 255);
34
+ }
35
+ ```
36
+
37
+ **Example: add a red colour scale and remap accent tokens**
38
+
39
+ ```css
40
+ /* app/assets/styles/red-palette.css */
41
+ :root {
42
+ /* Red scale — 00 (lightest) to 10 (darkest) */
43
+ --red-00: oklch(0.99 0.005 20);
44
+ --red-01: oklch(0.96 0.020 20);
45
+ --red-02: oklch(0.90 0.055 20);
46
+ --red-03: oklch(0.82 0.105 20);
47
+ --red-04: oklch(0.72 0.155 20);
48
+ --red-05: oklch(0.62 0.185 20);
49
+ --red-06: oklch(0.53 0.185 20);
50
+ --red-07: oklch(0.44 0.165 20);
51
+ --red-08: oklch(0.36 0.140 20);
52
+ --red-09: oklch(0.28 0.110 20);
53
+ --red-10: oklch(0.20 0.080 20);
54
+
55
+ /* Remap semantic accent tokens to red */
56
+ --colour-text-accent: var(--red-08);
57
+ --colour-text-eyebrow: var(--red-08);
58
+ --colour-link-default: var(--red-09);
59
+ --colour-link-hover: var(--red-08);
60
+ }
61
+ ```
62
+
63
+ **Example: button overrides only**
64
+
65
+ ```css
66
+ /* app/assets/styles/button-overrides.css */
67
+ :root {
68
+ --theme-button-primary-surface: oklch(0.55 0.20 280);
69
+ --theme-button-primary-surface-hover: oklch(0.48 0.22 280);
70
+ --theme-button-primary-surface-active: oklch(0.42 0.22 280);
71
+ --theme-button-primary-text: oklch(1 0 0);
72
+ --theme-button-primary-text-hover: oklch(1 0 0);
73
+ }
74
+ ```
75
+
76
+ ### 2. Register it in nuxt.config.ts
77
+
78
+ Import your override file **after** the layer styles:
79
+
80
+ ```ts
81
+ export default defineNuxtConfig({
82
+ extends: "srcdev-nuxt-components",
83
+ css: [
84
+ "~/assets/styles/form-overrides.css", // or whichever file(s) you created
85
+ ],
86
+ });
87
+ ```
88
+
89
+ The layer's own CSS loads via the `extends` mechanism before app CSS, so your unlayered overrides
90
+ always win.
91
+
92
+ ### 3. Dark mode (optional)
93
+
94
+ If you need dark mode variants of your overrides, add them in the same file using the `html.dark`
95
+ class selector:
96
+
97
+ ```css
98
+ :root {
99
+ --theme-input-surface: oklch(0.98 0.005 250);
100
+ }
101
+
102
+ :where(html.dark) {
103
+ --theme-input-surface: oklch(0.15 0.02 255);
104
+ }
105
+ ```
106
+
107
+ ## Scoped overrides (section or component level)
108
+
109
+ To restrict an override to a specific section of the page, scope to a wrapper class instead of
110
+ `:root`:
111
+
112
+ ```css
113
+ .pricing-section {
114
+ --theme-button-primary-surface: oklch(0.55 0.20 140); /* green for pricing CTA */
115
+ }
116
+ ```
117
+
118
+ ## Token reference
119
+
120
+ See `CONSUMER-STYLING.md` in the layer package for the full list of available tokens, grouped
121
+ by category (typography, colours, form inputs, buttons, checkboxes, toggle, glass panel,
122
+ StepperList).
123
+
124
+ ## Notes
125
+
126
+ - Only override the tokens you actually need — unset tokens fall back to layer defaults.
127
+ - Use oklch for all colour values. Use an oklch colour picker (e.g. oklch.com) to build scales.
128
+ - For a full palette replacement (replacing the entire default theme), use
129
+ `theming-override-default.md` instead.
130
+ - `--slate-*` neutral tokens come from the layer and do not need to be redefined unless you
131
+ genuinely want different neutrals.
@@ -0,0 +1,74 @@
1
+ # Vue Video Autoplay on Client-Side Navigation
2
+
3
+ ## Overview
4
+
5
+ In Vue/Nuxt SPAs, binding `:src` directly on a `<video>` element silently skips the browser fetch when Vue patches the element during client-side navigation. The video appears but never loads — no network request is made. Use a `<source>` child element instead, combined with `:key` and an explicit `load()` call.
6
+
7
+ ## The problem
8
+
9
+ When Vue patches an existing `<video>` DOM element (e.g. on route change), setting `src` via `:src` doesn't always trigger the browser to re-fetch the media resource. This fails silently — no console error, no network request, poster image just sits there.
10
+
11
+ ## The fix
12
+
13
+ ```vue
14
+ <!-- ✅ Correct — src in <source> child -->
15
+ <video
16
+ :key="src"
17
+ autoplay
18
+ muted
19
+ loop
20
+ playsinline
21
+ preload="auto"
22
+ :poster="poster"
23
+ >
24
+ <source :src="src" type="video/mp4" />
25
+ </video>
26
+
27
+ <!-- ❌ Wrong — :src on <video> silently skips fetch on Vue patches -->
28
+ <video autoplay muted loop playsinline preload="auto" :src="src"></video>
29
+ ```
30
+
31
+ ## Three things working together
32
+
33
+ 1. **`<source>` child** — the browser re-reads source children when `load()` is called, reliably initiating the fetch.
34
+ 2. **`:key="src"`** — forces Vue to unmount/remount the `<video>` element when the source changes (covers same-component src swaps).
35
+ 3. **Explicit `v.load()` call** — programmatically re-initiates fetching after mount and on src change. Do not rely on `autoplay` alone for client-side navigation.
36
+
37
+ ## Autoplay pattern for Nuxt components
38
+
39
+ ```ts
40
+ const videoEl = shallowRef<HTMLVideoElement | null>(null);
41
+
42
+ const tryPlay = async () => {
43
+ const v = videoEl.value;
44
+ if (!v) return;
45
+ v.muted = true; // required for programmatic autoplay in all browsers
46
+ try {
47
+ await v.play();
48
+ } catch {
49
+ // autoplay blocked — poster remains visible
50
+ }
51
+ };
52
+
53
+ const kickOffLoad = async () => {
54
+ await nextTick();
55
+ const v = videoEl.value;
56
+ if (!v) return;
57
+ v.load();
58
+ void tryPlay();
59
+ };
60
+
61
+ // Covers initial mount and src prop changes
62
+ watch(() => props.src, () => void kickOffLoad(), { immediate: true, flush: "post" });
63
+
64
+ // Covers keep-alive re-activation
65
+ onActivated(() => void kickOffLoad());
66
+ ```
67
+
68
+ Attach `@loadeddata="() => void tryPlay()"` and `@canplay="() => void tryPlay()"` to the `<video>` element as additional retry points once data arrives.
69
+
70
+ ## Notes
71
+
72
+ - `preload="auto"` tells the browser to buffer eagerly — essential for autoplay reliability.
73
+ - `v.muted = true` set programmatically (in addition to the `muted` attribute) works around a Safari bug where the attribute alone is insufficient for programmatic play.
74
+ - This pattern applies to any Vue/Nuxt video component, not just background/ambient video.
package/README.md CHANGED
@@ -8,7 +8,7 @@
8
8
 
9
9
  Althought this repo is public and feel free to do what you wish with it, this has been developed for use with websites we develop.
10
10
 
11
- ## Install Nuxt Forms layer
11
+ ## Install Nuxt Components layer
12
12
 
13
13
  ```bash
14
14
  npm install --save srcdev-nuxt-components
@@ -17,10 +17,7 @@ npm install --save srcdev-nuxt-components
17
17
  ```ts
18
18
  defineNuxtConfig({
19
19
  extends: "srcdev-nuxt-components",
20
- css: [
21
- "srcdev-nuxt-components/app/assets/styles/main.css",
22
- "./app/assets/styles/main.css",
23
- ],
20
+ css: ["srcdev-nuxt-components/app/assets/styles/main.css", "./app/assets/styles/main.css"],
24
21
  });
25
22
  ```
26
23
 
@@ -44,6 +41,34 @@ npm run setup:claude
44
41
 
45
42
  Skills are copied into `.claude/skills/srcdev-nuxt-components/` so they never conflict with or overwrite skills your own project defines. Re-running the script after a package update is safe.
46
43
 
44
+ ### Automate with postinstall (recommended)
45
+
46
+ To ensure skills are always up to date and `nuxt prepare` is never forgotten, combine both into a `postinstall` script. npm runs this automatically after every `npm install`:
47
+
48
+ ```json
49
+ "scripts": {
50
+ "setup:claude": "cp -r node_modules/srcdev-nuxt-components/.claude/skills .claude/skills/srcdev-nuxt-components",
51
+ "postinstall": "nuxt prepare && npm run setup:claude"
52
+ }
53
+ ```
54
+
55
+ > If your app uses a standalone env flag for `nuxt prepare` (e.g. `NUXT_STANDALONE=true`), include it in the `postinstall` command. This is project-specific — check your own `nuxt.config.ts` to confirm whether it is needed.
56
+
57
+ ---
58
+
59
+ ## Scaffolding a New App
60
+
61
+ A Claude Code skill is included to scaffold a new Nuxt consumer app from scratch. It generates
62
+ `package.json`, `nuxt.config.ts`, ESLint/Prettier config, the full `app/` directory structure,
63
+ and a `CLAUDE.md` — all pre-wired to extend this layer correctly.
64
+
65
+ **Trigger it by saying to Claude Code:**
66
+
67
+ > "Scaffold a new layer consumer app. Repo: `/path/to/repo`, name: `my-app`, domain: `myapp.co.uk`, fonts: `Fraunces, Manrope`."
68
+
69
+ The skill is available at `.claude/skills/new-app-scaffold.md` once copied into your project
70
+ via `npm run setup:claude`.
71
+
47
72
  ---
48
73
 
49
74
  ## Consumer App Configuration