🎉 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:
285
docs/implementation-artifacts/2-1-composant-projectcard.md
Normal file
285
docs/implementation-artifacts/2-1-composant-projectcard.md
Normal file
@@ -0,0 +1,285 @@
|
||||
# Story 2.1: Composant ProjectCard
|
||||
|
||||
Status: ready-for-dev
|
||||
|
||||
## 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
|
||||
|
||||
- [ ] **Task 1: Créer le composant ProjectCard.vue** (AC: #1, #2, #3)
|
||||
- [ ] Créer le fichier `frontend/app/components/feature/ProjectCard.vue`
|
||||
- [ ] Définir les props TypeScript : `project` (object avec slug, image, title, shortDescription)
|
||||
- [ ] Utiliser `<NuxtImg>` pour l'image avec format WebP et lazy loading
|
||||
- [ ] Intégrer `useI18n()` pour le titre et la description traduits
|
||||
- [ ] Afficher titre (`project.title`) et description courte (`project.shortDescription`)
|
||||
|
||||
- [ ] **Task 2: Implémenter le hover effect et CTA** (AC: #4)
|
||||
- [ ] Créer un overlay qui apparaît au hover avec transition CSS
|
||||
- [ ] Ajouter un CTA "Découvrir" (traduit via i18n) centré dans l'overlay
|
||||
- [ ] Animation subtile : fade-in + léger scale (0.98 → 1)
|
||||
- [ ] Utiliser les classes Tailwind pour les transitions
|
||||
|
||||
- [ ] **Task 3: Implémenter la navigation** (AC: #5)
|
||||
- [ ] Rendre la card entièrement cliquable avec `<NuxtLink>`
|
||||
- [ ] Utiliser `localePath()` pour générer l'URL correcte selon la langue
|
||||
- [ ] URL pattern : `/projets/{slug}` (FR) ou `/en/projects/{slug}` (EN)
|
||||
|
||||
- [ ] **Task 4: Gérer `prefers-reduced-motion`** (AC: #6)
|
||||
- [ ] Créer une media query CSS pour détecter `prefers-reduced-motion: reduce`
|
||||
- [ ] Désactiver les transitions et animations si motion réduite
|
||||
- [ ] Le hover effect reste visible mais sans animation
|
||||
|
||||
- [ ] **Task 5: Rendre le composant responsive** (AC: #7)
|
||||
- [ ] Mobile : card pleine largeur, hauteur fixe ou aspect-ratio
|
||||
- [ ] Desktop : card avec largeur flexible pour grille (min 280px, max 400px)
|
||||
- [ ] Image qui remplit la card avec `object-cover`
|
||||
- [ ] Texte tronqué si trop long (ellipsis)
|
||||
|
||||
- [ ] **Task 6: Accessibilité** (AC: #8)
|
||||
- [ ] Focus visible sur la card (outline accent)
|
||||
- [ ] `role="article"` sur la card container
|
||||
- [ ] `alt` descriptif sur l'image (utiliser le titre du projet)
|
||||
- [ ] Navigation au clavier fonctionnelle (Tab, Enter)
|
||||
|
||||
- [ ] **Task 7: Tests et validation**
|
||||
- [ ] Tester le composant avec des données de projet fictives
|
||||
- [ ] Vérifier l'affichage en FR et EN
|
||||
- [ ] Vérifier le hover effect et la navigation
|
||||
- [ ] Tester sur mobile et desktop
|
||||
- [ ] Valider l'accessibilité avec axe DevTools
|
||||
|
||||
## Dev Notes
|
||||
|
||||
### Structure du composant
|
||||
|
||||
```vue
|
||||
<!-- frontend/app/components/feature/ProjectCard.vue -->
|
||||
<script setup lang="ts">
|
||||
interface Project {
|
||||
slug: string
|
||||
image: string
|
||||
title: string
|
||||
shortDescription: string
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
project: Project
|
||||
}>()
|
||||
|
||||
const { t } = useI18n()
|
||||
const localePath = useLocalePath()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NuxtLink
|
||||
:to="localePath(`/projets/${project.slug}`)"
|
||||
class="project-card group"
|
||||
role="article"
|
||||
>
|
||||
<div class="relative overflow-hidden rounded-lg">
|
||||
<!-- Image avec lazy loading -->
|
||||
<NuxtImg
|
||||
:src="project.image"
|
||||
:alt="project.title"
|
||||
format="webp"
|
||||
loading="lazy"
|
||||
class="w-full h-48 object-cover transition-transform duration-300 group-hover:scale-105"
|
||||
/>
|
||||
|
||||
<!-- Overlay au hover -->
|
||||
<div class="absolute inset-0 bg-sky-dark/70 opacity-0 group-hover:opacity-100 transition-opacity duration-300 flex items-center justify-center">
|
||||
<span class="text-sky-accent font-ui text-lg">
|
||||
{{ t('projects.discover') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Contenu texte -->
|
||||
<div class="p-4">
|
||||
<h3 class="font-ui text-lg text-sky-text font-semibold truncate">
|
||||
{{ project.title }}
|
||||
</h3>
|
||||
<p class="font-ui text-sm text-sky-text-muted line-clamp-2 mt-1">
|
||||
{{ project.shortDescription }}
|
||||
</p>
|
||||
</div>
|
||||
</NuxtLink>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* Respect prefers-reduced-motion */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.project-card * {
|
||||
transition: none !important;
|
||||
animation: none !important;
|
||||
}
|
||||
|
||||
.project-card:hover img {
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* Focus visible */
|
||||
.project-card:focus-visible {
|
||||
outline: 2px solid theme('colors.sky-accent.DEFAULT');
|
||||
outline-offset: 2px;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
### 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
|
||||
|
||||
{{agent_model_name_version}}
|
||||
|
||||
### Debug Log References
|
||||
|
||||
### Completion Notes List
|
||||
|
||||
### Change Log
|
||||
| Date | Change | Author |
|
||||
|------|--------|--------|
|
||||
| 2026-02-04 | Story créée avec contexte complet | SM Agent |
|
||||
|
||||
### File List
|
||||
|
||||
Reference in New Issue
Block a user