🎉 Init monorepo Nuxt 4 + Laravel 12 (Story 1.1)

Setup complet de l'infrastructure projet :
- Frontend Nuxt 4 (SSR, TypeScript, i18n, Pinia, TailwindCSS)
- Backend Laravel 12 API-only avec middleware X-API-Key et CORS
- Design tokens (sky-dark, sky-accent, sky-text) et polices (Merriweather, Inter)
- Documentation planning et implementation artifacts

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-05 02:08:56 +01:00
commit ec1ae92799
116 changed files with 55669 additions and 0 deletions

View File

@@ -0,0 +1,439 @@
---
stepsCompleted: [1, 2, 3, 4]
inputDocuments:
- docs/prd-gamification.md
- docs/planning-artifacts/ux-design-specification.md
- docs/brainstorming-gamification-2026-01-26.md
workflowType: 'architecture'
project_name: 'skycel'
user_name: 'Célian'
date: '2026-02-01'
---
# Architecture Decision Document
_This document builds collaboratively through step-by-step discovery. Sections are appended as we work through each architectural decision together._
## Project Context Analysis
### Requirements Overview
**Functional Requirements:**
14 FRs couvrant : double entrée visiteur (FR1), transitions animées seamless (FR2), narrateur-guide contextuel (FR3), carte interactive Konva.js (FR4), arbre de compétences vis.js (FR5), compétences cliquables → projets (FR6), dialogues PNJ typewriter (FR7), barre de progression globale (FR8), chemins narratifs multiples 4-8 parcours (FR9), challenge/puzzle avant contact (FR10), easter eggs cachés (FR11), sauvegarde LocalStorage (FR12), bilingue FR/EN avec détection URL (FR13), contact comme récompense narrative (FR14).
Architecturalement, ces FRs dessinent un système à **forte interactivité côté client** avec un **backend API relativement simple** (CRUD + contact + progression). La complexité réside dans l'orchestration frontend : état de progression, navigation narrative adaptative, et composants lourds en lazy-loading.
**Non-Functional Requirements:**
- **NFR1** : Bundle JS ≤ 170kb gzip (Nuxt + Konva + vis.js) avec lazy-loading
- **NFR2** : LCP < 2.5s sur 3G
- **NFR3** : Responsive avec expérience mobile adaptée (carte simplifiée)
- **NFR4** : Navigateurs modernes (Chrome, Firefox, Safari, Edge — 2 dernières versions)
- **NFR5** : URLs SEO-friendly, contenu accessible aux crawlers (SSR)
- **NFR6** : Respect `prefers-reduced-motion` (accessibilité animations)
- **NFR7** : i18n SSR via @nuxtjs/i18n avec fichiers JSON
- **NFR8** : Images WebP avec lazy loading
Les NFRs les plus structurants pour l'architecture sont le budget JS (NFR1), le SSR pour SEO (NFR5/NFR7), et le responsive avec deux paradigmes de navigation (NFR3).
**Scale & Complexity:**
- Domaine principal : Full-stack web (Nuxt 4 SSR + Laravel 12 API REST)
- Niveau de complexité : **Moyenne-haute** — richesse des interactions frontend, faible volume de données
- Composants architecturaux estimés : ~15-20 (pages, composants custom, stores, composables, API endpoints, modèles)
### Technical Constraints & Dependencies
- **Nuxt 4 SSR** : Impose une architecture hybride serveur/client avec nouvelle structure `app/`. Les composants Konva.js et vis.js doivent être exclusivement client-side (`.client.vue`)
- **Laravel 12 API-only** : Backend découplé, communication via API REST JSON. CORS requis. Upgrade vers Laravel 13 prévu dès sa sortie stable (Q1 2026)
- **MariaDB** : Schéma relationnel défini dans le brainstorming (7 tables). Migration vers Eloquent ORM
- **Budget JS 170kb** : Konva (~50kb) + vis-network (~50kb) + Nuxt (~50kb) = marge très faible. Stratégie de lazy-loading critique
- **Monorepo** : `/frontend` (Nuxt) + `/api` (Laravel) dans le même repo — décision validée pour un projet solo avec frontend/backend fortement couplés
- **Hébergement dual** : Node.js pour Nuxt SSR + PHP 8.2+ pour Laravel — deux runtimes distincts
### Cross-Cutting Concerns Identified
1. **Gestion d'état & progression** : Le store Pinia `useProgressionStore` irrigue toute l'application — carte, narrateur, barre XP, déblocage contact, easter eggs. Doit être persisté (LocalStorage) et compatible SSR
2. **Internationalisation (i18n)** : Bilingue FR/EN à travers toutes les couches — SSR, API responses, textes narrateur, dialogues PNJ, challenges. Stratégie `prefix_except_default` pour URLs
3. **Système de héros** : Le choix du personnage (Recruteur/Client/Dev) impacte le vouvoiement, le ton du narrateur, le contenu des challenges, et potentiellement l'ordre des suggestions. Transversal à toute la couche de présentation
4. **Accessibilité (WCAG AA)** : Contraste, navigation clavier, `prefers-reduced-motion`, screen readers, skip links. Impacte chaque composant custom (carte, PNJ, narrateur, skill tree)
5. **Performance & lazy-loading** : Composants lourds (Konva, vis.js) chargés à la demande. Images WebP, fonts variables, SSR pour le premier rendu. Budget strict
## Starter Template Evaluation
### Primary Technology Domain
Full-stack web (Nuxt 4 SSR + Laravel API REST) basé sur l'analyse des exigences projet.
### Starter Options Considered
| Option | Version | Statut | Notes |
|--------|---------|--------|-------|
| **Nuxt 4** | 4.3+ | Stable (juillet 2025) | Nouvelle structure `app/`, TypeScript strict, data fetching amélioré |
| ~~Nuxt 3~~ | 3.21 | EOL juillet 2026 | Écarté — fin de vie trop proche pour un nouveau projet |
| **Laravel 12** | 12.x | Stable (février 2025) | Release de maintenance, support bugs jusqu'en août 2026 |
| Laravel 13 | 13.x | Imminent (Q1 2026) | PHP 8.3+, support jusqu'en 2028. Upgrade depuis 12 attendu facile |
### Selected Starters
**Frontend : Nuxt 4**
**Rationale :** Version stable et actuelle. Structure `app/` plus propre, meilleur TypeScript, Nuxt 3 en fin de vie. Tous les modules clés (@nuxtjs/i18n, @pinia/nuxt, @nuxtjs/tailwindcss, nuxt/image, @nuxtjs/sitemap) sont compatibles Nuxt 4.
**Initialization Command :**
```bash
npx nuxi@latest init frontend
```
**Backend : Laravel 12 (upgrade vers 13 dès sa sortie)**
**Rationale :** Stable et maintenu. Laravel 13 est imminent mais pas encore sorti. Démarrer sur 12 avec PHP 8.2+ permet de commencer immédiatement. L'upgrade vers 13 sera minimal.
```bash
composer create-project laravel/laravel api
```
### Architectural Decisions Provided by Starters
**Nuxt 4 fournit :**
- Structure `app/` avec auto-imports (components, composables, utils)
- SSR natif avec hydration client
- Routing fichier-based (`app/pages/`)
- Nitro comme serveur (build optimisé)
- TypeScript par défaut
- DevTools intégrés
**Laravel 12 fournit :**
- Structure MVC avec Eloquent ORM
- Routing API (`routes/api.php`)
- Migration system pour le schéma BDD
- Form Requests pour la validation
- API Resources pour les transformations JSON
- Rate limiting, CORS, middleware stack
- Pest/PHPUnit pour les tests
### Structure Monorepo (Nuxt 4)
```
skycel/
├── frontend/ # Application Nuxt 4
│ ├── app/ # Code applicatif (structure Nuxt 4)
│ │ ├── pages/
│ │ ├── components/
│ │ ├── composables/
│ │ ├── stores/
│ │ ├── layouts/
│ │ ├── plugins/
│ │ ├── assets/
│ │ └── app.vue
│ ├── server/ # Server routes/API Nuxt (si besoin)
│ ├── public/
│ ├── i18n/
│ ├── nuxt.config.ts
│ └── package.json
├── api/ # Backend Laravel 12
│ ├── app/
│ ├── database/
│ ├── routes/
│ ├── config/
│ ├── tests/
│ └── composer.json
├── docs/ # Documentation projet
└── README.md
```
### Third-Party Services
| Service | Solution | Intégration |
|---------|----------|-------------|
| **Email** | PHPMailer via Laravel Mail | Backend |
| **Anti-spam** | Google reCAPTCHA v3 | Frontend + Backend validation |
| **Images** | `nuxt/image` + Sharp | Frontend (local) |
| **Sitemap** | `@nuxtjs/sitemap` | Frontend |
| **Analytics** | Matomo (self-hosted) | Frontend script |
| **Error tracking** | Sentry | Frontend + Backend |
| **Monitoring** | Uptime Kuma | Externe (existant) |
| **Backups BDD** | Script cron mysqldump | Serveur |
## Core Architectural Decisions
### Decision Priority Analysis
**Critical Decisions (Block Implementation) :**
- Stratégie i18n hybride (JSON statique + table translations centralisée)
- Architecture API REST avec API Key + CORS strict
- Structure composants frontend (ui / feature / layout)
- Stratégie lazy-loading pour respecter le budget JS ≤ 170kb
- Store Pinia de progression avec persistance LocalStorage
**Important Decisions (Shape Architecture) :**
- Abandon Swup.js → transitions Nuxt natives + GSAP
- Double validation frontend + backend
- Format de réponse API Resources avec enveloppe standard
- Bandeau RGPD intégré à l'immersion narrative
- Environnement staging avec sous-domaine
**Deferred Decisions (Post-MVP) :**
- Endpoints CRUD admin protégés par tokens exclusifs (après MVP)
- Upgrade Laravel 12 → 13 (dès sortie stable)
- Sauvegarde cloud de progression via email (Phase 2)
### Data Architecture
**Stratégie i18n : Hybride**
- **Contenu statique UI** : Fichiers JSON via @nuxtjs/i18n (`i18n/fr.json`, `i18n/en.json`). Labels, boutons, messages d'interface, textes de navigation
- **Contenu dynamique** : Table `translations` centralisée en MariaDB. Les tables métier (projects, skills, testimonials, narrator_texts, easter_eggs) stockent des clés i18n (`title_key`, `text_key`). La table `translations` contient les valeurs par langue
- **Rationale** : Flexibilité pour ajouter une langue sans modifier le schéma. Séparation claire entre UI (déployée avec le frontend) et contenu (géré via API/BDD)
**Schéma table translations :**
```sql
CREATE TABLE translations (
id INT AUTO_INCREMENT PRIMARY KEY,
lang VARCHAR(5) NOT NULL,
key_name VARCHAR(255) NOT NULL,
value TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY unique_translation (lang, key_name),
INDEX idx_lang (lang)
);
```
**Cache : File cache Laravel**
- Driver : `file` (config `CACHE_DRIVER=file`)
- Suffisant pour la volumétrie d'un portfolio (faible nombre de requêtes, peu de données)
- Pas de dépendance externe (Redis non requis)
**Validation : Double validation**
- **Frontend (Nuxt)** : Validation légère en temps réel pour l'UX (champs requis, format email, longueur). Via les composables Vue ou VeeValidate
- **Backend (Laravel)** : Validation complète via Form Requests. Source de vérité pour la sécurité. Rejette toute donnée invalide avec réponse 422
### Authentication & Security
**Protection formulaire de contact :**
- Google reCAPTCHA v3 (invisible, score-based) côté frontend
- Honeypot field (champ caché) comme seconde couche
- Rate limiting Laravel : 5 requêtes/minute par IP sur `POST /api/contact`
**Sécurité API :**
- **API Key** : Token partagé entre Nuxt et Laravel via header `X-API-Key`. Stocké dans les `.env` des deux applications. Middleware Laravel vérifie la présence et validité du token sur chaque requête
- **CORS strict** : N'accepte que le domaine du frontend (`Access-Control-Allow-Origin: https://skycel.fr`)
- **Endpoints CRUD admin** (post-MVP) : Protégés par tokens exclusifs différents de l'API Key publique. Middleware dédié avec permissions granulaires
**Protection des données visiteur :**
- Session ID : UUID v4 généré côté client, stocké en LocalStorage
- Email : Optionnel, uniquement pour la sauvegarde cloud de progression
- Pas de tracking sans consentement
**RGPD :**
- Bandeau de consentement intégré à l'immersion narrative (dialogue PNJ ou narrateur araignée, style "pacte d'aventurier")
- Consentement requis avant activation de Matomo et stockage LocalStorage de progression
- État du consentement stocké dans le store Pinia (`consentGiven`) et persisté en LocalStorage
### API & Communication Patterns
**Design pattern : REST classique**
Endpoints publics (lecture) :
| Méthode | Endpoint | Description |
|---------|----------|-------------|
| `GET` | `/api/projects` | Liste des projets |
| `GET` | `/api/projects/{slug}` | Détail d'un projet |
| `GET` | `/api/skills` | Arbre de compétences |
| `GET` | `/api/testimonials` | Témoignages PNJ |
| `GET` | `/api/narrator/{context}` | Textes narrateur par contexte |
| `GET` | `/api/easter-eggs` | Métadonnées easter eggs (pas les réponses) |
| `GET` | `/api/progress/{session_id}` | Récupérer progression |
| `POST` | `/api/progress` | Sauvegarder progression |
| `POST` | `/api/contact` | Formulaire contact (rate limited + reCAPTCHA) |
Endpoints admin CRUD (post-MVP, tokens exclusifs) :
| Méthode | Endpoint | Description |
|---------|----------|-------------|
| `POST` | `/api/admin/projects` | Créer un projet |
| `PUT` | `/api/admin/projects/{id}` | Modifier un projet |
| `DELETE` | `/api/admin/projects/{id}` | Supprimer un projet |
| _idem_ | _pour skills, testimonials, narrator, easter-eggs_ | _CRUD complet_ |
**Gestion de la langue : Header `Accept-Language`**
- Le frontend Nuxt envoie `Accept-Language: fr` ou `Accept-Language: en` dans chaque requête API
- Middleware Laravel extrait la langue et la passe au query builder pour joindre la table `translations`
- Fallback : `fr` si header absent ou langue non supportée
**Format de réponse : Laravel API Resources**
Réponse standard :
```json
{
"data": [
{ "id": 1, "slug": "skycel", "title": "Skycel Portfolio", "..." : "..." }
],
"meta": {
"total": 5,
"lang": "fr"
}
}
```
**Gestion d'erreurs : Format standard**
```json
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Le champ email est requis",
"details": {}
}
}
```
Codes HTTP : 400 (bad request), 401 (API key invalide), 404 (not found), 422 (validation), 429 (rate limit), 500 (erreur serveur)
### Frontend Architecture
**Architecture des composants :**
```
app/components/
├── ui/ # Composants atomiques réutilisables
│ ├── BaseButton.vue
│ ├── BaseBadge.vue
│ ├── BaseModal.vue
│ └── ...
├── feature/ # Composants métier
│ ├── NarratorDialogue.vue
│ ├── PnjCard.vue
│ ├── SkillTree.client.vue # Client-only (vis-network)
│ ├── InteractiveMap.client.vue # Client-only (Konva.js)
│ ├── ProgressBar.vue
│ ├── HeroSelector.vue
│ ├── ChallengePanel.vue
│ └── EasterEgg.vue
├── layout/ # Structure de page
│ ├── AppHeader.vue
│ ├── AppFooter.vue
│ ├── ConsentBanner.vue # RGPD immersif
│ └── NarratorOverlay.vue
```
**Stratégie lazy-loading :**
| Couche | Chargement | Poids estimé (gzip) |
|--------|------------|---------------------|
| Nuxt core + Vue + Pinia | Immédiat | ~50kb |
| TailwindCSS (purgé) | Immédiat | ~10kb |
| Pages | Lazy (navigation) | ~5-10kb/page |
| Konva.js | Lazy (page carte desktop uniquement) | ~50kb |
| vis-network | Lazy (page skills uniquement) | ~50kb |
| GSAP | Lazy (première animation complexe) | ~25kb |
| reCAPTCHA v3 | Lazy (page contact uniquement) | Externe |
Budget initial (premier chargement) : **~60-70kb gzip** — bien sous le budget de 170kb. Les librairies lourdes ne se chargent qu'à la demande.
**Store Pinia `useProgressionStore` :**
```typescript
interface ProgressionState {
sessionId: string // UUID v4
hero: 'recruteur' | 'client' | 'dev' | null
currentPath: string // Chemin narratif actuel
visitedSections: string[] // Sections visitées
completionPercent: number // 0-100
easterEggsFound: string[] // Slugs des easter eggs trouvés
challengeCompleted: boolean
contactUnlocked: boolean
narratorStage: number // 1-5 (évolution de l'araignée)
choices: Record<string, string> // Choix narratifs
consentGiven: boolean // RGPD
}
```
- Persistance : `pinia-plugin-persistedstate` → LocalStorage
- Synchronisation API : `POST /api/progress` déclenché quand le visiteur fournit son email (sauvegarde cloud optionnelle)
- Compatibilité SSR : Le store s'initialise vide côté serveur, se réhydrate côté client depuis LocalStorage
**Transitions et animations :**
- **Transitions de page** : Système natif Nuxt/Vue (`<NuxtPage>` + `<Transition>`) avec CSS animations
- **Animations complexes** : GSAP (narrateur araignée, révélation progressive, transitions de zone immersives)
- **Swup.js** : Abandonné — redondant avec les transitions Nuxt natives et potentiellement conflictuel
- **`prefers-reduced-motion`** : Respecté via media query, animations réduites ou désactivées
### Infrastructure & Deployment
**Architecture serveur :**
```
┌─────────────────────────┐
│ Nginx (port 80/443) │
│ SSL + gzip + cache │
└─────────┬───────────────┘
┌─────────────┴─────────────┐
│ │
▼ ▼
┌───────────────────┐ ┌───────────────────┐
│ Node.js :3000 │ │ PHP-FPM :9000 │
│ Nuxt 4 SSR │ │ Laravel 12 API │
└───────────────────┘ └───────────────────┘
┌───────────────────┐
│ MariaDB :3306 │
└───────────────────┘
```
- Nginx dispatch : `/api/*` → PHP-FPM, tout le reste → Node.js (Nuxt SSR)
- SSL via Let's Encrypt (certbot)
- Compression gzip activée
- Headers de cache pour les assets statiques
**Gestion des environnements :**
- **Production** : `skycel.fr` — branche `prod`
- **Staging** : `staging.skycel.fr` — branche `staging` ou `main`
- Fichiers `.env` distincts par environnement et par application (`frontend/.env.production`, `frontend/.env.staging`, `api/.env.production`, `api/.env.staging`)
**CI/CD : Script `deploy.sh` manuel**
```bash
# Déploiement déclenché manuellement
# Se base sur la branche 'prod'
./deploy.sh [production|staging]
```
Le script automatise :
1. `git pull origin prod` (ou staging)
2. `cd frontend && npm install && npm run build`
3. `cd api && composer install --no-dev && php artisan migrate --force`
4. `php artisan config:cache && php artisan route:cache`
5. Restart du process Node.js (PM2 ou systemd)
6. Notification de succès/échec
**Backups BDD :**
- Cron quotidien à 3h00 : `mysqldump` complet de la base skycel
- Rétention locale : 7 jours (rotation automatique, suppression des dumps > 7j)
- Réplication : Copie automatique vers un serveur distant via `rsync` ou `scp` après chaque dump
- Nommage : `skycel_backup_YYYY-MM-DD_HH-MM.sql.gz` (compressé)
### Decision Impact Analysis
**Séquence d'implémentation recommandée :**
1. Initialisation monorepo (Nuxt 4 + Laravel 12)
2. Configuration Nginx + environnements (.env, staging)
3. Schéma BDD + migrations + table translations
4. API endpoints publics (lecture) + middleware API Key + CORS
5. Store Pinia progression + persistance LocalStorage
6. Composants layout + transitions Nuxt natives
7. Pages et composants feature (par epic)
8. Intégrations tierces (reCAPTCHA, Matomo, Sentry)
9. Script deploy.sh + cron backup
10. Endpoints CRUD admin (post-MVP)
**Dépendances inter-composants :**
- Le store Pinia dépend du schéma de progression (BDD + API)
- Les composants feature dépendent de l'API (endpoints + format de réponse)
- L'i18n frontend dépend de la table translations (contenu dynamique)
- Le bandeau RGPD doit être en place avant l'activation de Matomo
- Le lazy-loading des composants lourds dépend de la structure de routing Nuxt

