Files
sucupira d6efb6736f Réécriture SvelteKit statique (Svelte 5 runes + TS, adapter-static)
- Données PeerTube (GADE), Castopod (KUTE) et Mastodon (BOKANTE) bakées au
  build via +page.server.ts (throttle + retry 429 + déduplication)
- Charte OKI complÚte : tokens, thÚme sombre par défaut (clair opt-in,
  anti-FOUC), Archivo/Inter self-hébergées, flag-bar, sprite SVG (zéro emoji
  en interface, zéro Font Awesome/CDN)
- i18n FR/EN par routage [[locale]], bundles JSON, hreflang, %lang% serveur
- Pages : accueil, /video/[uuid] (embed, téléchargements, partage,
  commentaires, JSON-LD VideoObject), /categories/[id], /recherche (index
  JSON baké, recherche client), /direct (live + annonce multi-fuseaux),
  /dons, /mentions-legales, /offline + 404 krĂ©yĂČl
- Motion gwoka : KineticText (scrub view()), ScrollProgressBar, FlagChip,
  reveal syncopé, View Transitions, gate prefers-reduced-motion unique
- PWA : manifest + Workbox generateSW (fallback /offline/), registerSW
  statique
- Sécurité : CSP par page en <meta> (SHA-256 des inline via
  scripts/postbuild-csp.mjs) + headers globaux (_headers Cloudflare et
  .htaccess o2switch), redirections des anciennes URLs PHP
- Doc : docs/DEPLOIEMENT-SVELTEKIT.md
2026-07-23 00:49:02 -04:00

