---
title: "Essai UI in-game 2a — design complet : paquets, rivière SVG, panneaux latéraux"
subtitle: "Toutes les sections du handoff 2a sont en code ; la rivière est un overlay SVG, pas un rewrite des axes 3D"
date: 2026-09-15
time: "03:53"
author: VFG / Rainbow Journey
publishedBy: idea
status: pub
tags: ["rainbowjourney", "devblog", "ui", "design", "essai", "hud", "svg", "pouch", "river"]
---

![Essai UI in-game 2a — design complet : paquets, rivière SVG, panneaux latéraux](../images/covers/2026-09-15-essai-ui-ingame-2a-design-complet-paquets-riviere-panneaux.png)


Le billet précédent s’arrêtait à T3 (suggestions ancrées). Cette vague ferme la promesse : tout ce que le handoff design 2a décrit — paquets par degré, rivière qui remonte, panneaux latéraux — est en code sur la branche `essai/ui-ingame-2a`. Le monde 3D n’a pas été touché.

## Ce qui manquait au handoff après T3

![Capture design UI in-game](../images/shots/ui-ingame-2a.png)


Trois sections entières du design restaient à implémenter :

- §2 **Paquets** (besaces par degré, jauges, respiration du plus rempli).
- §3 **Rivière** (rubans qui montent de `y=800` à `y=150`, deux passes par couloir).
- §6 **Panneaux latéraux** (`DERNIERE NOTE` à gauche, `JOURNAL` à droite, accents `#52d6d1` / `#7cf59a`).

Le runtime avait déjà D-STATE (compteurs par degré, pastilles en vol, dernière note, journal) — la moitié du travail côté données. Il restait à peindre la peau.

## Vague design 2a complète — sept chantiers

Plan : [`PLAN-ORCH-DESIGN-2a-COMPLET.md`](../plans/essai-ui-ingame-2a/PLAN-ORCH-DESIGN-2a-COMPLET.md). Sept chantiers en trois vagues :

| Vague | Chantiers |
| --- | --- |
| 1 (parallèle) | D-POLISH, D-STATE |
| 2 (parallèle) | D-POUCH, D-RIVER, D-PANELS |
| 3 (séquentiel) | D-MOUNT, D-VERIFY |

D-POLISH a refermé les fuites de la fidélité : Cinzel 500 chargé, regroupement `gap:14px` du bloc droit de la topbar, transition de teinte des couloirs via `@property --lane-color`.

D-STATE a exposé `runtime.pouchCounts`, `runtime.climbers`, `runtime.lastNote`, `runtime.journal` — quatre getters purs, sans réécriture du routage MIDI. La causalité « la pastille arrive → le paquet grossit » est portée par `settleClimbers` (appelé dans le `tick()`).

## D-POUCH — `PouchRow.tsx`

Une pochette par degré actif, centrée sur `x(i)`. Pas un compteur abstrait : une forme (84×74, `border-radius` organique `42%/30%`), un col, une jauge interne qui passe de `18px` à `78px` selon la part de session, un compte `Space Grotesk 700 15` avec `text-shadow: 0 0 12px #000`. Le paquet le plus rempli respire via `rjBreathe 3.4s ease-in-out`.

Positionnement : le composant s’ancre lui-même dans la bande utile `left:120 width:1040` et appelle `laneCenterX` — la même géométrie que les couloirs et les suggestions. Aucun risque de désynchronisation.

```ts
const centerXPct = ((laneCenterX(index, n) - BAND_LEFT) / BAND_WIDTH) * 100
```

## D-RIVER — `RiverOverlay.tsx`

Une décision structurante au passage : **pas de rewrite 3D** pour la rivière qui monte. Le canon produit (profondeur = temps) tient debout ; les couloirs 2a doivent se lire à plat, pas en projection 3D.

Donc : un SVG plein cadre (`viewBox 0 0 1280 800`), plein `position: absolute; inset: 0; z-index: 1`, avec un ruban par couloir. Deux passes :

- halo : `stroke-opacity: .3`, `stroke-width: 34`, filtre `feGaussianBlur stdDeviation=6` ;
- courant : `stroke-opacity: .55`, `stroke-width: 2`, `stroke-dasharray: 12 16`, `rjFlow` 7…13s linéaire.

Le chemin part et arrive sur `cx` :

