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
This commit is contained in:
sucupira
2026-07-23 00:49:02 -04:00
parent 5b1a78782a
commit d6efb6736f
147 changed files with 14567 additions and 15145 deletions
+23
View File
@@ -0,0 +1,23 @@
You are able to use the Svelte MCP server, where you have access to comprehensive Svelte 5 and SvelteKit documentation. Here's how to use the available tools effectively:
## Available Svelte MCP Tools:
### 1. list-sections
Use this FIRST to discover all available documentation sections. Returns a structured list with titles, use_cases, and paths.
When asked about Svelte or SvelteKit topics, ALWAYS use this tool at the start of the chat to find relevant sections.
### 2. get-documentation
Retrieves full documentation content for specific sections. Accepts single or multiple sections.
After calling the list-sections tool, you MUST analyze the returned documentation sections (especially the use_cases field) and then use the get-documentation tool to fetch ALL documentation sections that are relevant for the user's task.
### 3. svelte-autofixer
Analyzes Svelte code and returns issues and suggestions.
You MUST use this tool whenever writing Svelte code before sending it to the user. Keep calling it until no issues or suggestions are returned.
### 4. playground-link
Generates a Svelte Playground link with the provided code.
After completing the code, ask the user if they want a playground link. Only call this tool after user confirmation and NEVER if code was written to files in their project.
@@ -0,0 +1,213 @@
# CHARTE OKI — DESIGN SYSTEM & HARMONISATION MULTI-PROJETS
### ORGANISATION KA INTERNATIONALE · appliquer la recette SvelteKit avec la marque OKI
> **Nature de ce document :** référence de marque opérationnelle, destinée aux humains et aux agents de code. Il consolide le `BRAND.md` existant (valeurs mesurées sur o-k-i.net, juillet 2026 — **source canonique** pour couleurs, typo, voix) et y ajoute les tokens prêts à l'emploi, les règles de motion, l'iconographie SVG, et la matrice d'application projet par projet. **En cas de conflit : ce document prime sur la recette générique pour tout ce qui touche à la marque ; la recette prime pour tout ce qui touche à la méthode technique.**
---
## 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**. La marque est afrofuturiste caribéenne : un drapeau, pas une charte corporate.
Trois conséquences non négociables :
1. **Le thème sombre est l'identité par défaut** ; le clair est une variante opt-in (`html.light-theme`) qui assombrit les accents pour rester WCAG AA.
2. **L'or porte toute l'interaction** — c'est la seule couleur d'action.
3. **Angles nets partout** — aucune forme organique, aucune bulle très arrondie.
---
## 2. TOKENS — PRÊTS À L'EMPLOI
### 2.1 Couleurs (mesurées 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 : décorations et sémantique) ── */
--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);
}
```
Règles d'usage (mesurées, à faire respecter) :
- Texte secondaire = blanc crème à 7085 % d'opacité, **pas** une couleur dédiée.
- Filets = blanc à 10 %, jamais de gris plein.
- Rouge = signaler · Vert = valider · Or = agir. Ne jamais permuter ces rôles.
### 2.2 Typographie
```css
:root {
--font-display: 'Archivo', 'Arial Black', sans-serif; /* 600900, TITRES + BOUTONS */
--font-body: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; /* 400800 */
}
```
- Titres h1h4 : Archivo, **MAJUSCULES**, `letter-spacing: -0.01em`.
- Boutons : Archivo 700, uppercase, `letter-spacing: 0.03em`, bordure 2 px.
- Corps : Inter, `line-height: 1.5`. Graisses minimales : 400 body / 600 display (lisibilité écrans bas de gamme).
- **Self-hébergement obligatoire** : woff2 dans `fonts/` + `fonts/fonts.css`, `font-display: swap`. Aucun appel Google Fonts (contradiction avec la doctrine souveraineté — bug en cours de correction sur o-k-i.net).
### 2.3 Formes, espacements, layout
```css
:root {
--radius-sm: 3px; /* boutons, tags, badges */
--radius-md: 6px; /* cartes — jamais plus */
--border-card: 1px solid var(--line);
--border-btn: 2px solid var(--or-oki);
--space-1: 0.75rem; --space-2: 1rem; --space-3: 1.5rem; --space-4: 2rem; --space-5: 4rem;
--container: 1200px;
}
```
- Sections : `padding: var(--space-5) var(--space-4)` · Grille : `repeat(auto-fit, minmax(300px, 1fr))`.
- **Carte OKI canonique** : fond blanc 3 %, liseré gauche 4 px d'accent (vert ou or), survol `translateY(-2px à -4px)` + bordure d'accent.
- **Tag OKI canonique** : fond or 8 % + bordure or 1 px, rayon 3 px.
### 2.4 La flag-bar — signature n°1
Bandeau **6 px, 4 segments francs** : noir 025 % · or 2550 % · vert 5075 % · rouge 75100 %. Arrêts nets, **jamais de dégradé**.
```css
.flag-bar {
height: 6px;
background: linear-gradient(to right,
var(--noir-oki) 0 25%, var(--or-oki) 25% 50%,
var(--vert-oki) 50% 75%, var(--rouge-oki) 75% 100%);
}
```
Placement : bord inférieur de la navigation fixe ; peut ponctuer un héros ou un pied de page. **Une occurrence visible par écran maximum.** C'est l'élément le plus reconnaissable de la marque — le diluer, c'est le tuer.
### 2.5 Logotype
Monogramme « OKI » : arche or, point vert, accent rouge sur noir. Fichier canonique `logos/favicon-1.png` (512×512, transparent — aussi og:image et apple-touch-icon) ; déclinaisons `logos/favicon-0.ico`, `assets/maskable_oki.png`. Les `logos/header-img-*` sont des marques de **partenaires** (Joukawouvè, Aktivist Vybz, cyber-mawonaj, KA UBUNTU) : provenance conservée, **ne jamais les utiliser comme logo OKI**.
---
## 3. MOTION OKI — LES CADENCES GWOKA
La marque ne bouge pas comme une startup : elle pulse comme un ka. Tokens obligatoires dans tout projet OKI animé :
```css
:root {
--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 */
}
```
Règles de motion :
1. **Le stagger de marque OKI est syncopé (3+3+2)**, pas linéaire : délais `[0, 120, 300, 360, 600, 660, 900, 960]` ms pour 8 éléments. C'est subtil — c'est le but : le rythme est une identité, pas un effet.
2. Uniquement `transform` + `opacity` (recette P3/budgets).
3. `prefers-reduced-motion` : gate unique au provider, contenu statique complet (le site actuel n'a **aucune** garde — à corriger partout).
4. Durées toujours lues depuis les tokens, jamais hardcodées.
---
## 4. ICONOGRAPHIE — LA RÈGLE « ZÉRO EMOJI EN PRODUCTION »
Le site actuel utilise des emojis (✊🏿🔓🤝🏿🛡️) : rendu dépendant de l'OS, incohérent avec la précision du drapeau. **Tout projet harmonisé remplace les emojis par le set SVG OKI.**
Spécifications du set :
- ViewBox 24×24, **stroke 2 px**, angles nets (pas de courbes organiques), une seule couleur : `currentColor` (or par défaut sur noir).
- Remplissage réservé aux pictogrammes ≤ 16 px.
- Grille de nommage en KA : les fichiers portent des noms kréyòl.
Set initial à produire (correspondances avec l'existant) :
| Fichier | Sujet | Remplace / usage |
|---|---|---|
| `ka.svg` | tambour ka | icône maîtresse, 404, loader |
| `lambi.svg` | conque lambi | communication, annonces, « bòkaz » |
| `zetwal.svg` | étoile 4 branches | navigation, favoris, instances fédivers |
| `mawon.svg` | silhouette/brisure de chaîne stylisée | souveraineté, valeurs |
| `lakanmou.svg` | flamme | engagement, dons ponctuels |
| `jaden.svg` | pousse/jardin | solidarité, lakou, projets |
| `kannen.svg` | canne à sucre stylisée | patrimoine, histoire |
| `lanme.svg` | vague | océan, liens internationaux, Réunion↔Gwada |
| `glo.svg` | poing levé géométrique | ✊🏿 — luttes, tarifs solidaires |
| `kle.svg` | clé/cadenas ouvert | 🔓 — liberté, open-source |
| `pawol.svg` | bulle de parole angulaire | langues, traduction, PAWÒL-NU |
| `mizik-note.svg` | note de musique | MIZIK, DJANGOKAM |
Chaque picto livré en `<symbol>` dans un sprite `icons.svg` + usage `<svg><use href="icons.svg#ka"/></svg>` (une seule requête, stylable en CSS).
---
## 5. VOIX & TON (canonique, reprise du BRAND.md)
Vouvoiement direct et empathique, phrases courtes. On parle d'abord de la cause du visiteur — « Vous luttez, vous organisez, vous mobilisez » — puis de l'outil. La technique sert la lutte, jamais l'inverse.
- **À dire :** sur mesure · tarifs adaptés à vos moyens · organisations engagées · transition numérique · écosystème numérique libre · à la hauteur.
- **À éviter :** « template générique », jargon corporate/marketing, « clé en main », startup-speak.
- Piliers : ORGANISATION KA · INTERNATIONALE · Des solutions pour chaque besoin · Le budget ne doit jamais être un frein.
- **Langues :** FR par défaut ; le KA (kréyòl) en signature, titres de sections symboliques et microcopy (404, loader, remerciements) ; EN en version dédiée. Attribut `lang` correct à chaque bascule.
## 6. IMAGERIE (canonique)
Illustration numérique afrofuturiste caribéenne : scènes au coucher de soleil, personnages caribéens, pochettes musicales typographiées. Couleurs chaudes saturées sur fonds sombres. **Jamais** de photo de banque d'images corporate, jamais d'illustration générique de startup, jamais d'icône ou de logo en guise d'image. Formats : AVIF/WebP, self-hébergés, `loading="lazy"` hors LCP.
---
## 7. MATRICE D'APPLICATION PAR PROJET
Harmoniser ≠ tout refaire : chaque plateforme a un canal de personnalisation officiel. N'UTILISER QUE ces canaux (jamais de fork pour du style).
| Projet | Logiciel | Canal de theming | Application OKI |
|---|---|---|---|
| **o-k-i.net** | SvelteKit (migration en cours) | Natif | Référence absolue : tokens §2, flag-bar, village SVG, KineticText gwoka |
| **BOKANTE** | Mastodon | CSS personnalisé admin + `custom.css` | Variables Mastodon remappées sur tokens OKI : fond `--noir-oki`, accent `--or-oki`, boutons Archivo uppercase ; logo colonne = monogramme OKI ; flag-bar en haut de la colonne de composition |
| **GADE** | PeerTube | Thème CSS admin (champ « CSS personnalisé ») | Mêmes remappages ; player : contrôles or sur noir ; page À-propos = texte marque (« vidéos sans algorithme, dédiées aux cultures afro-diasporiques ») |
| **NIYAJ** | Nextcloud | App **Theming** officielle | Couleur primaire `#FDB813`, fond sombre, logo + slogan « Votre cloud, vos données » ; favicon OKI |
| **MIZIK** | Funkwhale | `custom.css` admin | Fond `--noir-oki`, accents or, player accentué or ; typographie : Inter si surchargeable, sinon système |
| **KUTE** | App PHP maison | Natif | Appliquer les tokens §2 en entier — c'est un projet first-party, aucune excuse de ne pas être au niveau d'o-k-i.net |
| **LABOLA** | Forgejo/Gitea | `custom/templates` + `custom/public/css` | Thème sombre OKI, accent or ; logo forge = monogramme ; **ne pas toucher aux templates Go au-delà du header/footer** (fragile aux upgrades) |
| **PAWÒL-NU / djangokam.pawol.nu / nyyoka.pawol.nu** | Selon stack | Natif | Charte complète + liberté artistique accrue (pochettes, kinetic type poussé) : c'est la vitrine culturelle, le motion peut y être plus riche qu'ailleurs |
| **fediverse.o-k-i.net** | Statique | Natif | **Constellation zétwal** : chaque instance = étoile SVG reliée, hover/focus = éclat or + label ; DOM/SVG léger uniquement |
### Le footer fédéré commun (tous les sous-domaines)
```
[monogramme OKI] Un service libre opéré par ORGANISATION KA INTERNATIONALE · o-k-i.net
```
+ flag-bar 6 px au-dessus du footer. Une seule ligne, `--muted`, lien or. C'est le fil rouge qui fait l'écosystème.
### Règles cross-projets
1. **Un domaine, une famille** : `*.o-k-i.net` = écosystème fédivers et outils ; `pawol.nu` = projets culturels/musicaux. Ne pas mélanger.
2. Chaque service porte un **nom KA + le nom du logiciel en sous-titre** (pattern existant : « BOKANTE — Mastodon », « NIYAJ — Nextcloud »). Ne jamais masquer le logiciel sous-jacent : la transparence est un pilier.
3. Favicon = monogramme OKI partout (déclinaisons ico/png/maskable).
4. og:image par service : monogramme sur `--noir-oki` + nom du service en Archivo or.
5. Toute nouvelle page statique du réseau démarre du template o-k-i.net (tokens inclus), jamais de zéro.
---
## 8. CHECKLIST D'HARMONISATION D'UN PROJET (DoD marque)
- [ ] Thème sombre par défaut, clair opt-in contrasté AA.
- [ ] Tokens §2 importés (pas de valeur recopiée à la main — un fichier `oki-tokens.css` partagé).
- [ ] Or = seule couleur d'action ; rouge/vert dans leurs rôles sémantiques.
- [ ] Archivo/Inter self-hébergées, zéro fonte externe.
- [ ] Flag-bar présente, segments francs, ≤ 1 par écran.
- [ ] Zéro emoji en interface (set SVG §4).
- [ ] Tokens motion §3 + gate `prefers-reduced-motion`.
- [ ] Footer fédéré + monogramme + favicon OKI.
- [ ] Voix conforme §5 (test : aucune occurrence de « clé en main », « solution innovante », « disruptive »).
- [ ] Zéro requête tierce au chargement (fonts, CDN, analytics) — vérifié onglet réseau.
- [ ] Nom KA + logiciel en sous-titre visible en page d'accueil du service.
@@ -0,0 +1,494 @@
# 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.
@@ -0,0 +1,79 @@
# PROMPT KIMI CLI — Transformation de o-k-i.net en SvelteKit
> **Mode d'emploi :** copie l'intégralité du bloc ci-dessous (entre les lignes `---`) dans Kimi CLI, en joignant les deux documents de référence : `recette-sveltekit-playbook-agent.md` (la méthode) et `charte-oki-design-system.md` (la marque). Ajuste la section « Périmètre de cette session » selon ce que tu veux faire en une passe.
---
# MISSION : Migrer o-k-i.net vers SvelteKit selon la recette et la charte OKI
Tu es un agent de code senior. Ta mission : reconstruire le site **o-k-i.net** (ORGANISATION KA INTERNATIONALE) en SvelteKit, en suivant **à la lettre** le playbook `recette-sveltekit-playbook-agent.md` (méthode, budgets, DoD) et la charte `charte-oki-design-system.md` (couleurs, typo, voix, iconographie — elle prime pour tout ce qui est marque).
## 1. Contexte du site existant (audit réel, juillet 2026)
**Identité :** association tech militante guadeloupéenne — « Le numérique au service de vos luttes ». Applis web sur mesure à tarifs solidaires pour associations, collectifs et organisations militantes. Positionnement souveraineté numérique : indépendance des GAFAM, logiciels libres, hébergement éthique. L'identité visuelle est afrofuturiste caribéenne : drapeau panafricain (noir/or/vert/rouge), **thème sombre par défaut**.
**Écosystème lié (liens externes à préserver, ne pas migrer) :**
- bokante.o-k-i.net → instance Mastodon
- gade.o-k-i.net → instance PeerTube
- mizik.o-k-i.net → instance Funkwhale
- niyaj.o-k-i.net → Nextcloud
- kute.o-k-i.net → application PHP maison
- labola.o-k-i.net → forge (code source public, dont celui du site)
- fediverse.o-k-i.net → portail fédivers
- pawol.nu + djangokam.pawol.nu + nyyoka.pawol.nu → paroles/traductions/karaokés (projet DJANGOKAM, artiste IA caribéen)
**Stack actuelle mesurée :** HTML statique + CSS vanilla (~44 KB) + 3 petits JS vanilla (main.js, theme.js, lang-redirect.js). Hébergement o2switch (mutualisé FR). Images via BunnyCDN (`organisationka.b-cdn.net`). Headers de sécurité exemplaires à **conserver à l'identique ou renforcer** : CSP `default-src 'self'` (+ style 'unsafe-inline' + fonts.googleapis.com), HSTS, `X-Frame-Options: DENY`, `X-Content-Type-Options: nosniff`, `Referrer-Policy: strict-origin-when-cross-origin`, `Permissions-Policy` restrictive.
**Bugs et défauts de production confirmés (à corriger dans la migration) :**
1. **CRITIQUE — Toutes les images renvoient 403** (pull zone BunnyCDN cassée) : logo header, logos partenaires, portrait DJANGOKAM, pochettes, logos clients, og:image. Décision cible : **self-héberger toutes les images** dans `static/images/` (AVIF/WebP générés au build via vite-imagetools), supprimer toute dépendance à BunnyCDN, retirer `https://organisationka.b-cdn.net` de la CSP.
2. **Google Fonts** chargé (fonts.googleapis.com + gstatic.com) alors que le site revendique l'indépendance des GAFAM → self-héberger Archivo (600-900) et Inter (400-800) en woff2, retirer les deux domaines de la CSP.
3. Aucun `prefers-reduced-motion` nulle part.
4. Iconographie 100 % emoji (✊🏿🔓🤝🏿🛡️💼🏛️) → set SVG conforme à la charte (angles nets, stroke 2 px, or sur noir).
5. Les 3 cards « publics » (Militants / Associations & Collectifs / Entreprises) apparaissent en double dans la page → une seule occurrence.
6. Lazy-loading d'images partiel (4/~14) → systématique hors LCP.
**Contenu éditorial (conserver tel quel, sauf mention contraire) :** one-page FR à ancres — Hero (« ORGANISATION KA INTERNATIONALE », 3 badges, 2 CTA) · Solutions ×3 publics · Projets web · Partenaires (Joukawouvè, Aktivist Vybz #AKV, cyber-mawonaj) · DJANGOKAM (artiste IA caribéen, lore Wanakaera/Karukera, liens pawol.nu, plateformes musicales, presse) · Mission + 4 valeurs (Engagement, Liberté, Solidarité, Souveraineté) · Développement web sur mesure · Clients (KA UBUNTU kaubuntu.com, KA UBUNTU Hub kaubuntu.re PWA, KBM kbm.gp) · Services hébergés (BOKANTE, GADE, NIYAJ, etc.) · FAQ « Qu'est-ce que le Ka ? » · CTA final + contact (kontak@o-k-i.net, WhatsApp, Telegram, Discord, forge) · footer avec mentions légales. Pages satellites : `/dons/` (Stripe + Liberapay), `/en/` (version anglaise complète), `/mentions-legales/`.
## 2. Exigences techniques
- **SvelteKit + Svelte 5 runes + TypeScript**, adapter-static (prerender intégral) — le site doit pouvoir être servi depuis o2switch en statique ou Cloudflare Pages, sans serveur Node.
- **i18n FR/EN** : FR par défaut, EN sur `/en/` comme actuellement ; attributs `lang` corrects ; pas de lib lourde si un routage par dossier suffit.
- **Thème sombre par défaut** (identité de marque), thème clair en opt-in persisté en localStorage, appliqué sans FOUC via snippet dans `app.html`.
- **Conserver toutes les URLs** existantes (/, /dons/, /en/, /mentions-legales/, ancres #solutions #projets #partenaires #djangokam #hebergement #contact).
- **Headers de sécurité** : reproduire la CSP et les headers actuels (via `_headers` Cloudflare ou doc d'htaccess o2switch), en retirant les domaines devenus inutiles (Google Fonts, BunnyCDN).
- **PWA légère** : manifest + service worker de cache du shell (vite-plugin-pwa) — le site doit rester lisible hors-ligne.
- OpenGraph complet (10 balises actuelles) reconduit, og:image self-hébergée.
- Budgets du playbook §4 : JS initial ≤ 170 KB, page d'accueil ≤ 2 MB, Lighthouse mobile ≥ 90/95/95/95.
## 3. Motion & composants (sélection depuis la bibliothèque du playbook §5)
Applique la sélection suivante — pas plus, le site doit rester sobre :
1. **KineticText** sur les titres de sections (Archivo uppercase), split par mots, stagger piloté par les tokens gwoka de la charte (`--dur-tanbou` 120 ms de base, pattern syncopé), scrub natif `animation-timeline: view()` avec fallback ScrollTrigger.
2. **ScrollProgressBar** en haut de page, gradient or→vert de la charte.
3. **PageTransition** légère (View Transitions + fallback), durée `--dur-mesure` 480 ms.
4. **Village écosystème** : remplacer la grille de cards « Services hébergés » par une scène SVG isométrique « village créole » (case à ka = BOKANTE, karbay = GADE, silo nuage = NIYAJ, scène de musique = MIZIK…), chaque bâtiment = lien vers l'instance, hover/focus = liseré or + label. **DOM/SVG uniquement, aucun WebGL** (P3 du playbook). Fallback : grille de cards accessible pour reduced-motion/mobile étroit.
5. **Flag-bar** (signature marque, 4 segments francs 6 px) : sous la nav fixe + en haut du footer, jamais plus d'une occurrence visible par écran.
6. **404 designé** en KA (texte créole + ka/tambour SVG), page hors-ligne PWA assortie.
7. CursorTracker : **ne pas installer** (audience mobile-dominante, ROI faible).
8. WebGL : **aucun** sur cette migration.
## 4. Périmètre de cette session
- [ ] Scaffold SvelteKit + tokens charte + thème sombre/clair + fonts self-hébergées
- [ ] Migration de la home complète (toutes sections, contenu FR repris à l'identique)
- [ ] Pages /dons, /mentions-legales, /en (structure, contenu EN repris)
- [ ] Composants motion §3 (KineticText, ScrollProgressBar, PageTransition, village SVG)
- [ ] 404 + page hors-ligne + manifest PWA
- [ ] Headers sécurité + _headers + doc déploiement (o2switch statique ET Cloudflare Pages)
- [ ] Rapport final : Lighthouse, poids par page, écarts éventuels avec ce brief
## 5. Critères d'acceptation
- La DoD du playbook §8 est intégralement vérifiée.
- Zéro requête vers un domaine tiers au chargement (vérifier l'onglet réseau) : ni Google, ni BunnyCDN, ni aucun CDN.
- Toutes les images présentes et optimisées (plus aucun 403, plus aucune image > 200 KB hors hero).
- Le contenu éditorial FR est repris à l'identique (aucune réécriture non demandée), hormis la déduplication des cards « publics ».
- La nav au clavier fonctionne sur tout le village SVG (chaque bâtiment focusable, label lisible).
- `npm run build && npm run preview` : zéro erreur, zéro warning a11y.
Commence par la Phase 1 du playbook (audit de confirmation du dépôt actuel) et présente-moi le plan avant d'écrire le moindre composant.
@@ -0,0 +1,908 @@
# 15 Award Sites → The SvelteKit Recipe
### An autopsy of Awwwards-grade websites + an actionable implementation guide for Cyber-mawonaj
> **Method note.** Stack claims are grounded in official case studies, Awwwards entries and technical write-ups wherever they exist (footnotes at the end of the document). Where no public source exists (marked *inference*), the analysis is based on observable rendering behavior and the studio's known toolchain. Every "stealable pattern" is something you can extract without the original codebase.
---
# PART 1 — SITE-BY-SITE ANALYSIS
---
## 1. unseen.co — Unseen Studio (Bristol, UK)
Unseen Studio is the rebrand of Green Chameleon, a studio with 35 SOTDs; their own site won SOTD at launch [^38^][^39^].
**[A] Visual language.** Dark, atmospheric base with volumetric lighting as the signature — their designers publish "volumetric lighting studies" as R&D, and that light-fog sensibility pervades the site [^39^]. Palette: near-black grounds, soft chromatic blooms (rainbow-adjacent volumetric glows), restrained UI chrome. Typography is large, confident grotesque with generous tracking; type never competes with the 3D — it floats above it. Composition is free-form asymmetry around a central 3D stage.
**[B] Motion.** Physics-feeling, inertia-heavy. Real-time WebGL particle simulations are an in-house specialty (they teased a "real-time WebGL particle sim" before project launches) [^39^]. Scroll acts as a camera dolly through project worlds; hover states distort or illuminate the medium rather than underline links. Cursor is part of the lighting rig.
**[C] Tech stack.** WebGL/Three.js confirmed by their own hiring and project tags (#webgl #threejs) [^39^]. Custom shader work for volumetrics and particles; DOM reserved for text/UI. Performance strategy: the studio iterates "directly in the browser" with live shader reloading.
**[D] UX.** Scroll-driven portfolio-as-journey. Navigation is minimal; the work *is* the menu. Accessibility trade-off: heavy reliance on pointer + WebGL, text contrast sometimes sacrificed to atmosphere. Mobile gets a simplified but genuine version, not a broken one.
**[E] Critical assessment.**
-**Strengths:** (1) volumetric light identity — instantly recognizable; (2) particle/cursor integration makes the UI feel alive; (3) brand confidence: the site demonstrates the product instead of describing it.
- ⚠️ **Weaknesses:** (1) WebGL-first means a hard floor for low-end devices; (2) atmosphere-over-contrast hurts readability on cheap screens.
- 🔓 **Stealable pattern:** **the cursor-as-light-source** — pass pointer position as a uniform to your scene shader so the user literally illuminates content. One uniform, huge perceived interactivity.
---
## 2. animejs.com — Anime.js v4 (Julian Garnier)
The v4 relaunch site (2025) went viral in r/webdev as "just incredible" — the 3D hero scene is Three.js, everything else is animated with the library itself as a living demo [^14^].
**[A] Visual language.** Deep-space dark UI, electric accent colors, dot-grid/blueprint textures referencing engineering drawing. Typography: technical sans with tabular numerals, kinetic type everywhere — the docs headlines themselves animate. Atmosphere: glassy panels + grain, "lab instrument" aesthetic.
**[B] Motion.** The philosophy *is* the product: timeline-orchestrated, stagger-driven, spring-eased. The v4 API is modular — `animate`, `createTimeline`, `createScope`, `createSpring`, `stagger`, `splitText`, `createDraggable` are separate named imports [^5^]. Scroll Observer API syncs animations to scroll with multiple sync modes and thresholds [^12^]. Micro-interactions: every parameter knob in the docs is draggable and live-updates the demo.
**[C] Tech stack.** Three.js for the hero scene; Anime.js v4 for all DOM/SVG animation [^14^]. Tree-shakeable ESM modules — you import only `animejs/animation`, `animejs/timeline`, etc. [^5^]. This modularity is itself a lesson: ship ≤ what you use.
**[D] UX.** Documentation as experience: interactive playground embedded in the narrative. Accessibility: motion-heavy but each demo is self-contained; reduced-motion handling is left to the implementer (a gap).
**[E] Critical assessment.**
-**Strengths:** (1) product demonstrates itself — zero stock imagery; (2) interactive docs collapse learning time; (3) spring physics defaults feel modern vs. GSAP's classic easings.
- ⚠️ **Weaknesses:** (1) animation overload can fatigue on long doc sessions; (2) no first-party reduced-motion story.
- 🔓 **Stealable pattern:** **`createScope` per component** — scope animation queries to a component root and `revert()` on cleanup. Maps 1:1 to Svelte's component lifecycle (see §6).
---
## 3. immersive-g.com — Immersive Garden (Paris)
3× Awwwards Studio of the Year, 90+ awards; the 2025 rebuild won SOTM and is fully documented in an official case study [^24^][^27^].
**[A] Visual language.** Minimalist-yet-atmospheric: bas-relief 3D compositions of natural elements, Roman numerals rendered in 3D as menu anchors, muted stone/bone palettes with deep shadows [^27^]. It's "digital craftsmanship" as brand — tactile, sculptural, almost geological.
**[B] Motion.** Restrained and ceremonial. GSAP for transitions, **Lenis for scroll** [^27^]. Nothing bounces; everything glides. The wow is in scene transitions and reveal choreography, not jitter.
**[C] Tech stack (confirmed):** Three.js + Blender/Houdini/ZBrush asset pipeline; **Vue/Nuxt frontend; GSAP + Lenis; Strapi + Node backend; Vercel deployment** [^27^]. Performance strategy is the headline: **server-side KTX GPU texture compression, channel-packed textures, gltf-transform automated exports** with custom Blender/JS scripts [^27^].
**[D] UX.** One-click access navigation doctrine: any case study reachable in a single interaction. The **"Backstage" section** — behind-the-scenes technical breakdowns per project — is a hidden-gem IA pattern that turns process into content [^27^].
**[E] Critical assessment.**
-**Strengths:** (1) compression pipeline is industry best practice (KTX + channel packing); (2) Backstage = transparency as marketing; (3) minimalism with one strong sculptural motif instead of ten effects.
- ⚠️ **Weaknesses:** (1) Strapi + Node backend adds hosting weight you'd feel on a 50 €/mo budget; (2) realism-first 3D = large asset payloads even compressed.
- 🔓 **Stealable pattern:** **the Backstage section** — document your process publicly per project. For Cyber-mawonaj this doubles as pedagogical content (open knowledge = sovereignty).
---
## 4. davidwhyte.com/experience — David Whyte Experience (by Immersive Garden)
SOTM Dec 2024; official case study available [^17^][^20^].
**[A] Visual language.** Watercolor paintings by Matthew Phinn as the entire visual substrate — organic pigment bleeds, paper texture, muted landscape hues. Typography is literary serif, quiet. This is the anti-neon site: proof that "immersive" doesn't mean cyber.
**[B] Motion.** Two hero techniques: a **cursor-driven watercolor fluid simulation** — one fluid sim per visible painting, optimized via a **simulation atlas + stencil buffer** so only active paper regions compute [^20^]; and a **generative watercolor reveal** — noise-layered droplets dissolving the painting into view, with **noise baked in advance** to avoid real-time cost [^20^]. Long-press reveals video of the real location.
**[C] Tech stack (confirmed):** Nuxt/Vue, Sass + BEM, Three.js (Blender-driven camera recreated in WebGL, custom shaders), GSAP, Lenis, WordPress CMS, Vercel [^20^].
**[D] UX.** Scroll through landscapes; each poem gets full-screen focus. Long-press is a deliberate slow-interaction — forces contemplation, matches the content's tempo. Accessibility: text remains real DOM text (good); the fluid sim is decorative-only (also good).
**[E] Critical assessment.**
-**Strengths:** (1) baked-noise reveal = generative feel at zero runtime cost; (2) stencil-buffer atlas is a genuinely clever perf pattern; (3) motion tempo matched to content (slow poetry, slow interaction).
- ⚠️ **Weaknesses:** (1) fluid sim still taxes low-end GPUs; (2) long-press is undiscoverable without onboarding hints.
- 🔓 **Stealable pattern:** **bake your noise** — precompute generative textures offline (Blender/Python/ComfyUI for you), ship them as KTX2, blend in shader. Generative look, static-asset perf. Perfect fit for your ComfyUI pipeline.
---
## 5. kriss.ai/home/aftercare — Kriss.ai (by Studio 28K)
Awwwards case study available [^28^].
**[A] Visual language.** Deliberate rejection of "cold AI tech" aesthetics: **70s-inspired palette, organic shapes, soft warm tones** — a dental AI presented as a human service [^28^]. The whole site is a "Dollhouse": an open 3D cutaway of a dental clinic where each room (front desk, surgery, aftercare) is a content section.
**[B] Motion.** Spatial navigation through the dollhouse: camera moves between rooms on scroll/click. Motion serves wayfinding — you always know where you are in the building, hence in the offer.
**[C] Tech stack (confirmed):** WebGL 3D built in Blender; **deployed on Cloudflare Pages + R2 for media** — a notable choice: heavy visual assets on an edge CDN, zero traditional backend [^28^]. Figma → Blender → VSCode pipeline.
**[D] UX.** Metaphor-driven IA: product features mapped to physical rooms. This collapses the learning curve for non-technical buyers (dentists) — the spatial metaphor carries the explanation.
**[E] Critical assessment.**
-**Strengths:** (1) narrative pivot "from tech to human" executed in the medium itself; (2) dollhouse = site map you can see; (3) **Cloudflare Pages/R2 architecture proves you can host heavy WebGL at near-zero backend cost** — directly relevant to your budget.
- ⚠️ **Weaknesses:** (1) the 3D metaphor forces all content into rooms — awkward for dense text; (2) 70s warmth risks reading as dated outside the US market.
- 🔓 **Stealable pattern:** **the dollhouse IA** — map AKILPA's or Cyber-mawonaj's sections to physical Caribbean spaces (case à ka, marché, karbay, plage). Navigation becomes cultural storytelling. Free pattern, no shaders required.
---
## 6. zentry.com — Zentry (by Resn)
Full Awwwards case study available — and the tech decision is the most instructive of all 15 sites [^6^].
**[A] Visual language.** Bold, vibrant, "evil-corporation-playful": saturated color fields, chunky display type, 3D portal masks as signature motif. Sound-designed: dynamic soundtrack evolving from upbeat to ambient as you explore [^6^].
**[B] Motion.** Motion system designed **before** the website: every reaction, curve and principle specced upfront [^6^]. Flashy, fast, reactive — but (see below) done entirely in DOM.
**[C] Tech stack (confirmed):** **Nuxt/Vue + Vite, proprietary utility framework — and deliberately NO WebGL.** The team needed massive DOM throughput (text, dynamic media) and chose to build the 3D portal-mask effects as **custom DOM/CSS solutions instead of Three.js**, explicitly to minimize complexity and maximize DOM performance [^6^].
**[D] UX.** Portal motifs as content reveals; sound as orientation layer. The lesson: "immersive" is a design outcome, not a rendering technology.
**[E] Critical assessment.**
-**Strengths:** (1) **proof that award-grade "3D" can be pure DOM** — transform3d, clip-path, masks, perspective; (2) motion-first design process (curves before comps); (3) sound design treated as first-class UX.
- ⚠️ **Weaknesses:** (1) proprietary framework = not reproducible as-is; (2) audio autoplay remains an accessibility/UX minefield.
- 🔓 **Stealable pattern:** **DOM-only portal masks**`clip-path` + `transform-style: preserve-3d` + a GSAP scrub timeline gives you 80% of the Zentry wow at 5% of the GPU cost. **This is your #1 pattern for 4G/low-end Gwada devices.**
---
## 7. igloo.inc — Igloo Inc (by Abeto + Bureaux)
SOTD Jul 2024 (7.92/10); unusually detailed case study + WebGPU showcase write-up [^3^][^7^][^1^]. **And it's built in Svelte — your stack.**
**[A] Visual language.** A frozen landscape: each portfolio project encased in a **procedurally generated ice block** (custom crystal-growth algorithm inside container shapes — unique every time, scales without manual modeling) [^1^]. Chromatic aberration + frost-dissolve scene transitions; intro sequence "between sci-fi title crawl and luxury brand ad" [^1^].
**[B] Motion.** Camera drifts between scenes; footer is an interactive particle simulation that coalesces into different 3D shapes per hovered link, **color shifting with particle velocity** [^1^]. Text glitches via shaders; **letter-scramble swaps SDF texture offsets instead of forcing DOM relayout** — a performance decision doubling as a creative one [^1^].
**[C] Tech stack (confirmed):** **Three.js + Svelte + GSAP**, Houdini + Blender, plus proprietary tooling including a **custom VDB-to-browser exporter** compressing volume data smaller than a typical website image [^1^]. Entire UI rendered in WebGL. Workflow: grey-box previs animations, then iterate directly in browser with real-time shader/model reloading, continuously measuring on low-end devices [^7^].
**[D] UX.** Three sections only — the constraint forced a scroll experience dense with interaction [^7^]. Navigation = the journey itself.
**[E] Critical assessment.**
-**Strengths:** (1) **Svelte at the absolute top tier of the craft** — kills any "can Svelte do this?" doubt; (2) procedural assets scale content without artists; (3) SDF-text-in-shader eliminates DOM relayout cost.
- ⚠️ **Weaknesses:** (1) full-WebGL UI = screen-reader hostile, needs a parallel DOM layer; (2) proprietary VDB pipeline is not reproducible on a budget.
- 🔓 **Stealable pattern:** **velocity-reactive particles** — map particle/element color or size to pointer *velocity*, not just position. Trivially implementable in a Svelte action (`$derived` from delta of pointer positions), huge tactile payoff. Also: **SDF text rendering** if you go WebGL-UI (troika-three-text does this for free).
---
## 8. kprverse.com — KPR (by Resn)
SOTD Dec 2022 (7.98) + SOTM; official making-of available [^35^][^45^].
**[A] Visual language.** "New Eden" concept-art tableaux: hand-painted assets, bold concept-art style, terminal/console easter-egg aesthetics (fake file-system loading messages, "HTTPS://KPRVERSE.COM/KPCO/..." loader strings) [^41^]. Mixes 3D characters with 2D design elements, "interactive motion revealing a hidden dimension" [^45^].
**[B] Motion.** **Click-and-hold** as the signature: holding reveals a second interactive layer (geography, factions, lifestyle of the world) [^45^]. Scroll moves through parallax tableaux (the Keep, the Factions, the World). Loading is theatrical: console-style progress theater.
**[C] Tech stack.** Resn proprietary (Nuxt-era Resn = Vue-based, per their Zentry write-up [^6^]); layered 2D/3D compositing rather than full 3D scenes.
**[D] UX.** Narrative gating: lore revealed in layers, community invited to "shape the story". Notably **hard device gating**: "Your Browser resolution is currently not supported" and portrait-only enforcement on mobile [^41^] — a controversial but deliberate trade.
**[E] Critical assessment.**
-**Strengths:** (1) click-and-hold "second layer" is a brilliant lore-delivery mechanic; (2) loader-as-fiction (terminal theater) turns wait time into worldbuilding; (3) tableaux = content with room for community contribution.
- ⚠️ **Weaknesses:** (1) **resolution gating is hostile** — for Gwada's mobile-dominant reality this pattern is toxic; ship a degraded mode instead; (2) hand-painted asset volume = production cost.
- 🔓 **Stealable pattern:** **click-and-hold reveal layer** — pointer-down starts a GSAP timeline that cross-fades a hidden info stratum; release reverses it. One timeline, two states, infinite narrative uses (patrimoine layers: colonial map ↔ créole map).
---
## 9. hape.io — HAPE (by Dogstudio/DEPT for Digimental)
SOTD Apr 2022 (7.84, dev award animations 9.4/10); official case study available [^36^][^37^][^40^].
**[A] Visual language.** High-fashion-magazine web design: black/gold footer palette, editorial grids, the 3D ape treated like a couture model [^36^][^37^].
**[B] Motion.** **The HAPE walk is the scroll**: page opens zoomed on the ape's head; scrolling rotates the camera and dollies out to reveal the full strutting character — a walk cycle art-directed for "attitude" and **synchronized to a handpicked soundtrack** [^40^]. A "hapewalk mode" hands rotation/zoom control to the user [^40^].
**[C] Tech stack (confirmed):** **custom OGL micro-framework** — OGL (minimal-abstraction WebGL library) extended in-house with PBR rendering, skinning, post-processing and an animation player [^37^]. Built in 4 weeks [^37^].
**[D] UX.** One character, one gesture (scroll), one attitude — radical focus. Results: 57K visitors launch month, 4.58 min average session [^37^].
**[E] Critical assessment.**
-**Strengths:** (1) scroll-synced skeletal animation with music sync is a masterclass in single-idea execution; (2) OGL proves you don't need Three.js if your needs are narrow; (3) fashion-editorial framing elevates NFT kitsch to design.
- ⚠️ **Weaknesses:** (1) skinned 3D + PBR is heavy for low-end mobile; (2) audio-synced motion degrades silently when sound is off/blocked.
- 🔓 **Stealable pattern:** **scroll-scrubbed skeletal camera** — bind `scrollProgress` to a baked animation clip's time (`mixer.setTime(progress * duration)`). Works with any GLTF walk/dance cycle. Imagine it with a **gwoka dancer** or a lambi procession: cultural motion as scroll engine.
---
## 10. labs.noomoagency.com — Noomo Labs
Developer Award + SOTD 2024; official behind-the-scenes available [^16^][^13^].
**[A] Visual language.** A dreamlike underwater journey with a glass jellyfish: 3D glass materials, refractive transparency, calm ambient sound design ("sound of the glass sphere appearing", music complementing the jellyfish) [^13^]. It's a hub for experiments — R&D made public.
**[B] Motion.** The technical doctrine: **all 3D assembled in ONE Blender scene; all non-interactive animation (camera, jellyfish, words) baked in Blender; materials baked to textures** [^13^]. Camera rigged to two Empties — one at the focal point, one animating the camera object [^13^]. Runtime = playback + interactive garnish.
**[C] Tech stack (confirmed):** Three.js + GSAP, **explicitly avoiding React Three Fiber "to maintain flexibility"**, Nuxt framework, Blender-baked assets [^13^]. The storytelling sister site scrolls a Three.js camera through five differently-lit "rooms" of case studies [^8^].
**[D] UX.** Labs = experiments before they "grow up" [^8^] — an institutional pattern: public R&D as marketing.
**[E] Critical assessment.**
-**Strengths:** (1) **bake-everything philosophy = cinematic quality at playback cost**; (2) one-scene assembly avoids runtime scene-graph juggling; (3) labs-as-content is a sustainable publishing model.
- ⚠️ **Weaknesses:** (1) baked = rigid; content changes require re-bakes; (2) glass/refraction shaders are expensive on Mali-class GPUs.
- 🔓 **Stealable pattern:** **the two-Empty camera rig** — in Blender, parent your camera to a target-Empty + path-Empty, bake, export GLTF, and in SvelteKit just scrub `cameraAnimation.time` from Lenis scroll progress. Zero camera math at runtime. Your ComfyUI/Blender pipeline already supports this.
---
## 11. noomoagency.com — Noomo Agency
Awwwards **Website of the Year** winner [^9^][^16^].
**[A] Visual language.** Glassmorphism at its most disciplined: glass cards on-scroll animations, 3D hover effects, a **dynamic logo incorporating handwritten elements from each team member** — personalization as brand system [^9^][^11^]. Boutique-agency warmth inside high-tech execution.
**[B] Motion.** "Every interaction and hover has its unique purpose" [^9^] — hover states are individually art-directed (button hovers, 3D case previews, showreel controls) [^11^]. Scroll animations drive case reveals; 3D transitions between pages.
**[C] Tech stack (confirmed):** **Nuxt 3 front AND back (Node), Three.js + GSAP** [^9^].
**[D] UX.** Classic agency IA (work/story/contact) elevated by transition choreography — proof you don't need experimental IA if execution is flawless.
**[E] Critical assessment.**
-**Strengths:** (1) hover-as-portfolio: micro-interactions individually designed, not defaulted; (2) humanizing details (handwritten logo) offset tech flex; (3) full-stack Nuxt keeps ops simple.
- ⚠️ **Weaknesses:** (1) glass everywhere = blur = GPU cost on weak devices; (2) 3D page transitions can delay content access.
- 🔓 **Stealable pattern:** **budgeted hover craft** — pick your 5 most-tapped elements and give each a unique, purposeful micro-interaction; leave the rest at system defaults. Award juries notice the ratio of intentional to generic interactions.
---
## 12. nudot.com.tw — Nudot (Taiwan)
*No public case study found — this section is inference from the live site and studio profile; treat stack claims as low-confidence.*
**[A] Visual language.** Taiwanese studio aesthetic in the lineage of Ultra Combos/Studio Pros: precise CJK+Latin bilingual typography, generous whitespace punctuated by abrupt kinetic moments, muted palette with one aggressive accent. Bilingual typesetting (Chinese vertical/horizontal + English) is itself the design flex.
**[B] Motion.** Text-led motion: character-level reveals, marquee systems, cursor-following media previews on project lists. Motion is typographic rather than 3D.
**[C] Tech stack (inference).** DOM/CSS + GSAP-class animation, canvas accents; likely Nuxt or similar Vue meta-framework (dominant in Taiwanese award studios).
**[D] UX.** Project-list-as-gallery with hover media previews; bilingual toggle as first-class nav item — relevant precedent for FR/KRÉ bilingual switching.
**[E] Critical assessment.**
-**Strengths:** (1) bilingual typography treated as design material, not i18n afterthought; (2) restraint — effects deployed sparingly; (3) fast: typography-led sites are inherently light.
- ⚠️ **Weaknesses:** (1) hover-previews have no touch equivalent (mobile gets a lesser list); (2) minimal feedback cues can confuse first-time visitors.
- 🔓 **Stealable pattern:** **bilingual kinetic type** — design your FR/KRÉ switch as an *animation event* (lines rotate/mask-swap in place) rather than a page reload. Language toggle becomes brand moment. Implement with `splitText` + a shared-layout transition.
---
## 13. 375.studio/en/studio — Studio375 (Vicenza, Italy)
SOTD Apr 30, 2026 (7.33, plus Developer Award) [^2^][^15^].
**[A] Visual language.** Two-color discipline (their earlier HM site was strict #000/#FFF) [^10^], clean-flat-minimal portfolio aesthetic with colorful punctuations [^10^]. Communications-agency clarity rather than tech-lab showing-off.
**[B] Motion.** Hero image animation, mouse interaction, scroll-linked text decoration, animated 404 — a **complete system of small moments** rather than one big WebGL bet [^10^]. Dev-award scores show the craft is in transitions (animations 7.8, their strongest dev metric) [^2^].
**[C] Tech stack (inference).** DOM-first, GSAP-class orchestration; no heavy WebGL signature. Accessibility dev-score 7.2 is above the award-site average [^2^] — suggests semantic care.
**[D] UX.** Conventional studio IA executed with transition polish; even the 404 is a designed moment [^10^].
**[E] Critical assessment.**
-**Strengths:** (1) proves SOTD is achievable without WebGL; (2) systematic micro-motion (every page state designed, incl. errors); (3) strong accessibility score for the category.
- ⚠️ **Weaknesses:** (1) less memorably distinctive than the WebGL cohort; (2) creativity scores (7.57) lag the experimental sites.
- 🔓 **Stealable pattern:** **design your error/empty/404 states with the same rigor as the hero.** For an association site (AKILPA), the 404, the "no events yet", the offline page are touchpoints where grassroots personality can outshine budget.
---
## 14. ten.375.studio/en — "Ten Years Away" (Studio375)
SOTD Jun 26, 2026 (7.32; users rated creativity 9.3) [^4^].
**[A] Visual language.** **An interactive comic** chronicling ten years of the studio — "real characters, real stories" [^4^]. Illustrated panels, comic-page composition translated to scroll, hand-drawn texture over digital precision.
**[B] Motion.** Mouse trail as a signature element, page transitions between comic chapters, intro sequence [^4^]. Motion serves panel-to-panel reading rhythm — scroll as page-turn.
**[C] Tech stack (inference).** DOM/SVG illustration + GSAP-class scrubbing; the comic format suggests asset sequencing rather than runtime 3D.
**[D] UX.** Narrative chronology as IA: ten years = ten chapters. Personal/historical storytelling instead of service listing — an anniversary site that earns attention through vulnerability (real people, real stories).
**[E] Critical assessment.**
-**Strengths:** (1) format courage — a comic, not a portfolio grid; (2) chaptered narrative maps perfectly to scroll; (3) authenticity as differentiator (users scored creativity 9.3, far above jury [^4^]).
- ⚠️ **Weaknesses:** (1) niche payoff — low utility for prospective clients; (2) illustrated asset production is time-expensive.
- 🔓 **Stealable pattern:** **the chaptered scroll-comic** — for AKILPA, a "10 dates du patrimoine gwada" or "l'histoire du lambi" as an interactive scroll-comic. Chapters = routes (`/histoire/[slug]`), panels = SVG/AI-generated assets (your Qwen/Seedream pipeline), transitions = shared-element morphs. High storytelling ROI per byte.
---
## 15. apple.com/airpods-pro — Apple
The canonical scroll-scrubbed product page; technique fully documented by CSS-Tricks and the GSAP community [^19^][^18^].
**[A] Visual language.** Product-photographic maximalism on pure black; type appears only when the product pauses. No texture tricks — the hardware render *is* the visual language.
**[B] Motion.** **Scroll-scrubbed image sequences**: a 148-frame JPEG flipbook drawn to a fixed `<canvas>`, frame index = scroll fraction × frame count [^19^]. Content sections are one big master timeline with percentage-hand-picked timings; "fake pins" hold content in place [^18^]. requestAnimationFrame for tear-free frame swaps [^19^].
**[C] Tech stack (documented technique).** Canvas 2D + image preloading; no WebGL needed [^19^]. **Performance doctrine: on slow connections Apple ships a single fallback image instead of the sequence** — and offers smaller sequences per device class [^19^].
**[D] UX.** Scroll = product film. Information hierarchy: specs arrive only after desire is built. Accessibility: canvas content is mirrored by real DOM text below.
**[E] Critical assessment.**
-**Strengths:** (1) the flipbook is the most copied award pattern alive — simple, robust, GPU-cheap; (2) **explicit slow-network fallback strategy** — exactly your 4G doctrine; (3) master-timeline percentage timing keeps everything in sync across viewports [^18^].
- ⚠️ **Weaknesses:** (1) 148 frames is a data hog without fallbacks; (2) canvas imagery needs parallel DOM for a11y/SEO.
- 🔓 **Stealable pattern:** **the scroll flipbook with connection-aware loading**`navigator.connection.effectiveType` decides: full sequence / reduced frame set / single hero image. Ship it as a Svelte action (see §5, bonus component). **This is the single most Gwada-appropriate award pattern in this list.**
---
---
# PART 2 — THE SVELTEKIT RECIPE
## 1. Core Principles (the common DNA)
**Principle 1 — Scroll is the primary navigation driver.** 13 of 15 sites bind narrative progression to scroll. Canonical stack: **Lenis + GSAP ScrollTrigger, wrapped as Svelte actions/context** (Immersive Garden ships exactly this combo [^20^][^27^]). In SvelteKit: one Lenis instance in the root layout, exposed via context; never instantiate per component.
**Principle 2 — Bake at build, not at runtime.** David Whyte bakes noise [^20^]; Noomo bakes camera, materials and character animation in Blender [^13^]; Immersive Garden compresses textures server-side to KTX [^27^]; Igloo compresses volumes to sub-image size [^1^]. Rule: **if a value doesn't depend on live user input, it has no business being computed in the browser.** Your ComfyUI/Blender pipeline is your "baking" layer — treat it as part of the build system.
**Principle 3 — DOM first, WebGL only where it pays.** Zentry did its award-winning "3D" portals in pure DOM deliberately [^6^]; Apple's flipbook is Canvas 2D [^19^]. Reserve WebGL for: particle systems, fluid sims, real 3D assets with user-controlled cameras. Everything else: CSS transforms, clip-path, SVG. **For 4G Guadeloupe this principle is non-negotiable.**
**Principle 4 — Motion is designed before pages.** Zentry specced its motion system before design started [^6^]; HAPE art-directed a walk cycle, then built a site around it [^40^]. Define your easing vocabulary, durations and rhythm (for you: gwoka cadences — see Part 3) as **design tokens**, before any component exists.
**Principle 5 — Every state is a designed moment.** Studio375's animated 404 [^10^], KPR's terminal-theater loader [^41^], Noomo's per-element hover craft [^9^]. Loading, error, empty, offline: these are where personality lives when budget doesn't.
---
## 2. SvelteKit Architecture Patterns
### 2.1 Route structure for immersive experiences
```
src/routes/
├── +layout.svelte ← SmoothScrollProvider (Lenis) + CursorTracker live HERE
├── +layout.ts ← export const ssr = true (default)
├── (site)/ ← layout group: classic content pages
│ ├── +page.svelte ← home (light hero, DOM effects)
│ ├── histoire/[slug]/ ← chaptered scroll-comic (Ten Years Away pattern)
│ └── patrimoine/[slug]/ ← case studies, "Backstage" pattern (Immersive Garden)
├── (immersive)/ ← layout group: canvas-heavy experiences
│ ├── +layout.svelte ← WebGLCanvas provider, ssr disabled per-page
│ └── mawonaj/+page.svelte
└── +error.svelte ← designed 404 (Principle 5)
```
Rules:
- **Layout groups** isolate the WebGL context to the routes that need it — the canvas provider never mounts on content pages.
- **Dynamic imports** for anything heavy: `const { WebGLCanvas } = await import('$lib/webgl/WebGLCanvas.svelte')` behind `{#await}` or `{#if browser}` gates.
- **Progressive enhancement**: every immersive route must render meaningful SSR HTML underneath the canvas (Apple mirrors its canvas with DOM text [^19^] — do the same).
### 2.2 Where WebGL lives
Use **Svelte context**, created once in the `(immersive)` layout:
```ts
// $lib/webgl/context.ts
import { setContext, getContext } from 'svelte';
import type * as THREE from 'three';
export interface WebGLContext {
renderer: THREE.WebGLRenderer;
scene: THREE.Scene;
camera: THREE.PerspectiveCamera;
canvas: HTMLCanvasElement;
}
const KEY = Symbol('webgl');
export const provideWebGL = (ctx: WebGLContext) => setContext(KEY, ctx);
export const useWebGL = () => getContext<WebGLContext>(KEY);
```
Child scenes register/unregister in `$effect` cleanup. **One renderer per app, many scenes.** Never two `<canvas>` WebGL contexts on one page for low-end targets.
For Three.js in Svelte, evaluate **Threlte** (the Svelte-native Three layer) for standard scenes, and raw Three.js for custom shader-heavy work (Igloo-style). Both coexist: Threlte components render into your single context.
### 2.3 Animation orchestration
| Need | Tool | Why |
|---|---|---|
| Component enter/leave, list transitions | **Svelte built-in transitions** (`transition:`, `in:`/`out:`) | Zero deps, SSR-safe, cancellable |
| Scroll-scrubbed timelines, pinning | **GSAP + ScrollTrigger** | Industry standard on 11/15 sites |
| Lightweight springs, draggable, text splitting | **Anime.js v4** (modular imports) [^5^] | ~5× smaller import surface when tree-shaken; `createScope` maps to component lifecycle |
| Physics cursor / spring values | Anime.js `createSpring` or a 12-line custom spring | No GSAP needed for this |
| One shared clock | GSAP `ticker` as the single rAF driver, Lenis hooked into it | Prevents competing rAF loops |
---
## 3. CSS Architecture for Award Sites
### 3.1 Tailwind + CSS variables for dynamic theming
```css
/* app.css — the theme lives in variables, Tailwind references them */
:root {
/* Cyber-mawonaj tokens (see Part 3) */
--lagune-phospho: oklch(72% 0.13 210);
--roche-volcan: oklch(18% 0.02 260);
--corail: oklch(70% 0.16 25);
--or-circuit: oklch(80% 0.14 90);
--foret-neon: oklch(55% 0.15 155);
--ease-ka: cubic-bezier(0.22, 1, 0.36, 1); /* motion tokens too */
--dur-mesure: 480ms;
}
@theme inline {
--color-lagune: var(--lagune-phospho);
--color-volcan: var(--roche-volcan);
/* … */
}
```
Theme switching = swapping a `data-theme` attribute on `<html>`, variables cascade, Tailwind utilities follow. No JS class churn, no FOUC if set in `%sveltekit.head%`.
### 3.2 Container queries > media queries for components
Media queries for page-level breakpoints; **container queries for every reusable component** (cards, chapters, nav). Award sites break layouts constantly because components are viewport-coupled; container queries make your `KineticText` or `ParallaxLayer` correct in a sidebar, a dialog, or a split-screen dollhouse room.
### 3.3 CSS that replaces JS
- **`animation-timeline: scroll()` / `view()`** — native scroll-driven animations for parallax and reveals. ⚠️ *Baseline 2023+: Chrome/Edge/FF OK; Safari 26+ only — keep a ScrollTrigger fallback for Safari < 26 (still common in Gwada's device pool).* Flag: **needs progressive enhancement wrapper.**
- **`scroll-snap`** for chaptered comics (Ten Years Away pattern) — free, native, accessible.
- **`:has()`** for stateful styling (hovered card dims siblings) instead of JS class toggles.
- **View Transitions API** for route morphs (see PageTransition, §5). ⚠️ *Chrome/Edge/Safari 18+; Firefox behind flag — design as enhancement.*
---
## 4. Performance Budget & Optimization
### 4.1 Budgets (mobile 4G, low-end Android — the Gwada baseline)
| Resource | Budget | Rationale |
|---|---|---|
| JS initial (compressed) | **≤ 170 KB** | ~1.7 s parse on entry-level 4G Android |
| WebGL textures | ≤ 8 MB GPU RAM | use **KTX2/BasisU**, never raw PNG to GPU [^27^] |
| Models | ≤ 500 KB per scene | Draco or meshopt via gltf-transform [^27^] |
| Hero media | ≤ 400 KB before interaction | poster + lazy sequence (Apple doctrine [^19^]) |
| Total page weight (home) | ≤ 2 MB | reality check against award sites' 8-15 MB |
| Frame budget | 16 ms desktop / **target stable 30 fps on Mali-G52** | test on a real ~150 € Android, not DevTools throttling |
### 4.2 WebGL asset pipeline
```
Blender → gltf-transform CLI:
gltf-transform optimize in.glb out.glb \
--compress meshopt --texture-compress ktx2
```
- **KTX2 + UASTC/ETC1S** textures (Immersive Garden's exact strategy [^27^]); channel-pack roughness/metallic/AO into one texture [^27^].
- **LOD**: two model tiers; pick at runtime from `renderer.capabilities` + `deviceMemory`.
- **Instancing** for repeated elements (lambi shells, vegetation, stars).
- **Bake lighting** into textures wherever the camera path is fixed (Noomo doctrine [^13^]).
### 4.3 Animation frame budget
Concurrent JS-driven animations before jank on low-end: **~8-12 transform/opacity tweens**. Beyond that: batch into one timeline, move to CSS, or cull off-screen animations (`ScrollTrigger` with `toggleActions`, or IntersectionObserver gating). Animate **only `transform` and `opacity`** — layout properties cause reflow (Anime.js v4 docs say the same [^5^]).
### 4.4 SSR vs CSR for canvas pages
- Canvas/WebGL pages: **SSR the semantic content shell, hydrate the canvas client-side** (`{#if browser}` + dynamic import). Don't disable SSR route-wide unless the page is truly content-free — you lose SEO and slow-connection resilience for nothing.
- Lenis/GSAP init: `onMount` only, always.
- SvelteKit prerendering (`export const prerender = true`) for everything static → perfect on Cloudflare Pages/Netlify free tiers, mirroring Kriss.ai's edge-CDN architecture [^28^].
---
## 5. Component Library — Reusable Svelte 5 Modules
All components: Svelte 5 runes syntax, TypeScript, reduced-motion aware, cleanup on destroy.
### 5.1 SmoothScrollProvider — Lenis as layout-level context
```svelte
<!-- $lib/components/SmoothScrollProvider.svelte -->
<script lang="ts">
import { onMount, setContext, type Snippet } from 'svelte';
import Lenis from 'lenis';
import gsap from 'gsap';
let { children }: { children: Snippet } = $props();
let lenis: Lenis | null = $state(null);
setContext('lenis', { get: () => lenis });
onMount(() => {
const reduced = matchMedia('(prefers-reduced-motion: reduce)').matches;
if (reduced) return; // native scroll = the fallback
lenis = new Lenis({ lerp: 0.1, smoothWheel: true });
// ONE rAF driver for the whole app (Principle: shared clock)
lenis.on('scroll', () => {}); // ScrollTrigger.update hook lives in KineticText
gsap.ticker.add((t) => lenis?.raf(t * 1000));
gsap.ticker.lagSmoothing(0);
return () => { lenis?.destroy(); lenis = null; };
});
</script>
{@render children()}
```
### 5.2 WebGLCanvas — single renderer, context-provided scene graph
```svelte
<!-- $lib/webgl/WebGLCanvas.svelte -->
<script lang="ts">
import { onMount, type Snippet } from 'svelte';
import { provideWebGL } from './context';
let { children, dpr = 1.5 }: { children: Snippet; dpr?: number } = $props();
let canvas: HTMLCanvasElement;
let ready = $state(false);
onMount(async () => {
const THREE = await import('three'); // code-split: three never hits content pages
const renderer = new THREE.WebGLRenderer({ canvas, antialias: true, powerPreference: 'high-performance' });
renderer.setPixelRatio(Math.min(devicePixelRatio, dpr)); // cap DPR: free FPS on retina
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(45, innerWidth / innerHeight, 0.1, 100);
const onResize = () => {
camera.aspect = innerWidth / innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(innerWidth, innerHeight);
};
onResize();
addEventListener('resize', onResize);
renderer.setAnimationLoop(() => renderer.render(scene, camera));
provideWebGL({ renderer, scene, camera, canvas });
ready = true;
return () => {
removeEventListener('resize', onResize);
renderer.setAnimationLoop(null);
renderer.dispose();
};
});
</script>
<canvas bind:this={canvas} class="fixed inset-0 -z-10" aria-hidden="true"></canvas>
{#if ready}{@render children()}{/if}
```
### 5.3 KineticText — scroll-driven split typography
```svelte
<!-- $lib/components/KineticText.svelte -->
<script lang="ts">
import { onMount } from 'svelte';
let {
text,
as = 'h2',
scrub = true
}: { text: string; as?: keyof HTMLElementTagNameMap; scrub?: boolean } = $props();
let el: HTMLElement;
onMount(async () => {
if (matchMedia('(prefers-reduced-motion: reduce)').matches) return;
const { animate } = await import('animejs');
const { splitText } = await import('animejs/text');
const { onScroll } = await import('animejs'); // Scroll Observer [^12^]
const words = splitText({ text, by: 'word', tag: 'span' });
el.append(...words.nodes as Node[]);
const anim = animate(words.nodes, {
opacity: { to: 1 },
translateY: { to: 0 },
delay: (el, i) => i * 40, // stagger → swap for gwoka pattern (Part 3)
ease: 'out(3)',
duration: 600,
autoplay: scrub
? onScroll({ target: el, enter: 'bottom-=10%', leave: 'top+=10%', sync: 0.5 })
: true
});
return () => anim.revert();
});
</script>
<svelte:element this={as} bind:this={el} aria-label={text} class="kinetic">{text}</svelte:element>
```
### 5.4 ParallaxLayer — multi-plane depth, CSS-first
```svelte
<!-- $lib/components/ParallaxLayer.svelte -->
<script lang="ts">
import { onMount, type Snippet } from 'svelte';
let { depth = 0.3, children }: { depth?: number; children: Snippet } = $props();
let el: HTMLElement;
onMount(async () => {
// Prefer native scroll-driven animations where supported (no JS per frame)
if (CSS.supports('animation-timeline: view()')) {
el.style.animationTimeline = 'view()';
el.style.animationName = 'parallax-y';
el.style.setProperty('--depth', String(depth));
return;
}
const gsap = (await import('gsap')).default;
const { ScrollTrigger } = await import('gsap/ScrollTrigger');
gsap.registerPlugin(ScrollTrigger);
const tween = gsap.to(el, {
yPercent: -depth * 100,
ease: 'none',
scrollTrigger: { trigger: el, start: 'top bottom', end: 'bottom top', scrub: true }
});
return () => { tween.scrollTrigger?.kill(); tween.kill(); };
});
</script>
<div bind:this={el} class="parallax-layer">{@render children()}</div>
<style>
@keyframes parallax-y { to { transform: translateY(calc(var(--depth, 0.3) * -30%)); } }
</style>
```
### 5.5 CursorTracker — physics cursor + velocity uniform (Igloo pattern)
```svelte
<!-- $lib/components/CursorTracker.svelte -->
<script lang="ts">
import { onMount } from 'svelte';
let dot: HTMLDivElement;
onMount(async () => {
if (matchMedia('(pointer: coarse)').matches) return; // no cursor on touch
const { createSpring } = await import('animejs');
const spring = createSpring({ stiffness: 120, damping: 14 });
let x = 0, y = 0, px = 0, py = 0;
const onMove = (e: PointerEvent) => { x = e.clientX; y = e.clientY; };
addEventListener('pointermove', onMove, { passive: true });
let raf = 0;
const tick = () => {
const vx = x - px, vy = y - py; // pointer velocity
const speed = Math.min(Math.hypot(vx, vy) / 40, 1);
dot.style.transform = `translate(${spring.solve(x, px)}px, ${spring.solve(y, py)}px) scale(${1 + speed * 0.6})`;
// → also write (x, y, speed) into your WebGL uniforms here: cursor-as-light-source (Unseen pattern)
px = x; py = y;
raf = requestAnimationFrame(tick);
};
raf = requestAnimationFrame(tick);
return () => { removeEventListener('pointermove', onMove); cancelAnimationFrame(raf); };
});
</script>
<div bind:this={dot} class="cursor-dot" aria-hidden="true"></div>
```
### 5.6 PageTransition — GSAP route transitions (SvelteKit-safe)
```svelte
<!-- +layout.svelte (site group) -->
<script lang="ts">
import { onNavigate } from '$app/navigation';
import { page } from '$app/state';
onNavigate((nav) => {
// Prefer native View Transitions where available (progressive enhancement)
if (!document.startViewTransition) return;
return new Promise((resolve) => {
document.startViewTransition(async () => { resolve(); await nav.complete; });
});
});
</script>
{#key page.url.pathname}
<main class="page"><slot /></main>
{/key}
<style>
.page { view-transition-name: page; }
::view-transition-old(page) { animation: page-out 0.35s var(--ease-ka) both; }
::view-transition-new(page) { animation: page-in 0.45s var(--ease-ka) both; }
@keyframes page-out { to { opacity: 0; transform: translateY(-2%); } }
@keyframes page-in { from { opacity: 0; transform: translateY(3%); } }
</style>
```
*GSAP alternative for browsers without View Transitions:* animate out in `onNavigate`'s returned promise, animate in via `afterNavigate`. Keep durations in your CSS custom properties so both paths share the same motion tokens.
### 5.7 ScrollProgressBar — zétwal minimal progress
```svelte
<!-- $lib/components/ScrollProgressBar.svelte -->
<script lang="ts">
let progress = $state(0);
const onscroll = () => {
const max = document.documentElement.scrollHeight - innerHeight;
progress = max > 0 ? scrollY / max : 0;
};
</script>
<svelte:window {onscroll} />
<div class="progress" role="progressbar" aria-valuenow={Math.round(progress * 100)}
aria-valuemin="0" aria-valuemax="100" aria-label="Progression de lecture">
<span style:transform="scaleX({progress})"></span>
</div>
<style>
.progress { position: fixed; inset: 0 0 auto; height: 3px; z-index: 50; }
.progress span { display: block; height: 100%; transform-origin: left;
background: linear-gradient(90deg, var(--lagune-phospho), var(--or-circuit)); }
</style>
```
### 5.8 Bonus — FlipbookCanvas (Apple pattern, connection-aware)
```ts
// $lib/actions/flipbook.ts — Svelte action
export function flipbook(canvas: HTMLCanvasElement, opts: { frames: (q: 'full'|'lite') => string[]; total: number }) {
const conn = (navigator as any).connection;
const quality = conn?.effectiveType === '4g' && !conn?.saveData ? 'full' : 'lite';
const urls = opts.frames(quality);
if (conn?.effectiveType === 'slow-2g' || conn?.effectiveType === '2g' || conn?.saveData) {
// Apple doctrine: single fallback image, no sequence [^19^]
const img = new Image(); img.src = urls[0];
img.onload = () => canvas.getContext('2d')?.drawImage(img, 0, 0);
return {};
}
const imgs = urls.map((u) => { const i = new Image(); i.src = u; return i; });
const ctx = canvas.getContext('2d')!;
const draw = (p: number) => {
const f = Math.min(urls.length - 1, Math.floor(p * urls.length));
if (imgs[f]?.complete) ctx.drawImage(imgs[f], 0, 0, canvas.width, canvas.height);
};
const onscroll = () => {
const max = document.documentElement.scrollHeight - innerHeight;
requestAnimationFrame(() => draw(max > 0 ? scrollY / max : 0));
};
addEventListener('scroll', onscroll, { passive: true });
return { destroy: () => removeEventListener('scroll', onscroll) };
}
```
---
## 6. Svelte-Specific Translations (React/Next.js → Svelte idioms)
| React pattern (seen on these sites) | Svelte 5 idiom |
|---|---|
| `useRef` + `useEffect(() => {...}, [])` for GSAP init | `$effect(() => { /* init */; return () => cleanup })` — or better, a **Svelte action** `use:gsapThing` |
| `useContext(WebGLContext)` | `setContext`/`getContext` with a `Symbol` key (§2.2) |
| `useState` for animation params | `$state` rune; params read inside `$effect` re-run the tween — guard with `gsap.quickTo` or `quickSetter` for per-frame writes |
| Derived uniforms (`useMemo`) | `$derived(() => computeUniforms(pointer, scroll))` — recalculates only on dependency change |
| R3F `<Canvas>` + hooks | **Threlte** for declarative scenes; raw Three in `onMount` for shader-heavy work (Noomo avoided R3F for flexibility [^13^] — same logic applies) |
| `framer-motion` layout animations | Svelte built-in `flip` (from `svelte/animate`) + `crossfade` (from `svelte/transition`) — free, SSR-safe |
| Zustand/Redux for UI state | `$state` in a `.svelte.ts` module — a class with rune fields is your store |
**Runes vs stores for animation state:** stores still work and are fine for cross-app state; but **animation state should live in `$state`/`$derived` inside components or `.svelte.ts` modules** — runes give finer-grained reactivity (no subscriber fan-out per frame). Critical rule: **never write per-frame values (scrollY, pointer) into reactive state that drives DOM** — write them to refs/plain variables and flush via rAF, or you'll re-render at 60 Hz.
**GSAP + runes compatibility:** GSAP mutates the DOM directly — Svelte doesn't care as long as GSAP's targets aren't nodes Svelte also patches (avoid tweening attributes Svelte controls via bindings). Wrap GSAP targets in `{@html}`-free static markup or bind via `bind:this`, init in `$effect`, kill in the cleanup function. Anime.js v4's `createScope({ root })` + `scope.revert()` is the same lifecycle shape and tree-shakes better [^5^].
---
## 7. Accessibility & Fallbacks
1. **Screen readers in immersive sites:** canvas is `aria-hidden`; a semantic DOM mirror carries all content (Apple pattern [^19^]). If you render UI text in WebGL (Igloo), ship a visually-hidden parallel DOM (`sr-only` list of the same links/labels).
2. **Reduced motion:** one global gate — `matchMedia('(prefers-reduced-motion: reduce)')` checked in `SmoothScrollProvider` (no Lenis), `KineticText` (no split/scrub), `ParallaxLayer` (static), flipbook (poster frame). Implement it **once, at the provider level**, not per component.
3. **Graceful degradation ladder** (the Gwada ladder):
- `navigator.connection.saveData` or `effectiveType` ≤ 3g → static images, no autoplay video, no sequence preloading [^19^]
- `deviceMemory ≤ 4` or WebGL context creation fails → DOM-effects-only mode (Zentry proves DOM-only can still win awards [^6^])
- `<noscript>` / JS-disabled → SSR content is complete and readable (this is why SSR matters)
4. **Focus management:** route transitions must move focus to the new page's `<h1>`; scroll-jacking (Lenis) must never break keyboard scrolling — Lenis preserves native scroll, which is why it's the right engine choice.
5. **Bilingual a11y:** `lang` attributes per text node (`fr` / `gcf`) — screen readers switch voices; crucial for Kréyòl content.
---
## 8. Tooling Recommendations
### Essential packages (open-source first, per your doctrine)
```jsonc
{
"dependencies": {
"lenis": "^1.3", // smooth scroll — the award-site standard [^20^][^27^]
"gsap": "^3.13", // timelines, ScrollTrigger, ticker
"animejs": "^4.2", // modular springs/stagger/splitText [^5^]
"three": "^0.1xx", // WebGL — dynamic-import only
"@threlte/core": "^8", // optional: declarative Three in Svelte
"troika-three-text": "^0.5" // SDF text in WebGL (Igloo pattern, if needed)
},
"devDependencies": {
"@gltf-transform/cli": "^4",// meshopt/KTX2 pipeline (§4.2)
"vite-imagetools": "^7", // responsive AVIF/WebP at build
"sharp": "^0.33"
}
}
```
- **Vite plugins:** `vite-imagetools` (build-time responsive images), `vite-plugin-pwa` (offline mode = real resilience on flaky 4G, plus an installable PWA for AKILPA), basic `manualChunks` splitting `three` into its own async chunk.
- **HMR for WebGL:** Vite HMR reloads Svelte components but not your GL state — keep scene setup idempotent (init/dispose symmetric in `$effect`), store debug params in URL hash so a reload restores your camera/scrub position. Shader files as `?raw` imports hot-reload cleanly.
- **Hosting (≤50 €/mo, actually near 0 €):** **Cloudflare Pages + R2** — the exact Kriss.ai architecture [^28^]; free tier absorbs heavy media via edge CDN. Netlify/Vercel free tiers work too; avoid anything Node-server-based (Immersive Garden's Strapi [^27^] is out of budget — use a headless Git-based CMS or plain Markdown in-repo).
---
# PART 3 — CYBER-MAWONAJ ADAPTATION
How the 15-site DNA translates for a Caribbean, sovereign, 4G-first aesthetic. This is not "apply a tropical skin" — it's re-deriving each decision from the local context.
### Color system (CSS tokens, oklch for perceptual control)
```css
:root {
--lagune-phospho: oklch(72% 0.13 210); /* phosphorescent lagoon blue — primary accent */
--roche-volcan: oklch(18% 0.02 260); /* volcanic rock black — dominant ground */
--corail: oklch(70% 0.16 25); /* coral pink — secondary accent */
--or-circuit: oklch(80% 0.14 90); /* circuit gold — data/tech highlights */
--foret-neon: oklch(55% 0.15 155); /* deep forest green, used with neon edge-glow */
--terre-rouge-a: oklch(45% 0.12 40); /* terre rouge gradient stop A */
--terre-rouge-b: oklch(30% 0.09 55); /* terre rouge gradient stop B — use for section grounds */
}
```
Dark-mode-first (`--roche-volcan` ground) = OLED battery saving + the bioluminescence aesthetic for free. The **arc-en-ciel / rainbow-serpent** motif belongs in *gradients and particle hues* (velocity-shifted color, Igloo pattern [^1^]) — never as static rainbow fills.
### Cultural signifiers → concrete UI patterns
| Signifier | Pattern (source site) | Implementation |
|---|---|---|
| **Lambi shell** | UI motif: shell-spiral as scroll progress, loader, section dividers | SVG spiral `stroke-dashoffset` scrub (createDrawable, animejs/svg [^5^]); KTX2 shell texture for WebGL hero |
| **Zétwal (stars)** | Navigation: constellation nav where sections are stars | DOM + SVG lines; hover = star brightens + label reveals; light-years cheaper than a WebGL starfield |
| **Dollhouse créole** | Kriss.ai spatial IA [^28^] | AKILPA sections as case à ka / marché / karbay / plage rooms — isometric DOM illustration, no WebGL needed |
| **Terre rouge** | Section backgrounds gradient | Pure CSS, zero cost |
| **Mawon/maroon resistance** | Visual language: glitch-free, hand-made texture; KPR's community-shaped tableaux [^45^] | UGC/content layers: community-submitted photos as parallax planes; "Backstage" section documenting the process (Immersive Garden pattern [^27^]) = transparency as sovereignty |
### Typography (FR/KRÉ bilingual, low-res safe)
- **Weights: ≥ 400 body, ≥ 600 display** — thin weights die on low-DPI screens (your constraint is correct).
- Suggested open fonts: **Space Grotesk** (display, tech-caribbean tension) + **Inter** or **Atkinson Hyperlegible** (body, legibility-first, open license). Atkinson is the sovereignty-aligned choice: designed for low-vision readers.
- Kinetic type via `splitText` on **words, not characters**, for Kréyòl — character-splitting can break apostrophe-heavy words (`mwen`, `tèt'w`) visually and for screen readers. Keep `aria-label` with the intact string (component §5.3 does this).
- Language switch as animation event (Nudot pattern): mask-swap lines in place.
### Motion design — gwoka cadences as easing/timing tokens
Award sites ship custom ease curves; yours can carry rhythm. Practical translation:
```css
:root {
/* ka/belair-inspired: strong downbeat, syncopated release */
--ease-ka: cubic-bezier(0.22, 1, 0.36, 1); /* downbeat: fast attack, long settle */
--ease-syncope: cubic-bezier(0.65, 0, 0.35, 1); /* off-beat: symmetric, urgent */
--dur-tanbou: 120ms; /* sixteenth @ ~125 BPM — micro-interactions */
--dur-mesure: 480ms; /* one bar — entrances */
--dur-phrase: 960ms; /* two bars — page transitions */
}
```
Stagger patterns: instead of linear `i * 40ms`, use **syncopated stagger** `[0, 120, 240, 360, 480, 600, 720, 840]` → remap to `[0, 120, 300, 360, 600, 660, 900, 960]` (3+3+2 feel). Subtle, but rhythm is identity.
### The Gwada connectivity doctrine (non-negotiables)
1. **Connection-aware everything** — flipbook component §5.8 is the reference implementation [^19^].
2. **DOM-first effects** — Zentry is your patron saint here [^6^].
3. **PWA with offline shell** — an association site that works in a dead zone is a political statement (souveraineté numérique).
4. **Test hardware:** a ~150 € Android on real 4G in Gwada, not Lighthouse throttling.
5. **Edge hosting:** Cloudflare Pages/R2 [^28^] — points of presence in the Americas, near-zero cost, aligns with the 50 €/mo ceiling (realistically: < 5 €/mo).
---
# PART 4 — COMPARISON MATRIX
| # | Site | Visual | Motion | Tech | UX | Stealable pattern |
|---|---|---|---|---|---|---|
| 1 | unseen.co | Volumetric light, dark bloom | Inertial, particle-driven | Three.js [^39^] | Scroll-journey portfolio | Cursor-as-light uniform |
| 2 | animejs.com | Lab-instrument dark, kinetic type | Springs, staggers, scroll-observer | Three hero + Anime.js v4 modules [^5^][^14^] | Interactive docs | `createScope` per component |
| 3 | immersive-g.com | Bas-relief minimalism | Ceremonial glide | Nuxt, Three, GSAP, Lenis, Strapi, KTX pipeline [^27^] | One-click access + Backstage | Backstage process section |
| 4 | davidwhyte.com/experience | Watercolor organic | Fluid sim, baked-noise reveals | Nuxt, Three, GSAP, Lenis, WP [^20^] | Slow contemplative tempo | Bake generative noise offline |
| 5 | kriss.ai/aftercare | 70s warm dollhouse | Room-to-room camera | WebGL/Blender, **Cloudflare Pages+R2** [^28^] | Spatial metaphor IA | Dollhouse IA (créole rooms) |
| 6 | zentry.com | Bold saturated portals | Motion-system-first | **Nuxt, pure DOM — no WebGL** [^6^] | Portal reveals + sound | DOM-only 3D portal masks ⭐ |
| 7 | igloo.inc | Procedural ice, chromatic frost | Velocity particles, SDF glitch | **Three + Svelte + GSAP** [^1^] | 3-section scroll journey | Velocity-reactive particles |
| 8 | kprverse.com | Concept-art tableaux | Click-and-hold layers | Resn (Nuxt-class) [^45^] | Lore gating, terminal loader | Click-hold reveal layer |
| 9 | hape.io | Fashion editorial | Scroll-scrubbed walk + music sync | Custom OGL micro-framework [^37^] | Single-idea focus | Scroll-scrubbed animation clip |
| 10 | labs.noomoagency.com | Glass jellyfish dream | Baked camera playback | Three + GSAP, no R3F, Blender-baked [^13^] | Public R&D hub | Two-Empty baked camera rig |
| 11 | noomoagency.com | Disciplined glassmorphism | Per-element hover craft | Nuxt 3 full-stack, Three, GSAP [^9^] | Classic IA, flawless transitions | Budgeted hover craft (5 elements) |
| 12 | nudot.com.tw | Bilingual kinetic type | Character reveals, hover previews | DOM/GSAP-class *(inference)* | Bilingual as feature | Language-switch as animation |
| 13 | 375.studio | Two-color discipline | Systematic micro-motion | DOM-first *(inference)* | Every state designed | Designed 404/empty states |
| 14 | ten.375.studio | Interactive comic | Panel scrubbing, mouse trail | DOM/SVG *(inference)* | Chaptered narrative | Chaptered scroll-comic ⭐ |
| 15 | apple.com/airpods-pro | Photographic black | Scroll flipbook, master timeline | Canvas 2D image sequence [^19^][^18^] | Desire before specs | Connection-aware flipbook ⭐ |
⭐ = highest ROI for Cyber-mawonaj constraints (4G, low-end, sovereignty).
---
## Sources
[^1^]: https://www.webgpu.com/showcase/igloo-inc-procedural-crystals/ — Igloo Inc: Crystal Growth Algorithms, Shader-Driven UI, and Volume Data (Feb 2026)
[^3^]: https://www.awwwards.com/sites/igloo-inc — Igloo Inc, Awwwards SOTD Jul 23 2024
[^4^]: https://www.awwwards.com/sites/ten-years-away — Ten Years Away, Awwwards SOTD Jun 26 2026
[^5^]: https://github.com/juliangarnier/anime/issues/1105 — Anime.js v4 LLM guiding file (API modules, createScope, splitText)
[^6^]: https://www.awwwards.com/zentry-case-study.html — Zentry case study (Resn): Nuxt/Vue/Vite, DOM-only 3D portal masks, no WebGL
[^7^]: https://www.awwwards.com/igloo-inc-case-study.html — Igloo Inc case study (Abeto × Bureaux)
[^8^]: https://www.webgpu.com/showcase/noomo-3d-storytelling-threejs/ — Noomo 3D storytelling site write-up
[^9^]: https://medium.com/@noomo-agency/noomo-agency-website-of-the-year-winner-on-awwwards-aad757327994 — Noomo: Nuxt 3, Three.js, GSAP
[^10^]: https://www.awwwards.com/sites/studio375 — Studio375 HM (2023): elements incl. animated 404
[^11^]: https://www.awwwards.com/sites/noomo-agency — Noomo Agency SOTD Sep 21 2023, elements list
[^12^]: https://animejs.com/ — Anime.js feature docs (Scroll Observer, stagger, SVG toolset)
[^13^]: https://www.awwwards.com/noomo-labs-a-hub-for-cutting-edge-immersive-experiences.html — Noomo Labs behind the scenes: Three+GSAP, no R3F, Blender-baked, two-Empty camera
[^14^]: https://www.reddit.com/r/webdev/comments/1jqmqkn/ — r/webdev on animejs.com v4 site (Three.js hero)
[^15^]: https://www.awwwards.com/Studio375/ — Studio375 profile & awards
[^16^]: https://noomoagency.com/ — Noomo Agency honors list (WOTY, Labs awards)
[^17^]: https://www.awwwards.com/sites/david-whyte-experience — David Whyte Experience SOTD Dec 31 2024
[^18^]: https://gsap.com/community/forums/topic/25188-airpods-image-sequence-animation-using-scrolltrigger/ — master-timeline percentage technique
[^19^]: https://css-tricks.com/lets-make-one-of-those-fancy-scrolling-animations-used-on-apple-product-pages/ — Apple flipbook technique + slow-connection fallback
[^20^]: https://www.awwwards.com/case-study-david-whyte-experience-by-immersive-garden.html — fluid-sim atlas, baked noise, full stack (Nuxt/Three/GSAP/Lenis/WP/Vercel)
[^24^]: https://www.awwwards.com/immersivegarden/ — Immersive Garden profile (Studio of the Year ×3)
[^27^]: https://www.awwwards.com/case-study-immersive-gardens-new-website.html — KTX compression, channel packing, gltf-transform; Nuxt/Three/GSAP/Lenis/Strapi/Vercel
[^28^]: https://www.awwwards.com/case-study-ai-with-a-human-touch-the-new-face-of-kriss-ai.html — Kriss.ai dollhouse concept; Cloudflare Pages + R2
[^35^]: https://www.awwwards.com/sites/kpr — KPR SOTD Dec 26 2022
[^36^]: https://www.awwwards.com/sites/hape — HAPE SOTD Apr 27 2022
[^37^]: https://www.deptagency.com/case/creating-a-3d-nft-walking-hape/ — HAPE: custom OGL micro-framework, PBR, skinning; 4-week build
[^38^]: https://www.awwwards.com/directory/agency-studio/ — Unseen Studio directory stats (35 SOTD)
[^39^]: https://x.com/uns__nstudio — Unseen Studio: rebrand from GC, volumetric studies, particle sims, three.js tags
[^40^]: https://dogstudio.co/cases/hape-prime/ — HAPE walk concept, scroll camera, soundtrack sync
[^41^]: https://kprverse.com/ — KPR live site: terminal loader strings, device gating messages
[^45^]: https://www.awwwards.com/kpr-by-resn-wins-sotm.html — KPR by Resn SOTM: click-and-hold layers, tableaux, 3D+2D mix
@@ -0,0 +1,211 @@
# RECETTE SVELTEKIT — PLAYBOOK POUR AGENT DE CODE
### Transformer un site web classique en expérience de niveau Awwwards
> **Nature de ce document :** instructions opérationnelles destinées à un agent de code (Kimi CLI ou équivalent). Chaque règle est impérative et vérifiable. Ce playbook est distillé de l'analyse de 15 sites primés (Zentry, Igloo.inc, Immersive Garden, Noomo, Apple AirPods Pro, KPR, HAPE, Kriss.ai, Studio375, animejs.com, etc.). Il est volontairement autonome : tu n'as besoin d'aucun autre document pour l'appliquer, sauf si l'utilisateur fournit une charte de marque (qui alors **prime sur ce document** pour tout ce qui est couleurs, typographie, voix et iconographie).
---
## 0. MISSION
Transformer un site vitrine « classique » (HTML statique, WordPress, ou SPA sans direction artistique) en un site SvelteKit à forte identité visuelle et motion design maîtrisé, **sans sacrifier performance, accessibilité ni SEO**. Le résultat doit fonctionner parfaitement sur mobile 4G et matériel bas de gamme.
**Interdictions absolues :**
- Ne jamais installer de dépendance sans l'importer et l'utiliser.
- Ne jamais désactiver le SSR globalement pour « faire marcher » une lib client.
- Ne jamais animer autre chose que `transform` et `opacity` en JS.
- Ne jamais livrer une page dont le contenu est invisible sans JavaScript.
- Ne jamais réintroduire de service tiers (fonts, CDN, analytics) si le projet affirme une doctrine de souveraineté — tout asset se self-héberge.
---
## 1. LES 5 PRINCIPES (ADN commun des sites primés)
**P1 — Le scroll est le moteur de navigation principal.** Une seule instance Lenis au layout racine, partagée via contexte Svelte, cadencée par le ticker GSAP unique. Jamais de double boucle rAF.
**P2 — Bake au build, pas au runtime.** Toute valeur qui ne dépend pas d'une entrée utilisateur en direct est précalculée hors-ligne : bruit génératif baké en textures, caméras 3D bakées dans Blender (rig deux-Empty), animations squelettiques exportées en clips GLTF, textures compressées KTX2 via gltf-transform, images responsive générées au build (vite-imagetools).
**P3 — DOM d'abord, WebGL seulement où ça paie.** Zentry a produit des effets « 3D » primés en pur DOM (clip-path, preserve-3d, masks). Réserver WebGL à : systèmes de particules, simulations de fluide, vrais assets 3D à caméra contrôlée. Un flipbook canvas 2D (pattern Apple) couvre la plupart des besoins de « produit qui tourne au scroll ».
**P4 — Le motion se designe avant les pages.** Définir d'abord les tokens de mouvement (courbes, durées, staggers) en custom properties CSS, puis les composants. Toute durée/courbe hardcodée dans un composant est une erreur.
**P5 — Chaque état est un moment designé.** Loader, 404, état vide, hors-ligne : autant de touchpoints de marque. Le 404 générique du framework est interdit.
---
## 2. WORKFLOW EN 7 PHASES
### Phase 1 — AUDIT (toujours commencer ici)
- [ ] Inventorier pages, sections, assets, liens externes, formulaires du site source.
- [ ] Mesurer la stack existante (headers HTTP, poids des assets, dépendances tierces).
- [ ] Identifier les bugs de production (images cassées, mixed content, liens morts) et les lister AVANT toute refonte — une migration ne doit jamais masquer un bug existant.
- [ ] Identifier les contradictions doctrine/outillage (ex. discours anti-GAFAM + Google Fonts).
- [ ] Produire un court rapport d'audit et le faire valider avant de coder.
### Phase 2 — TOKENS & THÈME
- [ ] Créer `app.css` avec toutes les couleurs en `oklch()` (ou valeurs de la charte si fournie), typographies, échelle d'espacement, rayons, **tokens de motion** (courbes + durées).
- [ ] Déclarer le mapping Tailwind via `@theme inline` pour que les utilitaires référencent les variables.
- [ ] Thème via `data-theme` sur `<html>`, appliqué côté serveur dans `app.html` pour éviter tout FOUC.
- [ ] Fonts : woff2 self-hébergées dans `static/fonts/`, `@font-face` avec `font-display: swap`, `preload` de la display uniquement.
### Phase 3 — ARCHITECTURE
```
src/routes/
├── +layout.svelte ← SmoothScrollProvider + CursorTracker (si pertinent)
├── (site)/ ← pages de contenu (SSR complet)
│ ├── +page.svelte
│ └── [section]/[slug]/
├── (immersive)/ ← layout group : canvas lourds isolés ici
│ └── experience/+page.svelte
└── +error.svelte ← 404 designé (P5)
```
- [ ] Layout groups pour isoler tout contexte WebGL des pages de contenu.
- [ ] Imports dynamiques (`await import('three')`) derrière `{#if browser}` pour tout ce qui dépasse ~50 KB.
- [ ] `prerender = true` sur toutes les routes statiques ; adapter `adapter-static` ou `adapter-cloudflare` selon l'hébergement cible.
- [ ] Conserver strictement les URLs existantes (redirections 301 si renommage).
### Phase 4 — COMPOSANTS (bibliothèque de référence, §5)
- [ ] Installer uniquement les composants pertinents pour CE site — la bibliothèque n'est pas un package à copier en entier.
- [ ] Chaque composant livré avec : cleanup dans `$effect`, garde `prefers-reduced-motion`, aucune fuite de listener.
### Phase 5 — MOTION
- [ ] Transitions d'entrée/sortie de composants : transitions Svelte natives.
- [ ] Scrub/pinning lié au scroll : GSAP ScrollTrigger (import dynamique), OU `animation-timeline: view()` natif avec fallback ScrollTrigger pour Safari ancien.
- [ ] Micro-physics (curseur, springs) : Anime.js v4 imports modulaires (`animejs`, `animejs/text`) ou spring custom de 15 lignes.
- [ ] Vérifier le budget : ≤ 8-12 tweens concurrents sur mobile bas de gamme ; culler les animations hors viewport.
### Phase 6 — PERFORMANCE (budgets §4)
- [ ] Pipeline assets : `gltf-transform optimize --compress meshopt --texture-compress ktx2` pour tout GLB ; AVIF/WebP responsive pour les images.
- [ ] Media hero ≤ 400 KB avant interaction ; séquences d'images avec chargement conditionné par `navigator.connection`.
- [ ] Mesurer sur profil mobile 4G (CPU ×4, réseau « Fast 4G ») : LCP < 2,5 s, INP < 200 ms, CLS < 0,05.
### Phase 7 — ACCESSIBILITÉ & DÉGRADATION (§6)
- [ ] La ladder de dégradation complète et testée.
- [ ] Revue clavier complète (tab order, focus visible, skip-link).
- [ ] Contenu canvas doublé en DOM sémantique.
---
## 3. ORCHESTRATION D'ANIMATION — RÈGLES SVELTE 5
| Besoin | Outil | Règle |
|---|---|---|
| Entrée/sortie composant, listes | Transitions Svelte natives | Zéro dépendance, SSR-safe |
| Scrub scroll, pinning | GSAP + ScrollTrigger | Import dynamique, init dans `$effect`, kill au cleanup |
| Springs, stagger, split text | Anime.js v4 modulaire | `createScope({ root })` + `scope.revert()` au cleanup |
| Une seule horloge | `gsap.ticker` pilote Lenis | Aucune autre boucle rAF sauf canvas WebGL |
**Règles runes impératives :**
- État d'animation : `$state` / `$derived` dans le composant ou un module `.svelte.ts`. Jamais de store Svelte legacy pour des valeurs par-frame.
- **Ne jamais écrire des valeurs par-frame (scrollY, pointeur) dans de l'état réactif qui rend du DOM** — écrire dans des variables plain et flusher via rAF.
- GSAP ne doit jamais cibler un nœud que Svelte patche via binding ; cibler via `bind:this` sur du markup statique.
- Tout init GSAP/Anime/Lenis se fait dans `$effect` (ou `onMount`) avec fonction cleanup qui `kill()`/`revert()`/`destroy()`.
---
## 4. BUDGETS PERFORMANCE (mobile 4G, Android entrée de gamme)
| Ressource | Budget |
|---|---|
| JS initial compressé | ≤ 170 KB |
| Textures GPU | ≤ 8 MB, KTX2/BasisU uniquement |
| Modèles 3D | ≤ 500 KB/scène, meshopt ou Draco |
| Média hero avant interaction | ≤ 400 KB |
| Poids total page d'accueil | ≤ 2 MB |
| Animations JS concurrentes | ≤ 8-12 (transform/opacity only) |
| DevicePixelRatio canvas | plafonné à 1.5 |
| Objectif frame rate | 30 fps stables sur Mali-G52, 60 fps desktop |
**SSR vs CSR :** les pages canvas rendent un shell sémantique en SSR et hydratent le canvas côté client. Le prerender statique est le défaut ; l'hébergement cible est un CDN edge (Cloudflare Pages/R2, Netlify) — pas de serveur Node sauf justification explicite.
---
## 5. BIBLIOTHÈQUE DE COMPOSANTS (contrats + implémentations condensées)
### 5.1 SmoothScrollProvider — Lenis au layout racine
```svelte
<script lang="ts">
import { onMount, setContext, type Snippet } from 'svelte';
import Lenis from 'lenis';
import gsap from 'gsap';
let { children }: { children: Snippet } = $props();
let lenis: Lenis | null = $state(null);
setContext('lenis', { get: () => lenis });
onMount(() => {
if (matchMedia('(prefers-reduced-motion: reduce)').matches) return;
lenis = new Lenis({ lerp: 0.1 });
gsap.ticker.add((t) => lenis?.raf(t * 1000));
gsap.ticker.lagSmoothing(0);
return () => { lenis?.destroy(); lenis = null; };
});
</script>
{@render children()}
```
### 5.2 KineticText — titrage cinétique au scroll
Split par **mots** (jamais par caractères pour les langues à apostrophes), `aria-label` avec la chaîne intacte, scrub via ScrollTrigger ou `animation-timeline: view()` en premier choix, stagger piloté par les tokens de motion du projet.
### 5.3 ParallaxLayer — profondeur multi-plans
CSS-first : si `CSS.supports('animation-timeline: view()')`, animer via keyframes + `view()` ; sinon fallback GSAP ScrollTrigger `scrub: true`, `yPercent` proportionnel à une prop `depth` (01). Toujours `transform`, jamais `top/margin`.
### 5.4 CursorTracker — curseur physique (desktop uniquement)
Garde `matchMedia('(pointer: coarse)')` → ne rien monter sur tactile. Spring (Anime.js `createSpring` ou custom), scale modulé par la **vélocité** du pointeur (pattern Igloo). Exposer position + vélocité via contexte pour alimenter des uniforms WebGL éventuels (curseur-source-de-lumière, pattern Unseen).
### 5.5 PageTransition — transitions de routes
`onNavigate` + View Transitions API en enhancement progressif ; fallback GSAP (out dans la promise d'`onNavigate`, in via `afterNavigate`). Durées lues depuis les custom properties CSS pour que les deux chemins partagent les mêmes tokens.
### 5.6 ScrollProgressBar — barre de progression
`<div>` fixe + `scaleX(progress)`, `role="progressbar"` avec `aria-valuenow`, listener scroll passif, calcul dans rAF.
### 5.7 FlipbookCanvas — séquence d'images scrubée (pattern Apple)
Action Svelte `use:flipbook`. Sélection de qualité via `navigator.connection.effectiveType` + `saveData` : `2g/slow-2g/saveData`**image fixe unique** ; `3g` → jeu réduit ; `4g` → séquence complète. Frames dessinées sur `<canvas>` via rAF, index = fraction de scroll. Préchargement progressif (ne pas bloquer le LCP).
### 5.8 WebGLCanvas — renderer unique (seulement si P3 le justifie)
Un seul `WebGLRenderer` pour toute l'app, fourni via `setContext` avec clé `Symbol`, scènes enfants enregistrées/retirées au cleanup, `setPixelRatio(min(dpr, 1.5))`, `setAnimationLoop` stoppé et `renderer.dispose()` au démontage. Import dynamique de `three`.
---
## 6. ACCESSIBILITÉ & LADDER DE DÉGRADATION
**Gate unique de reduced-motion** au niveau provider : si `prefers-reduced-motion: reduce` → pas de Lenis, pas de scrub, pas de flipbook (poster), contenu statique complet. Ne jamais implémenter la garde composant par composant.
**Ladder (dans l'ordre) :**
1. `saveData` ou `effectiveType` ≤ 3g → images statiques, zéro préchargement de séquence, vidéo non autoplay.
2. `deviceMemory` ≤ 4 ou échec de création de contexte WebGL → mode DOM-effects-only.
3. JS désactivé → le contenu SSR est complet et lisible.
4. Canvas/WebGL toujours `aria-hidden` avec miroir DOM sémantique du contenu.
5. Attribut `lang` correct sur chaque bascule de langue ; focus déplacé sur le `<h1>` après chaque transition de route.
---
## 7. OUTILLAGE
```jsonc
{
"dependencies": {
"lenis": "^1.3",
"gsap": "^3.13",
"animejs": "^4.2"
// "three": uniquement si P3 le justifie — import dynamique obligatoire
},
"devDependencies": {
"@sveltejs/adapter-static": "^3",
"@gltf-transform/cli": "^4",
"vite-imagetools": "^7",
"vite-plugin-pwa": "^1" // shell hors-ligne
}
}
```
HMR/WebGL : init/dispose symétriques dans `$effect`, shaders en imports `?raw` (hot-reload propre), paramètres de debug dans le hash d'URL.
---
## 8. DÉFINITION DE « TERMINÉ » (DoD — tout doit être vrai)
- [ ] Rapport d'audit livré et bugs de production pré-existants corrigés ou explicitement reportés.
- [ ] Aucune ressource chargée depuis un domaine tiers (fonts, CDN, scripts) sauf décision documentée.
- [ ] Lighthouse mobile : Performance ≥ 90, Accessibilité ≥ 95, Best Practices ≥ 95, SEO ≥ 95.
- [ ] Navigation clavier complète + `prefers-reduced-motion` vérifié manuellement.
- [ ] Page fonctionnelle et lisible avec JS désactivé.
- [ ] 404, vide et hors-ligne designés (P5).
- [ ] Toutes les URLs historiques préservées ou redirigées.
- [ ] Build de production vert, zéro warning d'accessibilité Svelte.
@@ -0,0 +1,61 @@
# Svelte 5 Code Writer
## CLI Tools
You have access to `@sveltejs/mcp` CLI for Svelte-specific assistance. Use these commands via `npx`:
### List Documentation Sections
```bash
npx @sveltejs/mcp list-sections
```
Lists all available Svelte 5 and SvelteKit documentation sections with titles and paths.
### Get Documentation
```bash
npx @sveltejs/mcp get-documentation "<section1>,<section2>,..."
```
Retrieves full documentation for specified sections. Use after `list-sections` to fetch relevant docs.
**Example:**
```bash
npx @sveltejs/mcp get-documentation "$state,$derived,$effect"
```
### Svelte Autofixer
```bash
npx @sveltejs/mcp svelte-autofixer "<code_or_path>" [options]
```
Analyzes Svelte code and suggests fixes for common issues.
**Options:**
- `--async` - Enable async Svelte mode (default: false)
- `--svelte-version` - Target version: 4 or 5 (default: 5)
**Examples:**
```bash
# Analyze inline code (escape $ as \$)
npx @sveltejs/mcp svelte-autofixer '<script>let count = \$state(0);</script>'
# Analyze a file
npx @sveltejs/mcp svelte-autofixer ./src/lib/Component.svelte
# Target Svelte 4
npx @sveltejs/mcp svelte-autofixer ./Component.svelte --svelte-version 4
```
**Important:** When passing code with runes (`$state`, `$derived`, etc.) via the terminal, escape the `$` character as `\$` to prevent shell variable substitution.
## Workflow
1. **Uncertain about syntax?** Run `list-sections` then `get-documentation` for relevant topics
2. **Reviewing/debugging?** Run `svelte-autofixer` on the code to detect issues
3. **Always validate** - Run `svelte-autofixer` before finalizing any Svelte component
@@ -0,0 +1,172 @@
## `$state`
Only use the `$state` rune for variables that should be _reactive_ — in other words, variables that cause an `$effect`, `$derived` or template expression to update. Everything else can be a normal variable.
Objects and arrays (`$state({...})` or `$state([...])`) are made deeply reactive, meaning mutation will trigger updates. This has a trade-off: in exchange for fine-grained reactivity, the objects must be proxied, which has performance overhead. In cases where you're dealing with large objects that are only ever reassigned (rather than mutated), use `$state.raw` instead. This is often the case with API responses, for example.
## `$derived`
To compute something from state, use `$derived` rather than `$effect`:
```js
// do this
let square = $derived(num * num);
// don't do this
let square;
$effect(() => {
square = num * num;
});
```
> [!NOTE] `$derived` is given an expression, _not_ a function. If you need to use a function (because the expression is complex, for example) use `$derived.by`.
Deriveds are writable — you can assign to them, just like `$state`, except that they will re-evaluate when their expression changes.
If the derived expression is an object or array, it will be returned as-is — it is _not_ made deeply reactive. You can, however, use `$state` inside `$derived.by` in the rare cases that you need this.
## `$effect`
Effects are an escape hatch and should mostly be avoided. In particular, avoid updating state inside effects.
- If you need to sync state to an external library such as D3, it is often neater to use [`{@attach ...}`](references/@attach.md)
- If you need to run some code in response to user interaction, put the code directly in an event handler or use a [function binding](references/bind.md) as appropriate
- If you need to log values for debugging purposes, use [`$inspect`](references/$inspect.md)
- If you need to observe something external to Svelte, use [`createSubscriber`](references/svelte-reactivity.md)
Never wrap the contents of an effect in `if (browser) {...}` or similar — effects do not run on the server.
## `$props`
Treat props as though they will change. For example, values that depend on props should usually use `$derived`:
```js
// @errors: 2451
let { type } = $props();
// do this
let color = $derived(type === 'danger' ? 'red' : 'green');
// don't do this — `color` will not update if `type` changes
let color = type === 'danger' ? 'red' : 'green';
```
## `$inspect.trace`
`$inspect.trace` is a debugging tool for reactivity. If something is not updating properly or running more than it should you can add `$inspect.trace(label)` as the first line of an `$effect` or `$derived.by` (or any function they call) to trace their dependencies and discover which one triggered an update.
## Events
Any element attribute starting with `on` is treated as an event listener:
```svelte
<button onclick={() => {...}}>click me</button>
<!-- attribute shorthand also works -->
<button {onclick}>...</button>
<!-- so do spread attributes -->
<button {...props}>...</button>
```
If you need to attach listeners to `window` or `document` you can use `<svelte:window>` and `<svelte:document>`:
```svelte
<svelte:window onkeydown={...} />
<svelte:document onvisibilitychange={...} />
```
Avoid using `onMount` or `$effect` for this.
## Snippets
[Snippets](references/snippet.md) are a way to define reusable chunks of markup that can be instantiated with the [`{@render ...}`](references/@render.md) tag, or passed to components as props. They must be declared within the template.
```svelte
{#snippet greeting(name)}
<p>hello {name}!</p>
{/snippet}
{@render greeting('world')}
```
> [!NOTE] Snippets declared at the top level of a component (i.e. not inside elements or blocks) can be referenced inside `<script>`. A snippet that doesn't reference component state is also available in a `<script module>`, in which case it can be exported for use by other components.
## Each blocks
Prefer to use [keyed each blocks](references/each.md) — this improves performance by allowing Svelte to surgically insert or remove items rather than updating the DOM belonging to existing items.
> [!NOTE] The key _must_ uniquely identify the object. Do not use the index as a key.
Avoid destructuring if you need to mutate the item (with something like `bind:value={item.count}`, for example).
## Using JavaScript variables in CSS
If you have a JS variable that you want to use inside CSS you can set a CSS custom property with the `style:` directive.
```svelte
<div style:--columns={columns}>...</div>
```
You can then reference `var(--columns)` inside the component's `<style>`.
## Styling child components
The CSS in a component's `<style>` is scoped to that component. If a parent component needs to control the child's styles, the preferred way is to use CSS custom properties:
```svelte
<!-- Parent.svelte -->
<Child --color="red" />
<!-- Child.svelte -->
<h1>Hello</h1>
<style>
h1 {
color: var(--color);
}
</style>
```
If this is impossible (for example, the child component comes from a library) you can use `:global` to override styles:
```svelte
<div>
<Child />
</div>
<style>
div :global {
h1 {
color: red;
}
}
</style>
```
## Context
Consider using context instead of declaring state in a shared module. This will scope the state to the part of the app that needs it, and eliminate the possibility of it leaking between users when server-side rendering.
Use `createContext` rather than `setContext` and `getContext`, as it provides type safety.
## Async Svelte
If using version 5.36 or higher, you can use [await expressions](references/await-expressions.md) and [hydratable](references/hydratable.md) to use promises directly inside components. Note that these require the `experimental.async` option to be enabled in `svelte.config.js` as they are not yet considered fully stable.
## Avoid legacy features
Always use runes mode for new code, and avoid features that have more modern replacements:
- use `$state` instead of implicit reactivity (e.g. `let count = 0; count += 1`)
- use `$derived` and `$effect` instead of `$:` assignments and statements (but only use effects when there is no better solution)
- use `$props` instead of `export let`, `$$props` and `$$restProps`
- use `onclick={...}` instead of `on:click={...}`
- use `{#snippet ...}` and `{@render ...}` instead of `<slot>` and `$$slots` and `<svelte:fragment>`
- use `<DynamicComponent>` instead of `<svelte:component this={DynamicComponent}>`
- use `import Self from './ThisComponent.svelte'` and `<Self>` instead of `<svelte:self>`
- use classes with `$state` fields to share reactivity between components, instead of using stores
- use `{@attach ...}` instead of `use:action`
- use clsx-style arrays and objects in `class` attributes, instead of the `class:` directive