# Story 4.3: Chemins narratifs différenciés

Status: ready-for-dev

## Story

As a visiteur,
I want que mes choix aient un impact visible sur mon parcours,
so that je sens que mon expérience est vraiment personnalisée.

## Acceptance Criteria

1. **Given** le visiteur fait des choix tout au long de l'aventure **When** il navigue entre les zones **Then** 2-3 points de choix binaires créent 4-8 parcours possibles
2. **And** chaque choix est enregistré dans `choices` du store
3. **And** l'ordre suggéré des zones varie selon le chemin choisi
4. **And** les textes du narrateur s'adaptent au chemin (transitions contextuelles)
5. **And** tous les chemins permettent de visiter tout le contenu
6. **And** tous les chemins mènent au contact (pas de "mauvais" choix)
7. **And** le `currentPath` du store reflète le chemin actuel
8. **And** à la fin de chaque zone, le narrateur propose un choix vers la suite

## Tasks / Subtasks

- [ ] **Task 1: Définir l'arbre des chemins** (AC: #1, #5, #6)
  - [ ] Créer `frontend/app/data/narrativePaths.ts`
  - [ ] Définir 2-3 points de choix créant 4-8 parcours
  - [ ] S'assurer que tous les chemins visitent toutes les zones
  - [ ] S'assurer que tous les chemins mènent au contact

