# Plan : Navigation par slides horizontaux
## Contexte
Le site source (world.game) utilise fullpage.js v4 avec un plugin scrollHorizontally propriétaire. Toutes les pages sont dans le DOM simultanément et la navigation fait glisser un conteneur avec `transform: translate3d`. On reproduit ce comportement en Svelte 5 sans dépendance externe.
**Approche retenue :**
- Charger d'abord la page demandée (chargement initial rapide)
- Charger les autres en arrière-plan en parallèle (HTTP/2, payloads légers)
- Si l'utilisateur navigue avant qu'une page soit chargée → attendre qu'elle le soit
- Hack back/forward inspiré de decarb.one
**Pourquoi le chargement progressif plutôt qu'un endpoint unique `/all-pages.json` ?**
Un endpoint unique bloquerait le premier rendu jusqu'à ce que Kirby ait tout construit. Le chargement progressif affiche la page initiale immédiatement, les 5 autres chargent en parallèle. Avec HTTP/2 et des payloads JSON légers (6 pages), le coût est négligeable. Chaque page peut aussi être mise en cache indépendamment.
---
## Ordre des slides
**Dynamique, tiré de `site->pages->listed` (Kirby).** Ne pas hardcoder l'ordre.
Au chargement initial, la réponse JSON d'une page inclut `data.site.navigation` (déjà exposé par le template Kirby et stocké dans `site.svelte.js`). Ce tableau donne l'ordre et les chemins des pages listées. C'est lui qui construit le tableau `SLIDES_CONFIG` au runtime.
Exemple de ce que Kirby expose déjà :
```json
{
"site": {
"navigation": [
{ "id": "home", "url": "/home", "title": "Home" },
{ "id": "expertise", "url": "/expertise", "title": "Expertise" },
...
]
}
}
```
Le store `slides` sera initialisé depuis ce tableau dynamiquement après le premier fetch.
---
## Architecture cible
### Conteneur slides (App.svelte)
```
[body: overflow hidden]
[Header - fixed]
[.slides-wrapper: display flex, width: N*100vw, transition 1000ms]
[.slide × N: width 100vw, height 100vh, flex-shrink 0]
[] ← rendu seulement si data chargée
```
`transform: translateX(-{activeIndex * 100}vw)` géré via style inline réactif.
---
## Fichiers à créer
### `src/state/slides.svelte.js` (nouveau)
Store central de l'état des slides :
```js
// Initialisé dynamiquement depuis site.navigation
// slidesData: Array<{ id, path, template, data, loaded, loading }>
// activeIndex: number
// pendingPath: string | null
export const slides = {
get all(), get activeIndex(), get active(), get pendingPath(),
init(siteNavigation), // construit slidesData depuis les pages listées de Kirby
setData(path, data), // stocke les données + loaded=true d'une slide
setLoading(path, bool),
slideTo(path), // anime si chargée, sinon set pendingPath
resolvePending(), // appelé quand une slide finit de charger
getIndexByPath(path),
}
```
### `src/router/index.js` (réécriture quasi-complète)
Remplace navaid par une logique custom :
```js
// loadSlide(path) :
// fetch(`${path}.json`) → slides.setData() + site/locale init (1er appel seulement)
//
// loadAllSlidesInBackground(exceptPath) :
// slides.all.filter(s => s.path !== exceptPath).forEach(s => loadSlide(s.path))
//
// slideTo(path, { skipHistory = false }) :
// - slide déjà chargée → slides.slideTo() + history.pushState si !skipHistory
// - slide non chargée → slides.setPending(path) (la nav se déclenche quand loaded)
//
// initRouter() :
// 1. path initial = window.location.pathname (ou '/home' si '/')
// 2. loadSlide(initialPath) :
// → initialise slides.init(site.navigation) après le 1er fetch
// → set active slide
// 3. loadAllSlidesInBackground(exceptPath: initialPath)
// 4. popstate listener : slideTo(window.location.pathname, { skipHistory: true })
// 5. Intercepter clics internes : slideTo()
```
**Hack popstate :**
```js
window.addEventListener('popstate', () => {
slideTo(window.location.pathname, { skipHistory: true })
})
```
Le navigateur change déjà l'URL sur ← → on lit simplement `window.location.pathname` et on slide sans re-pousser dans l'historique.
**Export : `initRouter`, `slideTo`**
---
## Fichiers à modifier
### `src/App.svelte`
```svelte