```
M cx 800 C cx-16 660 cx+16 520 cx 400 C cx-12 320 cx+12 250 cx 150
```

Le balancement gauche/droite est léger — il donne du courant sans déplacer la moyenne. Si la position du ruban dérivait, le X de la note ne tomberait plus sur le centre du couloir, et tout le reste se casserait. Le test `riverPath` vérifie que `M cx 800` ouvre et `cx 150` ferme.

`prefers-reduced-motion` coupe `rjFlow`, le SVG reste statique. Le monde 3D en arrière-plan n’est pas modifié.

## D-PANELS — `LastNotePanel` + `JournalPanel`

Deux `FloatingPanel` 214px de large, `top: 470px`, à 18px des bords gauche / droit. Accents 2px :

- gauche `DERNIERE NOTE` : bordure `#52d6d1`, nom en `Cinzel 700 30` à la couleur de la note avec `text-shadow: 0 0 24px #52d6d155`. Une ligne `▲ V · appui · couloir 3`, une ligne `tension 0.15 · velocite 90`.
- droite `JOURNAL` : bordure `#7cf59a`, lignes `dt`/`dd` JetBrains Mono 10, `safePct`/`explorePct` (de `styleScores.computeStyleScores`), paquet le plus rempli (`fullestChroma`/`fullestCount`), total recueilli.

Toutes les données viennent de `runtime.lastNote` / `runtime.journal`. Aucune métrique inventée : `safePct` est `null` tant que `styleSampleSize === 0`, et le panneau affiche `—` dans ce cas.

Le calcul du couloir passe par `laneIndexForMidi(midi, tonic, mask)` — la même fonction que `DegreeCorridors` et `SuggestionStrip`. Si FG change la gamme, la mention « couloir N » suit, ou « hors gamme » si la note n’est plus dans le mask.

## Ce qui n’est pas fait

| Élément | Statut |
| --- | --- |
| Climbers DOM (pastilles qui montent, handoff §4) | hors essai 2a — runtime prêt, rendu visuel à faire si FG le demande |
| Pulse `rjFill` à l’arrivée (handoff §2 « pochette pulse une fois ») | non animé — la transition `height .35s` suffit pour la causalité |
| Proposition rewrite axes 3D | hors essai (canon profondeur = temps) |
| Gate visuelle FG 1280×800 | recommandée avant de déclarer T4/T5 verts |
| Migration des layouts persistés `rainbowjourney.panel.*` | non demandée |

## Contôles réellement exécutés

- `node node_modules/typescript/bin/tsc -b --force` : OK.
- `npm run lint` : OK.
- `npx vitest run` : 215/215 (30 fichiers ; +8 tests `riverGeometry`, +1 test `hudDefaults`).
- `npm run build` : OK (CSS 99 kB, JS 1.08 MB gzip 301 kB ; avertissements Zod et bundle volumineux préexistants).
- Serveur `npx vite --port 5183 --strictPort` : HTTP 200, `Journey.tsx` et ses nouveaux imports servis sans erreur.

Pas de Playwright dans cette passe (E2E complet = zone outillage, hors essai). Smoke DOM 1280×800 reste à faire par FG.

## Pourquoi publier maintenant

Parce que la peau du jeu changeait sans qu’aucun billet public ne le raconte. Trois sections entières du design 2a, c’est un visage différent — pas une variation cosmétique. Le runtime porte la causalité, la géométrie est partagée, les panneaux latéraux sortent le joueur des panneaux flottants persistés pour entrer dans la composition 2a.

La branche `essai/ui-ingame-2a` reste en working tree après ce billet. Aucun commit n’a été poussé vers `main` ni vers le remote sans l’ordre explicite FG. La prochaine étape est la gate visuelle 1280×800 : c’est FG qui décide si le rendu matche, si la rivière SVG est trop chargée, si les paquets respirent au bon tempo.

---

*Sources : `docs/plans/essai-ui-ingame-2a/PLAN-ORCH-DESIGN-2a-COMPLET.md`, `handoffs/D-POUCH.md`, `handoffs/D-RIVER.md`, `handoffs/D-PANELS.md`, `handoffs/D-MOUNT.md`, `handoffs/D-VERIFY.md`, `handoffs/2026-09-15-design-2a-complet-handoff.md`. Branche : `essai/ui-ingame-2a`. Miroir local : `RainbowPiano/docs/devblog/`.*