Files
portfolio/.planning/phases/02-ssr-shell/02-UI-SPEC.md
T

263 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
phase: 2
slug: ssr-shell
status: draft
shadcn_initialized: false
preset: none
created: 2026-04-08
---
# Phase 2 — UI Design Contract: SSR Shell
> Visual and interaction contract for Phase 2: SSR Shell.
> Generated by gsd-ui-researcher, verified by gsd-ui-checker.
---
## Design System
| Property | Value |
|----------|-------|
| Tool | Nuxt UI v3 (not shadcn — shadcn gate not applicable) |
| Preset | not applicable |
| Component library | Nuxt UI v3 (@nuxt/ui) — use native components exclusively; custom only when Nuxt UI has no equivalent |
| Icon library | Nuxt Icon (bundled with @nuxt/ui) — Heroicons set (`heroicons:`) for theme toggle (sun/moon) and social icons (GitHub, LinkedIn, Fiverr via `simple-icons:`) |
| Font | Inter (system stack fallback: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif) — sourced: `--font-family-sans` from existing `main.css` |
**Source:** D-17, D-18, D-20 from 02-CONTEXT.md. No components.json found; shadcn gate skipped.
---
## Spacing Scale
Declared values (multiples of 4 only). Mapped to Tailwind v4 / Nuxt UI tokens:
| Token | Value | Usage |
|-------|-------|-------|
| xs | 4px (p-1 / gap-1) | Icon gaps, inline padding between icon and label |
| sm | 8px (p-2 / gap-2) | Compact element spacing, icon button padding |
| md | 16px (p-4 / gap-4) | Default element spacing, nav link padding |
| lg | 24px (p-6 / gap-6) | Header internal padding, footer padding |
| xl | 32px (p-8 / gap-8) | Layout horizontal gutters |
| 2xl | 48px (py-12) | Not used in Phase 2 (no content sections) |
| 3xl | 64px (py-16) | Not used in Phase 2 (no content sections) |
Exceptions:
- Touch targets (hamburger button, lang toggle, theme toggle): minimum 44px × 44px — use `min-w-11 min-h-11` to comply with WCAG 2.5.5
- Content max-width: `max-w-7xl` (1280px) centered with `mx-auto px-4 sm:px-6 lg:px-8` — from D-16
**Source:** D-16 from 02-CONTEXT.md; existing spacing tokens in src/assets/main.css.
---
## Typography
Phase 2 covers only the header and footer — no page content. Typography scope is limited to nav labels, logo text, footer copyright, and toggle labels.
| Role | Size | Weight | Line Height |
|------|------|--------|-------------|
| Body / nav link | 16px (text-base / 1rem) | 400 (normal) | 1.5 |
| Label / small copy | 14px (text-sm / 0.875rem) | 400 (normal) | 1.5 |
| Logo name | 18px (text-lg / 1.125rem) | 600 (semibold) | 1.2 |
| Footer copyright | 14px (text-sm / 0.875rem) | 400 (normal) | 1.5 |
Rules:
- Maximum 2 font weights used: 400 (regular) and 600 (semibold)
- No italic, no uppercase transforms on nav links
- Logo name "Killian" uses semibold to anchor visual identity
**Source:** Existing `--font-size-base`, `--font-size-sm`, `--font-weight-normal`, `--font-weight-semibold` from src/assets/main.css.
---
## Color
### Light Mode
| Role | Value | Usage |
|------|-------|-------|
| Dominant (60%) | `#ffffff` | Page background, header background |
| Secondary (30%) | `#f3f4f6` (gray-100) | Footer background band, subtle separators |
| Accent (10%) | `#85cb85` | CTA buttons, active nav link underline, hover states on nav links, social icon hover |
| Destructive | `#ef4444` | Not used in Phase 2 — no destructive actions |
### Dark Mode (default for new visitors — D-08)
| Role | Value | Usage |
|------|-------|-------|
| Dominant (60%) | `#111827` (gray-900) | Page background, header background |
| Secondary (30%) | `#1f2937` (gray-800) | Footer background band, drawer background |
| Accent (10%) | `#a3d6a3` | CTA buttons, active nav link underline, hover states on nav links, social icon hover (lightened for dark bg) |
| Destructive | `#ef4444` | Not used in Phase 2 |
### Accent Reserved For (explicit list)
1. Active nav link — bottom border/underline indicator
2. Nav link hover state — text color change
3. Language toggle hover state — text color
4. Theme toggle icon hover state — icon color
5. Social icon links in footer — hover color
Accent is NOT used for: passive text, borders, backgrounds, icons in default (non-hover) state.
### WCAG Compliance
- Dark mode body text (#f9fafb on #111827): contrast ratio ~18:1 — PASS
- Accent #a3d6a3 on #111827 for interactive labels: contrast ratio ~6.2:1 — PASS (4.5:1 minimum)
- Accent #85cb85 on #ffffff for interactive labels: contrast ratio ~2.5:1 — FAIL for text; use as decoration/border only in light mode. Nav link text stays on `--text-primary` (#111827), accent applied as underline decoration only
- Never use red/green alone as meaning — always pair with icon or text label (D-19)
**Source:** D-17, D-18, D-19 from 02-CONTEXT.md; existing CSS variables from src/assets/main.css.
---
## Component Inventory
Components delivered in this phase only:
### AppHeader (COMP-05)
- Container: `<header>` with `position: sticky; top: 0; z-index: 1020`
- Inner wrapper: `max-w-7xl mx-auto px-4 sm:px-6 lg:px-8` — height 64px (`h-16`)
- Layout: flex row, `items-center justify-between`
- Left: Logo (40×40px image + "Killian" text)
- Center: Desktop nav links (`hidden md:flex gap-6`) using `UNavigationMenu` or native `<nav>` with `<NuxtLink>` — active link uses `aria-current="page"` + accent underline
- Right: LanguageToggle (FR/EN text button) + ThemeToggle (icon button) + HamburgerButton (mobile only, `md:hidden`)
- Background: `bg-white dark:bg-gray-900` with subtle bottom border `border-b border-gray-200 dark:border-gray-800`
### LanguageToggle (inside COMP-05)
- Renders as a `<button>` displaying current locale code in uppercase: "FR" or "EN"
- Click switches locale (D-04 — text toggle, no dropdown, no flags)
- Size: minimum 44×44px touch target
- Style: ghost button, no background. Accent color on hover.
### ThemeToggle (inside COMP-05)
- Renders `heroicons:sun` (light mode active) or `heroicons:moon` (dark mode active)
- Icon size: 20px (w-5 h-5)
- Click toggles `@nuxtjs/color-mode` (D-09)
- Transition: `transition-colors duration-300` on icon swap — no flash
- Size: minimum 44×44px touch target
### MobileDrawer (inside COMP-05)
- Uses `UDrawer` component from Nuxt UI v3 (D-02)
- Opens from left, triggered by hamburger icon (`heroicons:bars-3`)
- Close icon: `heroicons:x-mark` inside drawer
- Contains: nav links (stacked, full-width) + LanguageToggle + ThemeToggle
- Overlay: `bg-black/50` backdrop
### AppFooter (COMP-06)
- Single band: `py-6 bg-gray-100 dark:bg-gray-800`
- Layout: flex row on md+, flex column on mobile — `items-center justify-between gap-4`
- Left: copyright text — "© 2026 Killian Dalcin"
- Right: social icon links — GitHub (`simple-icons:github`), LinkedIn (`simple-icons:linkedin`), Fiverr (`simple-icons:fiverr`)
- Icon size: 20px (w-5 h-5). Hover: accent color with `transition-colors duration-150`
- All links open in `_blank` with `rel="noopener noreferrer"` and `aria-label`
---
## Interaction States
All interactive elements must implement all four states:
| Element | Default | Hover | Focus | Active |
|---------|---------|-------|-------|--------|
| Nav link | `text-gray-700 dark:text-gray-300` | accent color text | `focus-visible:ring-2 ring-primary-500 ring-offset-2` | accent underline |
| Active nav link | accent underline `border-b-2 border-primary-500` | — | same focus ring | — |
| Language toggle | `text-gray-700 dark:text-gray-300 font-medium` | accent color | focus ring | — |
| Theme toggle icon | `text-gray-600 dark:text-gray-400` | accent color | focus ring | — |
| Social icon | `text-gray-500 dark:text-gray-400` | accent color | focus ring | scale-110 |
| Hamburger button | `text-gray-700 dark:text-gray-300` | accent color | focus ring | — |
Focus ring spec: `outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2` — keyboard navigation only, never on click.
---
## Copywriting Contract
Phase 2 scope: header nav labels, footer copyright, mobile drawer, language/theme toggles, ARIA labels.
| Element | Copy (FR) | Copy (EN) |
|---------|-----------|-----------|
| Logo aria-label | "Killian Dalcin — Développeur Full Stack — Retour à l'accueil" | "Killian Dalcin — Full Stack Developer — Back to homepage" |
| Nav: Home | "Accueil" | "Home" |
| Nav: Projects | "Projets" | "Projects" |
| Nav: About | "À propos" | "About" |
| Nav: Contact | "Contact" | "Contact" |
| Nav: Fiverr | "Fiverr" | "Fiverr" |
| Nav: Formation | "Formation" | "Training" |
| Hamburger open aria-label | "Ouvrir le menu de navigation" | "Open navigation menu" |
| Hamburger close aria-label | "Fermer le menu de navigation" | "Close navigation menu" |
| Drawer close button aria-label | "Fermer le menu" | "Close menu" |
| Language toggle aria-label | "Changer la langue — actuellement Français" | "Change language — currently English" |
| Theme toggle aria-label (dark) | "Activer le mode clair" | "Switch to light mode" |
| Theme toggle aria-label (light) | "Activer le mode sombre" | "Switch to dark mode" |
| Footer copyright | "© 2026 Killian Dalcin" | "© 2026 Killian Dalcin" |
| GitHub icon aria-label | "GitHub de Killian Dalcin (nouvelle fenêtre)" | "Killian Dalcin on GitHub (opens in new tab)" |
| LinkedIn icon aria-label | "LinkedIn de Killian Dalcin (nouvelle fenêtre)" | "Killian Dalcin on LinkedIn (opens in new tab)" |
| Fiverr icon aria-label | "Fiverr de Killian Dalcin (nouvelle fenêtre)" | "Killian Dalcin on Fiverr (opens in new tab)" |
Destructive confirmation: none — Phase 2 has no destructive actions.
Empty state: none — Phase 2 has no data-driven content.
Error state: none — Phase 2 has no form submissions or async data.
**Source:** D-04, D-05, COMP-05, COMP-06 from 02-CONTEXT.md. Translations to be added to fr.json / en.json under keys `nav.*`, `footer.*`, `a11y.*`.
---
## SEO Contract (server-rendered metadata)
Each route in Phase 2 must include the following in SSR HTML output (verified by `curl`):
| Tag | Requirement |
|-----|-------------|
| `<title>` | Per-route via `useSeoMeta({ title })` |
| `<meta name="description">` | Per-route, max 160 chars |
| `<meta property="og:title">` | Same as title |
| `<meta property="og:description">` | Same as description |
| `<meta property="og:image">` | Absolute URL via nuxt-og-image (D-12) |
| `<link rel="canonical">` | Absolute URL for current locale route |
| `<link rel="alternate" hreflang="fr">` | FR URL |
| `<link rel="alternate" hreflang="en">` | EN URL |
| JSON-LD script | Homepage only: `Person` + `ProfessionalService` schema (D-11) |
Phase 2 uses placeholder routes (no real pages yet) — SEO metadata is wired but content is minimal stubs until Phase 3 fills pages.
---
## Registry Safety
| Registry | Blocks Used | Safety Gate |
|----------|-------------|-------------|
| Nuxt UI v3 (@nuxt/ui) | UDrawer, UNavigationMenu, UButton, UIcon | Built-in module — no registry vetting required |
| shadcn | none | Not used |
| Third-party | none | Not applicable |
No third-party component registries are used in this phase. All components come from `@nuxt/ui` which is installed as a verified Nuxt module.
---
## Implementation Notes for Executor
1. **No components.json** — shadcn is not used. All component imports are via Nuxt UI v3 auto-imports (`UDrawer`, `UButton`, etc.) or native HTML.
2. **app.config.ts** must define primary color token mapping to `#85cb85` (light) / `#a3d6a3` (dark) using Nuxt UI v3 token format.
3. **@nuxtjs/color-mode** must be added to `nuxt.config.ts` modules for FOUC-free dark mode persistence. Default: `dark`.
4. **nuxt-og-image** must be added to `nuxt.config.ts` modules (D-12 advanced from v2).
5. Header `z-index` must be `1020` (`z-sticky`) to sit above page content but below modals (Phase 3).
6. The drawer overlay must trap focus while open (keyboard accessibility).
7. Lang toggle button must call `setLocale()` from `@nuxtjs/i18n` composable.
---
## Checker Sign-Off
- [ ] Dimension 1 Copywriting: PASS
- [ ] Dimension 2 Visuals: PASS
- [ ] Dimension 3 Color: PASS
- [ ] Dimension 4 Typography: PASS
- [ ] Dimension 5 Spacing: PASS
- [ ] Dimension 6 Registry Safety: PASS
**Approval:** pending
---
*Phase: 02-ssr-shell*
*UI-SPEC generated: 2026-04-08*