feat(frontend): système narrateur contextuel avec arc de révélation

Story 3.3 : Textes narrateur contextuels et arc de révélation
- Composable useNarrator.ts avec queue de messages prioritaires
- Composable useIdleDetection.ts (détection inactivité 30s)
- Plugin narrator-transitions.client.ts (déclencheurs de navigation)
- Layout adventure.vue avec NarratorBubble intégré
- Store progression: narratorStage devient un getter calculé (0-20-40-60-80%)
- Pages projets, competences, temoignages, parcours utilisent layout adventure
- Messages: intro, transitions, encouragements 25/50/75%, hints, contact_unlocked

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-07 03:04:07 +01:00
parent e882cd3e7a
commit 99fa61fcaa
12 changed files with 324 additions and 54 deletions

View File

@@ -11,11 +11,21 @@ export interface ProgressionState {
easterEggsFound: string[]
challengeCompleted: boolean
contactUnlocked: boolean
narratorStage: number
choices: Record<string, string>
consentGiven: boolean | null
}
const NARRATOR_STAGE_THRESHOLDS = [0, 20, 40, 60, 80]
function calculateNarratorStage(percent: number): number {
for (let i = NARRATOR_STAGE_THRESHOLDS.length - 1; i >= 0; i--) {
if (percent >= NARRATOR_STAGE_THRESHOLDS[i]) {
return i + 1
}
}
return 1
}
const SECTIONS = ['projets', 'competences', 'temoignages', 'parcours'] as const
/**
@@ -69,7 +79,6 @@ export const useProgressionStore = defineStore('progression', {
easterEggsFound: [],
challengeCompleted: false,
contactUnlocked: false,
narratorStage: 1,
choices: {},
consentGiven: null,
}),
@@ -82,6 +91,8 @@ export const useProgressionStore = defineStore('progression', {
progressPercent: (state) => Math.round((state.visitedSections.length / SECTIONS.length) * 100),
hasExistingProgress: (state) => state.visitedSections.length > 0 || state.hero !== null,
narratorStage: (state) => calculateNarratorStage(state.completionPercent),
},
actions: {
@@ -121,12 +132,6 @@ export const useProgressionStore = defineStore('progression', {
this.contactUnlocked = true
},
updateNarratorStage(stage: number) {
if (stage >= 1 && stage <= 5) {
this.narratorStage = stage
}
},
makeChoice(choiceId: string, value: string) {
this.choices[choiceId] = value
},