# DATA_MODEL.md Content model for the bilingual content collections. All schemas zod-validated at build; a schema violation fails CI. (Conceptual field lists — not code.) --- ## 1. Collections overview ``` content/ ├── profile/ singleton per locale (hero, chips, about) ├── projects/ 3 entries × 2 locales (jobtrack, inboxintel, homelab) ├── experience/ 5 entries × 2 locales (council, side roles, education, 2023-now) ├── skills/ 3 group entries × 2 locales └── meta/ colophon page, 404 copy, contact page copy × 2 locales ``` Locale-invariant fields live on the EN entry and merge into NO at load (I18N_SPEC §3). Below, fields marked **[inv]** are invariant; everything else is per-locale prose. ## 2. `profile` (singleton) - `name` [inv], `roleLine`, `heroTagline` (the one-sentence claim), `heroSummary` (≤ 2 lines) - `chips[]`: `{ id [inv], label }` — location, permit, availability, languages - `statusLine` (trace-dot tooltip: "open to work" / "åpen for muligheter") - `aboutParagraphs[]` (3), `interestsLine` - `photo` [inv]: asset ref + per-locale `alt`, `caption` - `links` [inv]: email, linkedinRedirect (`/Linkedin`), gitea, phone - `cv` [inv]: `{ en: {path, sizeKb, updated}, no: {...} }` ## 3. `projects` - `id`/`slug` [inv, per-locale slug via slug map], `name` [inv], `order` [inv] - `status` [inv]: `active | in-development | archived` (drives chip semantics per COLOUR_SYSTEM §3.4) - `valueProp` (one-liner), `cardTeaser` (2 lines for homepage card) - `stack[]` [inv]: `{ name, contextLine? per-locale }` (chip + tooltip) - `links[]` [inv]: `{ type: repo|live|docs, url, public: bool }` (repo links only if/when repos are public — publish decision is content, not code) - `tldr`: `{ what, why, stack, role }` (4 strings) - `sections[]`: ordered `{ anchorId per-locale, heading, body (markdown) }` — canonical section kinds: `problem`, `architecture`, `decisions`, `security`, `screenshots`, `next` - `decisions[]`: structured `{ n, choice, alternative, rationale }` (rendered as the numbered trade-off blocks) - `diagram` [inv]: SVG asset ref + per-locale `title`, `desc` (a11y), `nodes[]` label overrides per locale - `media[]` [inv refs]: `{ image, width, height, per-locale alt + caption, order }` — dimensions mandatory (CLS 0) - `template` [inv]: `case-study | capability` (homelab uses the lighter template) - `summaryOnly?` (NO-side flag per I18N_SPEC §4.2) ## 4. `experience` - `id` [inv], `employer` [inv], `location` [inv], `period` [inv]: `{ from, to|null }` - `role`, `summary`, `highlights[]` (featured only) - `emphasis` [inv]: `featured | compact` - `progression?` [inv]: for the council entry — `[{from: 2015, to: 2017, label-per-locale: "Apprentice"}, {from: 2017, to: 2023, label: "System Developer"}]` - `anchor?` — the confirmed concrete bullet (streetlight/highways system) is content, phrased per CANONICAL_CONTENT - Entries: `warwickshire` (featured), `royal-vapes`, `hodcarrier`, `nuffield` (compact, `alongside: true` [inv] flag renders the "alongside" grouping), `education`, `norway-now` (2023–, norskkurs + products) ## 5. `skills` - `id` [inv], `order` [inv], `title`, `contextLine`, `skills[]`: `{ name [inv], tooltip? }` - Groups: `development`, `devops-infrastructure`, `practices` ## 6. Page-meta (every routable page) - `title`, `description` (≤155 chars), `ogImage` [generated per locale], `pageId` [inv → slug map join] - Case studies additionally emit structured-data fields (SEO_SPEC §3) ## 7. Asset conventions - Screenshots: `assets/projects//.png` source of truth at 2×, seeded demo data only (Phase 3 prerequisite); pipeline emits AVIF/WebP/fallback + srcset. - Diagrams: hand-authored SVGs following the design-system diagram style; text within diagrams uses per-locale label injection at build (single SVG source, two rendered outputs) — avoids maintaining twin diagrams. - Fonts/CVs/favicons in `public/` (unprocessed, stable URLs). ## 8. Validation rules (CI) 1. Locale parity (I18N_SPEC §4.1). 2. Every `media[]` entry has dimensions + both alts. 3. Every project has ≥3 `decisions[]` and a `security` section (the site's promise to P2 is structural). 4. `status` transitions logged in git only — no "archived" entry may appear on the homepage cards (query rule). 5. Description lengths, title lengths enforced (SEO). 6. All external `links[].url` respond 200 in a scheduled link-check workflow (not per-commit).