Files
Portfolio-Game/frontend/app/components/feature/EasterEggPopup.vue
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

163 lines
4.3 KiB
Vue

<template>
<Teleport to="body">
<Transition name="popup">
<div
v-if="visible && reward"
class="fixed inset-0 z-50 flex items-center justify-center p-4"
>
<!-- Overlay -->
<div
class="absolute inset-0 bg-black/70 backdrop-blur-sm"
@click="emit('close')"
/>
<!-- Modal -->
<div
class="relative bg-sky-dark-50 rounded-2xl p-8 max-w-md w-full border border-sky-accent/50 shadow-2xl shadow-sky-accent/20 animate-bounce-in"
>
<!-- Confetti -->
<div class="absolute -top-4 left-1/2 -translate-x-1/2 text-4xl animate-bounce">
<span aria-hidden="true">*</span>
</div>
<!-- Type icon -->
<div class="text-6xl text-center mb-4">
{{ rewardIcon }}
</div>
<!-- Title -->
<h2 class="text-2xl font-ui font-bold text-sky-accent text-center mb-2">
{{ $t('easterEgg.found') }}
</h2>
<!-- Counter -->
<p class="text-sm text-sky-text/60 text-center mb-6">
{{ $t('easterEgg.count', { found: foundCount, total: totalEasterEggs }) }}
</p>
<!-- Reward -->
<div class="bg-sky-dark rounded-lg p-4 mb-6">
<!-- Code snippet -->
<pre
v-if="reward.reward_type === 'snippet'"
class="font-mono text-sm text-sky-accent overflow-x-auto whitespace-pre-wrap"
><code>{{ reward.reward }}</code></pre>
<!-- Anecdote -->
<p
v-else-if="reward.reward_type === 'anecdote'"
class="font-narrative text-sky-text italic"
>
{{ reward.reward }}
</p>
<!-- Badge -->
<div
v-else-if="reward.reward_type === 'badge'"
class="text-center"
>
<p class="font-ui text-sky-text">{{ reward.reward }}</p>
</div>
<!-- Image -->
<div
v-else-if="reward.reward_type === 'image'"
class="text-center"
>
<p class="font-ui text-sky-text">{{ reward.reward }}</p>
</div>
</div>
<!-- Difficulty -->
<div class="flex items-center justify-center gap-1 mb-6">
<span class="text-xs text-sky-text/60 mr-2">{{ $t('easterEgg.difficulty') }}:</span>
<span
v-for="i in 5"
:key="i"
class="text-sm"
:class="i <= reward.difficulty ? 'text-sky-accent' : 'text-sky-dark-100'"
>
*
</span>
</div>
<!-- Close button -->
<button
type="button"
class="w-full py-3 bg-sky-accent text-white font-ui font-semibold rounded-lg hover:bg-sky-accent/90 transition-colors"
@click="emit('close')"
>
{{ $t('common.continue') }}
</button>
</div>
</div>
</Transition>
</Teleport>
</template>
<script setup lang="ts">
import type { EasterEggReward } from '~/composables/useFetchEasterEggs'
const props = defineProps<{
visible: boolean
reward: EasterEggReward | null
}>()
const emit = defineEmits<{
close: []
}>()
const progressionStore = useProgressionStore()
const { availableEasterEggs } = useFetchEasterEggs()
const totalEasterEggs = computed(() => availableEasterEggs.value.length || 8)
const foundCount = computed(() => progressionStore.easterEggsFoundCount)
// Icon based on reward type
const rewardIcon = computed(() => {
if (!props.reward) return ''
const icons: Record<string, string> = {
snippet: '</>', // Code icon
anecdote: '!', // Story icon
image: '[]', // Image icon
badge: '*', // Trophy icon
}
return icons[props.reward.reward_type] || ''
})
</script>
<style scoped>
.popup-enter-active,
.popup-leave-active {
transition: all 0.3s ease;
}
.popup-enter-from,
.popup-leave-to {
opacity: 0;
}
.popup-enter-from .relative,
.popup-leave-to .relative {
transform: scale(0.9);
}
@keyframes bounce-in {
0% {
transform: scale(0.5);
opacity: 0;
}
50% {
transform: scale(1.05);
}
100% {
transform: scale(1);
opacity: 1;
}
}
.animate-bounce-in {
animation: bounce-in 0.4s ease-out;
}
</style>