Files
skycel 7e87a341a2 feat(epic-4): chemins narratifs, easter eggs, challenge et contact
Epic 4: Chemins Narratifs, Challenge & Contact

Stories implementees:
- 4.1: Composant ChoiceCards pour choix narratifs binaires
- 4.2: Sequence d'intro narrative avec Le Bug
- 4.3: Chemins narratifs differencies avec useNarrativePath
- 4.4: Table easter_eggs et systeme de detection (API + composable)
- 4.5: Easter eggs UI (popup, notification, collection)
- 4.6: Page challenge avec puzzle de code
- 4.7: Page revelation "Monde de Code"
- 4.8: Page contact avec formulaire et stats

Fichiers crees:
- Frontend: ChoiceCards, IntroSequence, ZoneEndChoice, EasterEggPopup,
  CodePuzzle, ChallengeSuccess, CodeWorld, et pages intro/challenge/revelation
- API: EasterEggController, Model, Migration, Seeder

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-08 13:35:12 +01:00

105 lines
2.5 KiB
Vue

<template>
<div class="max-w-7xl mx-auto px-4 py-8 md:py-12">
<h1 class="text-3xl md:text-4xl font-ui font-bold text-sky-text mb-8">
{{ $t('projects.title') }}
</h1>
<!-- Loading state -->
<div v-if="pending" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
<div v-for="i in 6" :key="i" class="animate-pulse">
<div class="bg-sky-text/5 rounded-xl h-44 md:h-48" />
<div class="p-4">
<div class="bg-sky-text/5 h-6 rounded w-3/4 mb-2" />
<div class="bg-sky-text/5 h-4 rounded w-full" />
</div>
</div>
</div>
<!-- Error state -->
<div v-else-if="error" class="text-center py-16">
<p class="text-sky-text/60 font-narrative mb-6">
{{ $t('projects.load_error') }}
</p>
<button
class="px-6 py-3 bg-sky-accent text-sky-dark font-ui font-semibold rounded-lg hover:opacity-90 transition-opacity"
@click="refresh()"
>
{{ $t('common.retry') }}
</button>
</div>
<!-- Empty state -->
<div v-else-if="!projects || projects.length === 0" class="text-center py-16">
<p class="text-sky-text/60 font-narrative">
{{ $t('projects.no_projects') }}
</p>
</div>
<!-- Projects grid -->
<div
v-else
class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6"
>
<FeatureProjectCard
v-for="(project, index) in projects"
:key="project.id"
:project="project"
class="project-card-animated"
:style="{ '--animation-delay': `${index * 80}ms` }"
/>
</div>
<!-- Choice for next zone -->
<FeatureZoneEndChoice />
</div>
</template>
<script setup lang="ts">
import { useProgressionStore } from '~/stores/progression'
definePageMeta({
layout: 'adventure',
})
const { t } = useI18n()
const { setPageMeta } = useSeo()
const store = useProgressionStore()
const { data: projects, pending, error, refresh } = await useFetchProjects()
setPageMeta({
title: t('projects.page_title'),
description: t('projects.page_description'),
})
onMounted(() => {
store.visitSection('projets')
})
</script>
<style scoped>
.project-card-animated {
animation: fadeInUp 0.5s ease-out forwards;
animation-delay: var(--animation-delay, 0ms);
opacity: 0;
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@media (prefers-reduced-motion: reduce) {
.project-card-animated {
animation: none;
opacity: 1;
}
}
</style>