Compare commits
5 commits
0a8ef64527
...
fc439eda08
| Author | SHA1 | Date | |
|---|---|---|---|
| fc439eda08 | |||
| 30c4794520 | |||
| 2eaef7b677 | |||
| 063ece498e | |||
| f64908d94e |
6 changed files with 203 additions and 4 deletions
6
.gitignore
vendored
6
.gitignore
vendored
|
|
@ -18,3 +18,9 @@ dist-ssr
|
||||||
pnpm-debug.log*
|
pnpm-debug.log*
|
||||||
|
|
||||||
.claude/
|
.claude/
|
||||||
|
|
||||||
|
# Tooling scratch dirs (Playwright MCP snapshots/console output;
|
||||||
|
# inspect/ is a working folder for screenshots and style dumps during
|
||||||
|
# visual QA).
|
||||||
|
.playwright-mcp/
|
||||||
|
inspect/
|
||||||
|
|
|
||||||
12
.mcp.json
12
.mcp.json
|
|
@ -3,6 +3,18 @@
|
||||||
"shadcn": {
|
"shadcn": {
|
||||||
"command": "npx",
|
"command": "npx",
|
||||||
"args": ["shadcn-vue@latest", "mcp"]
|
"args": ["shadcn-vue@latest", "mcp"]
|
||||||
|
},
|
||||||
|
"playwright": {
|
||||||
|
"type": "stdio",
|
||||||
|
"command": "npx",
|
||||||
|
"args": [
|
||||||
|
"@playwright/mcp@latest",
|
||||||
|
"--executable-path",
|
||||||
|
"/etc/profiles/per-user/padreug/bin/chromium",
|
||||||
|
"--caps",
|
||||||
|
"devtools"
|
||||||
|
],
|
||||||
|
"env": {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
174
CLAUDE.md
Normal file
174
CLAUDE.md
Normal file
|
|
@ -0,0 +1,174 @@
|
||||||
|
# boilerplate-website / CLAUDE.md
|
||||||
|
|
||||||
|
Guidance for Claude sessions working in this repo (and any aiolabs site
|
||||||
|
forked off `main`). Stack: Vue 3.5 + Vite 8 + TypeScript 6 + Tailwind 4
|
||||||
|
+ shadcn-vue + reka-ui + vue-router + vue-i18n + Pinia.
|
||||||
|
|
||||||
|
## Reach for shadcn-vue primitives before hand-rolling
|
||||||
|
|
||||||
|
**Default:** if a UI pattern has a shadcn-vue primitive, use it. Don't
|
||||||
|
roll your own.
|
||||||
|
|
||||||
|
Installed (or known to work) primitives in this branch:
|
||||||
|
|
||||||
|
| Primitive | Use for |
|
||||||
|
|---|---|
|
||||||
|
| `Button` (gold-pill variants tuned for this brand) | Every CTA. Use `as-child` to wrap a `<RouterLink>` or `<a>` so the link is the click target. |
|
||||||
|
| `Card` + `CardContent` + `CardHeader` + `CardTitle` | Any bordered content block. Pattern: `<Card class="p-6">...</Card>` for prose cards; `<Card class="overflow-hidden">...<img/><CardContent>...</CardContent></Card>` for image-bearing cards. |
|
||||||
|
| `Badge` (default/secondary/destructive/outline) | Status pills (`Open` / `Coming soon` / labels). |
|
||||||
|
| `NavigationMenu` family | Desktop top-nav with dropdown groups. Opens on hover/focus, not click — that's by design. |
|
||||||
|
| `Sheet` family | Mobile/side panels. Use with `<SheetTrigger as-child>` wrapping a hamburger button. |
|
||||||
|
| `Alert` + `AlertTitle` + `AlertDescription` | Callout asides, inline notices. |
|
||||||
|
|
||||||
|
When you need a new primitive, prefer it over a custom solution unless
|
||||||
|
the trade-off is well-justified in a comment.
|
||||||
|
|
||||||
|
### The shadcn-vue CLI is broken on nix-managed pnpm
|
||||||
|
|
||||||
|
`pnpm dlx shadcn-vue@latest add <name>` invokes `corepack pnpm add ...`
|
||||||
|
under the hood, which conflicts with our nix-store pnpm and exits with
|
||||||
|
a vague "Run 'pnpm approve-builds'…" error. The components themselves
|
||||||
|
install fine; only the dep-install step fails.
|
||||||
|
|
||||||
|
**Workaround:** fetch the component JSON from the registry directly
|
||||||
|
and write the files yourself:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Find the URL
|
||||||
|
curl -s https://www.shadcn-vue.com/r/styles/default/<name>.json
|
||||||
|
```
|
||||||
|
|
||||||
|
Each file lives at `files[].path` / `files[].content` inside that
|
||||||
|
JSON. Write to `src/components/ui/<name>/` matching the path. Then
|
||||||
|
`pnpm add <runtime-deps>` manually for anything in the registry's
|
||||||
|
`dependencies` field (typically `reka-ui` and `@vueuse/core`, both
|
||||||
|
already installed).
|
||||||
|
|
||||||
|
### Two fixes to apply when copying from the registry
|
||||||
|
|
||||||
|
1. **lucide imports:** the registry ships `import { X } from
|
||||||
|
"lucide-vue-next"` but our project uses `@lucide/vue`. Swap the
|
||||||
|
path before saving the file.
|
||||||
|
2. **Tailwind v4 CSS-variable syntax:** the registry uses v3 arbitrary
|
||||||
|
values like `h-[--reka-navigation-menu-viewport-height]`. Under
|
||||||
|
Tailwind v4 the inner string is treated as a literal — the value
|
||||||
|
resolves to nothing and the element collapses. Wrap with `var()`:
|
||||||
|
`h-[var(--reka-navigation-menu-viewport-height)]`. Reported upstream
|
||||||
|
as [unovue/shadcn-vue#1853](https://github.com/unovue/shadcn-vue/issues/1853).
|
||||||
|
|
||||||
|
## Semantic theme-aware Tailwind, never colour literals
|
||||||
|
|
||||||
|
The shadcn-vue palette is exposed as CSS variables in `src/style.css`
|
||||||
|
and the `@theme inline` block maps each variable to a Tailwind utility.
|
||||||
|
Use the **semantic tokens** so the same component looks right when a
|
||||||
|
site forks off main and swaps the palette to its own theme.
|
||||||
|
|
||||||
|
Use these, not the underlying colour:
|
||||||
|
|
||||||
|
| Use | Don't |
|
||||||
|
|---|---|
|
||||||
|
| `bg-background` | `bg-zinc-950` |
|
||||||
|
| `bg-card` | `bg-stone-900` |
|
||||||
|
| `bg-secondary` | `bg-emerald-900` |
|
||||||
|
| `bg-muted` | `bg-zinc-800` |
|
||||||
|
| `bg-primary` | `bg-amber-500` |
|
||||||
|
| `bg-accent` | `bg-yellow-600` |
|
||||||
|
| `text-foreground` | `text-stone-50` |
|
||||||
|
| `text-muted-foreground` | `text-zinc-400` |
|
||||||
|
| `text-primary` | `text-amber-500` |
|
||||||
|
| `text-accent-foreground` | `text-zinc-950` |
|
||||||
|
| `border-border` | `border-zinc-800` |
|
||||||
|
| `ring-ring` | `ring-amber-500` |
|
||||||
|
|
||||||
|
A site retheme is then a single edit to `src/style.css`: change the
|
||||||
|
HSL values behind `--background`, `--primary`, `--accent` etc. and
|
||||||
|
every component re-skins.
|
||||||
|
|
||||||
|
Translucent variants (`bg-background/65`, `bg-card/40`, etc.) compose
|
||||||
|
the same way — keep using the semantic token, just append the opacity
|
||||||
|
shorthand.
|
||||||
|
|
||||||
|
**Exception** — fully opaque overlays where the colour is the visual
|
||||||
|
intent, not the token (e.g. `bg-black/30` for a dialog overlay, plain
|
||||||
|
`bg-zinc-950/80` for a near-black callout panel). For everything else,
|
||||||
|
semantic.
|
||||||
|
|
||||||
|
## Tailwind 4 / vite / tsconfig setup that the shadcn CLI needs
|
||||||
|
|
||||||
|
If `pnpm dlx shadcn-vue@latest add <name>` later starts working, it
|
||||||
|
runs a `resolvedPaths` validator that fails unless these files match:
|
||||||
|
|
||||||
|
`tsconfig.json` — must declare `baseUrl` + `paths`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"files": [],
|
||||||
|
"references": [...],
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": { "@/*": ["./src/*"] },
|
||||||
|
"ignoreDeprecations": "6.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`tsconfig.app.json` — same `baseUrl` + `paths` + `ignoreDeprecations`
|
||||||
|
inside its `compilerOptions`.
|
||||||
|
|
||||||
|
`ignoreDeprecations` is required because TypeScript 6 emits a
|
||||||
|
deprecation error for `baseUrl` (slated for removal in TS 7) and
|
||||||
|
`vue-tsc` treats it as a hard failure.
|
||||||
|
|
||||||
|
`components.json` — no `framework` key, no empty `"tailwind.config":
|
||||||
|
""` field. Both have been removed from current schema versions.
|
||||||
|
|
||||||
|
## Asset patterns
|
||||||
|
|
||||||
|
- `src/assets/*` — imported and Vite content-hashes the URL on build.
|
||||||
|
Use this for everything reused or that benefits from cache-busting
|
||||||
|
(logos, hero photography, repeating textures).
|
||||||
|
- `public/*` — served as-is, no hash. Avoid for things that change.
|
||||||
|
- Prefer modern formats: AVIF for stills with transparency, WebP for
|
||||||
|
animation, MP4/WebM for longer-duration motion. The browsers we
|
||||||
|
target (Chrome ≥85, Firefox ≥93, Safari ≥16, Edge ≥121) handle all
|
||||||
|
three.
|
||||||
|
- `<picture>` with `<source media="(prefers-reduced-motion: reduce)">`
|
||||||
|
is the right hook for animation fallbacks.
|
||||||
|
|
||||||
|
## Routing and i18n stay vanilla
|
||||||
|
|
||||||
|
Vue Router and vue-i18n don't get the shadcn treatment — they stay as
|
||||||
|
the standard plugin wiring. Localised strings live in
|
||||||
|
`src/i18n/locales/{en,fr,…}.json`; don't inline copy.
|
||||||
|
|
||||||
|
## Frosted-glass sticky header recipe (used here, reusable)
|
||||||
|
|
||||||
|
```
|
||||||
|
bg-background/65 backdrop-blur-xl backdrop-saturate-150 border-b border-white/10
|
||||||
|
```
|
||||||
|
|
||||||
|
`bg-background/N` keeps it theme-aware. The `border-white/10` is
|
||||||
|
intentional — it reads as "glass edge" rather than a hard rule and
|
||||||
|
looks correct across both light and dark themes.
|
||||||
|
|
||||||
|
## Pinned hero photo pattern
|
||||||
|
|
||||||
|
For full-bleed hero photos that stay put as the page scrolls, put a
|
||||||
|
`position: fixed` `<img>` at the top of the view's template with a
|
||||||
|
negative z-index, and let the hero section above be transparent:
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<img
|
||||||
|
:src="heroLandscape"
|
||||||
|
alt=""
|
||||||
|
aria-hidden="true"
|
||||||
|
class="fixed inset-0 -z-50 h-screen w-screen object-cover"
|
||||||
|
/>
|
||||||
|
<section class="relative isolate flex min-h-screen flex-col items-center justify-center overflow-hidden">
|
||||||
|
<!-- transparent over the pinned image; sections below have their own opaque backgrounds -->
|
||||||
|
</section>
|
||||||
|
```
|
||||||
|
|
||||||
|
The opaque sections below (welcome / pricing / footer / etc.) cover the
|
||||||
|
fixed photo as the user scrolls past, so it visually exits when the
|
||||||
|
hero leaves the viewport.
|
||||||
|
|
@ -3,13 +3,11 @@
|
||||||
"style": "default",
|
"style": "default",
|
||||||
"typescript": true,
|
"typescript": true,
|
||||||
"tailwind": {
|
"tailwind": {
|
||||||
"config": "",
|
|
||||||
"css": "src/style.css",
|
"css": "src/style.css",
|
||||||
"baseColor": "neutral",
|
"baseColor": "neutral",
|
||||||
"cssVariables": true,
|
"cssVariables": true,
|
||||||
"prefix": ""
|
"prefix": ""
|
||||||
},
|
},
|
||||||
"framework": "vite",
|
|
||||||
"aliases": {
|
"aliases": {
|
||||||
"components": "@/components",
|
"components": "@/components",
|
||||||
"composables": "@/composables",
|
"composables": "@/composables",
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,10 @@
|
||||||
"exclude": ["src/**/__tests__/*"],
|
"exclude": ["src/**/__tests__/*"],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||||
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./src/*"]
|
"@/*": ["./src/*"]
|
||||||
}
|
},
|
||||||
|
"ignoreDeprecations": "6.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,11 @@
|
||||||
{
|
{
|
||||||
"files": [],
|
"files": [],
|
||||||
"references": [{ "path": "./tsconfig.node.json" }, { "path": "./tsconfig.app.json" }]
|
"references": [{ "path": "./tsconfig.node.json" }, { "path": "./tsconfig.app.json" }],
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
},
|
||||||
|
"ignoreDeprecations": "6.0"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue