# 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/<id>/<capture>.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).
