Personal website for Stuart Clark — a Nuxt 4 app built on Nuxt UI v3, the
@stuartclark/ui design system, and headless @nuxt/content (no Druxt).
Static-generated.
- Nuxt 4 + Nuxt UI v3 (Tailwind v4, semantic utilities)
- @nuxt/content v3 (Markdown articles, typed collections)
- @nuxt/fonts (self-hosted Archivo + JetBrains Mono)
- @stuartclark/ui design system (25 Vue SFCs, consumed as
link:../../ui) - Headless (no Druxt) — content via @nuxt/content + typed TS data
- Light + dark mode via Nuxt UI color mode
nuxt-cloudflared-tunnelmodule (dev-only live preview: site + Storybook tunnels)
mise install # Node 24 + pnpm 10
mise run install # pnpm install (in nuxt/)
mise run dev # http://localhost:3000
mise run hooks:install # optional: enable commit-msg + pre-commit hooksDDEV is an open source tool that makes it dead simple to get local PHP development environments up and running within minutes.
DDEV manages the Drupal instance and provides a CLI for common Drupal tasks
(ddev drush, ddev phpunit, ddev phpcs, ddev phpstan). Commands must be
run from within the drupal folder:
cd drupal
ddev start
ddev installpnpm typecheck # vue-tsc
pnpm lint # ESLint
pnpm lint:style # Stylelint
pnpm lint:knip # Dead code detection
pnpm lint:spell # cspell
pnpm test # Vitest (100% coverage enforced)
pnpm test:coverage # Same, with report
pnpm generate # Static build → .output/public/
pnpm test:visual # Playwright visual + SEO (needs generate first)Or via mise:
mise run ci # typecheck + lint + style + knip + spell + tests
mise run ci:full # + visual regression + SEO audit| Port | Service |
|---|---|
3000 |
Nuxt.js |
3003 |
Storybook |
8080 |
Drupal |
Colors are registered via @theme static in app/assets/css/main.css:
| Palette | Alias | Usage |
|---|---|---|
| magenta | primary |
links, tags, CTAs, active states |
| sand | neutral |
backgrounds, text, borders |
| electric | — | accent highlights |
| coral | — | secondary accents |
Nuxt UI semantic utilities (text-highlighted, text-muted, text-dimmed,
bg-default, bg-muted, bg-elevated, border-default) automatically adapt
to light/dark mode. app.config.ts sets primary: 'magenta', neutral: 'sand'.
Fonts: Archivo (display/body) + JetBrains Mono (labels/numerals), self-hosted by @nuxt/fonts.
The design system lives in the sibling @stuartclark/ui repo (25 Vue
SFCs built on Nuxt UI primitives). It is consumed here as link:../../ui:
| Category | Components |
|---|---|
| Layout | AppHeader, AppFooter, AppLogo, ThemeToggle |
| Atoms | Eyebrow, StatusPill, StatBlock, StatBand, Quote, SectionHeader |
| Data | ModuleRow, ActivityRow, ContributionHeatmap |
| Cards | ProjectCard, FeaturedCard, DrupalConCard, UsesGroup, GiveBackCard |
| Content | ArticleRow, Steps, CodeBlock, CommentItem, CommentComposer |
| Media | ImageSlot, Gallery |
| Route | Description |
|---|---|
/ |
Hero, stats band, heartbeat, selected work |
/about |
Bio, headshot, expertise, elsewhere links; "Get in touch" opens contact modal |
/open-source |
Profiles, module installs, contribution heatmap, activity, flagship DruxtJS |
/community |
Talks, Splash Award, DrupalCons attended, organising & training |
/uses |
Tools, hardware, services |
/drupalgive |
Maintained projects, DrupalCons |
/styleguide |
Component showcase |
/writing |
Article list — disabled for first launch (accessible via direct URL) |
/writing/[slug] |
Article detail (@nuxt/content) — disabled for first launch |
/photos |
Photography gallery — disabled for first launch (accessible via direct URL) |
Writing and photos are hidden from nav and the homepage teaser is commented out. The page files remain in place for re-enabling post-launch.
Auto-imported from app/composables/. Most fetch live data from the Drupal.org
or GitHub APIs at build time (SSG) with static fallbacks.
| Composable | Purpose |
|---|---|
useSite |
Site config singleton (name, tagline, socials) |
useStats |
Stats band data; ffpSites exposes the live File (Field) Paths install count |
useModules |
Drupal.org project_module installs, ranked by usage |
useCoMaintainedModules |
Curated co-maintained modules from Drupal.org API |
useNpmPackages |
npm download counts + GitHub stars |
useActivity |
Merged GitHub + Drupal GitLab activity feed |
useContributions |
Contribution heatmap cells (GitHub + Drupal) |
useDrupalCons |
DrupalCon attendance from Drupal.org profile API |
useOSSProfiles |
Open-source profile aggregates (Drupal, GitHub, npm) |
useContactModal |
Shared useState for the layout-level contact modal (any page can open it) |
nuxt/
app/
app.vue # Root: UApp + head/SEO meta
app.config.ts # Nuxt UI: primary=magenta, neutral=sand
assets/css/main.css # @theme static: magenta/sand palettes + bg-stripes
components/ # App wrappers (StatBand, ActivityFeed, etc.) + DevGrid
composables/ # 10 auto-imported composables (see above)
data/ # Typed TS data (site, stats, projects, modules, etc.)
layouts/default.vue # AppHeader + slot + AppFooter + ContactModal
pages/ # 7 active routes (+ writing, photos disabled)
content/articles/ # @nuxt/content Markdown articles
content.config.ts # Article collection schema (/writing prefix)
tests/ # Vitest unit (100% cov), Playwright visual + SEO
.githooks/ # Mise-driven commit-msg + pre-commit hooks
.gitlab/ # CI pipeline + helper scripts
GitLab CI (.gitlab-ci.yml) runs on MRs with stages lint → test → build → visual → audit → preview. Every install job clones + builds @stuartclark/ui
as a sibling so the link:../../ui dependency resolves. GitHub Actions
(.github/workflows/) mirror the pipeline for when GitHub CI is in use, and
add a drupal job (PHPCS, PHPStan, PHPUnit via DDEV) for the backend.
Netlify deploys automatically from the connected branch — build command
pnpm generate, base directory nuxt, via netlify.toml and the
clone-ui build plugin (see nuxt/netlify/plugins/clone-ui).
7 routes active (home, about, open-source, community, uses, drupalgive, styleguide). Writing and photos are built but disabled for first launch. Live data sources (Drupal.org API, GitHub API) are wired via composables. Remaining work:
- Port real article bodies from Drupal (for when /writing re-enables)
- Generate Playwright visual baselines (x86_64 only — via the
visual:updateCI job) - Curate real photography content (for when /photos re-enables)