495 lines
41 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# PLAYBOOK UNIFIÉ — OKI × SVELTEKIT
### Document de rĂ©fĂ©rence opĂ©rationnel pour agent de code — marque OKI, mĂ©thode Awwwards-grade, conventions Svelte 5, briques rĂ©utilisables
> **Nature :** fusion de `charte-oki-design-system.md`, `recette-sveltekit-playbook-agent.md`, `recette-sveltekit-15-sites-awwwards.md`, `svelte-5_code_writer.md`, `svelte_core_bestpractices.md` et `AGENTS.md`, enrichie des leçons réelles de **6 missions** (juillet 2026) : migration o-k-i.net, refonte atlas-fediverse, finalisation exitchatcontrol, migration gwada-sirius, refonte UX/motion du jeu JWE, réparation et modernisation d'oki-podcast-reader (voir §5, §5b et §5c). Document **local, non versionné** (`.gitignore`).
>
> **Usage :** à fournir à l'agent pour toute demande du type « transforme ce projet web en projet Svelte aux couleurs OKI, style Awwwards » ou « crée un site/PWA dans cette charte ». Compléter avec le brief de mission du §7.
>
> **Priorités en cas de conflit :** la marque (§1) prime pour couleurs, typo, voix, iconographie · la méthode (§2) prime pour la technique · les budgets et l'accessibilité ne se négocient jamais.
---
## 0. TYPES DE MISSION
**A. Transformation** d'un site existant (HTML statique, WordPress, Eleventy, SPA sans DA) → SvelteKit aux couleurs OKI. Toujours commencer par l'audit (§2.4 phase 1) — le descriptif du site source est **toujours** Ă  vĂ©rifier contre le dĂ©pĂŽt rĂ©el (la migration o-k-i.net partait d'un brief « HTML statique » alors que le site Ă©tait Eleventy + 38 JSON i18n).
**B. Création** d'un nouveau site/PWA dans la charte OKI. Commencer au §2.4 phase 2, en copiant les briques du §4.
**C. Finalisation** — le chantier est dĂ©jĂ  avancĂ© (migration Ă  90 %, WIP non commitĂ©). Le travail est de **finir, pas recommencer** : builder d'abord, committer l'existant cohĂ©rent, puis combler les manques (typiquement SEO et rĂ©gressions du changement de gĂ©nĂ©rateur).
**D. Refonte ciblĂ©e UX/motion** — la stack est dĂ©jĂ  SvelteKit. Le travail est chirurgical : remplacer les piĂšces dĂ©faillantes (fond, carte, timeline, layout) et appliquer charte + motion, sans tout réécrire.
Quatre rĂšgles transverses, apprises sur 5 projets :
1. **Contenu éditorial jamais réécrit** sans instruction explicite ; les données priment sur les suppositions (ex. KUTE = Castopod dans les JSON, pas « app PHP » comme le brief le supposait).
2. **Toujours vĂ©rifier l'existence de git en premier** (atlas-fediverse n'avait AUCUN dĂ©pĂŽt — `git init` + commit de l'existant avant toute modification, sinon travail irrĂ©versible).
3. **Charte §7 « harmoniser ≠ tout refaire »** : si le projet a dĂ©jĂ  un design system de la famille OKI (tokens panafricains, polices accessibilitĂ©), on ne rebrande pas — on applique mĂ©thode et qualitĂ© (exitchatcontrol, gwada-sirius). La charte complĂšte ne s'applique qu'aux projets sans identitĂ© propre (atlas).
4. **VĂ©rifier l'Ă©tat rĂ©el avant de scoper** : sur 5 projets, 2 Ă©taient dĂ©jĂ  SvelteKit (atlas, JWE), 1 en migration Ă  90 % (exitchatcontrol), 1 Ă  80 % (gwada-sirius) — une seule vraie migration complĂšte.
---
## 1. MARQUE OKI (canonique — prime sur tout le reste)
### 1.1 Identité en une phrase
Un studio web militant caribĂ©en dont la marque est un **drapeau panafricain — noir, or, vert, rouge — posĂ© sur un fond presque noir, en capitales Archivo**. Afrofuturiste caribĂ©en : un drapeau, pas une charte corporate.
Trois conséquences non négociables :
1. **ThÚme sombre = identité par défaut** ; le clair est un opt-in (`html.light-theme`) qui assombrit les accents pour WCAG AA.
2. **L'or porte toute l'interaction** — seule couleur d'action.
3. **Angles nets partout** — aucune forme organique, aucune bulle trùs arrondie.
### 1.2 Tokens couleurs (mesurés sur le site réel)
```css
:root {
/* Noyau (thÚme sombre = défaut) */
--noir-oki: #0D0D0D; /* background */
--noir-profond: #1A0F1A; /* surface */
--blanc-creme: #FFF8E7; /* foreground */
--line: rgba(255,255,255,0.10); /* filets — jamais de gris plein */
--or-oki: #FDB813; /* accent : SEULE couleur d'action */
--rouge-oki: #FF1654; /* signal, ponctuation — jamais un lien/bouton */
/* Étendue (parcimonie) */
--vert-oki: #00D66C; /* succĂšs, validation, dons mensuels */
--turquoise-caraibes: #00CED1;
--jaune-soleil: #FFD700;
--orange-flamme: #FF6B35;
--violet-nuit: #6B2D5C;
--bleu-ocean: #0077BE;
--or-clair: #FFE066; /* survol des boutons */
--gris-sombre: #2D1B2E;
/* Sémantique dérivée */
--muted: color-mix(in srgb, var(--blanc-creme) 70%, transparent);
--card-bg: rgba(255,255,255,0.03);
}
/* Thùme clair — opt-in, accents assombris WCAG AA */
html.light-theme {
--noir-oki: #FFF8E7; --blanc-creme: #0D0D0D;
--noir-profond: #F5F0E8; --gris-sombre: #E8DDD0;
--or-oki: #B87A00; --vert-oki: #006B3D; --rouge-oki: #A01030;
--turquoise-caraibes: #006B75; --bleu-ocean: #004B7F;
--or-clair: #D99000; --jaune-soleil: #B87A00; --orange-flamme: #C85000;
--line: rgba(0,0,0,0.12); --card-bg: rgba(0,0,0,0.03);
}
```
RĂšgles d'usage : texte secondaire = crĂšme Ă  70-85 % (`--muted`), pas de couleur dĂ©diĂ©e · filets = blanc 10 % · **Rouge = signaler · Vert = valider · Or = agir — ne jamais permuter**.
### 1.3 Typographie
```css
--font-display: 'Archivo', 'Arial Black', sans-serif; /* 600-900 : TITRES + BOUTONS */
--font-body: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; /* 400-800 */
```
- Titres h1-h4 : Archivo, MAJUSCULES, `letter-spacing: -0.01em`.
- Boutons : Archivo 700, uppercase, `letter-spacing: 0.03em`, bordure 2 px.
- Corps : Inter, `line-height: 1.5`.
- **Self-hébergement obligatoire** : woff2 dans `static/fonts/` + `fonts.css`, `font-display: swap`, preload de la display 700. Aucun appel Google Fonts (doctrine souveraineté). Source pratique : `@fontsource/archivo` + `@fontsource/inter` (npm), copier les `*-latin-*-normal.woff2`.
### 1.4 Formes, espacements, layout
```css
--radius-sm: 3px; --radius-md: 6px; /* jamais plus */
--border-card: 1px solid var(--line);
--border-btn: 2px solid var(--or-oki);
--space-1: .75rem; --space-2: 1rem; --space-3: 1.5rem; --space-4: 2rem; --space-5: 4rem;
--container: 1200px;
```
- Sections : `padding: var(--space-5) 0` · Grilles : `repeat(auto-fit, minmax(300px, 1fr))`.
- **Carte canonique** : fond `--card-bg`, liseré gauche 4 px d'accent, survol `translateY(-2px à -4px)` + bordure d'accent.
- **Tag canonique** : fond or 8 % + bordure or 1 px, rayon 3 px.
- **Bouton canonique** : balayage or `::after scaleX(0→1)` origin left au hover, `--ease-syncope`.
### 1.5 Flag-bar — signature n°1
Bandeau **6 px, 4 segments francs** : noir 0-25 % · or 25-50 % · vert 50-75 % · rouge 75-100 %. ArrĂȘts nets, **jamais de dĂ©gradĂ© entre segments**.
```css
.flag-bar { height: 6px; background: linear-gradient(to right,
#0D0D0D 0 25%, var(--or-oki) 25% 50%, var(--vert-oki) 50% 75%, var(--rouge-oki) 75% 100%); }
```
Placement : bord infĂ©rieur de la nav fixe + haut du footer. **Une occurrence pleine largeur visible par Ă©cran max.** Pour ponctuer ailleurs (titres de section, loader), utiliser le **flag chip** : mĂȘme 4 segments, 56 × 6 px (voir §4). En thĂšme clair, le segment noir devient `#000` pur.
### 1.6 Logotype
Monogramme « OKI » : arche or, point vert, accent rouge sur noir. Fichier canonique 512×512 transparent (aussi og:image, apple-touch-icon, maskable PWA). Ne jamais utiliser un logo de partenaire comme logo OKI.
### 1.7 Motion — les cadences gwoka
```css
--ease-ka: cubic-bezier(0.22, 1, 0.36, 1); /* temps fort : attaque franche, longue tenue */
--ease-syncope: cubic-bezier(0.65, 0, 0.35, 1); /* contretemps : symétrique, urgent */
--dur-tanbou: 120ms; /* double-croche — micro-interactions, hovers */
--dur-mesure: 480ms; /* une mesure — entrĂ©es de section */
--dur-phrase: 960ms; /* deux mesures — transitions de page, moments solennels */
```
1. **Le stagger de marque est syncopé (3+3+2)** : délais `[0, 120, 300, 360, 600, 660, 900, 960]` ms pour 8 éléments (puis cycles de +1200 ms). Le rythme est une identité, pas un effet.
2. Uniquement `transform` + `opacity` (+ `clip-path` pour les masques).
3. **Gate unique `prefers-reduced-motion`** au niveau global, contenu statique complet sans JS ni motion.
4. Durées toujours lues depuis les tokens, jamais hardcodées.
### 1.8 Iconographie — zĂ©ro emoji en production
Set SVG OKI : viewBox 24×24, **stroke 2 px**, angles nets, `currentColor` (or par dĂ©faut), remplissage rĂ©servĂ© aux ≀ 16 px, **noms krĂ©yĂČl**. LivrĂ© en `<symbol>` dans un sprite `icons.svg` + `<svg class="icon"><use href="/icons.svg#ID"/></svg>`.
Set de rĂ©fĂ©rence (existant dans o-k-i.net) : `ka` (tambour, icĂŽne maĂźtresse, 404/loader) · `lambi` (conque — annonces) · `zetwal` (Ă©toile 4 branches — navigation, instances) · `mawon` (brisure de chaĂźne — souverainetĂ©) · `lakanmou` (flamme — engagement, dons) · `jaden` (pousse — solidaritĂ©, projets) · `kannen` (canne — patrimoine, institutions) · `lanme` (vague — international) · `glo` (poing — luttes, tarifs solidaires) · `kle` (cadenas ouvert — libertĂ©, open-source) · `pawol` (bulle angulaire — langues, traduction) · `mizik-note` (note — musique).
**Pattern contenu** : si des emojis vivent dans les textes (JSON), utiliser un helper `splitLeadingEmoji()` qui convertit l'emoji décoratif en picto du sprite sans toucher au texte (implémenté dans o-k-i.net `sections/pictos.ts`).
### 1.9 Voix & ton
Vouvoiement direct et empathique, phrases courtes. La cause du visiteur d'abord, l'outil ensuite. **À dire :** sur mesure · tarifs adaptĂ©s Ă  vos moyens · organisations engagĂ©es · transition numĂ©rique · Ă©cosystĂšme numĂ©rique libre. **À Ă©viter :** « template gĂ©nĂ©rique », « clĂ© en main », jargon corporate, startup-speak. Piliers : ORGANISATION KA · INTERNATIONALE · Des solutions pour chaque besoin · Le budget ne doit jamais ĂȘtre un frein. **Langues :** FR par dĂ©faut ; KA (krĂ©yĂČl) en signature et microcopy (404, loader, remerciements — attribut `lang="gcf"`) ; EN en version dĂ©diĂ©e.
### 1.10 Imagerie
Illustration afrofuturiste caribéenne : couleurs chaudes saturées sur fonds sombres. Jamais de stock corporate, jamais d'illustration startup générique. AVIF/WebP self-hébergés, `loading="lazy"` hors LCP.
### 1.11 Écosystùme
- Nom KA + logiciel en sous-titre (« BOKANTE — Mastodon ») : la transparence est un pilier.
- Footer fédéré sur tous les sous-domaines : `[monogramme] Un service libre opéré par ORGANISATION KA INTERNATIONALE · o-k-i.net` + flag-bar 6 px au-dessus.
- Favicon = monogramme partout. `*.o-k-i.net` = outils/fédivers ; `pawol.nu` = projets culturels (motion plus riche autorisé).
---
## 2. MÉTHODE TECHNIQUE (la recette)
### 2.1 Les 5 principes
- **P1 — Le scroll est le moteur.** Une seule instance Lenis au layout racine, cadencĂ©e par le ticker GSAP unique. Jamais de double rAF.
- **P2 — Bake au build, pas au runtime.** Tout ce qui ne dĂ©pend pas d'une entrĂ©e live est prĂ©calculĂ© : images responsive (vite-imagetools), textures, polices.
- **P3 — DOM d'abord, WebGL seulement oĂč ça paie.** Zentry a fait du « 3D » primĂ© en pur DOM (clip-path, preserve-3d, masks). Pour l'audience OKI (mobile 4G, Mali-G52) : **DOM/SVG uniquement** par dĂ©faut.
- **P4 — Le motion se designe avant les pages** (tokens §1.7 d'abord, composants ensuite).
- **P5 — Chaque Ă©tat est un moment designĂ©** : loader, 404, vide, hors-ligne — en KA avec le tambour `ka`.
### 2.2 Interdictions absolues
- Jamais de dĂ©pendance installĂ©e sans ĂȘtre importĂ©e et utilisĂ©e (les outils d'audit — lighthouse, puppeteer — s'installent en `--no-save` ou se retirent).
- Jamais de SSR désactivé globalement pour « faire marcher » une lib client.
- Jamais animer autre chose que `transform`/`opacity`/`clip-path` en JS.
- Jamais de page dont le contenu est invisible sans JavaScript.
- Jamais de service tiers (fonts, CDN, analytics) — tout asset se self-hĂ©berge.
### 2.3 Stack cible
```
SvelteKit 2 + Svelte 5 (runes) + TypeScript strict
@sveltejs/adapter-static (prerender intégral, trailingSlash 'always')
vite-imagetools (AVIF/WebP responsive au build)
vite-plugin-pwa (generateSW — shell hors-ligne)
lenis + gsap (imports dynamiques uniquement)
@fontsource/* (polices woff2 copiées dans static/fonts/)
CSS vanilla : oki-tokens.css + base.css + styles scopĂ©s — pas de Tailwind
```
### 2.4 Workflow en 7 phases
1. **Audit** (toujours) : pages, sections, assets, liens externes, stack mesurée, bugs de production listés AVANT toute refonte, contradictions doctrine/outillage (ex. discours anti-GAFAM + Google Fonts). Rapport validé avant de coder.
2. **Tokens & thÚme** : `oki-tokens.css` (§1), thÚme sombre défaut + clair opt-in, fonts self-hébergées, anti-FOUC par script externe (CSP).
3. **Architecture** : routes, i18n, SEO, layout. `prerender = true` partout. URLs historiques conservées.
4. **Composants** : uniquement ceux pertinents pour CE site. Chacun avec cleanup et garde reduced-motion.
5. **Motion** : transitions Svelte natives · scrub via `animation-timeline: view()/scroll()` natif d'abord, fallback GSAP ScrollTrigger en import dynamique · une seule horloge (gsap.ticker → Lenis).
6. **Performance** : budgets §2.7, pipeline images, mesure sur profil mobile.
7. **Accessibilité & dégradation** : ladder §2.8, revue clavier, contenu canvas/SVG doublé en DOM.
### 2.5 Architecture de référence (éprouvée sur o-k-i.net)
```
src/
app.html # %lang% + scripts externes (theme, lang-redirect, registerSW)
hooks.server.ts # lang fr/en via transformPageChunk (replaceAll '%lang%')
app.d.ts / imagetools.d.ts
lib/
styles/oki-tokens.css # charte §1 — LE fichier de tokens partagĂ©
styles/base.css # reset, primitives (btn, card, tag, flag-bar, icon), gate reduced-motion
i18n/ # bundles JSON par locale + index.ts (getBundle, localeFromPath, alternatePath)
assets/images/ # sources pour vite-imagetools
motion/ # tokens.ts, scroll.ts (Lenis), reveal.ts, tilt.ts
components/ # Seo, Nav, Footer, ResponsiveImage, motion/, icons/, sections/
routes/
+layout.svelte # skip-link, loader, progress, nav, footer, View Transitions, Lenis
+layout.ts # prerender = true, trailingSlash = 'always'
+error.svelte # 404/erreurs designée (KA)
offline/+page.svelte # cible navigateFallback du SW
static/
fonts/ icons.svg images/ theme.js lang-redirect.js registerSW.js
manifest.webmanifest 404.html robots.txt sitemap.xml _headers .htaccess
```
### 2.6 Composants motion (contrats)
- **KineticText** — titrage au scroll, split par **mots** (jamais caractĂšres — apostrophes), `aria-label` sur titres h1-h4 (jamais sur span : prohibĂ©), scrub `animation-timeline: view()` + fallback ScrollTrigger dynamique, stagger syncopĂ© converti en plages de scroll.
- **ScrollProgressBar** — `scaleX` via rAF + Ă©criture DOM directe (jamais d'Ă©tat rĂ©actif par frame), `role="progressbar"`.
- **PageTransition** — `onNavigate` + View Transitions API, durĂ©e lue des tokens ; fallback sobre = navigation instantanĂ©e.
- **IntroLoader** — cĂ©rĂ©monie 1re visite (`sessionStorage`), skippable, 100 % CSS pilotĂ©e par une classe posĂ©e par le script externe anti-FOUC (pas de JS inline — CSP).
- **Marquee** — bande typographique CSS pure, contenu dupliquĂ© `aria-hidden`, pause au hover, coupĂ©e en reduced-motion.
- **use:reveal** — action IO, cascade syncopĂ©e via `--d`, Ă©tat cachĂ© **uniquement** sous `html.js` + `prefers-reduced-motion: no-preference`.
- **use:tilt** — tilt 3D pointeur via variables CSS `--rx/--ry` + rAF, off tactile + reduced-motion.
- **FlagChip** — mini flag-bar 56×6 px qui se dessine (`scaleX`) Ă  l'entrĂ©e du titre.
- **Village Ă©cosystĂšme** — scĂšne SVG isomĂ©trique des services, chaque bĂątiment = `<a>` focusable (`aria-label` « NOM — Logiciel »), hover/focus = liserĂ© or + label, fallback grille accessible (reduced-motion + mobile Ă©troit, bascule `display: none` pour Ă©viter les doubles tab stops).
- **CursorTracker, WebGL** : non retenus pour l'audience OKI (mobile-dominante).
### 2.7 Budgets (mobile 4G, Android entrée de gamme)
| Ressource | Budget | Mesuré o-k-i.net |
|---|---|---|
| JS initial compressĂ© | ≀ 170 Ko | 69 Ko gzip (+45 Ko dynamiques) |
| Poids total accueil | ≀ 2 Mo | ~0,9 Mo |
| MĂ©dia hero avant interaction | ≀ 400 Ko | ~12 Ko (logo) |
| Lighthouse mobile | ≄ 90/95/95/95 | 91/100/100/100 |
| Animations JS concurrentes | ≀ 8-12, transform/opacity | OK |
| LCP / INP / CLS | < 2,5 s / < 200 ms / < 0,05 | 2,9 s / 59 ms TBT / 0,001 |
### 2.8 Accessibilité & ladder de dégradation
1. `saveData`/`effectiveType` ≀ 3g → images statiques, zĂ©ro prĂ©chargement.
2. JS dĂ©sactivĂ© → contenu SSR complet et lisible (Ă©tats cachĂ©s conditionnĂ©s Ă  `html.js`).
3. `prefers-reduced-motion` → gate unique : pas de Lenis, pas de loader, pas de scrub, contenu statique complet.
4. Canvas/SVG décoratifs `aria-hidden` + miroir DOM sémantique.
5. `lang` correct à chaque bascule ; focus déplacé sur le contenu aprÚs transition de route ; skip-link ; focus visible or.
6. PiÚges éprouvés : `aria-label` interdit sur `<span>` générique · `<figcaption>` enfant direct de `<figure>` · liens dans le texte soulignés (scoper la rÚgle à `main` pour ne pas toucher nav/footer).
### 2.9 PWA légÚre
- `static/manifest.webmanifest` manuel (nom KA, monogramme any + maskable, `theme_color`/`background_color` `--noir-oki`, `display: standalone`).
- `vite-plugin-pwa` : `registerType: 'autoUpdate'`, `injectRegister: false`, `manifest: false`, workbox `navigateFallback: '/offline/index.html'`, `navigateFallbackDenylist` pour les assets binaires, `globIgnores` pour les gros fichiers (PDF), `maximumFileSizeToCacheInBytes: 3 Mo`, `cleanupOutdatedCaches`, `clientsClaim`, `skipWaiting`.
- Enregistrement par **fichier statique** `static/registerSW.js` avec chemins absolus (`navigator.serviceWorker.register('/sw.js', { scope: '/' })`), lié dans `app.html`. Ne PAS utiliser l'injection du plugin : sans `@vite-pwa/sveltekit` elle n'atteint pas le HTML pré-rendu, et le `registerSW.js` généré utilise un chemin relatif `./sw.js` cassé sur les routes imbriquées (`/en/`, `/dons/`).
### 2.10 Sécurité & CSP
CSP de référence (tout self-hébergé) :
```
default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline';
img-src 'self' data:; font-src 'self'; connect-src 'self';
frame-ancestors 'none'; base-uri 'self'; form-action 'self'; object-src 'none'
```
+ HSTS `max-age=31536000`, `X-Frame-Options: DENY`, `X-Content-Type-Options: nosniff`, `Referrer-Policy: strict-origin-when-cross-origin`, `Permissions-Policy` restrictive. Livrée en **deux formats** : `static/.htaccess` (Apache/o2switch : + `ErrorDocument 404 /404.html`, redirect HTTPS, cache immutable `/_app/` et `/fonts/`) et `static/_headers` (Cloudflare Pages). RÚgles : **aucun script inline** (snippets thÚme/langue en fichiers externes) ; `style` attributes OK (`unsafe-inline` style) ; 404 statique autonome (`static/404.html`, zéro dépendance).
### 2.11 SEO
Composant `Seo.svelte` : title/description par page, canonical, hreflang fr/en/x-default via mapping de paires d'URLs, OG ×10 (og:image self-hĂ©bergĂ©e), Twitter Card, JSON-LD (Organization + entitĂ©s du projet). `robots.txt` + `sitemap.xml` statiques Ă  jour. Astuce : les `</script>` dans les template literals `{@html}` doivent ĂȘtre Ă©chappĂ©s (`<\/script>`).
---
## 3. CONVENTIONS SVELTE 5 (obligatoires)
- **Runes only** : `$state` (uniquement pour du rĂ©actif — sinon variable plain ; gros objets rĂ©assignĂ©s : `$state.raw`), `$derived` (jamais d'`$effect` pour calculer), `$props` (traiter les props comme changeantes : valeurs dĂ©pendantes en `$derived`).
- **ÉvĂ©nements** : `onclick={...}` (jamais `on:click`) ; listeners window/document via `<svelte:window>` / `<svelte:document>`.
- **`{#each}` à clé unique** (jamais l'index).
- **CSS** : variables JS → directive `style:--var={val}` ; styles enfants via custom properties, `:global` en dernier recours ; Ă©tats globaux (`html.js`, `html.light-theme`, `html.intro-pending`) via `:global(...)`.
- **États rĂ©actifs par-frame interdits** : scrollY/pointeur → variables plain + flush rAF (ou variables CSS directement).
- **Init/cleanup** : GSAP/Lenis/IO dans `onMount`/`$effect` avec cleanup symétrique (`kill()`/`revert()`/`destroy()`/`disconnect()`).
- **Liens internes** : `resolve()` / assets via `asset()` de `$app/paths` ; externes en `rel="external noopener noreferrer"` + `target="_blank"`.
- **{@html}** : uniquement sur contenu first-party (JSON du dépÎt), commenté comme tel.
- **Autofixer (AGENTS.md)** : aprĂšs tout composant modifiĂ© —
```bash
npx @sveltejs/mcp list-sections # doc Svelte 5
npx @sveltejs/mcp get-documentation "$state,$derived"
npx @sveltejs/mcp svelte-autofixer ./src/lib/MonComposant.svelte
```
(Échapper `\$` dans le code inline. La sortie est un objet JS, pas du JSON strict.) `npm run check` à **0 erreur / 0 warning** avant tout commit.
---
## 4. BRIQUES RÉUTILISABLES (rĂ©fĂ©rence : dĂ©pĂŽt o-k-i.net, branche `svelte`)
Copier et adapter, ne pas réécrire. Chaque brique a un contrat stable.
| Brique | Chemin dans o-k-i.net | Contrat / usage |
|---|---|---|
| Tokens charte | `src/lib/styles/oki-tokens.css` | importé en premier dans `+layout.svelte` |
| Primitives | `src/lib/styles/base.css` | `.btn .card .tag .icon .flag-bar .container .section`, gate reduced-motion, reveal |
| Fonts | `static/fonts/` + `fonts.css` | Archivo 600-900, Inter 400-800, woff2 latin, preload 700 |
| Anti-FOUC thĂšme | `static/theme.js` | pose `.js`, `.light-theme`, `.intro-pending` ; localStorage `oki-theme` |
| Redirection langue | `static/lang-redirect.js` | préférence `oki-lang-pref`, sinon `navigator.language` |
| i18n | `src/lib/i18n/index.ts` | bundles JSON par locale ; `getBundle(locale)`, `localeFromPath`, `alternatePath` (paires FR↔EN) |
| SEO | `src/lib/components/Seo.svelte` | `<Seo title? description? path locale />` — meta + OG + Twitter + JSON-LD |
| Lang par route | `src/hooks.server.ts` | `%lang%` dans `app.html` + `transformPageChunk` |
| Lenis | `src/lib/motion/scroll.ts` | `initSmoothScroll()` (dynamic), `scrollToAnchor(hash)` |
| Tokens motion | `src/lib/motion/tokens.ts` | `prefersReducedMotion()`, `syncopatedDelay(i)` (3+3+2) |
| Reveal | `src/lib/motion/reveal.ts` | `use:reveal={index}` — cascade gwoka |
| Tilt | `src/lib/motion/tilt.ts` | `use:tilt` — CSS `--rx/--ry` |
| KineticText | `components/motion/KineticText.svelte` | `<KineticText text as="h2" class="section-title" />` |
| ScrollProgressBar | `components/motion/ScrollProgressBar.svelte` | global, gradient or→vert |
| IntroLoader | `components/motion/IntroLoader.svelte` | cérémonie 1re visite, skippable |
| Marquee | `components/motion/Marquee.svelte` | `<Marquee {t} />` |
| FlagChip | `components/motion/FlagChip.svelte` | ornement de titre, dessin au scroll |
| Images | `components/ResponsiveImage.svelte` + `imagetools.d.ts` | `import pic from './x.png?format=avif;webp;png&w=
&as=picture'` → `<ResponsiveImage picture={pic} alt />`. **Le wildcard `declare module '*&as=picture'` doit vivre dans un `.d.ts` NON-module** |
| Sprite OKI | `static/icons.svg` | 12 pictos KA (§1.8) — `<use href="/icons.svg#ID">` |
| Logos marques | `components/icons/BrandIcon.svelte` | `<BrandIcon name="mastodon|peertube|nextcloud|gitea|castopod|discord|telegram|whatsapp|email|stoat|tiktok|x" />` |
| Village | `components/sections/HostingVillage.svelte` | scÚne SVG écosystÚme + fallback grille |
| Nav/Footer | `components/Nav.svelte`, `Footer.svelte` | nav active (IO), dropdown accessible, thÚme, langue ; footer fédéré |
| Emojis→pictos | `components/sections/pictos.ts` | `splitLeadingEmoji(str)`, `pictoFor(emoji)` |
| Bridge tokens | `atlas-fediverse/src/styles/oki-bridge.css` | alias des anciens noms de tokens vers la charte — applique la charte à un projet existant **sans retoucher chaque composant** |
| Layout bakĂ© | `atlas-fediverse/scripts/build-layout.mjs` | d3-force en script node → positions dĂ©terministes figĂ©es en JSON (init hashĂ©e FNV-1a + ticks fixes = bit-identique). Rejouer quand les donnĂ©es changent |
| État partagĂ© inter-sections | `atlas-fediverse/src/lib/software-modal.svelte.ts` | module `$state` + `requestX()/consumeX()` : ouvrir une modale d'un composant depuis une autre section (timeline → catalogue) |
| Layout app 100dvh | `JWE/app/src/lib/styles/jeu.css` | `height: 100dvh` (pas min-height) + overflow par panneau + overlays consolidĂ©s (une puce de statut, rangĂ©es de boutons hors de la carte, barre d'actions opaque z-indexĂ©e) — zĂ©ro scroll, zĂ©ro superposition |
| Count-up score | `JWE/app/src/lib/motion/countup.ts` | `use:compte={cible}` : rAF → Ă©criture DOM directe, span animĂ© `aria-hidden` + valeur finale en `.sr-only` (rĂ©gion `aria-live`) |
| Reveal SSR | `JWE/app/src/lib/motion/reveal.ts` | variante dont l'état initial est rendu cÎté serveur, masqué uniquement sous `html.js` + `no-preference` |
| i18n dossiers | `gwada-sirius/src/lib/i18n/index.ts` | catalogues au format Paraglide/inlang consommĂ©s par un routeur maison (pas de middleware) — statique-friendly, l'outillage inlang reste utilisable |
| Support volume iOS | `svelte-podcast/src/lib/volume-support.ts` | `can_set_volume()` : sonde l'Ă©criture de `volume` sur un Ă©lĂ©ment jetable (mĂ©moĂŻsĂ©e) — dĂ©tecte iOS par capacitĂ©, pas par UA sniffing |
| PWA | `vite.config.ts` (bloc VitePWA) + `static/registerSW.js` + `static/manifest.webmanifest` | voir §2.9 |
| Headers | `static/.htaccess` + `static/_headers` | voir §2.10 |
| 404 / offline | `static/404.html` + `routes/+error.svelte` + `routes/offline/` | KA + tambour `ka`, autonomes |
---
## 5. LEÇONS DE LA MIGRATION o-k-i.net (piĂšges dĂ©jĂ  rĂ©solus — ne pas les rĂ©-apprendre)
1. **Toujours auditer le dĂ©pĂŽt rĂ©el** : le brief disait « HTML statique », c'Ă©tait Eleventy + i18n JSON. Le contenu vivait dans les donnĂ©es — les reprendre, pas réécrire.
2. **Les données priment sur le brief** : le village suit `services.json` (4 instances), pas les exemples du brief ; la section cible se décide d'aprÚs le contenu JSON, pas le titre supposé.
3. **vite preview meurt si l'on rebuilde pendant qu'il tourne** (il sert `.svelte-kit/output`) — redĂ©marrer aprĂšs chaque build.
4. **CSP `script-src 'self'`** : anti-FOUC, lang-redirect et registerSW en fichiers externes statiques ; aucun inline.
5. **Preview headless** : `--virtual-time-budget` fige les animations d'entrĂ©e (faux nĂ©gatifs visuels) — valider le motion en temps rĂ©el via puppeteer (`--no-save`), pas en screenshot one-shot.
6. **Puppeteer QA** : `page.emulateMediaFeatures` pour reduced-motion ; le tilt se teste par `dispatchEvent(PointerEvent)` (le scroll Lenis décale `mouse.move`).
7. **eslint/autofixer `no-navigation-without-resolve`** : `resolve()` pour routes internes, `asset()` pour fichiers statiques, `rel="external"` pour sortir du scope de la rùgle — sans jamais envelopper une URL externe ou une ancre `#`.
8. **Grep de vĂ©ritĂ©** avant de livrer : zĂ©ro domaine tiers dans `build/` (hors liens `<a>` mĂ©tier), zĂ©ro emoji dans le HTML buildĂ©, toutes les URLs en 200, `og:` ×10, JSON-LD prĂ©sents.
9. **Lighthouse aprĂšs chaque passe** (mobile) et corriger ce qui est remontĂ© — les deux points gagnĂ©s sur o-k-i.net : `aria-label` sur span (KineticText) et liens non soulignĂ©s.
10. **Lighthouse `link-in-text-block`** : scoper `text-decoration: underline` Ă  `main` — sinon la nav et le footer hĂ©ritent de soulignĂ©s partout.
## 5b. LEÇONS DES MISSIONS atlas / exitchatcontrol / gwada-sirius / JWE
**Gouvernance & process**
1. **git d'abord** : atlas n'avait aucun dĂ©pĂŽt — `git init` + commit de l'existant avant toute ligne de code.
2. **WIP non commité d'autrui** : builder d'abord ; si c'est cohérent, committer tel quel, puis améliorer par commits séparés (exitchatcontrol).
3. **Orchestration d'agents parallĂšles** : sĂ©rialiser tout ce qui touche `package.json` / npm (un seul agent ou le parent) ; dĂ©finir les contrats (props des composants, noms de tokens, chemins) AVANT de lancer — les agents s'intĂšgrent alors sans conflit, et dĂ©couvrent mĂȘme le travail des autres (module `software-modal` rĂ©utilisĂ© en vol).
4. **Conflits de ports** : avant tout audit automatisĂ©, vĂ©rifier ce qui tourne (`vite preview` d'un autre projet sur le mĂȘme port a faussĂ© un audit axe — vĂ©rifier le `<title>` servi).
**Technique SvelteKit**
5. **`paths.relative: false`** quand `paths.base` est utilisĂ© : par dĂ©faut `$app/paths.base` vaut `".."` au prerender, ce qui casse toutes les comparaisons de pathname (`startsWith(base)`, switcher de langue, `aria-current`) et produit des hrefs relatifs bizarres dans le HTML prĂ©rendu. L'hydratation masque le bug — le vĂ©rifier dans le HTML buildĂ©, pas seulement au clic.
6. **`lang` par route avec base path** : retirer `base` dans `hooks.server.ts` AVANT de déduire la locale du premier segment.
7. **i18n sans middleware** : un routeur par dossiers (`/`, `/en/`, `/ht/`) + loader JSON maison bat Paraglide-officiel pour le statique (URLs et prerender sous contrĂŽle total) tout en gardant les catalogues au format inlang.
8. **Îlots → composants natifs** : le montage manuel (`mount.js` + IntersectionObserver) disparaĂźt ; le fallback SSR (table, liste) devient le markup mĂȘme du composant — meilleur pour no-JS ET pour le CLS.
9. **Leaflet sous SSR** : `await import('leaflet')` dans `onMount` (l'import statique plante cÎté Node), CSS en import statique OK, `L.divIcon` CSS au lieu d'images de marqueurs. Tuiles OSM = exception documentée au zéro-tiers, à faire trancher par le propriétaire.
10. **CSP d'un jeu/app riche** (adapter-node → `hooks.server.ts`) : Ă©numĂ©rer les domaines rĂ©els en inspectant le code ET les node_modules (Mapillary a besoin de `graph.` + `tiles.` + `images.mapillary.com`, Wikimedia d'`upload.` + `commons.`) — puis jouer une manche complĂšte en capturant la console : zĂ©ro violation exigĂ©e.
11. **Layout « app » sans scroll** : `height: 100dvh` (jamais `min-height`) + overflow géré par panneau + indices dans des panneaux scrollables internes. Les bugs de superposition mobile viennent d'overlays absolus empilés : consolider les badges en une puce, sortir les rangées de boutons de la carte, barre d'actions opaque avec `z-index` explicite, `pointer-events: none` sur tout conteneur décoratif. Tester chaque bouton par `elementFromPoint`.
12. **Le « moment de rĂ©vĂ©lation »** (jeux) : ligne de distance dessinĂ©e + score en count-up + marqueur qui tombe. Count-up : rAF → DOM direct, jamais d'Ă©tat rĂ©actif par frame ; span animĂ© `aria-hidden` + valeur finale `.sr-only` dans une rĂ©gion `aria-live`.
13. **CSP hashĂ©e** (script postbuild qui hashe les inline) : un JSON-LD inline ajoutĂ© est hashĂ© automatiquement — mais vĂ©rifier le compte d'empreintes aprĂšs build.
14. **Icînes PNG lourdes** : `PIL.Image.quantize(256)` suffit pour des logos (121 Ko → 9 Ko, perte invisible) — toujours relire l'image aprùs.
**Contenu & données**
15. **Audit factuel des donnĂ©es** en plus de l'audit technique : la timeline d'atlas contenait 4 dates fausses et un compteur pĂ©rimĂ© (Twitter 2021→2022, Mastodon 2015→2016, PeerTube 2015→2017, Bluesky 2023→2024, « 55+ »→105). Les champs de donnĂ©es inutilisĂ©s (`month`, `softwareId`) sont souvent des fonctionnalitĂ©s gratuites.
16. **RĂ©gressions SEO typiques au changement de gĂ©nĂ©rateur** : sitemap, OG hors fiches, og:image, canonical — checklist Ă  passer systĂ©matiquement (exitchatcontrol).
**Déploiement YunoHost**
17. Le package ne consomme que des **archives de tag** : taguer l'app (`vX.Y.Z`), pousser le tag, télécharger l'archive `/archive/<tag>.tar.gz` depuis la forge, recalculer son sha256, mettre à jour `manifest.toml` (version `X.Y.Z~ynhN` + url + sha256), commit/push le package. `autoupdate.strategy = "latest_forgejo_tag"` détecte les tags suivants.
## 5c. LEÇONS DE LA MISSION oki-podcast-reader (lecteur audio)
**Gouvernance & forks**
1. **SĂ©curiser le WIP AVANT tout** : toute la transformation applicative Ă©tait non commitĂ©e sur un fork — premier commit de protection avant la premiĂšre analyse dĂ©taillĂ©e (rĂšgle gĂ©nĂ©rale : `git add -A && git commit "WIP sĂ©curisĂ©"` dĂšs qu'un working tree contient du travail non versionnĂ©).
2. **Fork de librairie → application** : purger la dette de packaging (champs `exports`/`files`/`peerDependencies`, changesets, publint, workflows CI de la lib) — le `package.json` d'une app est `private: true` et nu.
3. **Le remote `origin` d'un fork pointe chez l'auteur original** : le renommer `upstream` avant d'ajouter le remote de déploiement, sinon le push part chez l'original (échoue ou pire).
**Upgrades de framework**
4. **Le mode compat Svelte 5 est une voie complĂšte** : SvelteKit 1→2 + Svelte 4→5 SANS réécriture en runes — la syntaxe legacy (`on:click`, `$:`, `export let`, `<slot>`) compile Ă  0 erreur/0 warning. Upgrader les paquets, traiter uniquement les breaking changes (imports `vitePreprocess`, options TS supprimĂ©es, sĂ©rialisation), ne JAMAIS réécrire la syntaxe dans la mĂȘme passe. La réécriture en runes est une mission sĂ©parĂ©e, facultative.
5. **Respecter le gestionnaire de paquets du projet** (yarn.lock ≠ package-lock.json) — ne pas mĂ©langer.
**RÚgle d'architecture des loads (la plus généralisable)**
6. **Universal load (`+page.ts`) = le corps brut des fetch est inlinĂ© dans le HTML** : un flux RSS de 10 Mo produisait une page de 11,9 Mo. **Server load (`+page.server.ts`) garde le fetch cĂŽtĂ© serveur** — rĂšgle d'un coup : bloat supprimĂ© (−88 %), ET les problĂšmes CORS en navigation client disparus (le navigateur ne refait jamais le fetch : il lit `__data.json`). Toute donnĂ©e tierce fetchĂ©e appartient Ă  `+page.server.ts`. Corollaire : les pages qui en dĂ©pendent ne buildent pas hors-ligne — Ă  documenter.
7. Les redirections CORS se contrĂŽlent Ă  chaque saut : une 302 sans `Access-Control-Allow-Origin` tue le fetch navigateur mĂȘme si la destination finale l'autorise — raison de plus pour fetcher cĂŽtĂ© serveur.
**Audio / média**
8. **Les prĂ©fĂ©rences mĂ©dia persistĂ©es doivent ĂȘtre re-appliquĂ©es en continu** (souscription), pas seulement au chargement de la source — et `?? 1`, jamais `|| 1` (un 0 persistĂ© est une valeur lĂ©gitime).
9. **`HTMLMediaElement.volume` est en lecture seule sur iOS** : détecter par capacité (sonder l'écriture sur un élément jetable, mémoïser), masquer le slider, garder le mute (`el.muted`, lui, fonctionne). Source de vérité unique dans le store persisté, synchronisée dans les deux sens.
10. **Cibles tactiles sur les lecteurs** : Ă©paisseur visuelle ≠ zone de hit (piste fine de 10 px OK si la zone fait 44 px). Un `<input type="range">` ne repositionne PAS le thumb au tap tactile — handlers `touchstart`/`touchmove` explicites requis (tester avec `page.tap` + `hasTouch`).
11. **Media Session API est non négociable pour une app audio** (contrÎles écran verrouillé) : `metadata` à chaque piste + handlers `play`/`pause`/`previoustrack`/`nexttrack`, gardé par `'mediaSession' in navigator`.
12. **SW d'une app audio** : ne JAMAIS intercepter les streams (requĂȘtes `Range`, extensions mĂ©dia) — prĂ©cache shell, cache-first sur pochettes uniquement.
**Perf de listes**
13. **`content-visibility: auto` + `contain-intrinsic-size`** : le gain de perf de listes le moins cher qui existe (CSS seul). Avant toute virtualisation : pagination « charger plus » 50×50 (1022 lignes DOM → 50).
---
## 6. PATTERNS AWWWARDS (distillĂ© des 15 sites — quoi voler, Ă  quel coĂ»t)
| Pattern (source) | Coût | Statut pour OKI |
|---|---|---|
| DOM-only masks/clip-path « 3D » (Zentry) | 5 % du coût GPU | **Adopté** (wipes pochettes) |
| Motion system spec'd avant les pages (Zentry, P4) | 0 | **Adopté** (tokens gwoka) |
| Hover craft budgĂ©tĂ© — 5 interactions intentionnelles (Noomo) | faible | **AdoptĂ©** (balayage boutons, tilt, soulignĂ©s, village) |
| Loader-as-fiction (KPR) | faible | **Adopté** (IntroLoader) |
| États designĂ©s : 404, vide, offline (Studio375) | faible | **AdoptĂ©** (KA + tambour) |
| Dollhouse IA — sections = lieux physiques (Kriss.ai) | moyen | **AdoptĂ©** (village crĂ©ole) |
| Constellation zetwal (charte §7 — prescription portails fĂ©divers) | faible | **AdoptĂ©** (hero atlas, remplace un shader Three.js de 708 Ko) |
| Layout bakĂ© au build — positions de graphe figĂ©es (dĂ©clinaison P2) | faible | **AdoptĂ©** (carte atlas : SVG 2D dĂ©terministe, −750 Ko de JS) |
| Moment de rĂ©vĂ©lation designĂ© — ligne de distance + count-up + chute de marqueur (jeux) | faible | **AdoptĂ©** (JWE) |
| Bilingual kinetic type — la bascule de langue comme Ă©vĂ©nement (Nudot) | faible | **Candidat** (Ă©volution du lang-switch FR/KA) |
| Chaptered scroll-comic (ten.375.studio) | moyen | **Candidat** (lore DJANGOKAM, patrimoine) |
| Scroll flipbook connection-aware (Apple) | moyen | **Candidat** si séquence visuelle un jour (gating `effectiveType` obligatoire) |
| Bake noise/textures offline (David Whyte) | faible | **Candidat** (fonds génératifs bakés) |
| Backstage / process public (Immersive Garden) | faible | **Candidat** (contenu pédagogique = souveraineté) |
| Cursor-as-light, velocity particles (Unseen, Igloo) | élevé | **Rejeté** (audience tactile) |
| WebGL full-UI (Igloo), skeletal scroll camera (HAPE), flipbook géant | trÚs élevé | **Rejeté** (budgets 4G/Mali-G52) |
| Resolution gating (KPR) | — | **Interdit** (hostile mobile) |
---
## 7. TEMPLATE DE BRIEF DE MISSION (Ă  remplir par projet)
```markdown
# MISSION : [Transformer X / CrĂ©er Y] selon le PLAYBOOK UNIFIÉ OKI × SVELTEKIT
## 1. Contexte
- Site/projet : [URL/dĂ©pĂŽt] — stack actuelle mesurĂ©e : [Ă  auditer, §2.4 phase 1]
- Identité : [association/projet, positionnement]
- ÉcosystĂšme liĂ© (liens externes Ă  prĂ©server) : [liste]
- Bugs de production confirmés : [liste audit]
## 2. Contenu éditorial
- [Reprendre à l'identique / réécriture autorisée : périmÚtre]
- Langues : [FR / EN / KA — URLs]
## 3. Exigences techniques
- Stack : §2.3 du playbook (dĂ©faut) — Ă©carts Ă©ventuels : [liste]
- URLs Ă  conserver : [liste + ancres]
- Hébergement cible : [o2switch statique / Cloudflare Pages]
- PWA : [oui/non]
## 4. Motion & composants (sĂ©lection depuis §2.6 — rester sobre)
- [ ] KineticText titres [ ] IntroLoader [ ] Marquee [ ] Village/scĂšne
- [ ] Autre : [prĂ©ciser] — WebGL : [non par dĂ©faut]
## 5. PérimÚtre de cette session
- [ ] 

## 6. CritĂšres d'acceptation
- DoD §8 intégralement vérifiée + [spécifiques projet]
```
---
## 8. DOD — DÉFINITION DE « TERMINÉ » (tout doit ĂȘtre vrai)
**Fondations**
- [ ] Rapport d'audit livré ; bugs pré-existants corrigés ou explicitement reportés · **git vérifié/initialisé avant tout travail**.
- [ ] Tokens charte importés (fichier partagé, pas de valeurs recopiées) ; thÚme sombre défaut, clair opt-in AA.
- [ ] Fonts self-hĂ©bergĂ©es ; zĂ©ro requĂȘte tierce au chargement (vĂ©rifiĂ© onglet rĂ©seau + grep du build).
- [ ] `npm run check` : 0 erreur, 0 warning · `npm run build` vert · `svelte-autofixer` propre sur les fichiers modifiés.
- [ ] Si `paths.base` : `paths.relative: false` et hrefs du HTML prérendu inspectés (pas de `../` ni de double préfixe).
- [ ] **Audit factuel des données** (dates, compteurs, champs inutilisés) en plus de l'audit technique.
- [ ] Toute donnée tierce fetchée passe par `+page.server.ts` (un universal load inline le corps brut des fetch dans le HTML).
**Marque**
- [ ] Or = seule couleur d'action ; rouge/vert dans leurs rĂŽles · angles nets (≀ 6 px).
- [ ] Flag-bar segments francs, ≀ 1 pleine largeur par Ă©cran · zĂ©ro emoji en interface (sprite SVG).
- [ ] Tokens motion gwoka + stagger syncopé · gate `prefers-reduced-motion` vérifiée manuellement.
- [ ] Voix conforme (test : aucune occurrence de « clé en main », « solution innovante », « disruptive »).
**Expérience & accessibilité**
- [ ] Navigation clavier complĂšte (tab order, focus visible, skip-link, village/scĂšne/carte focusable).
- [ ] **Zéro superposition** : chaque bouton testé par `elementFromPoint` (mobile 390 px) ; `pointer-events: none` sur tout conteneur décoratif.
- [ ] Layout app : `height: 100dvh` si « tout visible sans scroll » est exigĂ© — `scrollHeight === innerHeight` mesurĂ© mobile ET desktop.
- [ ] Page fonctionnelle et lisible avec JS désactivé.
- [ ] 404, vide et hors-ligne designés (KA + tambour) · loader designé si présent, skippable.
- [ ] `lang` correct par locale · hreflang · OG complet self-hébergé · JSON-LD.
**Mesures**
- [ ] Lighthouse mobile : Performance ≄ 90, AccessibilitĂ© ≄ 95, Best Practices ≄ 95, SEO ≄ 95.
- [ ] JS initial ≀ 170 Ko gzip · accueil ≀ 2 Mo · images optimisĂ©es (aucune > 200 Ko hors hero, lazy hors LCP).
- [ ] Toutes les URLs historiques préservées ou redirigées · sitemap/robots à jour.