diff --git a/kanban/cards/1-urgent/020-regles-firebase-storage-ouvertes-a-tous.md b/kanban/cards/1-urgent/020-regles-firebase-storage-ouvertes-a-tous.md
new file mode 100644
index 0000000..12e7b5b
--- /dev/null
+++ b/kanban/cards/1-urgent/020-regles-firebase-storage-ouvertes-a-tous.md
@@ -0,0 +1,15 @@
+---
+title: Le bucket Firebase est ouvert en écriture et en suppression à tous
+area: backend infra
+tags: manager-service, sécurité, Studio
+flag: critical | À régler impérativement avant/avec la mise en production de la version actuelle
+src: conversation 14/09 — relevé des règles via l'API firebaserules
+---
+
Relevé le 14/09 sur le projet mymuseum-3b97f (release firebase.storage/mymuseum-3b97f.appspot.com, ruleset 025f98db-…, inchangé depuis le 08/01/2024) :
+match /{allPaths=**} {
+ allow read, write; //: if request.time < timestamp.date(2024, 1, 12)
+}
+La garde temporelle du template Firebase a été commentée pour qu'elle n'expire pas. N'importe qui connaissant le nom du bucket — il est dans l'URL de chaque image, donc public — peut lire, lister, écrire et supprimer le contenu de tous les clients. Vérifié sans aucune authentification : GET firebasestorage.googleapis.com/v0/b/mymuseum-3b97f.appspot.com/o répond 200 avec la liste des fichiers.
+Pourquoi ce n'est pas déjà fermé : le manager-app déployé écrit dans le bucket sans Firebase Auth. Fermer avant de déployer casse ses uploads. La fermeture est donc séquencée après la mise en production du lot 0 du Studio (upload par URL signée + ingestion serveur), comme le prévoit v2/studio-plan.md §8. ⚠️ La date de ce déploiement est donc une date de sécurité, pas seulement une date de feature — ne pas mettre la version actuelle en production sans traiter ce point dans la foulée.
+Piège à traiter dans le même geste : le CORS du bucket de prod n'a aucun responseHeader. Le jour du déploiement, le PUT signé portant Content-Type et x-goog-content-length-range sera refusé au préflight et tous les uploads casseront. À corriger sur le bucket avant la bascule. Le bucket de dev mymuseum-3b97f-dev, lui, est déjà configuré correctement (CORS, cycle de vie incoming/, règles fermées) et sert de modèle.
+Une piste applicable avant le déploiement, à vérifier : restreindre read/list seul. Les apps visiteur lisent par URL à jeton, qui ne passe pas par les règles ; reste à confirmer qu'aucun code client ne lit par le SDK Firebase.
diff --git a/kanban/cards/5-planifie/285-loader-anime-presets-paremetriques.md b/kanban/cards/5-planifie/285-loader-anime-presets-paremetriques.md
new file mode 100644
index 0000000..8d16f03
--- /dev/null
+++ b/kanban/cards/5-planifie/285-loader-anime-presets-paremetriques.md
@@ -0,0 +1,23 @@
+---
+title: Loader animé — presets paramétriques, pas de format de fichier
+area: manager
+horizon: v2
+tags: manager-app, visitapp, tablet, vr
+flag: good | Décision arrêtée, à exécuter après la bascule
+src: décision 2026-09-15 — analyse des formats de loader animé
+---
+Aujourd'hui le loader est une image fixe : Configuration.LoaderImageUrl / LoaderImageId, rendus par loading_common.dart dans les deux apps Flutter et par SplashScreen côté web. Le besoin : que le client compose un loader animé depuis le manager, sans fournir de fichier.
+
+Décision : pas de format de fichier animé. On transporte des paramètres. Un nouveau champ de configuration porte {preset, couleurs[], logoResourceId, vitesse} — quelques centaines d'octets — et chaque front implémente nativement les 5-6 mêmes presets : AnimationController en Flutter, CSS/Canvas en Next.js, animation Unity côté casque. Le preset n°1 existe déjà et tourne : manager-app/lib/Components/loader_animated_pieces.dart (6 pièces vectorielles, onde d'opacité, rotation, flottement) — il suffit d'en sortir les couleurs et les timings, aujourd'hui en dur.
+
+Les couleurs sont pré-remplies depuis VisualIdentityDTO.palette, qui existe déjà. Aucun crédit Studio débité : le rendu est local au navigateur, rien ne passe par un modèle.
+
+⛔ Le SVG animé est écarté : flutter_svg ignore <animate>, SMIL et les keyframes CSS — il rendrait une image figée dans les trois apps Flutter — et Unity n'a aucun rendu SVG. Un seul des quatre fronts l'afficherait.
+
+⛔ Lottie est écarté à ce stade, malgré son écosystème. Format de fait et non norme, gouverné par une société unique (LottieFiles) sur une spec récente ; surtout, aucun runtime moteur de jeu n'est listé sur le site officiel. Les deux options Unity sont fragiles : thorvg.unity (Android arm64 annoncé, mais 26 commits et 14 étoiles, successeur de Lottity archivé en 11/2025) et unity-rlottie (plus fourni mais en experimental, avec un ticket ouvert sur une texture NULL en build Android). Les deux rastérisent en Texture2D à chaque frame côté CPU — inacceptable sur Quest à 72-90 Hz pour un écran de chargement.
+
+Porte de sortie assumée : le jour où un client veut apporter sa propre animation faite par une agence, on ajoute Lottie sur les 3 fronts 2D seulement, avec le PNG de première frame en repli côté VR. Le champ loader accepte alors soit des paramètres, soit une URL — rien de ce qui est fait ici n'est à refaire.
+
+Coût réel : les presets. Chacun est du code dans 4 dépôts, à tester sur les 4 fronts. En sortir 5-6, pas 20 : au-delà le client ne choisit plus, il se perd. Stocker les paramètres permet la réédition six mois plus tard.
+
+⚠️ Pas un bloquant V1 — le loaderImageUrl actuel fait le travail avec un PNG. À ouvrir après la bascule prod.
diff --git a/v2/section-form-plan.md b/v2/section-form-plan.md
new file mode 100644
index 0000000..3dd4b63
--- /dev/null
+++ b/v2/section-form-plan.md
@@ -0,0 +1,177 @@
+# SectionForm — formulaires visiteurs
+
+> Chantier **V2**, reporté le 2026-08-07, **analysé dans le code le 2026-09-15**.
+> Spec d'origine : `todo-features.md` § SectionForm · carte kanban `cards/5-planifie/240-…`
+> Ce document ne rouvre pas la décision « V2 » : il dit **comment** on le fait quand on le fait.
+
+---
+
+## Pourquoi ce type de section
+
+Tout le flux produit va aujourd'hui du lieu vers le visiteur. Ce qui remonte est **dérivé** :
+`VisitEvent` (télémétrie anonyme) et `VisitorQuestion` (ce qui a été demandé au guide IA).
+Aucun mécanisme ne permet à un lieu de **poser une question**.
+
+Use cases visés, dans l'ordre de valeur commerciale : enquête de satisfaction (les musées
+publics en produisent pour leurs rapports de subsides), livre d'or, sondage d'exposition,
+inscription à un atelier.
+
+---
+
+## Décisions actées
+
+| Décision | Conséquence |
+|---|---|
+| **Réponses anonymes**, sans compte ni identification | Pas de champ « nom », pas de liaison à une personne. Ça allège massivement le volet RGPD — on reste sur le régime déjà décrit au §8 des CGU |
+| **Les trois fronts visiteur**, kiosk compris | `mymuseum-visitapp`, `visitapp-web`, **`tablet-app`** — une borne en fin de parcours est le meilleur emplacement pour une enquête de satisfaction |
+| **Table de réponses dédiée**, pas `VisitEvent.Metadata` | Rétention propre, export, suppression ciblée, requêtes par champ |
+| **Les réponses ne sont jamais indexées** pour le RAG | Seuls les libellés des questions le sont |
+| Pas d'upload de fichier en V1 | Les règles Firebase Storage de prod sont encore ouvertes en écriture — mauvais moment pour ajouter une porte d'upload public |
+
+---
+
+## Ce qui sert de socle (déjà écrit)
+
+La moitié de la plomberie existe. Trois précédents à reprendre plutôt qu'à réinventer :
+
+1. **`StatsController.TrackEvent`** (`Controllers/StatsController.cs`) — le **seul** endpoint
+ `[AllowAnonymous]` où un visiteur écrit en base. C'est exactement le contrat de soumission
+ d'un formulaire : modèle à copier, y compris la validation défensive.
+2. **`SectionQuiz` + `QuizQuestion`** (`Data/SubSection/`) — structurellement un formulaire :
+ liste ordonnée de questions traduites en `jsonb`, options en `jsonb`, un `QuestionType`.
+ `SectionForm` c'est le même squelette **moins** la bonne réponse, **plus** le stockage.
+3. **`VisitorQuestionPurgeService`** (`Services/`) — le job de purge par rétention existe et
+ tourne : `FormSubmission` s'y branche sans rien inventer.
+
+Et le coût d'ajout d'un type de section est balisé : `Scene3D`, le dernier ajouté, a touché
+11 fichiers backend et 8 dans `manager-app`. La partie réellement neuve ici, c'est **l'écran
+de réponses**, qui n'a aucun équivalent.
+
+---
+
+## Modèle de données
+
+```
+DTOs/SectionType.cs
+ Form ← ajouté EN FIN d'enum
+ (persisté en int : cf. le commentaire de Scene3D)
+
+Data/SubSection/SectionForm.cs : Section
+ List Fields
+ List IntroText jsonb
+ List ConfirmationText jsonb
+ DateTime? ClosingDate
+ int RetentionDays
+
+Data/SubSection/FormField.cs
+ Label jsonb traduit (même forme que QuizQuestion.Label)
+ Order, FieldType, IsRequired
+ Options jsonb (pour single_choice / multiple_choice)
+
+Data/FormSubmission.cs
+ Id, InstanceId, SectionId, ConfigurationId
+ SessionId, Language, Answers jsonb, Timestamp
+ [Index(InstanceId)] [Index(SectionId)]
+```
+
+`FormSubmission` vit à la racine de `Data/`, avec `VisitEvent` et `VisitorQuestion` — c'est de
+la donnée de visite, pas du contenu éditorial.
+
+### Types de champs — V1 volontairement courte
+
+`text` (court), `long_text`, `single_choice`, `multiple_choice`, `rating` (1-5).
+
+Pas d'email, pas de date, pas d'upload. Les quatre premiers couvrent l'enquête de
+satisfaction et le sondage, qui sont les deux vrais cas.
+
+---
+
+## Backend
+
+- `DTOs/SubSection/FormDTO.cs` + branchement dans **`Services/SectionFactory.cs`**
+ (le `switch` de désérialisation **et** le `switch` de construction).
+- `Controllers/SectionFormController.cs`, sur le modèle de `SectionScene3DController` :
+ - CRUD admin, authentifié ;
+ - `POST /api/SectionForm/submit` en **`[AllowAnonymous]`** ;
+ - `GET /api/SectionForm/{id}/submissions` (admin) — liste + agrégats ;
+ - `GET /api/SectionForm/{id}/export` — CSV ;
+ - `DELETE /api/SectionForm/{id}/submissions` — purge manuelle.
+- `GetEmbeddableText` : **les libellés de questions oui, les réponses jamais.** Le
+ raisonnement est déjà écrit dans `SectionQuiz` (« le guide les réciterait au premier
+ visiteur qui demande ») ; ici ce serait pire — le guide recracherait les avis d'autres
+ visiteurs, y compris désobligeants, à un visiteur suivant.
+- **L'endpoint de soumission est ouvert** : rate-limit par `SessionId`/IP (la politique
+ `UseRateLimiter()` existe déjà depuis le chantier API Keys), respect de `ClosingDate`,
+ et cap de soumissions par session.
+
+---
+
+## manager-app
+
+Fichiers à toucher, identifiés sur le précédent `Scene3D` :
+
+| Fichier | Nature |
+|---|---|
+| `lib/client.dart` + `manager_api_new/` | client généré, **édité à la main** |
+| `lib/Screens/Configurations/new_section_popup.dart` | entrée « Formulaire » |
+| `lib/Components/fetch_section_icon.dart` | icône du type |
+| `lib/Screens/Configurations/Section/section_detail_screen.dart` | routage vers l'éditeur |
+| `lib/Screens/Configurations/Section/SubSection/Form/form_config.dart` | **neuf** — constructeur de formulaire |
+| `lib/Screens/Configurations/Section/SubSection/Form/form_submissions.dart` | **neuf** — onglet réponses |
+| `lib/l10n/app_{fr,en,nl}.arb` | i18n obligatoire, aucun littéral dans un widget |
+
+**Où vivent les réponses** : dans un **onglet de l'écran de la section**, pas dans
+`Screens/Statistics/`. L'admin qui ouvre son formulaire veut ses réponses sous les yeux ;
+`Statistics` répond à une autre question (la fréquentation).
+
+Contenu de l'onglet : en-tête d'agrégats (nombre de réponses, moyenne des `rating`,
+répartition des choix en barres horizontales — la charte de `stats-screen-plan.md`
+s'applique), puis la liste des textes libres, puis l'export CSV.
+
+---
+
+## Fronts visiteur
+
+| Repo | Fichier | Note |
+|---|---|---|
+| `mymuseum-visitapp` | `lib/Screens/Sections/Form/form_page.dart` | à côté de `Quiz/quizz_page.dart` |
+| `visitapp-web` | `src/components/sections/FormSection.tsx` | **obligatoire** — le plan Essentiel est web-only. Lire le Flutter d'abord, c'est la référence de comportement |
+| `tablet-app` | `lib/Screens/Form/form_view.dart` | à côté de `Screens/Quizz/quizz_view.dart` |
+
+### Le point d'attention propre au kiosk
+
+Sur une borne fixe, le visiteur suivant hérite de l'écran du précédent. Même anonyme, une
+saisie libre à moitié écrite qui reste affichée est un défaut visible. Il faut donc, sur
+`tablet-app` uniquement : **reset du formulaire après soumission** et **reset sur
+inactivité**, sur le même timer que celui qui ramène déjà la borne à l'accueil.
+
+---
+
+## RGPD — allégé, pas nul
+
+L'anonymat retire le gros du sujet : pas de personne identifiée, donc pas de droit d'accès
+à exercer par visiteur. Restent deux points, tous deux déjà traités ailleurs dans le produit :
+
+- **La saisie libre peut contenir des données personnelles spontanées.** C'est exactement ce
+ que le §8 des CGU écrit déjà pour le champ de question du guide IA — la même clause couvre
+ ce cas, il suffit de l'étendre au formulaire lors de la relecture juridique déjà planifiée
+ (carte `195-faire-valider-les-cgu-par-un-juriste`).
+- **Rétention** : `RetentionDays` paramétrable + purge automatique via le service existant.
+
+---
+
+## Hors périmètre V1 du chantier
+
+Logique conditionnelle entre champs, pages multiples, notification email à chaque réponse,
+upload de fichier, réponses liées à un visiteur identifié. Le risque de ce chantier est de
+glisser vers « on refait Google Forms » — or la valeur ici n'est pas la richesse de
+l'éditeur, c'est d'être **déclenchable dans le parcours de visite** (fin d'un parcours
+guidé, beacon de sortie, borne de fin).
+
+---
+
+## Positionnement commercial
+
+À réserver aux plans **Pro et supérieurs** : la collecte de retours a une valeur propre, et
+le plan Essentiel est déjà le plan « vitrine ». À arbitrer avec la grille de
+`myinfomate-landing`, qui reste la source de vérité des prix.