- [ ] **Task 2: Créer le composable useNarrativePath** (AC: #2, #3, #7)
  - [ ] Créer `frontend/app/composables/useNarrativePath.ts`
  - [ ] Calculer le chemin actuel basé sur les choix
  - [ ] Exposer la prochaine zone suggérée
  - [ ] Exposer les zones restantes dans l'ordre

- [ ] **Task 3: Ajouter les textes de transition contextuels** (AC: #4)
  - [ ] Créer des contextes spécifiques : `transition_after_projects_to_skills`, etc.
  - [ ] Variantes selon le chemin pris
  - [ ] Commentaires du narrateur sur les choix précédents

- [ ] **Task 4: Intégrer les choix après chaque zone** (AC: #8)
  - [ ] Composant `ZoneEndChoice.vue` affiché à la fin de chaque page de zone
  - [ ] Proposer les options de destination selon le chemin
  - [ ] Utiliser ChoiceCards pour la présentation

- [ ] **Task 5: Mettre à jour le store** (AC: #2, #7)
  - [ ] Ajouter `currentPath` computed au store
  - [ ] Ajouter `suggestedNextZone` computed
  - [ ] Méthode pour obtenir le choix à un point donné

- [ ] **Task 6: Créer l'API pour les transitions contextuelles** (AC: #4)
  - [ ] Endpoint `/api/narrator/transition-contextual`
  - [ ] Paramètres : from_zone, to_zone, path_choices
  - [ ] Retourner un texte adapté au contexte

- [ ] **Task 7: Tests et validation**
  - [ ] Tester tous les chemins possibles (4-8)
  - [ ] Vérifier que tous mènent au contact
  - [ ] Valider les textes contextuels
  - [ ] Tester la suggestion de zone suivante

## Dev Notes

### Arbre des chemins narratifs

```typescript
// frontend/app/data/narrativePaths.ts

// Points de choix dans l'aventure
export const NARRATIVE_CHOICE_POINTS = {
  // Point 1 : Après l'intro
  intro: {
    id: 'intro',
    options: ['projects', 'skills'],
  },
  // Point 2 : Après la première zone
  after_first_zone: {
    id: 'after_first_zone',
    options: ['testimonials', 'journey'],
  },
  // Point 3 : Après la deuxième zone
  after_second_zone: {
    id: 'after_second_zone',
    // Les options dépendent de ce qui reste
  },
}

// Chemins possibles (4-8 combinaisons)
// Format : intro_choice -> after_first -> after_second -> contact
export const NARRATIVE_PATHS = [
  // Chemin 1 : Projets → Témoignages → Compétences → Parcours → Contact
  ['projects', 'testimonials', 'skills', 'journey', 'contact'],
  // Chemin 2 : Projets → Témoignages → Parcours → Compétences → Contact
  ['projects', 'testimonials', 'journey', 'skills', 'contact'],
  // Chemin 3 : Projets → Parcours → Témoignages → Compétences → Contact
  ['projects', 'journey', 'testimonials', 'skills', 'contact'],
  // Chemin 4 : Projets → Parcours → Compétences → Témoignages → Contact
  ['projects', 'journey', 'skills', 'testimonials', 'contact'],
  // Chemin 5 : Compétences → Témoignages → Projets → Parcours → Contact
  ['skills', 'testimonials', 'projects', 'journey', 'contact'],
  // Chemin 6 : Compétences → Témoignages → Parcours → Projets → Contact
  ['skills', 'testimonials', 'journey', 'projects', 'contact'],
  // Chemin 7 : Compétences → Parcours → Témoignages → Projets → Contact
  ['skills', 'journey', 'testimonials', 'projects', 'contact'],
  // Chemin 8 : Compétences → Parcours → Projets → Témoignages → Contact
  ['skills', 'journey', 'projects', 'testimonials', 'contact'],
]

// Mapper zone key -> route
export const ZONE_ROUTES: Record<string, { fr: string; en: string }> = {
  projects: { fr: '/projets', en: '/en/projects' },
  skills: { fr: '/competences', en: '/en/skills' },
  testimonials: { fr: '/temoignages', en: '/en/testimonials' },
  journey: { fr: '/parcours', en: '/en/journey' },
  contact: { fr: '/contact', en: '/en/contact' },
}
```

### Composable useNarrativePath

```typescript
// frontend/app/composables/useNarrativePath.ts
import { NARRATIVE_PATHS, ZONE_ROUTES } from '~/data/narrativePaths'

export function useNarrativePath() {
  const progressionStore = useProgressionStore()
  const { locale } = useI18n()

  // Déterminer le chemin actuel basé sur les choix
  const currentPath = computed(() => {
    const choices = progressionStore.choices

    // Trouver le premier choix (intro)
    const introChoice = choices.find(c => c.id === 'intro_first_choice')
    if (!introChoice) return null

    const startZone = introChoice.value === 'choice_projects_first' ? 'projects' : 'skills'

    // Filtrer les chemins qui commencent par cette zone
    let possiblePaths = NARRATIVE_PATHS.filter(path => path[0] === startZone)

    // Affiner avec les choix suivants
    const afterFirstChoice = choices.find(c => c.id === 'after_first_zone')
    if (afterFirstChoice && possiblePaths.length > 1) {
      const secondZone = afterFirstChoice.value.includes('testimonials') ? 'testimonials' : 'journey'
      possiblePaths = possiblePaths.filter(path => path[1] === secondZone)
    }

    return possiblePaths[0] || null
  })

  // Zone actuelle basée sur la route
  const currentZone = computed(() => {
    const route = useRoute()
    const path = route.path.toLowerCase()

    for (const [zone, routes] of Object.entries(ZONE_ROUTES)) {
      if (path.includes(routes.fr.slice(1)) || path.includes(routes.en.slice(4))) {
        return zone
      }
    }
    return null
  })

  // Index de la zone actuelle dans le chemin
  const currentZoneIndex = computed(() => {
    if (!currentPath.value || !currentZone.value) return -1
    return currentPath.value.indexOf(currentZone.value)
  })

  // Prochaine zone suggérée
  const suggestedNextZone = computed(() => {
    if (!currentPath.value || currentZoneIndex.value === -1) return null

    const nextIndex = currentZoneIndex.value + 1
    if (nextIndex >= currentPath.value.length) return null

    return currentPath.value[nextIndex]
  })

  // Zones restantes à visiter
  const remainingZones = computed(() => {
    if (!currentPath.value) return []

    const visited = progressionStore.visitedSections
    return currentPath.value.filter(zone =>
      zone !== 'contact' && !visited.includes(zone as any)
    )
  })

  // Obtenir la route pour une zone
  function getZoneRoute(zone: string): string {
    const routes = ZONE_ROUTES[zone]
    if (!routes) return '/'
    return locale.value === 'fr' ? routes.fr : routes.en
  }

  // Générer le choix pour après la zone actuelle
  function getNextChoicePoint() {
    if (!remainingZones.value.length) {
      // Plus de zones, aller au contact
      return {
        id: 'go_to_contact',
        choices: [
          {
            id: 'contact',
            textFr: 'Rencontrer le développeur',
            textEn: 'Meet the developer',
            icon: '📧',
            destination: getZoneRoute('contact'),
            zoneColor: '#fa784f',
          },
        ],
      }
    }

    // Proposer les 2 prochaines zones
    const nextTwo = remainingZones.value.slice(0, 2)

    return {
      id: `after_${currentZone.value}`,
      questionFr: 'Où vas-tu ensuite ?',
      questionEn: 'Where to next?',
      choices: nextTwo.map(zone => ({
        id: `choice_${zone}`,
        textFr: getZoneLabel(zone, 'fr'),
        textEn: getZoneLabel(zone, 'en'),
        icon: getZoneIcon(zone),
        destination: getZoneRoute(zone),
        zoneColor: getZoneColor(zone),
      })),
    }
  }

  return {
    currentPath,
    currentZone,
    suggestedNextZone,
    remainingZones,
    getZoneRoute,
    getNextChoicePoint,
  }
}

// Helpers
function getZoneLabel(zone: string, locale: string): string {
  const labels: Record<string, { fr: string; en: string }> = {
    projects: { fr: 'Découvrir les créations', en: 'Discover the creations' },
    skills: { fr: 'Explorer les compétences', en: 'Explore the skills' },
    testimonials: { fr: 'Écouter les témoignages', en: 'Listen to testimonials' },
    journey: { fr: 'Suivre le parcours', en: 'Follow the journey' },
  }
  return labels[zone]?.[locale] || zone
}

function getZoneIcon(zone: string): string {
  const icons: Record<string, string> = {
    projects: '💻',
    skills: '⚡',
    testimonials: '💬',
    journey: '📍',
  }
  return icons[zone] || '?'
}

function getZoneColor(zone: string): string {
  const colors: Record<string, string> = {
    projects: '#3b82f6',
    skills: '#10b981',
    testimonials: '#f59e0b',
    journey: '#8b5cf6',
  }
  return colors[zone] || '#fa784f'
}
```

### Composant ZoneEndChoice

```vue
<!-- frontend/app/components/feature/ZoneEndChoice.vue -->
<script setup lang="ts">
const { getNextChoicePoint, remainingZones } = useNarrativePath()
const narrator = useNarrator()

const choicePoint = computed(() => getNextChoicePoint())

// Afficher un message du narrateur avant le choix
onMounted(async () => {
  if (remainingZones.value.length > 0) {
    await narrator.showTransitionChoice()
  } else {
    await narrator.showContactReady()
  }
})
</script>

<template>
  <div class="zone-end-choice py-16 px-4 border-t border-sky-dark-100 mt-16">
    <div class="max-w-2xl mx-auto">
      <!-- Message narratif -->
      <p class="font-narrative text-xl text-sky-text text-center mb-8 italic">
        {{ $t('narrative.whatNext') }}
      </p>

      <!-- Choix -->
      <ChoiceCards
        v-if="choicePoint.choices?.length"
        :choice-point="choicePoint"
      />

      <!-- Si une seule option (contact) -->
      <div
        v-else-if="choicePoint.choices?.length === 1"
        class="text-center"
      >
        <NuxtLink
          :to="choicePoint.choices[0].destination"
          class="inline-flex items-center gap-3 px-8 py-4 bg-sky-accent text-white font-ui font-semibold rounded-xl hover:bg-sky-accent/90 transition-colors"
        >
          <span class="text-2xl">{{ choicePoint.choices[0].icon }}</span>
          <span>{{ $i18n.locale === 'fr' ? choicePoint.choices[0].textFr : choicePoint.choices[0].textEn }}</span>
        </NuxtLink>
      </div>
    </div>
  </div>
</template>
```

### Schéma des chemins narratifs

```
                    ┌─────────────┐
                    │    INTRO    │
                    └──────┬──────┘
                           │
              ┌────────────┴────────────┐
              ▼                         ▼
       ┌──────────┐               ┌──────────┐
       │ PROJETS  │               │ COMPÉT.  │
       └────┬─────┘               └────┬─────┘
            │                          │
    ┌───────┴───────┐          ┌───────┴───────┐
    ▼               ▼          ▼               ▼
┌────────┐    ┌────────┐  ┌────────┐    ┌────────┐
│TÉMOIGN.│    │PARCOURS│  │TÉMOIGN.│    │PARCOURS│
└───┬────┘    └───┬────┘  └───┬────┘    └───┬────┘
    │             │           │             │
    ▼             ▼           ▼             ▼
  (suite)       (suite)     (suite)       (suite)
    │             │           │             │
    └──────┬──────┴───────────┴──────┬──────┘
           │                         │
           ▼                         ▼
    ┌──────────────────────────────────────┐
    │              CONTACT                  │
    │     (tous les chemins y mènent)      │
    └──────────────────────────────────────┘
```

### Dépendances

**Cette story nécessite :**
- Story 4.1 : ChoiceCards
- Story 4.2 : Intro narrative (premier choix)
- Story 3.5 : Store de progression (choices)

**Cette story prépare pour :**
- Story 4.7 : Révélation (fin des chemins)
- Story 4.8 : Page contact (destination finale)

### Project Structure Notes

**Fichiers à créer :**
```
frontend/app/
├── data/
│   └── narrativePaths.ts                # CRÉER
├── composables/
│   └── useNarrativePath.ts              # CRÉER
└── components/feature/
    └── ZoneEndChoice.vue                # CRÉER
```

**Fichiers à modifier :**
```
frontend/app/pages/projets.vue           # AJOUTER ZoneEndChoice
frontend/app/pages/competences.vue       # AJOUTER ZoneEndChoice
frontend/app/pages/temoignages.vue       # AJOUTER ZoneEndChoice
frontend/app/pages/parcours.vue          # AJOUTER ZoneEndChoice
```

### References

- [Source: docs/planning-artifacts/epics.md#Story-4.3]
- [Source: docs/planning-artifacts/ux-design-specification.md#Narrative-Paths]
- [Source: docs/brainstorming-gamification-2026-01-26.md#Parcours-Narratifs]

### Technical Requirements

| Requirement | Value | Source |
|-------------|-------|--------|
| Points de choix | 2-3 | Epics |
| Parcours possibles | 4-8 | Epics |
| Toutes zones visitables | Oui | Epics |
| Tous chemins → contact | Oui | Epics |

## Dev Agent Record

### Agent Model Used

{{agent_model_name_version}}

### Debug Log References

### Completion Notes List

### Change Log
| Date | Change | Author |
|------|--------|--------|
| 2026-02-04 | Story créée avec contexte complet | SM Agent |

### File List

