🎉 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,402 @@
# Story 1.7: Page résumé express et mode pressé
Status: ready-for-dev
## Story
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
1. **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)
2. **And** les compétences clés avec stack technique sont visibles (10s)
3. **And** 3-4 projets highlights avec liens sont affichés (10s)
4. **And** un CTA de contact direct est visible (5s)
5. **And** un bouton discret "Voir l'aventure" invite à l'expérience complète
6. **And** la page est fonctionnelle en FR et EN
7. **And** les données sont chargées depuis l'API (projets, skills)
8. **And** les meta tags SEO sont optimisés pour cette page
9. **And** le layout `minimal.vue` est utilisé
## Tasks / Subtasks
- [ ] **Task 1: Structure de la page résumé** (AC: #1, #9)
- [ ] Implémenter `frontend/app/pages/resume.vue`
- [ ] Utiliser le layout minimal : `definePageMeta({ layout: 'minimal' })`
- [ ] Structure en sections verticales : Hero → Skills → Projets → Contact
- [ ] Design épuré, scannable en 30 secondes
- [ ] **Task 2: Section Hero (5s)** (AC: #1)
- [ ] Photo/avatar de Célian (image optimisée via nuxt/image)
- [ ] Nom : "Célian" (ou nom complet)
- [ ] Titre : "Développeur Full-Stack"
- [ ] Accroche courte : 1-2 phrases percutantes traduites
- [ ] Liens sociaux : GitHub, LinkedIn (icônes cliquables)
- [ ] **Task 3: Section Compétences (10s)** (AC: #2, #7)
- [ ] Titre de section : "Stack technique"
- [ ] Afficher les compétences principales par catégorie (Frontend, Backend, Tools)
- [ ] Format compact : badges ou liste avec icônes
- [ ] Charger depuis l'API `/api/skills` (filtrer les principales)
- [ ] Limiter à 8-12 compétences max pour la lisibilité
- [ ] **Task 4: Section Projets highlights (10s)** (AC: #3, #7)
- [ ] Titre de section : "Projets récents"
- [ ] Afficher 3-4 projets featured
- [ ] Format compact : titre + 1 ligne description + lien
- [ ] Charger depuis l'API `/api/projects?featured=true`
- [ ] Liens vers les détails (ouvre dans nouvel onglet ou garde sur resume)
- [ ] **Task 5: Section Contact (5s)** (AC: #4)
- [ ] CTA principal : "Me contacter" (lien vers `/contact` ou email direct)
- [ ] Email visible (cliquable mailto:)
- [ ] Optionnel : téléphone si souhaité
- [ ] Style accent pour le CTA principal
- [ ] **Task 6: Bouton "Voir l'aventure"** (AC: #5)
- [ ] Position discrète mais visible (en bas ou en sidebar)
- [ ] Texte : "Envie d'explorer ? Découvrir l'aventure complète"
- [ ] Lien vers `/` (landing page)
- [ ] Style secondaire, pas en compétition avec le CTA contact
- [ ] **Task 7: Chargement des données API** (AC: #7)
- [ ] Utiliser `useFetch` ou `useAsyncData` pour charger skills et projets
- [ ] Gérer les états loading et error
- [ ] Cache côté client pour éviter les appels répétés
- [ ] SSR : données chargées côté serveur pour SEO
- [ ] **Task 8: Traductions bilingue** (AC: #6)
- [ ] Ajouter toutes les traductions dans `i18n/fr.json` et `i18n/en.json`
- [ ] Section titles, accroche, CTA labels
- [ ] Le contenu API est déjà traduit (Story 1.3)
- [ ] **Task 9: Meta tags SEO optimisés** (AC: #8)
- [ ] Utiliser `useSeo()` avec meta spécifiques
- [ ] Title : "Célian - Développeur Full-Stack | CV Express"
- [ ] Description : optimisée pour les recruteurs
- [ ] Open Graph image : image de preview professionnelle
- [ ] Structured data (JSON-LD) pour Person/Developer (optionnel)
- [ ] **Task 10: Responsive et accessibilité** (AC: #1)
- [ ] Mobile : sections empilées verticalement
- [ ] Desktop : layout plus aéré, possible 2 colonnes pour skills/projets
- [ ] Contraste suffisant (WCAG AA)
- [ ] Navigation clavier fluide
- [ ] Skip link vers le contenu principal
- [ ] **Task 11: Validation finale** (AC: tous)
- [ ] Page accessible via `/resume` (FR) et `/en/resume` (EN)
- [ ] Chargement < 2s (données légères)
- [ ] Toutes les sections visibles sans scroll excessif sur desktop
- [ ] CTA contact fonctionnel
- [ ] Lien vers aventure fonctionne
- [ ] Layout minimal utilisé (pas de header complet)
- [ ] SEO : vérifier meta tags dans le code source
## Dev Notes
### Structure de la page résumé
```
┌─────────────────────────────────────────────────────────────────┐
│ PAGE RÉSUMÉ EXPRESS │
│ (Layout minimal) │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────┐ │
│ │ Photo │ Célian │
│ │ │ Développeur Full-Stack │
│ └─────────┘ "Passionné par les expériences web innovantes" │
│ [GitHub] [LinkedIn] │
│ │
├─────────────────────────────────────────────────────────────────┤
│ STACK TECHNIQUE │
│ ┌────────────────────────────────────────────────────────────┐│
│ │ Frontend: Vue.js • Nuxt • TypeScript • TailwindCSS ││
│ │ Backend: Laravel • PHP • Node.js • MariaDB ││
│ │ Tools: Git • Docker • CI/CD ││
│ └────────────────────────────────────────────────────────────┘│
│ │
├─────────────────────────────────────────────────────────────────┤
│ PROJETS RÉCENTS │
│ ┌────────────────────────────────────────────────────────────┐│
│ │ • Skycel Portfolio - Portfolio gamifié interactif [→] ││
│ │ • Projet E-commerce - Boutique en ligne moderne [→] ││
│ │ • Dashboard Analytics - Interface de visualisation [→] ││
│ └────────────────────────────────────────────────────────────┘│
│ │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────────────┐ │
│ │ ME CONTACTER │ │
│ └──────────────────────────┘ │
│ │
│ contact@skycel.fr │
│ │
│ "Envie d'explorer ? Voir l'aventure complète →" │
│ │
└─────────────────────────────────────────────────────────────────┘
```
### Implémentation de la page
```vue
<!-- frontend/app/pages/resume.vue -->
<template>
<div class="max-w-3xl mx-auto px-4 py-8">
<!-- Section Hero -->
<section class="text-center mb-12">
<NuxtImg
src="/images/avatar.jpg"
alt="Célian"
width="120"
height="120"
class="rounded-full mx-auto mb-4"
/>
<h1 class="text-3xl font-ui font-bold mb-2">Célian</h1>
<p class="text-xl text-sky-accent mb-3">{{ $t('resume.title') }}</p>
<p class="text-sky-text/80 font-narrative mb-4">{{ $t('resume.tagline') }}</p>
<div class="flex justify-center gap-4">
<a href="https://github.com/celian" target="_blank" rel="noopener" class="text-sky-text/60 hover:text-sky-accent transition-colors">
<span class="sr-only">GitHub</span>
<!-- GitHub icon -->
</a>
<a href="https://linkedin.com/in/celian" target="_blank" rel="noopener" class="text-sky-text/60 hover:text-sky-accent transition-colors">
<span class="sr-only">LinkedIn</span>
<!-- LinkedIn icon -->
</a>
</div>
</section>
<!-- Section Skills -->
<section class="mb-12">
<h2 class="text-xl font-ui font-semibold mb-4 text-sky-accent">
{{ $t('resume.skills_title') }}
</h2>
<div v-if="skillsLoading" class="text-sky-text/50">{{ $t('common.loading') }}</div>
<div v-else class="space-y-3">
<div v-for="category in skillsByCategory" :key="category.name">
<span class="text-sky-text/60 text-sm">{{ category.name }}:</span>
<span class="ml-2">
<span
v-for="(skill, i) in category.skills"
:key="skill.slug"
class="text-sky-text"
>
{{ skill.name }}<span v-if="i < category.skills.length - 1"> </span>
</span>
</span>
</div>
</div>
</section>
<!-- Section Projets -->
<section class="mb-12">
<h2 class="text-xl font-ui font-semibold mb-4 text-sky-accent">
{{ $t('resume.projects_title') }}
</h2>
<div v-if="projectsLoading" class="text-sky-text/50">{{ $t('common.loading') }}</div>
<ul v-else class="space-y-3">
<li v-for="project in featuredProjects" :key="project.slug" class="flex items-start gap-2">
<span class="text-sky-accent"></span>
<div>
<NuxtLink
:to="localePath(`/projets/${project.slug}`)"
class="font-semibold hover:text-sky-accent transition-colors"
>
{{ project.title }}
</NuxtLink>
<span class="text-sky-text/60 text-sm ml-2">{{ project.short_description }}</span>
</div>
</li>
</ul>
</section>
<!-- Section Contact -->
<section class="text-center mb-8">
<NuxtLink
:to="localePath('/contact')"
class="inline-block px-8 py-4 bg-sky-accent text-sky-dark font-ui font-bold rounded-lg hover:bg-sky-accent-hover transition-colors"
>
{{ $t('resume.cta_contact') }}
</NuxtLink>
<p class="mt-4 text-sky-text/60">
<a href="mailto:contact@skycel.fr" class="hover:text-sky-accent transition-colors">
contact@skycel.fr
</a>
</p>
</section>
<!-- Lien vers aventure -->
<div class="text-center border-t border-sky-text/10 pt-6">
<NuxtLink
:to="localePath('/')"
class="text-sky-text/50 hover:text-sky-accent text-sm transition-colors"
>
{{ $t('resume.adventure_link') }}
</NuxtLink>
</div>
</div>
</template>
<script setup lang="ts">
definePageMeta({
layout: 'minimal',
})
const { t } = useI18n()
const localePath = useLocalePath()
const { apiFetch } = useApi()
const { setPageMeta } = useSeo()
// SEO
setPageMeta({
title: t('resume.meta_title'),
description: t('resume.meta_description'),
})
// Chargement des skills
const { data: skills, pending: skillsLoading } = await useFetch('/api/skills', {
baseURL: useRuntimeConfig().public.apiUrl,
headers: {
'X-API-Key': useRuntimeConfig().public.apiKey,
'Accept-Language': useI18n().locale.value,
},
})
// Chargement des projets featured
const { data: projects, pending: projectsLoading } = await useFetch('/api/projects', {
baseURL: useRuntimeConfig().public.apiUrl,
headers: {
'X-API-Key': useRuntimeConfig().public.apiKey,
'Accept-Language': useI18n().locale.value,
},
query: { featured: true },
})
// Grouper les skills par catégorie
const skillsByCategory = computed(() => {
if (!skills.value?.data) return []
const categories = ['Frontend', 'Backend', 'Tools']
return categories.map(cat => ({
name: cat,
skills: skills.value.data.filter((s: any) => s.category === cat).slice(0, 4),
})).filter(c => c.skills.length > 0)
})
// Projets featured (max 4)
const featuredProjects = computed(() => {
return projects.value?.data?.slice(0, 4) || []
})
</script>
```
### Traductions à ajouter
```json
// frontend/i18n/fr.json
{
"resume": {
"title": "Développeur Full-Stack",
"tagline": "Passionné par les expériences web innovantes et immersives",
"skills_title": "Stack technique",
"projects_title": "Projets récents",
"cta_contact": "Me contacter",
"adventure_link": "Envie d'explorer ? Découvrir l'aventure complète",
"meta_title": "Célian - Développeur Full-Stack | CV Express",
"meta_description": "Développeur Full-Stack spécialisé en Vue.js, Nuxt, Laravel. Découvrez mon profil et mes projets en 30 secondes."
}
}
```
```json
// frontend/i18n/en.json
{
"resume": {
"title": "Full-Stack Developer",
"tagline": "Passionate about innovative and immersive web experiences",
"skills_title": "Tech Stack",
"projects_title": "Recent Projects",
"cta_contact": "Contact Me",
"adventure_link": "Want to explore? Discover the full adventure",
"meta_title": "Célian - Full-Stack Developer | Quick Resume",
"meta_description": "Full-Stack Developer specialized in Vue.js, Nuxt, Laravel. Discover my profile and projects in 30 seconds."
}
}
```
### Dépendances
**Cette story DÉPEND de :**
- Story 1.3 : API bilingue, useApi composable
- Story 1.4 : Layout minimal.vue, useSeo composable
- Story 1.2 : API projects et skills fonctionnels
**Cette story PRÉPARE pour :**
- URL directe pour candidatures (usage recruteurs)
- Alternative à l'expérience gamifiée
### Project Structure Notes
**Fichiers à créer/modifier :**
```
frontend/app/
├── pages/
│ └── resume.vue # CRÉER
├── public/
│ └── images/
│ └── avatar.jpg # AJOUTER (photo Célian)
└── i18n/
├── fr.json # MODIFIER (ajouter resume.*)
└── en.json # MODIFIER (ajouter resume.*)
```
### Performance
- **Budget temps** : Chargement < 2s
- **Données légères** : Skills (8-12 items), Projets (3-4 items)
- **SSR** : Données chargées côté serveur pour SEO optimal
- **Images** : Avatar optimisé via nuxt/image (WebP, dimensions fixes)
### References
- [Source: docs/planning-artifacts/epics.md#Story-1.7]
- [Source: docs/planning-artifacts/ux-design-specification.md#Page-Resume]
- [Source: docs/prd-gamification.md#FR1]
### Technical Requirements
| Requirement | Value | Source |
|-------------|-------|--------|
| Layout | minimal.vue | Architecture |
| Temps lecture | ~30 secondes | UX Design |
| Projets affichés | 3-4 featured | UX Design |
| Skills affichés | 8-12 max | UX Design |
| SSR | Required | NFR5 |
## Dev Agent Record
### Agent Model Used
{{agent_model_name_version}}
### Debug Log References
### Completion Notes List
### Change Log
| Date | Change | Author |
|------|--------|--------|
| 2026-02-03 | Story créée avec contexte complet | SM Agent |
### File List