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.
- package/.claude/settings.json +7 -2
- package/.claude/settings.local.json +2 -1
- package/.claude/skills/components/banner-video.md +178 -0
- package/.claude/skills/components/grid-stack.md +133 -0
- package/.claude/skills/components/scroll-reveal-frame.md +136 -0
- package/.claude/skills/components/scroll-reveal-image.md +161 -0
- package/.claude/skills/index.md +9 -1
- package/.claude/skills/new-app-scaffold.md +301 -0
- package/.claude/skills/setup-postinstall.md +45 -0
- package/.claude/skills/theming-override-default.md +6 -0
- package/.claude/skills/theming-partial-override.md +131 -0
- package/.claude/skills/vue-video-autoplay.md +74 -0
- package/README.md +30 -5
- package/app/components/01.atoms/banner-video/BannerVideo.vue +207 -0
- package/app/components/01.atoms/banner-video/stories/BannerVideo.stories.ts +180 -0
- package/app/components/01.atoms/banner-video/tests/BannerVideo.spec.ts +350 -0
- package/app/components/01.atoms/banner-video/tests/__snapshots__/BannerVideo.spec.ts.snap +13 -0
- package/app/components/01.atoms/grid-stack/GridStack.vue +34 -0
- package/app/components/01.atoms/grid-stack/stories/GridStack.stories.ts +162 -0
- package/app/components/01.atoms/grid-stack/tests/GridStack.spec.ts +66 -0
- package/app/components/01.atoms/scroll-reveal-frame/ScrollRevealFrame.vue +125 -0
- package/app/components/01.atoms/scroll-reveal-frame/stories/ScrollRevealFrame.stories.ts +166 -0
- package/app/components/01.atoms/scroll-reveal-frame/tests/ScrollRevealFrame.spec.ts +148 -0
- package/app/components/01.atoms/scroll-reveal-frame/tests/__snapshots__/ScrollRevealFrame.spec.ts.snap +13 -0
- package/app/components/01.atoms/scroll-reveal-image/ScrollRevealImage.vue +96 -0
- package/app/components/01.atoms/scroll-reveal-image/stories/ScrollRevealImage.stories.ts +181 -0
- package/app/components/01.atoms/scroll-reveal-image/tests/ScrollRevealImage.spec.ts +235 -0
- package/app/components/01.atoms/scroll-reveal-image/tests/__snapshots__/ScrollRevealImage.spec.ts.snap +13 -0
- package/app/components/03.organisms/services/services-card/stories/ServicesCard.stories.ts +1 -0
- package/app/components/03.organisms/services/services-card/tests/ServicesCard.spec.ts +1 -0
- package/app/components/03.organisms/services/services-grids/stories/ServicesCardGrid.stories.ts +1 -0
- package/app/components/03.organisms/services/services-grids/stories/ServicesSectionGrid.stories.ts +1 -0
- package/app/components/03.organisms/services/services-grids/tests/ServicesCardGrid.spec.ts +1 -0
- package/app/components/03.organisms/services/services-grids/tests/ServicesSectionGrid.spec.ts +1 -0
- package/app/components/03.organisms/services/services-grids/tests/__snapshots__/ServicesSectionGrid.spec.ts.snap +3 -3
- package/app/components/03.organisms/services/services-section/stories/ServicesSection.stories.ts +1 -0
- package/app/components/03.organisms/services/services-section/tests/ServicesSection.spec.ts +1 -0
- package/app/components/03.organisms/services/services-section/tests/__snapshots__/ServicesSection.spec.ts.snap +1 -1
- package/app/layouts/default.vue +2 -0
- package/app/pages/banner-video.vue +238 -0
- package/app/pages/grid-stack.vue +252 -0
- package/app/pages/index.vue +5 -0
- package/app/pages/ui/scroll-reveal-image.vue +483 -0
- package/app/types/types.services.ts +1 -0
- package/package.json +1 -1
- package/app/assets/styles/extends-layer/srcdev-components/components/_display-prompt-core.css +0 -94
- package/app/assets/styles/extends-layer/srcdev-components/components/_display-toast.css +0 -5
- package/app/assets/styles/extends-layer/srcdev-components/components/_expanding-panel.css +0 -37
- package/app/assets/styles/extends-layer/srcdev-components/components/index.css +0 -3
- package/app/assets/styles/extends-layer/srcdev-components/index.css +0 -1
- package/app/assets/styles/extends-layer/srcdev-forms/components/_form-fieldset.css +0 -38
- package/app/assets/styles/extends-layer/srcdev-forms/components/_input-button.css +0 -67
- package/app/assets/styles/extends-layer/srcdev-forms/components/_input-checkbox-radio-core.css +0 -87
- package/app/assets/styles/extends-layer/srcdev-forms/components/_input-checkbox-radio-options-button.css +0 -74
- package/app/assets/styles/extends-layer/srcdev-forms/components/_input-checkbox-radio-with-label.css +0 -14
- package/app/assets/styles/extends-layer/srcdev-forms/components/_input-description.css +0 -13
- package/app/assets/styles/extends-layer/srcdev-forms/components/_input-error.css +0 -54
- package/app/assets/styles/extends-layer/srcdev-forms/components/_input-label.css +0 -21
- package/app/assets/styles/extends-layer/srcdev-forms/components/_input-select.css +0 -143
- package/app/assets/styles/extends-layer/srcdev-forms/components/_input-text.css +0 -190
- package/app/assets/styles/extends-layer/srcdev-forms/components/_input-textarea.css +0 -96
- package/app/assets/styles/extends-layer/srcdev-forms/components/_input-toggle-switch.css +0 -68
- package/app/assets/styles/extends-layer/srcdev-forms/components/index.css +0 -12
- package/app/assets/styles/extends-layer/srcdev-forms/index.css +0 -2
- package/app/assets/styles/extends-layer/srcdev-forms/setup/_generic.css +0 -21
- package/app/assets/styles/extends-layer/srcdev-forms/setup/index.css +0 -3
- package/app/assets/styles/extends-layer/srcdev-forms/setup/sizes/_default.css +0 -13
- package/app/assets/styles/extends-layer/srcdev-forms/setup/sizes/_large.css +0 -12
- package/app/assets/styles/extends-layer/srcdev-forms/setup/sizes/_medium.css +0 -12
- package/app/assets/styles/extends-layer/srcdev-forms/setup/sizes/_small.css +0 -12
- package/app/assets/styles/extends-layer/srcdev-forms/setup/sizes/_x-small.css +0 -11
- package/app/assets/styles/extends-layer/srcdev-forms/setup/sizes/index.css +0 -23
- package/app/assets/styles/extends-layer/srcdev-forms/setup/themes/_default.css +0 -62
- package/app/assets/styles/extends-layer/srcdev-forms/setup/themes/_error.css +0 -63
- package/app/assets/styles/extends-layer/srcdev-forms/setup/themes/_ghost.css +0 -63
- package/app/assets/styles/extends-layer/srcdev-forms/setup/themes/_info.css +0 -63
- package/app/assets/styles/extends-layer/srcdev-forms/setup/themes/_primary.css +0 -66
- package/app/assets/styles/extends-layer/srcdev-forms/setup/themes/_secondary.css +0 -64
- package/app/assets/styles/extends-layer/srcdev-forms/setup/themes/_success.css +0 -63
- package/app/assets/styles/extends-layer/srcdev-forms/setup/themes/_tertiary.css +0 -63
- package/app/assets/styles/extends-layer/srcdev-forms/setup/themes/_warning.css +0 -63
- 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
|
|
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
|