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>
This commit is contained in:
2026-02-08 13:35:12 +01:00
parent 64b1a33d10
commit 7e87a341a2
38 changed files with 3037 additions and 96 deletions

View File

@@ -13,6 +13,7 @@ export interface ProgressionState {
contactUnlocked: boolean
choices: Record<string, string>
consentGiven: boolean | null
introSeen: boolean
}
const NARRATOR_STAGE_THRESHOLDS = [0, 20, 40, 60, 80]
@@ -81,6 +82,7 @@ export const useProgressionStore = defineStore('progression', {
contactUnlocked: false,
choices: {},
consentGiven: null,
introSeen: false,
}),
getters: {
@@ -93,6 +95,8 @@ export const useProgressionStore = defineStore('progression', {
hasExistingProgress: (state) => state.visitedSections.length > 0 || state.hero !== null,
narratorStage: (state) => calculateNarratorStage(state.completionPercent),
easterEggsFoundCount: (state) => state.easterEggsFound.length,
},
actions: {
@@ -124,6 +128,10 @@ export const useProgressionStore = defineStore('progression', {
}
},
markEasterEggFound(slug: string) {
this.findEasterEgg(slug)
},
completeChallenge() {
this.challengeCompleted = true
},
@@ -136,6 +144,10 @@ export const useProgressionStore = defineStore('progression', {
this.choices[choiceId] = value
},
setIntroSeen(seen: boolean) {
this.introSeen = seen
},
setConsent(given: boolean) {
this.consentGiven = given
if (given) {