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>
101 lines
2.8 KiB
Vue
101 lines
2.8 KiB
Vue
<template>
|
|
<div class="intro-background absolute inset-0 overflow-hidden">
|
|
<!-- Gradient de fond -->
|
|
<div class="absolute inset-0 bg-gradient-to-b from-sky-dark via-sky-dark-50 to-sky-dark" />
|
|
|
|
<!-- Particules flottantes (code fragments) -->
|
|
<div
|
|
v-if="!reducedMotion"
|
|
class="particles absolute inset-0"
|
|
>
|
|
<div
|
|
v-for="i in 20"
|
|
:key="i"
|
|
class="particle absolute text-sky-accent/10 font-mono text-xs"
|
|
:style="{
|
|
left: `${particlePositions[i - 1]?.x ?? 0}%`,
|
|
top: `${particlePositions[i - 1]?.y ?? 0}%`,
|
|
animationDelay: `${particlePositions[i - 1]?.delay ?? 0}s`,
|
|
animationDuration: `${10 + (particlePositions[i - 1]?.duration ?? 0)}s`,
|
|
}"
|
|
>
|
|
{{ codeSymbols[(i - 1) % codeSymbols.length] }}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Toile d'araignée stylisée (SVG) -->
|
|
<svg
|
|
class="absolute top-0 right-0 w-64 h-64 text-sky-dark-100/30"
|
|
viewBox="0 0 200 200"
|
|
>
|
|
<path
|
|
d="M100,100 L100,0 M100,100 L200,100 M100,100 L100,200 M100,100 L0,100 M100,100 L170,30 M100,100 L170,170 M100,100 L30,170 M100,100 L30,30"
|
|
stroke="currentColor"
|
|
stroke-width="1"
|
|
fill="none"
|
|
/>
|
|
<circle cx="100" cy="100" r="30" stroke="currentColor" stroke-width="1" fill="none" />
|
|
<circle cx="100" cy="100" r="60" stroke="currentColor" stroke-width="1" fill="none" />
|
|
<circle cx="100" cy="100" r="90" stroke="currentColor" stroke-width="1" fill="none" />
|
|
</svg>
|
|
|
|
<!-- Toile en bas à gauche -->
|
|
<svg
|
|
class="absolute bottom-0 left-0 w-48 h-48 text-sky-dark-100/20 transform rotate-180"
|
|
viewBox="0 0 200 200"
|
|
>
|
|
<path
|
|
d="M100,100 L100,0 M100,100 L200,100 M100,100 L170,30 M100,100 L170,170"
|
|
stroke="currentColor"
|
|
stroke-width="1"
|
|
fill="none"
|
|
/>
|
|
<circle cx="100" cy="100" r="40" stroke="currentColor" stroke-width="1" fill="none" />
|
|
<circle cx="100" cy="100" r="80" stroke="currentColor" stroke-width="1" fill="none" />
|
|
</svg>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
const reducedMotion = useReducedMotion()
|
|
|
|
const codeSymbols = ['</', '/>', '{}', '[]', '()', '=>', '&&', '||']
|
|
|
|
// Générer des positions aléatoires côté serveur-safe
|
|
const particlePositions = Array.from({ length: 20 }, (_, i) => ({
|
|
x: ((i * 17 + 7) % 100),
|
|
y: ((i * 23 + 13) % 100),
|
|
delay: (i * 0.3) % 5,
|
|
duration: (i % 10),
|
|
}))
|
|
</script>
|
|
|
|
<style scoped>
|
|
@keyframes float-up {
|
|
from {
|
|
transform: translateY(100vh) rotate(0deg);
|
|
opacity: 0;
|
|
}
|
|
10% {
|
|
opacity: 1;
|
|
}
|
|
90% {
|
|
opacity: 1;
|
|
}
|
|
to {
|
|
transform: translateY(-100vh) rotate(360deg);
|
|
opacity: 0;
|
|
}
|
|
}
|
|
|
|
.particle {
|
|
animation: float-up linear infinite;
|
|
}
|
|
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.particle {
|
|
animation: none;
|
|
}
|
|
}
|
|
</style>
|