diff --git a/.planning/research/ARCHITECTURE.md b/.planning/research/ARCHITECTURE.md
new file mode 100644
index 0000000..be85e46
--- /dev/null
+++ b/.planning/research/ARCHITECTURE.md
@@ -0,0 +1,230 @@
+# Architecture Patterns
+
+**Project:** Portfolio Killian Dalcin — Nuxt 4 SSR Migration
+**Researched:** 2026-04-07
+**Confidence:** HIGH (based on official Nuxt 4 conventions + existing codebase analysis)
+
+---
+
+## Recommended Architecture
+
+```
+[ Browser ]
+ |
+ | HTTP request (SSR-rendered HTML on first load)
+ v
+[ Nuxt 4 Server (Node 22) ]
+ |
+ |-- [ app/ ]
+ | |-- [ pages/ ] File-based routing
+ | |-- [ components/ ] Auto-imported UI components
+ | |-- [ composables/ ] Auto-imported reactive logic
+ | |-- [ layouts/ ] default.vue (header + footer)
+ | |-- [ assets/ ] Static assets (images, fonts)
+ | |-- [ plugins/ ] EmailJS init, gtag init
+ |
+ |-- [ server/ ]
+ | |-- (optional) api/ Not needed — no dynamic data
+ |
+ |-- [ data/ ] Static TS files (projects, testimonials, FAQ, techstack)
+ |
+ |-- nuxt.config.ts Modules, runtime config, i18n, color-mode
+```
+
+Nuxt 4 uses `app/` as the root source directory (replaces Nuxt 3's flat root layout). All pages, components, composables, layouts, and plugins live under `app/`.
+
+---
+
+## Component Boundaries
+
+| Component | Responsibility | Communicates With |
+|-----------|---------------|-------------------|
+| `layouts/default.vue` | Shell: TheHeader + TheFooter + `` | All pages via slot |
+| `TheHeader.vue` | Navigation + locale toggle + color-mode toggle | `useI18n()`, `useColorMode()` |
+| `TheFooter.vue` | Links, copyright, social | `useI18n()` |
+| `pages/index.vue` | Hero + featured projects + services + CTA | `useProjects()`, `useSeoMeta()` |
+| `pages/projects.vue` | Project list + filters | `useProjects()` |
+| `pages/project/[id].vue` | Project detail + image gallery modal | `useProjects()`, `UModal` (Nuxt UI) |
+| `pages/about.vue` | Bio, tech stack | `useI18n()`, static `techstack.ts` |
+| `pages/contact.vue` | UForm + EmailJS send | `useContactForm()`, EmailJS plugin |
+| `pages/fiverr.vue` | Fiverr landing, service cards | `useI18n()`, static config |
+| `pages/formation.vue` | Training/course landing | `useI18n()` |
+| `components/ProjectCard.vue` | Reusable card (list + featured) | Props only, no store |
+| `components/GalleryModal.vue` | UModal wrapper for project images | Emits only, props: images[] |
+| `composables/useProjects.ts` | Filter/search logic over static data | Imports `data/projects.ts` |
+| `composables/useSeoMeta.ts` | Per-route `useSeoMeta()` + JSON-LD | Nuxt built-in `useSeoMeta` |
+| `data/*.ts` | Static typed data — single source of truth | Imported by composables only |
+
+**Rule:** Pages import composables. Composables import data files. Components receive props and emit events. No page imports another page. No component imports data files directly.
+
+---
+
+## Data Flow
+
+```
+data/projects.ts (static TS, bilingual strings)
+ |
+ v
+composables/useProjects.ts
+ - filter by category
+ - find by id
+ - expose featuredProjects, allProjects
+ |
+ v
+pages/index.vue → featuredProjects →
+pages/projects.vue → allProjects + filters →
+pages/project/[id].vue → findById(route.params.id) → detail view +
+```
+
+```
+i18n locale (cookie, SSR-safe)
+ |
+ v
+@nuxtjs/i18n module (strategy: 'prefix_except_default', defaultLocale: 'fr')
+ - /fr/* and /* (default) both work
+ - /en/* for English
+ |
+ v
+All pages and composables via useI18n() (auto-imported by @nuxtjs/i18n)
+```
+
+```
+Color mode (cookie, SSR-safe)
+ |
+ v
+@nuxtjs/color-mode (cookie strategy, no FOUC)
+ |
+ v
+TheHeader.vue toggle → Tailwind dark: classes respond immediately
+```
+
+```
+Contact form
+ |
+ v
+pages/contact.vue → UForm validation → composables/useContactForm.ts
+ |
+ v
+EmailJS plugin (client-side send, no server route needed)
+```
+
+```
+SEO per route
+ |
+ v
+Each page calls useSeoMeta() with i18n-translated values
+ + JSON-LD script tag on pages/index.vue only
+ |
+ v
+@nuxtjs/sitemap generates sitemap.xml from route list at build time
+```
+
+---
+
+## i18n Architecture Decision
+
+Use `@nuxtjs/i18n` v9 with `strategy: 'prefix_except_default'`:
+- French (`fr`) is default, served at `/`, `/projects`, `/project/[id]`, etc.
+- English served at `/en`, `/en/projects`, `/en/project/[id]`, etc.
+- Locale detected from browser `Accept-Language` header on first visit (server-side), then persisted in cookie.
+- **No redirect strategy** — prefix_except_default avoids redirect chains that hurt Core Web Vitals.
+- Translation strings live in `app/i18n/locales/fr.ts` and `app/i18n/locales/en.ts` (migrated from existing `src/locales/`).
+
+The existing `useI18n.ts` composable wrapping vue-i18n is replaced entirely by the `useI18n()` auto-import provided by `@nuxtjs/i18n`.
+
+---
+
+## Static Data Layer
+
+Decision: static TS files in `data/` (not `@nuxt/content`, not `server/api`).
+
+Rationale:
+- All project data is known at build time and changes infrequently.
+- `@nuxt/content` adds markdown parsing overhead and a file-system watcher not needed for typed data.
+- `server/api` routes add network round-trips and cold-start latency for data that never changes.
+- Static TS files are tree-shakeable, fully typed, and zero-overhead.
+
+Migration from `src/data/` is direct: copy files to `data/`, ensure bilingual structure is preserved (FR/EN fields in same object, selected by locale in composables).
+
+---
+
+## Deployment: SSR vs SSG
+
+**Recommendation: `nuxt build` (SSR) not `nuxt generate` (SSG).**
+
+Rationale:
+- i18n with cookie-based locale detection requires server execution to read the cookie and render the correct language on first request. SSG pre-renders all routes in one language only.
+- `useSeoMeta()` with i18n-reactive values requires server-side execution per request.
+- The Docker image runs `node server/index.mjs` (the Nuxt nitro server) — not nginx serving static files.
+- SSR does not meaningfully increase operational complexity for a portfolio (low traffic, single container).
+
+Dockerfile pattern: multi-stage — build stage (`node:22-alpine` + `nuxt build`), production stage (copy `.output/` only, `CMD ["node", ".output/server/index.mjs"]`). No nginx layer needed.
+
+---
+
+## Suggested Build Order (Phase Dependencies)
+
+```
+1. nuxt.config.ts + nuxt.config modules
+ Depends on: nothing
+ Blocks: everything — module config must exist before page/component work
+
+2. data/ migration (static TS files)
+ Depends on: nothing
+ Blocks: composables, all pages that display content
+
+3. composables/ migration
+ Depends on: data/
+ Blocks: pages that use useProjects(), useSeoMeta()
+
+4. layouts/default.vue + TheHeader + TheFooter
+ Depends on: @nuxtjs/i18n working, @nuxtjs/color-mode working
+ Blocks: all page development (every page needs a shell)
+
+5. pages/ migration (one page at a time, start with index.vue)
+ Depends on: composables, layouts, Nuxt UI v3 components
+ Blocks: nothing else — pages are leaf nodes
+
+6. plugins/ (EmailJS, nuxt-gtag)
+ Depends on: contact page, nuxt.config
+ Blocks: contact form functionality, GA tracking
+
+7. Dockerfile + deployment
+ Depends on: all pages complete
+ Blocks: production ship
+```
+
+**Critical dependency:** `nuxt.config.ts` with `@nuxtjs/i18n`, `@nuxtjs/color-mode`, `@nuxt/ui`, and `@nuxtjs/sitemap` must be functional before any page/component work begins. All auto-imports, CSS variables, and the `useI18n()` composable availability depend on this configuration.
+
+---
+
+## Anti-Patterns to Avoid
+
+### Anti-Pattern 1: localStorage in SSR Context
+**What goes wrong:** `localStorage.setItem('locale', ...)` throws ReferenceError on server, causes hydration mismatch.
+**Prevention:** Use only `useCookie()` (Nuxt built-in) for any client-persisted state (locale, color mode). Both `@nuxtjs/i18n` and `@nuxtjs/color-mode` handle this when configured with `cookieName`.
+
+### Anti-Pattern 2: `document.*` or `window.*` at module scope
+**What goes wrong:** Runs during SSR, crashes server render.
+**Prevention:** Wrap in `onMounted()` or use `import.meta.client` guard. The existing router `beforeEach` that calls `document.title` must move to `useSeoMeta()` calls inside each page.
+
+### Anti-Pattern 3: Flat root structure (Nuxt 3 pattern in Nuxt 4)
+**What goes wrong:** Nuxt 4 expects `app/` as source directory. Files at project root are not auto-imported.
+**Prevention:** All Vue files, composables, components go under `app/`. Configure `srcDir: 'app'` in `nuxt.config.ts` (or rely on Nuxt 4 default).
+
+### Anti-Pattern 4: useAsyncData for static data
+**What goes wrong:** `useAsyncData` with a static import adds unnecessary async overhead and serialization. Static data does not need SSR serialization.
+**Prevention:** Import static TS data directly in composables. Reserve `useAsyncData` for genuine async operations (external fetch, server routes).
+
+### Anti-Pattern 5: Per-page SEO via router.beforeEach
+**What goes wrong:** `document.title` manipulation in router guards is SPA-only, invisible to crawlers.
+**Prevention:** Each page calls `useSeoMeta({ title, description, ogTitle, ogDescription, ogImage })` at setup scope — Nuxt handles server-side `
` injection.
+
+---
+
+## Sources
+
+- Nuxt 4 source directory convention: official Nuxt 4 migration guide (app/ directory)
+- Existing codebase analysis: `src/composables/`, `src/router/index.ts`, `src/locales/`
+- PROJECT.md constraints: cookie-only persistence, EmailJS, static TS data, Docker SSR deployment
+- Confidence: HIGH for Nuxt 4 file conventions; HIGH for SSR vs SSG decision given i18n cookie requirement; MEDIUM for @nuxtjs/i18n v9 prefix_except_default (verify exact config key names against current docs before implementing)
diff --git a/.planning/research/FEATURES.md b/.planning/research/FEATURES.md
new file mode 100644
index 0000000..cb45d77
--- /dev/null
+++ b/.planning/research/FEATURES.md
@@ -0,0 +1,155 @@
+# Feature Landscape
+
+**Domain:** Freelance developer portfolio — Nuxt 4 SSR migration
+**Researched:** 2026-04-07
+**Confidence:** MEDIUM — Nuxt UI v3 component coverage from training knowledge (cutoff Aug 2025); Nuxt 4 stable by then. Flag for validation against current ui.nuxt.com docs before implementation.
+
+---
+
+## Table Stakes
+
+Features users and search engines expect. Missing = product feels incomplete or hurts SEO directly.
+
+| Feature | Why Expected | Complexity | Nuxt UI v3 Coverage | Notes |
+|---------|--------------|------------|---------------------|-------|
+| SSR on every route | Google crawls without JS; core migration reason | Low (Nuxt default) | N/A — framework concern | `nuxt build` gives SSR; `nuxt generate` gives SSG. SSR preferred for dynamic og:image |
+| Per-route SEO meta | Each page needs unique title, description, og:image | Low | `useSeoMeta()` (Nuxt built-in) | Already implemented in SPA via custom `useSeo()` — replace with `useSeoMeta()` |
+| JSON-LD structured data | Enables rich results in Google for Person, CreativeWork, ContactPage | Low | `useHead()` with script injection | Already on Home + Contact + Projects — migrate all pages |
+| Sitemap.xml | Required for indexing; Google Search Console standard | Low | `@nuxtjs/sitemap` module | Out-of-the-box with i18n support |
+| robots.txt | Crawl control; expected by all search engines | Trivial | `@nuxtjs/sitemap` handles it | |
+| Dark/light mode — no FOUC | Flash of unstyled content = unprofessional | Medium | `@nuxtjs/color-mode` with cookie strategy | The SPA currently uses localStorage — causes FOUC on SSR. Cookie strategy required |
+| i18n FR/EN | Already a feature; SSR-safe version expected | Medium | `@nuxtjs/i18n` v9 (Nuxt 4 compatible) | Current vue-i18n with localStorage is not SSR-safe; cookie persistence required |
+| Language switch persisted across sessions | Users hate re-setting language on return | Low | `@nuxtjs/i18n` `detectBrowserLanguage` with `cookieSecure` | |
+| Responsive layout — mobile first | 60%+ of portfolio visitors on mobile | Low | Nuxt UI v3 + Tailwind v4 | All Nuxt UI components are mobile-first |
+| Project list with filters | Portfolio core feature; already built | Medium | `UInput` (search), `USelectMenu` or `UTabs` (filter), `UBadge` (category tags) | Current: custom `