View File

@@ -0,0 +1,801 @@
---
stepsCompleted: [1, 2, 3, 4]
status: complete
validatedAt: '2026-02-03'
inputDocuments:
- docs/prd-gamification.md
- docs/planning-artifacts/architecture.md
- docs/planning-artifacts/ux-design-specification.md
- docs/brainstorming-gamification-2026-01-26.md
---
# skycel - Epic Breakdown
## Overview
This document provides the complete epic and story breakdown for skycel, decomposing the requirements from the PRD, UX Design if it exists, and Architecture requirements into implementable stories.
## Requirements Inventory
### Functional Requirements
- **FR1** : Le système offre une double entrée au visiteur : "Partir à l'aventure" (expérience complète) ou "Je n'ai pas le temps" (mode express avec roadmap)
- **FR2** : Les transitions entre pages sont animées de manière seamless via Nuxt transitions, créant une impression de "changement de zone" immersive
- **FR3** : Un narrateur-guide accompagne le visiteur avec des textes contextuels tout au long de l'expérience
- **FR4** : Une carte interactive (Konva.js) permet la navigation non-linéaire et affiche la progression du visiteur
- **FR5** : Un arbre de compétences interactif (vis.js) visualise les skills avec niveaux évoluant selon les projets
- **FR6** : Les compétences sont cliquables et mènent directement aux projets qui les utilisent
- **FR7** : Les témoignages s'affichent sous forme de dialogues PNJ style Zelda avec avatar, bulles, effet typewriter et personnalités variées
- **FR8** : Une barre de progression globale indique l'avancement dans l'exploration du site
- **FR9** : Le système propose 2-3 choix binaires créant 4-8 parcours narratifs différents, tous menant au contact
- **FR10** : Un challenge/puzzle accessible doit être résolu pour accéder au formulaire de contact (avec système d'indices)
- **FR11** : Des easter eggs cachés récompensent l'exploration avec des snippets de code ou anecdotes
- **FR12** : La progression est sauvegardée automatiquement en LocalStorage pour permettre la reprise
- **FR13** : Le site supporte deux langues (FR par défaut + EN) avec détection par URL (/en/...)
- **FR14** : Le formulaire de contact est présenté comme la récompense finale "Tu m'as trouvé !" avec célébration
### NonFunctional Requirements
- **NFR1** : Le bundle JS total (Nuxt + Konva + vis.js) ne doit pas dépasser 170kb gzippé, avec lazy-loading des composants lourds
- **NFR2** : Le temps de chargement initial (LCP) doit rester sous 2.5 secondes sur connexion 3G
- **NFR3** : Le site doit être responsive et offrir une expérience adaptée mobile (carte simplifiée en Chemin Libre vertical)
- **NFR4** : Le site doit fonctionner sur les navigateurs modernes (Chrome, Firefox, Safari, Edge — 2 dernières versions)
- **NFR5** : Les URLs doivent être SEO-friendly et le contenu principal accessible aux crawlers (SSR Nuxt 4)
- **NFR6** : Les animations doivent respecter `prefers-reduced-motion` pour l'accessibilité
- **NFR7** : Le système i18n utilise @nuxtjs/i18n avec fichiers JSON, rendu SSR pour SEO optimal
- **NFR8** : Les images sont optimisées en WebP avec lazy loading
### Additional Requirements
**Architecture :**
- Starter template : Nuxt 4 (`npx nuxi@latest init`) + Laravel 12 (`composer create-project`) — impacte Epic 1 Story 1
- Structure monorepo `frontend/` (Nuxt 4 avec structure `app/`) + `api/` (Laravel 12)
- Table `translations` centralisée en MariaDB pour i18n du contenu dynamique (clés i18n dans les tables métier)
- API REST avec API Key (`X-API-Key`) + CORS strict (domaine frontend uniquement)
- Store Pinia `useProgressionStore` avec persistance LocalStorage via `pinia-plugin-persistedstate` + compatibilité SSR
- Architecture composants frontend : `ui/` (atomiques réutilisables), `feature/` (métier), `layout/` (structure page)
- GSAP pour animations complexes (Swup.js abandonné — redondant avec transitions Nuxt natives)
- Transitions Nuxt natives (`pageTransition` + `<Transition>`) + CSS animations
- Sécurité contact : reCAPTCHA v3 (invisible) + honeypot + rate limiting Laravel (5 req/min par IP)
- Bandeau RGPD intégré à l'immersion narrative (dialogue PNJ/narrateur, style "pacte d'aventurier")
- Déploiement : Nginx → Node.js :3000 (Nuxt SSR) + PHP-FPM :9000 (Laravel API) + MariaDB :3306
- CI/CD : Script `deploy.sh` manuel (git pull, build, migrate, restart)
- Backups BDD : cron quotidien mysqldump + réplication distante rsync/scp, rétention 7 jours
- Environnements : production (`skycel.fr`, branche `prod`) + staging (`staging.skycel.fr`)
- Cache : File cache Laravel (driver `file`, pas de Redis)
- Double validation : frontend (UX temps réel) + backend (Form Requests Laravel, source de vérité)
- Format réponse API : Laravel API Resources avec enveloppe `{ data, meta }`
- Gestion langue API : header `Accept-Language` → middleware Laravel → jointure table translations
**UX :**
- Système de héros : 3 personnages (Recruteur/Client/Dev) impactant vouvoiement, ton narrateur, type de challenges
- Narrateur = "Le Bug" (araignée, mascotte micro-entreprise) avec arc de révélation progressive (silhouette sombre → araignée complète en 5 étapes liées à la progression)
- Page résumé 30s (`/resume`) : URL directe pour candidatures, accès sans passer par la landing
- Déblocage contact après 2 zones visitées (pas de blocage excessif)
- Challenge final optionnel (jamais bloquant l'accès au contact)
- Challenge post-formulaire ("En attendant que le dev retrouve sa boîte mail...")
- "Monde de Code" : révélation finale — paysage en blocs de code ASCII art, avatar Célian au centre
- Navigation mobile : "Chemin Libre" vertical (ZoneCards scrollables) au lieu de Konva.js
- Bottom bar mobile fixe : Carte, Progression, Paramètres (thumb zone)
- Tous les feedbacks système passent par le narrateur (pas de toasts/notifications classiques)
- Headless UI / Radix UI pour composants standards accessibles (modals, tooltips, toggles, menus)
- Design tokens Tailwind : `sky-dark` (noir→bleu), `sky-accent` (#fa784f orange), `sky-text` (blanc cassé→jaune)
- Polices : serif élégante (narrateur/PNJ/narration) + sans-serif moderne (interface/UI)
- Approche Mobile First CSS
- WCAG AA : contraste ≥ 4.5:1, touch targets 44x44px min, skip links, `aria-live="polite"` narrateur, navigation clavier complète
- Sortie de zone par choix narratif du narrateur (pas de bouton "retour" froid)
- Couleurs par zone sur la carte (teintes uniques par section)
- Espacement aéré : spacing scale de 4px à 128px
**Brainstorming :**
- Schéma BDD détaillé : 8 tables (projects, skills, skill_project, testimonials, narrator_texts, easter_eggs, user_progress, translations)
- Personnalités PNJ : sage, sarcastique, enthousiaste, professionnel
- Easter eggs : triggers variés (click, hover, konami, scroll, sequence)
- Rewards easter eggs : snippet, anecdote, image, badge
- Sauvegarde cloud progression par email (optionnel, phase 2)
- Rappel email narratif après X jours (hors scope MVP)
### FR Coverage Map
| FR | Epic | Description |
|---|---|---|
| FR1 | Epic 1 | Double entrée Aventure / Mode Express |
| FR2 | Epic 1 | Transitions de page animées seamless |
| FR3 | Epic 3 | Narrateur-guide contextuel |
| FR4 | Epic 3 | Carte interactive navigation non-linéaire |
| FR5 | Epic 2 | Arbre de compétences interactif |
| FR6 | Epic 2 | Compétences cliquables → projets liés |
| FR7 | Epic 2 | Témoignages dialogues PNJ |
| FR8 | Epic 3 | Barre de progression globale |
| FR9 | Epic 4 | Choix binaires créant parcours multiples |
| FR10 | Epic 4 | Challenge/puzzle avant contact |
| FR11 | Epic 4 | Easter eggs cachés |
| FR12 | Epic 3 | Sauvegarde progression LocalStorage |
| FR13 | Epic 1 | Bilingue FR/EN |
| FR14 | Epic 4 | Contact comme récompense finale |
## Epic List
### Epic 1 : Fondations & Double Entrée
Le visiteur arrive sur le site, choisit son héros et son mode (Aventure ou Express), et peut naviguer entre les pages avec des transitions immersives. Le site est bilingue et fonctionnel en SSR.
**FRs couverts :** FR1, FR2, FR13
### Epic 2 : Contenu & Découverte
Le visiteur explore les zones de contenu : projets (galerie + détail), compétences organisées par catégories avec liens vers les projets associés, témoignages en dialogues PNJ, et parcours en timeline narrative.
**FRs couverts :** FR5, FR6, FR7
### Epic 3 : Navigation Gamifiée & Progression
Le visiteur navigue via la carte interactive (Konva.js desktop / Chemin Libre mobile), est accompagné par le narrateur-guide (Le Bug), et voit sa progression sauvegardée automatiquement avec une barre XP.
**FRs couverts :** FR3, FR4, FR8, FR12
### Epic 4 : Chemins Narratifs, Challenge & Contact
Le visiteur fait des choix qui créent son parcours unique, relève un challenge optionnel, et accède à la révélation finale "Monde de Code" + formulaire de contact comme récompense narrative. Les easter eggs récompensent l'exploration.
**FRs couverts :** FR9, FR10, FR11, FR14
---
## Epic 1 : Fondations & Double Entrée
Le visiteur arrive sur le site, choisit son héros et son mode (Aventure ou Express), et peut naviguer entre les pages avec des transitions immersives. Le site est bilingue et fonctionnel en SSR.
### Story 1.1 : Initialisation du monorepo et infrastructure
As a développeur,
I want un projet monorepo Nuxt 4 + Laravel 12 initialisé avec les configurations de base,
So that le développement peut commencer sur des fondations solides.
**Acceptance Criteria:**
**Given** un nouveau repository Git
**When** le projet est initialisé
**Then** la structure monorepo `frontend/` (Nuxt 4) + `api/` (Laravel 12) est en place
**And** Nuxt 4 est configuré avec SSR activé, TypeScript, et les modules `@nuxtjs/i18n`, `@nuxtjs/tailwindcss`, `@pinia/nuxt`, `nuxt/image`, `@nuxtjs/sitemap`
**And** Laravel 12 est configuré en mode API-only avec CORS autorisant le domaine frontend
**And** le middleware API Key (`X-API-Key`) est en place sur les routes API
**And** les fichiers `.env.example` existent pour frontend et backend
**And** TailwindCSS est configuré avec les design tokens (`sky-dark`, `sky-accent` #fa784f, `sky-text`)
**And** les polices sont définies (serif narrateur + sans-serif UI)
**And** le `.gitignore` est approprié pour les deux applications
### Story 1.2 : Base de données et migrations initiales
As a développeur,
I want le schéma de base de données MariaDB avec les tables nécessaires à l'Epic 1,
So that l'API peut servir du contenu bilingue.
**Acceptance Criteria:**
**Given** une connexion MariaDB configurée dans Laravel
**When** `php artisan migrate` est exécuté
**Then** la table `translations` est créée (id, lang, key_name, value, timestamps) avec index unique (lang, key_name)
**And** la table `projects` est créée (id, slug, title_key, description_key, short_description_key, image, url, github_url, date_completed, is_featured, display_order, timestamps)
**And** la table `skills` est créée (id, slug, name_key, description_key, icon, category, max_level, display_order)
**And** la table `skill_project` est créée (id, skill_id, project_id, level_before, level_after, level_description_key) avec foreign keys
**And** les Models Eloquent sont définis avec leurs relations (Project belongsToMany Skill, etc.)
**And** des Seeders de base sont disponibles avec données de test en FR et EN
**And** `php artisan db:seed` fonctionne correctement
### Story 1.3 : Système i18n frontend + API bilingue
As a visiteur,
I want voir le site dans ma langue (FR ou EN),
So that je comprends le contenu.
**Acceptance Criteria:**
**Given** le module `@nuxtjs/i18n` configuré avec stratégie `prefix_except_default`
**When** le visiteur accède à `/` ou `/en`
**Then** le contenu statique UI est affiché dans la langue correspondante via fichiers JSON (`i18n/fr.json`, `i18n/en.json`)
**And** les URLs FR sont par défaut (`/`, `/projets`, `/competences`, `/contact`)
**And** les URLs EN sont préfixées (`/en`, `/en/projects`, `/en/skills`, `/en/contact`)
**And** `useI18n()`, `$t()`, `localePath()`, `switchLocalePath()` fonctionnent en SSR
**And** les tags `hreflang` sont générés automatiquement dans le `<head>`
**And** l'attribut `lang` du `<html>` est dynamique (fr/en)
**And** le middleware Laravel extrait `Accept-Language` et joint la table `translations` pour le contenu dynamique
**And** les API Resources Laravel renvoient le contenu traduit selon la langue demandée
**And** le fallback est FR si langue non supportée
### Story 1.4 : Layouts, routing et transitions de page
As a visiteur,
I want une navigation fluide entre les pages avec des transitions immersives,
So that l'expérience ressemble à un changement de zone, pas à un rechargement.
**Acceptance Criteria:**
**Given** la structure de pages Nuxt 4 (`app/pages/`)
**When** le visiteur navigue entre les pages
**Then** les transitions de page sont animées (fade + slide) via `pageTransition` dans `nuxt.config.ts`
**And** la navigation utilise `<NuxtLink>` pour l'hydration SPA (pas de rechargement)
**And** le layout par défaut (`default.vue`) inclut le header avec barre de progression (placeholder) et sélecteur de langue
**And** un layout `minimal.vue` existe pour le mode express
**And** le `scrollBehavior` est personnalisé (smooth scroll, retour position sauvegardée)
**And** `prefers-reduced-motion` désactive les animations de transition via media query CSS
**And** une page 404 (`error.vue`) bilingue est en place
**And** les meta tags SEO dynamiques fonctionnent via `useHead()` et `useSeoMeta()`
**And** le favicon est configuré
### Story 1.5 : Landing page et choix du héros
As a visiteur,
I want choisir entre l'aventure et le mode express, puis sélectionner mon héros,
So that mon expérience est adaptée à mon profil et mon temps disponible.
**Acceptance Criteria:**
**Given** le visiteur arrive sur la landing page (`/`)
**When** la page se charge
**Then** deux CTA distincts sont visibles : "Partir à l'aventure" et "Mode express"
**And** un texte d'accroche intrigant bilingue est affiché
**And** une animation d'entrée subtile est présente (respectant `prefers-reduced-motion`)
**And** le design est responsive (mobile + desktop)
**And** au clic sur "Partir à l'aventure", le composant `HeroSelector` s'affiche avec 3 cards illustrées (Recruteur, Client, Développeur) avec nom et description courte
**And** le héros sélectionné est stocké dans le store Pinia `useProgressionStore` (champ `hero`)
**And** au clic sur "Mode express", le visiteur est redirigé vers la page résumé
**And** le `HeroSelector` est accessible au clavier (`role="radiogroup"`, flèches pour naviguer, Enter pour sélectionner)
### Story 1.6 : Store Pinia progression et bandeau RGPD
As a visiteur,
I want que ma progression soit sauvegardée et que mon consentement soit respecté,
So that je peux reprendre mon exploration et mes données sont protégées.
**Acceptance Criteria:**
**Given** le visiteur accède au site
**When** le consentement RGPD n'a pas encore été donné
**Then** un bandeau de consentement immersif s'affiche (style narratif/dialogue, pas un bandeau classique)
**And** le store Pinia `useProgressionStore` est initialisé avec : sessionId (UUID v4), hero, currentPath, visitedSections, completionPercent, easterEggsFound, challengeCompleted, contactUnlocked, narratorStage, choices, consentGiven
**And** la persistance LocalStorage est activée via `pinia-plugin-persistedstate` (uniquement après consentement)
**And** le store est compatible SSR (initialisation vide côté serveur, réhydratation client)
**And** si une progression existante est détectée, un message "Bienvenue à nouveau" est affiché
**And** l'action `$reset()` permet de réinitialiser la progression
### Story 1.7 : Page résumé express et mode pressé
As a visiteur pressé ou recruteur,
I want une vue condensée de toutes les informations essentielles,
So that je peux évaluer le développeur en 30 secondes.
**Acceptance Criteria:**
**Given** le visiteur accède à `/resume` (FR) ou `/en/resume` (EN) directement ou via "Mode express"
**When** la page se charge
**Then** le contenu affiché comprend : nom, titre, photo/avatar, accroche (5s)
**And** les compétences clés avec stack technique sont visibles (10s)
**And** 3-4 projets highlights avec liens sont affichés (10s)
**And** un CTA de contact direct est visible (5s)
**And** un bouton discret "Voir l'aventure" invite à l'expérience complète
**And** la page est fonctionnelle en FR et EN
**And** les données sont chargées depuis l'API (projets, skills)
**And** les meta tags SEO sont optimisés pour cette page
**And** le layout `minimal.vue` est utilisé
---
## Epic 2 : Contenu & Découverte
Le visiteur explore les zones de contenu : projets (galerie + détail), compétences organisées par catégories avec liens vers les projets associés, témoignages en dialogues PNJ, et parcours en timeline narrative.
### Story 2.1 : Composant ProjectCard
As a développeur,
I want un composant réutilisable de card de projet,
So that je peux afficher les projets de manière cohérente sur la galerie et ailleurs dans le site.
**Acceptance Criteria:**
**Given** le composant `ProjectCard` est implémenté
**When** il reçoit les données d'un projet en props
**Then** il affiche l'image du projet (WebP, lazy loading)
**And** il affiche le titre traduit selon la langue courante
**And** il affiche la description courte traduite
**And** un hover effect révèle un CTA "Découvrir" avec animation subtile
**And** le composant est cliquable et navigue vers `/projets/{slug}` (ou `/en/projects/{slug}`)
**And** le composant respecte `prefers-reduced-motion` pour les animations
**And** le composant est responsive (adaptation mobile/desktop)
**And** le composant est accessible (focus visible, `role` approprié)
### Story 2.2 : Page Projets - Galerie
As a visiteur,
I want voir la liste des projets réalisés par le développeur,
So that je peux évaluer son expérience et choisir lesquels explorer en détail.
**Acceptance Criteria:**
**Given** le visiteur accède à `/projets` (FR) ou `/en/projects` (EN)
**When** la page se charge
**Then** une grille responsive de `ProjectCard` s'affiche
**And** les projets sont triés par date avec les "featured" en tête
**And** une animation d'entrée progressive des cards est présente (respectant `prefers-reduced-motion`)
**And** les données sont chargées depuis l'API `/api/projects` avec le contenu traduit
**And** les meta tags SEO sont dynamiques pour cette page
**And** le layout s'adapte : grille sur desktop, cards empilées sur mobile
### Story 2.3 : Page Projet - Détail
As a visiteur,
I want voir les détails d'un projet spécifique,
So that je comprends le travail réalisé et les technologies utilisées.
**Acceptance Criteria:**
**Given** le visiteur accède à `/projets/{slug}` (FR) ou `/en/projects/{slug}` (EN)
**When** la page se charge
**Then** le titre, la description complète et l'image principale du projet s'affichent
**And** la date de réalisation est visible
**And** la liste des compétences utilisées s'affiche avec leurs niveaux (avant/après le projet)
**And** les liens externes sont présents : URL du projet live (si existe), repository GitHub (si existe)
**And** une navigation "Projet précédent / Projet suivant" permet de parcourir les projets
**And** un bouton retour vers la galerie est visible
**And** les meta tags SEO sont dynamiques (titre, description, image Open Graph)
**And** si le slug n'existe pas, une page 404 appropriée s'affiche
**And** le design est responsive (adaptation mobile/desktop)
### Story 2.4 : Page Compétences - Affichage par catégories
As a visiteur,
I want voir les compétences du développeur organisées par catégorie,
So that je comprends son profil technique global.
**Acceptance Criteria:**
**Given** le visiteur accède à `/competences` (FR) ou `/en/skills` (EN)
**When** la page se charge
**Then** les compétences sont affichées groupées par catégorie (Frontend, Backend, Tools, Soft skills)
**And** chaque compétence affiche : icône, nom traduit, niveau actuel (représentation visuelle)
**And** les données sont chargées depuis l'API `/api/skills` avec le contenu traduit
**And** une animation d'entrée des éléments est présente (respectant `prefers-reduced-motion`)
**And** sur desktop : préparé pour accueillir le skill tree vis.js (Epic 3)
**And** sur mobile : liste groupée par catégorie avec design adapté
**And** les meta tags SEO sont dynamiques pour cette page
**And** chaque compétence est visuellement cliquable (affordance)
### Story 2.5 : Compétences cliquables → Projets liés
As a visiteur,
I want cliquer sur une compétence pour voir les projets qui l'utilisent,
So that je peux voir des preuves concrètes de maîtrise.
**Acceptance Criteria:**
**Given** le visiteur est sur la page Compétences
**When** il clique sur une compétence
**Then** un panneau/modal s'ouvre avec la liste des projets liés à cette compétence
**And** pour chaque projet lié : titre, description courte, lien vers le détail
**And** l'indication du niveau avant/après chaque projet est visible (progression)
**And** une animation d'ouverture/fermeture fluide est présente (respectant `prefers-reduced-motion`)
**And** la fermeture est possible par clic extérieur, bouton close, ou touche Escape
**And** le panneau/modal utilise Headless UI pour l'accessibilité
**And** la navigation au clavier est fonctionnelle (Tab, Escape, Enter)
**And** le focus est piégé dans le modal quand ouvert (`focus trap`)
**And** les données viennent de la relation `skill_project` via l'API
### Story 2.6 : Page Témoignages et migrations BDD
As a visiteur,
I want voir les témoignages des personnes ayant travaillé avec le développeur,
So that j'ai une validation sociale de ses compétences.
**Acceptance Criteria:**
**Given** les migrations Laravel sont exécutées
**When** `php artisan migrate` est lancé
**Then** la table `testimonials` est créée (id, name, role, company, avatar, text_key, personality ENUM, project_id FK nullable, display_order, is_active, timestamps)
**And** les seeders de test sont disponibles avec des témoignages en FR et EN
**Given** le visiteur accède à `/temoignages` (FR) ou `/en/testimonials` (EN)
**When** la page se charge
**Then** la liste des témoignages s'affiche depuis l'API `/api/testimonials`
**And** chaque témoignage affiche : nom, rôle, entreprise, avatar, texte traduit
**And** la personnalité de chaque PNJ est indiquée visuellement (style différent selon personality)
**And** un lien vers le projet associé est présent si pertinent
**And** l'ordre d'affichage respecte `display_order`
**And** le design est préparé pour accueillir le composant DialoguePNJ (story suivante)
**And** les meta tags SEO sont dynamiques pour cette page
### Story 2.7 : Composant Dialogue PNJ
As a visiteur,
I want lire les témoignages comme des dialogues de personnages style Zelda,
So that l'expérience est immersive et mémorable.
**Acceptance Criteria:**
**Given** le composant `DialoguePNJ` est implémenté
**When** il reçoit les données d'un témoignage en props
**Then** l'avatar du PNJ s'affiche à gauche avec un style illustratif
**And** une bulle de dialogue s'affiche à droite avec le texte
**And** l'effet typewriter fait apparaître le texte lettre par lettre
**And** un clic ou appui sur Espace accélère l'animation typewriter (x3-x5)
**And** la personnalité du PNJ influence le style visuel de la bulle (sage, sarcastique, enthousiaste, professionnel)
**And** la police serif narrative est utilisée pour le texte du dialogue
**And** `prefers-reduced-motion` affiche le texte complet instantanément
**And** le texte complet est accessible via `aria-label` pour les screen readers
**And** une navigation entre témoignages est disponible (précédent/suivant)
**And** une transition animée s'effectue entre les PNJ
**And** un indicateur du témoignage actuel est visible (ex: 2/5)
**And** la navigation au clavier est fonctionnelle (flèches gauche/droite)
### Story 2.8 : Page Parcours - Timeline narrative
As a visiteur,
I want découvrir le parcours professionnel du développeur sous forme de timeline,
So that je comprends son évolution et son expérience.
**Acceptance Criteria:**
**Given** le visiteur accède à `/parcours` (FR) ou `/en/journey` (EN)
**When** la page se charge
**Then** une timeline verticale affiche les étapes chronologiques du parcours
**And** chaque étape affiche : date, titre, description narrative traduite
**And** sur desktop : les étapes alternent gauche/droite pour un effet visuel dynamique
**And** sur mobile : les étapes sont linéaires (toutes du même côté)
**And** une animation d'apparition au scroll est présente (respectant `prefers-reduced-motion`)
**And** des icônes ou images illustrent les étapes clés
**And** le contenu est bilingue (FR/EN) et chargé depuis l'API ou fichiers i18n
**And** les meta tags SEO sont dynamiques pour cette page
**And** la police serif narrative est utilisée pour les descriptions
---
## Epic 3 : Navigation Gamifiée & Progression
Le visiteur navigue via la carte interactive (Konva.js desktop / Chemin Libre mobile), est accompagné par le narrateur-guide (Le Bug), et voit sa progression sauvegardée automatiquement avec une barre XP.
### Story 3.1 : Table narrator_texts et API narrateur
As a développeur,
I want une infrastructure pour stocker et servir les textes du narrateur,
So that le narrateur peut afficher des messages contextuels variés.
**Acceptance Criteria:**
**Given** les migrations Laravel sont exécutées
**When** `php artisan migrate` est lancé
**Then** la table `narrator_texts` est créée (id, context, text_key, variant, timestamps)
**And** les contextes définis incluent : intro, transition_projects, transition_skills, transition_testimonials, transition_journey, hint, encouragement_25, encouragement_50, encouragement_75, contact_unlocked, welcome_back
**And** plusieurs variantes par contexte permettent une sélection aléatoire
**And** les seeders insèrent les textes de base en FR et EN dans la table `translations`
**Given** l'API `/api/narrator/{context}` est appelée
**When** un contexte valide est fourni
**Then** un texte aléatoire parmi les variantes de ce contexte est retourné
**And** le texte est traduit selon le header `Accept-Language`
**And** le ton est adapté au héros (vouvoiement pour Recruteur, tutoiement pour Client/Dev)
### Story 3.2 : Composant NarratorBubble (Le Bug)
As a visiteur,
I want voir un narrateur-guide qui m'accompagne dans mon exploration,
So that je me sens guidé et l'expérience est immersive.
**Acceptance Criteria:**
**Given** le composant `NarratorBubble` est implémenté
**When** le narrateur doit afficher un message
**Then** une bulle apparaît en bas de l'écran (desktop) ou au-dessus de la bottom bar (mobile)
**And** l'avatar du Bug (araignée) s'affiche avec son apparence selon le `narratorStage` du store
**And** le texte apparaît avec effet typewriter (lettre par lettre)
**And** un clic ou Espace accélère l'animation typewriter
**And** la bulle peut être fermée/minimisée sans bloquer la navigation
**And** le composant utilise `aria-live="polite"` et `role="status"` pour l'accessibilité
**And** `prefers-reduced-motion` affiche le texte instantanément
**And** la police serif narrative est utilisée pour le texte
**And** l'animation d'apparition/disparition est fluide et non-bloquante
### Story 3.3 : Textes narrateur contextuels et arc de révélation
As a visiteur,
I want que le narrateur réagisse à mes actions et évolue visuellement,
So that l'expérience est personnalisée et le narrateur devient familier.
**Acceptance Criteria:**
**Given** le visiteur navigue sur le site
**When** il effectue des actions clés
**Then** le narrateur affiche un message d'accueil à l'arrivée (adapté au héros choisi)
**And** des messages de transition s'affichent entre les zones
**And** des encouragements apparaissent à 25%, 50%, 75% de progression
**And** des indices s'affichent si le visiteur semble inactif (> 30s sans action)
**And** un message spécial "Bienvenue à nouveau" s'affiche si progression existante détectée
**And** le message de déblocage du contact s'affiche après 2 zones visitées
**Given** le visiteur progresse dans l'exploration
**When** le `completionPercent` atteint certains seuils
**Then** le `narratorStage` du store est mis à jour (1→5)
**And** l'apparence du Bug évolue : silhouette sombre (1) → forme vague (2) → pattes visibles (3) → araignée reconnaissable (4) → mascotte complète révélée (5)
**And** le ton du narrateur évolue de mystérieux à complice
### Story 3.4 : Barre de progression globale (XP bar)
As a visiteur,
I want voir ma progression dans l'exploration du site,
So that je sais combien il me reste à découvrir.
**Acceptance Criteria:**
**Given** le visiteur est en mode Aventure
**When** il navigue sur le site
**Then** une barre de progression discrète s'affiche dans le header
**And** le pourcentage est calculé selon les sections visitées (Projets, Compétences, Témoignages, Parcours)
**And** l'animation de la barre est fluide lors des mises à jour
**And** un tooltip au hover indique les sections visitées et restantes
**And** le design évoque une barre XP style RPG (cohérent avec `sky-accent`)
**And** la barre respecte `prefers-reduced-motion` (pas d'animation si activé)
**And** sur mobile, la progression est accessible via la bottom bar
**And** la barre n'est pas visible en mode Express/Résumé
### Story 3.5 : Logique de progression et déblocage contact
As a visiteur,
I want que ma progression débloque l'accès au contact,
So that l'exploration est récompensée sans être frustrante.
**Acceptance Criteria:**
**Given** le store `useProgressionStore` est actif
**When** le visiteur visite une nouvelle zone
**Then** la zone est ajoutée à `visitedSections`
**And** le `completionPercent` est recalculé automatiquement
**And** la progression est persistée en LocalStorage (si consentement RGPD donné)
**Given** le visiteur a visité 2 zones ou plus
**When** la condition est atteinte
**Then** `contactUnlocked` passe à `true`
**And** le narrateur annonce le déblocage avec un message spécial
**And** la zone Contact s'illumine sur la carte (si visible)
**And** le visiteur peut continuer à explorer ou aller au contact
**Given** le visiteur revient sur le site
**When** une progression existe en LocalStorage
**Then** le store est réhydraté avec l'état sauvegardé
**And** le narrateur affiche "Bienvenue à nouveau"
**And** la carte affiche l'état correct des zones visitées
### Story 3.6 : Carte interactive desktop (Konva.js)
As a visiteur desktop,
I want naviguer via une carte interactive visuelle,
So that j'explore librement le portfolio comme un monde.
**Acceptance Criteria:**
**Given** le visiteur est sur desktop (≥ 1024px) et accède à la carte
**When** la carte se charge
**Then** un canvas Konva.js affiche une carte stylisée avec les zones (Projets, Compétences, Parcours, Témoignages, Contact)
**And** le composant est chargé en lazy-loading (`.client.vue`) pour respecter le budget JS
**And** chaque zone a une apparence distincte (teinte unique, icône)
**And** les zones visitées ont une apparence différente des zones non visitées
**And** la zone Contact est verrouillée visuellement si `contactUnlocked` est `false`
**And** la position actuelle du visiteur est marquée sur la carte
**And** au hover sur une zone : le nom et le statut s'affichent (tooltip)
**And** au clic sur une zone : navigation vers la section correspondante avec transition
**And** un curseur personnalisé indique les zones cliquables
**And** la navigation au clavier est fonctionnelle (Tab entre zones, Enter pour naviguer)
**And** les zones ont des labels ARIA descriptifs
### Story 3.7 : Navigation mobile - Chemin Libre et Bottom Bar
As a visiteur mobile,
I want naviguer facilement avec une interface adaptée au tactile,
So that l'expérience reste immersive sur petit écran.
**Acceptance Criteria:**
**Given** le visiteur est sur mobile (< 768px)
**When** il accède à la navigation
**Then** le "Chemin Libre" affiche les zones en cards verticales scrollables (`ZoneCard`)
**And** chaque `ZoneCard` affiche : illustration, nom de la zone, statut (visité/nouveau/verrouillé)
**And** une ligne décorative relie les cards visuellement (effet chemin)
**And** un tap sur une zone navigue vers la section correspondante
**And** la zone Contact affiche un cadenas si `contactUnlocked` est `false`
**Given** la bottom bar mobile est affichée
**When** le visiteur interagit
**Then** 3 icônes sont accessibles : Carte (ouvre le Chemin Libre), Progression (affiche le %), Paramètres
**And** les touch targets font au minimum 48x48px
**And** la bottom bar est fixe et toujours visible
**And** le narrateur s'affiche au-dessus de la bottom bar quand actif
---
## Epic 4 : Chemins Narratifs, Challenge & Contact
Le visiteur fait des choix qui créent son parcours unique, relève un challenge optionnel, et accède à la révélation finale "Monde de Code" + formulaire de contact comme récompense narrative. Les easter eggs récompensent l'exploration.
### Story 4.1 : Composant ChoiceCards et choix narratifs
As a visiteur,
I want faire des choix qui influencent mon parcours,
So that mon expérience est unique et personnalisée.
**Acceptance Criteria:**
**Given** le composant `ChoiceCards` est implémenté
**When** le narrateur propose un choix
**Then** 2 cards s'affichent côte à côte (desktop) ou empilées (mobile)
**And** chaque card affiche : icône, texte narratif du choix
**And** un hover/focus highlight la card sélectionnable
**And** un clic enregistre le choix dans `choices` du store Pinia
**And** une transition animée mène vers la destination choisie
**And** le composant est accessible (`role="radiogroup"`, navigation clavier, focus visible)
**And** `prefers-reduced-motion` simplifie les animations
**And** le style est cohérent avec l'univers narratif (police serif, couleurs des zones)
### Story 4.2 : Intro narrative et premier choix
As a visiteur aventurier,
I want une introduction narrative captivante suivie d'un premier choix,
So that je suis immergé dès le début de l'aventure.
**Acceptance Criteria:**
**Given** le visiteur a sélectionné son héros sur la landing page
**When** il commence l'aventure
**Then** une séquence d'intro narrative s'affiche avec le narrateur (Le Bug)
**And** le texte présente le "héros mystérieux" (le développeur) à découvrir
**And** l'effet typewriter anime le texte (skippable par clic/Espace)
**And** l'ambiance visuelle est immersive (fond sombre, illustrations)
**And** un bouton "Continuer" permet d'avancer
**And** à la fin de l'intro, le premier choix binaire s'affiche via `ChoiceCards`
**And** le choix propose deux zones à explorer en premier (ex: Projets vs Compétences)
**And** le contenu est bilingue (FR/EN) et adapté au héros (vouvoiement/tutoiement)
**And** la durée de l'intro est courte (15-30s max, skippable)
### Story 4.3 : Chemins narratifs différenciés
As a visiteur,
I want que mes choix aient un impact visible sur mon parcours,
So that je sens que mon expérience est vraiment personnalisée.
**Acceptance Criteria:**
**Given** le visiteur fait des choix tout au long de l'aventure
**When** il navigue entre les zones
**Then** 2-3 points de choix binaires créent 4-8 parcours possibles
**And** chaque choix est enregistré dans `choices` du store
**And** l'ordre suggéré des zones varie selon le chemin choisi
**And** les textes du narrateur s'adaptent au chemin (transitions contextuelles)
**And** tous les chemins permettent de visiter tout le contenu
**And** tous les chemins mènent au contact (pas de "mauvais" choix)
**And** le `currentPath` du store reflète le chemin actuel
**And** à la fin de chaque zone, le narrateur propose un choix vers la suite
### Story 4.4 : Table easter_eggs et système de détection
As a développeur,
I want une infrastructure pour gérer les easter eggs cachés,
So that je peux ajouter des surprises récompensant l'exploration.
**Acceptance Criteria:**
**Given** les migrations Laravel sont exécutées
**When** `php artisan migrate` est lancé
**Then** la table `easter_eggs` est créée (id, slug, location, trigger_type ENUM, reward_type ENUM, reward_key, difficulty, is_active, timestamps)
**And** les trigger_types incluent : click, hover, konami, scroll, sequence
**And** les reward_types incluent : snippet, anecdote, image, badge
**And** les seeders insèrent 5-10 easter eggs avec leurs récompenses traduites
**Given** l'API `/api/easter-eggs` est appelée
**When** la requête est faite
**Then** les métadonnées des easter eggs actifs sont retournées (slug, location, trigger_type)
**And** les réponses/récompenses ne sont PAS incluses (pour éviter la triche)
**Given** l'API `/api/easter-eggs/{slug}/validate` est appelée
**When** un slug valide est fourni
**Then** la récompense traduite est retournée
**And** l'easter egg est marqué comme trouvé côté client (store)
### Story 4.5 : Easter eggs - Implémentation UI et collection
As a visiteur curieux,
I want découvrir des surprises cachées et voir ma collection,
So that l'exploration est récompensée.
**Acceptance Criteria:**
**Given** des easter eggs sont placés sur différentes pages
**When** le visiteur déclenche un easter egg (clic, hover, konami, scroll, sequence)
**Then** une animation de découverte s'affiche (popup, effet visuel)
**And** la récompense est affichée (snippet de code, anecdote, image, badge)
**And** le narrateur réagit avec enthousiasme
**And** une notification "Easter egg trouvé ! (X/Y)" s'affiche
**And** le slug est ajouté à `easterEggsFound` dans le store
**And** un bouton permet de fermer et continuer
**Given** le visiteur accède à sa collection (via paramètres ou zone dédiée)
**When** la collection s'affiche
**Then** une grille montre les easter eggs trouvés et des silhouettes mystère pour les non-trouvés
**And** les détails sont visibles pour les découverts
**And** un compteur X/Y indique la progression
**And** un badge spécial s'affiche si 100% trouvés
### Story 4.6 : Page Challenge - Structure et puzzle
As a visiteur,
I want relever un défi optionnel avant d'accéder au contact,
So that l'accès au développeur est une récompense méritée (mais pas bloquante).
**Acceptance Criteria:**
**Given** le visiteur accède à `/challenge` (après avoir débloqué le contact)
**When** la page se charge
**Then** une introduction narrative "Une dernière épreuve..." s'affiche
**And** un puzzle logique/code simple est présenté (réordonner, compléter, décoder)
**And** la difficulté est calibrée : 1-3 minutes pour résoudre
**And** le thème est lié au développement/code
**And** un système d'indices est disponible (bouton "Besoin d'aide ?")
**And** 3 niveaux d'indices progressifs sont proposés
**And** après 3 indices, une option "Passer" apparaît
**And** le challenge est TOUJOURS skippable (bouton discret "Passer directement au contact")
**And** une validation avec feedback clair indique succès/échec
**And** une animation de succès célèbre la réussite
**And** `challengeCompleted` est mis à `true` dans le store si réussi
### Story 4.7 : Révélation "Monde de Code"
As a visiteur ayant complété le parcours,
I want vivre un moment waouh de révélation finale,
So that la découverte du développeur est mémorable.
**Acceptance Criteria:**
**Given** le visiteur accède à la zone Contact (après challenge ou skip)
**When** la révélation se déclenche
**Then** une transition immersive mène vers le "Monde de Code"
**And** un paysage composé de blocs de code ASCII art s'affiche (montagnes, arbres, ville en code)
**And** le code scroll/apparaît progressivement (animation)
**And** l'avatar illustré de Célian est révélé au centre du monde de code
**And** le narrateur (Le Bug) commente : "Tu l'as trouvé !"
**And** le message "Tu m'as trouvé !" s'affiche avec effet de célébration
**And** sur mobile, une version allégée mais émotionnellement équivalente s'affiche
**And** `prefers-reduced-motion` affiche une version statique
**And** une description alternative est disponible pour les screen readers
**And** un bouton permet de continuer vers le formulaire de contact
### Story 4.8 : Page Contact - Formulaire et célébration
As a visiteur ayant trouvé le développeur,
I want le contacter facilement avec une célébration,
So that l'envoi du message est une conclusion satisfaisante.
**Acceptance Criteria:**
**Given** le visiteur est sur la page Contact après la révélation
**When** la page s'affiche
**Then** un message de félicitations avec stats du parcours est visible (zones visitées, easter eggs trouvés, temps passé)
**And** un formulaire de contact s'affiche : nom (requis), email (requis), message (requis)
**And** la validation temps réel est effectuée côté frontend (champs requis, format email)
**And** les erreurs sont communiquées par le narrateur (pas de messages d'erreur classiques)
**And** un champ honeypot invisible est présent (anti-spam)
**And** reCAPTCHA v3 est intégré de manière invisible
**And** le bouton d'envoi utilise la couleur accent (`sky-accent`)
**Given** le formulaire est soumis
**When** les données sont envoyées à l'API
**Then** la validation backend Laravel (Form Request) vérifie les données
**And** le rate limiting (5 req/min par IP) est appliqué
**And** l'email est envoyé via Laravel Mail
**And** une animation de célébration s'affiche (confettis ou similaire)
**And** le narrateur confirme l'envoi avec un message personnalisé
**And** en cas d'erreur, le narrateur explique le problème avec bienveillance
### Story 4.9 : Challenge post-formulaire
As a visiteur ayant envoyé un message,
I want m'amuser en attendant la réponse,
So that le temps d'attente est transformé en moment de jeu.
**Acceptance Criteria:**
**Given** le formulaire de contact a été envoyé avec succès
**When** la confirmation s'affiche
**Then** un message "En attendant que le développeur retrouve le chemin vers sa boîte mail..." est affiché
**And** un challenge optionnel bonus est proposé
**And** le challenge est différent du challenge principal (mini-jeu, quiz, exploration)
**And** le visiteur peut fermer et quitter à tout moment
**And** la participation est totalement optionnelle
**And** le résultat n'impacte rien (juste pour le fun)
**And** le narrateur commente avec humour

File diff suppressed because it is too large Load Diff