- 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
13 KiB
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
transformetopacityen 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.cssavec toutes les couleurs enoklch()(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 inlinepour que les utilitaires référencent les variables. - Thème via
data-themesur<html>, appliqué côté serveur dansapp.htmlpour éviter tout FOUC. - Fonts : woff2 self-hébergées dans
static/fonts/,@font-faceavecfont-display: swap,preloadde 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 = truesur toutes les routes statiques ; adapteradapter-staticouadapter-cloudflareselon 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, gardeprefers-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 ktx2pour 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/$deriveddans 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:thissur du markup statique. - Tout init GSAP/Anime/Lenis se fait dans
$effect(ouonMount) avec fonction cleanup quikill()/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
<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 (0–1). 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) :
saveDataoueffectiveType≤ 3g → images statiques, zéro préchargement de séquence, vidéo non autoplay.deviceMemory≤ 4 ou échec de création de contexte WebGL → mode DOM-effects-only.- JS désactivé → le contenu SSR est complet et lisible.
- Canvas/WebGL toujours
aria-hiddenavec miroir DOM sémantique du contenu. - Attribut
langcorrect sur chaque bascule de langue ; focus déplacé sur le<h1>après chaque transition de route.
7. OUTILLAGE
{
"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-motionvé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.