Live components
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.
The reusable controls. Everything else on the site is built from these.
Cards and blocks, mostly fed from Strapi.
Not primitives — assemblies worth documenting because they define styling that lives nowhere else.
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.