← Design system

Live components

13 components, extracted from codecave.pro

Twelve of the thirteen stories mount the real components — the verbatim .vue sources compiled by tools/build-storybook.mjs and rendered by the vendored Vue and GSAP runtimes against the site's own Tailwind theme (tw-bridge.css). The thumbnails below are those same mounts, miniaturized. The one .astro component is a hand-translated HTML port, and every gap between this storybook and production is written down on its page.

12 live Vue mounts 1 Astro port (HTML) Astro 7 · Vue 3.5 · Tailwind 4.1

Primitives

The reusable controls. Everything else on the site is built from these.

Content

Cards and blocks, mostly fed from Strapi.

Compositions

Not primitives — assemblies worth documenting because they define styling that lives nowhere else.

What this storybook found

55 findings across the thirteen components — 30 defects and 25 design observations — each recorded on its own story page. The six that would change behavior in production:

Two components animate against a variable they never define. Checkbox.vue:75 and Radio.vue:61 both declare transition: var(--default-transition-duration) transform ease-in-out;, and --default-transition-duration appears nowhere in the project's own code. It resolves anyway — to 150ms from Tailwind's default theme, which the build emits because transition-colors is in use. Lift either component out of a Tailwind build and the shorthand collapses, snapping the indicator instead of easing it.

tailwind.config.ts is never loaded. The project is Tailwind 4.1.13 configured CSS-first (@import "tailwindcss" plus @theme in global.css). In v4 a JS config is only read when a stylesheet names it with @config, and no file does. So darkMode: 'class', the content globs and the theme.extend block have no effect — including the *.stories exclusion that suggests a Storybook was once planned.

Button's isDisabled does not disable anything. It sets opacity and a cursor. The disabled attribute is never bound, so the control stays clickable and keyboard-reachable — and as a link it still navigates.

PainPointsItem renders unsanitized CMS markdown through v-html. Markdown permits raw HTML and no sanitizer runs, so the field is an XSS sink gated only by Strapi access control.

TextField syncs its model on change, not input. The parent's v-model only updates on blur, while InputText in the same form updates per keystroke.

Two dead styles. Radio's secondary hover needs a group ancestor that nothing renders, and Button's type prop is declared but never bound.