1f695d3932
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
4.4 KiB
4.4 KiB
COMPONENT_ARCHITECTURE.md
1. Principles
- Server components by default (Astro components, zero client JS). Client behaviour only via the five behaviour modules bound by
data-*attributes. - Components receive typed props from content + locale; they never fetch or read files. Layouts do the data plumbing.
- One component, both themes, both locales — no
HeroNo.astro, noCardDark.astro. Locale comes in as a prop bundle; theme is pure CSS. - Naming and states follow the Phase 1 component inventory (DESIGN_SYSTEM §5) 1:1 — design system and component tree share vocabulary.
2. Component tree
layouts/
Base html/head/meta, Seo, ThemeScript(inline), fonts preload, SkipLink,
Header, <slot/>, Footer, behaviour-module script tags (deferred)
Page Base + page-title block + prose container (about/contact/cv/colophon)
Home Base + section scaffolding with reveal data-attributes
CaseStudy Base + case-study scaffold (header block, MiniToc, section renderer)
components/core/
Header nav links (slug-map-driven), LangSwitch, ThemeToggle, SplitCvButton;
states: default/scrolled via data-scrolled (observer module)
MobileNav overlay panel (template in DOM, hidden; nav.ts toggles, traps focus)
Footer channels, CV links, language repeat, colophon link
Seo per-page meta, hreflang pairs (slug map), OG, JSON-LD builders
ThemeScript inline no-flash theme init (the one permitted inline script, CSP-hashed)
SkipLink first focusable element
components/ui/ (design-system atoms — dumb, prop-driven)
Chip(kind: fact|status|stack, status?) Button(variant: primary|secondary|ghost)
SplitCvButton(locale, cvMeta) LangSwitch(currentPageId, locale)
ThemeToggle Card(interactive?)
SectionLabel(number, text) TraceMotif(animated?)
FramedImage(media, loading) Tooltip(delay 600ms)
components/home/
Hero(profile) ProofStrip(4 tiles from profile/skills data)
SkillsGrid(skillGroups) ProjectCards(featured projects, homelab slim card)
ExperienceTimeline(entries) AboutTeaser(profile) ContactBand(profile)
components/case-study/
CsHeader(project) TldrBox(project.tldr)
MiniToc(sections, sticky ≥1200) SectionRenderer(section) — markdown → prose
DecisionList(decisions) ArchDiagram(diagram, locale labels)
Gallery(media) + Lightbox host NextPrev(project order)
3. Behaviour modules (the entire client JS surface)
| Module | Binds to | Responsibility | Budget |
|---|---|---|---|
theme.ts |
ThemeToggle + inline init | read/persist preference, data-theme swap, icon morph class |
~1 kB |
nav.ts |
Header/MobileNav | overlay open/close, focus trap, scroll lock, Esc; header data-scrolled flag |
~2 kB |
observer.ts |
[data-reveal], [data-spy] |
shared IntersectionObserver: reveal-once classes, scroll-spy for nav/MiniToc, timeline draw trigger | ~2 kB |
lightbox.ts |
Gallery | open/close/navigate, focus trap, key handling, swipe | ~3 kB |
form.ts |
Contact form | client validation, honeypot timing, POST to relay, A9 success/error panels, copy-to-clipboard buttons | ~3 kB |
Rules: modules are independent (no shared state beyond localStorage keys), fail-silent (feature simply absent if errored), loaded defer, total ≤ 15 kB gzip — well under the 40 kB budget with margin for the language-hint logic (~1 kB, lives in nav.ts).
4. Contracts between design tokens and components
- Components use Tailwind utilities mapped to tokens; no raw hex/px in component code (lint-guarded where feasible).
- State styling via data-attributes (
data-scrolled,data-open,data-revealed,data-status="active") so behaviour modules never touch inline styles — CSS owns all appearance. - Motion classes come exclusively from
motion.css(token durations + reduced-motion layer) — a component never declares its own transition values.
5. Reuse & variant policy
- Case-study template is one component set; JobTrack vs InboxIntel differ only in content. Homelab's
capabilitytemplate reuses CsHeader/SectionRenderer, omits TldrBox/MiniToc/DecisionList. - The 404, hint bar, and colophon reuse existing atoms only — no bespoke components for one-offs unless an atom can't express it (guard against component sprawl in a one-person codebase).