# 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 `` dans un sprite `icons.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 = `` 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 `` générique · `
` enfant direct de `
` · 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 `` 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 `` / ``. - **`{#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` | `` — 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` | `` | | 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` | `` | | 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'` → ``. **Le wildcard `declare module '*&as=picture'` doit vivre dans un `.d.ts` NON-module** | | Sprite OKI | `static/icons.svg` | 12 pictos KA (§1.8) — `` | | Logos marques | `components/icons/BrandIcon.svelte` | `` | | 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 `` 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 `` 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.