docs(phase-02): UI design contract for SSR Shell
This commit is contained in:
@@ -0,0 +1,262 @@
|
|||||||
|
---
|
||||||
|
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*
|
||||||
Reference in New Issue
Block a user