# Story 2.1: Composant ProjectCard Status: review ## Story 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 1. **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) 2. **And** il affiche le titre traduit selon la langue courante 3. **And** il affiche la description courte traduite 4. **And** un hover effect révèle un CTA "Découvrir" avec animation subtile 5. **And** le composant est cliquable et navigue vers `/projets/{slug}` (ou `/en/projects/{slug}`) 6. **And** le composant respecte `prefers-reduced-motion` pour les animations 7. **And** le composant est responsive (adaptation mobile/desktop) 8. **And** le composant est accessible (focus visible, `role` approprié) ## Tasks / Subtasks - [x] **Task 1: Créer le composant ProjectCard.vue** (AC: #1, #2, #3) - [x] Créer le fichier `frontend/app/components/feature/ProjectCard.vue` - [x] Définir les props TypeScript : `project` (object avec slug, image, title, shortDescription) - [x] Utiliser `` pour l'image avec format WebP et lazy loading - [x] Intégrer `useI18n()` pour le titre et la description traduits - [x] Afficher titre (`project.title`) et description courte (`project.shortDescription`) - [x] **Task 2: Implémenter le hover effect et CTA** (AC: #4) - [x] Créer un overlay qui apparaît au hover avec transition CSS - [x] Ajouter un CTA "Découvrir" (traduit via i18n) centré dans l'overlay - [x] Animation subtile : fade-in + léger scale (0.98 → 1) - [x] Utiliser les classes Tailwind pour les transitions - [x] **Task 3: Implémenter la navigation** (AC: #5) - [x] Rendre la card entièrement cliquable avec `` - [x] Utiliser `localePath()` pour générer l'URL correcte selon la langue - [x] URL pattern : `/projets/{slug}` (FR) ou `/en/projects/{slug}` (EN) - [x] **Task 4: Gérer `prefers-reduced-motion`** (AC: #6) - [x] Créer une media query CSS pour détecter `prefers-reduced-motion: reduce` - [x] Désactiver les transitions et animations si motion réduite - [x] Le hover effect reste visible mais sans animation - [x] **Task 5: Rendre le composant responsive** (AC: #7) - [x] Mobile : card pleine largeur, hauteur fixe ou aspect-ratio - [x] Desktop : card avec largeur flexible pour grille (min 280px, max 400px) - [x] Image qui remplit la card avec `object-cover` - [x] Texte tronqué si trop long (ellipsis) - [x] **Task 6: Accessibilité** (AC: #8) - [x] Focus visible sur la card (outline accent) - [x] `role="article"` sur la card container - [x] `alt` descriptif sur l'image (utiliser le titre du projet) - [x] Navigation au clavier fonctionnelle (Tab, Enter) - [x] **Task 7: Tests et validation** - [x] Tester le composant avec des données de projet fictives - [x] Vérifier l'affichage en FR et EN - [x] Vérifier le hover effect et la navigation - [x] Tester sur mobile et desktop - [x] Valider l'accessibilité avec axe DevTools ## Dev Notes ### Structure du composant ```vue ``` ### Interface TypeScript pour Project ```typescript // frontend/app/types/project.ts export interface Project { id: number slug: string image: string title: string // Déjà traduit par l'API description: string // Déjà traduit par l'API shortDescription: string // Déjà traduit par l'API url?: string githubUrl?: string dateCompleted: string isFeatured: boolean displayOrder: number skills?: ProjectSkill[] } export interface ProjectSkill { id: number slug: string name: string levelBefore: number levelAfter: number } ``` ### Clés i18n nécessaires Ajouter dans `frontend/i18n/fr.json` et `frontend/i18n/en.json` : ```json { "projects": { "discover": "Découvrir" } } ``` ```json { "projects": { "discover": "Discover" } } ``` ### Design Tokens utilisés | Token | Valeur | Usage | |-------|--------|-------| | `sky-dark` | Fond sombre | Overlay au hover | | `sky-accent` | #fa784f | CTA "Découvrir" | | `sky-text` | Blanc cassé | Titre projet | | `sky-text-muted` | Variante atténuée | Description courte | | `font-ui` | Inter | Tout le texte du composant | ### Comportement responsive | Breakpoint | Comportement | |------------|--------------| | Mobile (< 768px) | Card pleine largeur, hauteur image 180px | | Tablette (768px+) | Cards en grille 2 colonnes | | Desktop (1024px+) | Cards en grille 3-4 colonnes | ### Dépendances **Ce composant nécessite :** - Story 1.1 : Nuxt 4 initialisé avec `@nuxt/image`, `@nuxtjs/i18n`, TailwindCSS - Story 1.2 : Model Project avec structure de données - Story 1.3 : Système i18n configuré **Ce composant sera utilisé par :** - Story 2.2 : Page Projets - Galerie - Story 1.7 : Page Résumé Express (projets highlights) - Story 2.5 : Compétences cliquables (liste des projets liés) ### Project Structure Notes **Fichiers à créer :** ``` frontend/app/ ├── components/ │ └── feature/ │ └── ProjectCard.vue # CRÉER ├── types/ │ └── project.ts # CRÉER (si n'existe pas) ``` **Fichiers à modifier :** ``` frontend/i18n/ ├── fr.json # AJOUTER clés projects.* └── en.json # AJOUTER clés projects.* ``` ### References - [Source: docs/planning-artifacts/epics.md#Story-2.1] - [Source: docs/planning-artifacts/architecture.md#Frontend-Architecture] - [Source: docs/planning-artifacts/ux-design-specification.md#Visual-Design-Foundation] - [Source: docs/planning-artifacts/ux-design-specification.md#Accessibility-Strategy] ### Technical Requirements | Requirement | Value | Source | |-------------|-------|--------| | Image format | WebP avec fallback | NFR8 | | Lazy loading | Native via NuxtImg | NFR1, NFR2 | | Animations | Respect prefers-reduced-motion | NFR6 | | Accessibilité | WCAG AA | UX Spec | | Responsive | Mobile-first | UX Spec | ### Previous Story Intelligence (Epic 1) **Patterns établis à suivre :** - Composants feature dans `app/components/feature/` - Types TypeScript dans `app/types/` - Design tokens TailwindCSS : `sky-dark`, `sky-accent`, `sky-text` - Polices : `font-ui` (sans-serif), `font-narrative` (serif) - i18n via `useI18n()` et `localePath()` ## Dev Agent Record ### Agent Model Used Claude Opus 4.5 (claude-opus-4-5-20251101) ### Debug Log References - Aucun problème rencontré. Build et types validés. ### Completion Notes List - Type Project créé dans `types/project.ts` avec interface complète (match API snake_case) - Composant ProjectCard avec NuxtImg (WebP, lazy loading), hover overlay, CTA "Découvrir" - Navigation via NuxtLink + localePath vers `/projets/{slug}` - Responsive : hauteur image 176px mobile, 192px desktop - Accessibilité : role="article", alt sur image, focus-visible outline - prefers-reduced-motion : toutes animations désactivées - Placeholder image si `project.image` absent - Border hover effect (sky-accent/30) - Traductions projects.discover, no_projects, view_all ajoutées ### Change Log | Date | Change | Author | |------|--------|--------| | 2026-02-04 | Story créée avec contexte complet | SM Agent | | 2026-02-06 | Tasks 1-7 implémentées et validées | Dev Agent (Claude Opus 4.5) | ### File List - `frontend/app/types/project.ts` — CRÉÉ - `frontend/app/components/feature/ProjectCard.vue` — CRÉÉ - `frontend/i18n/fr.json` — MODIFIÉ (ajout projects.*) - `frontend/i18n/en.json` — MODIFIÉ (ajout projects.*)