Remplace la documentation PHP par celle de la branche svelte (FR/EN) : architecture statique et bake au build, fonctionnalités portées (Funkwhale inclus), référence de src/lib/config.ts, modèle CSP par page, PWA, structure du projet, prérequis Node + ImageMagick, mesures Lighthouse, déploiement
🎙️ ANNU KUTE CED — Hub multimédia du podcast (SvelteKit)
- 🇫🇷 Version française
- 📖 Description
- 🌳 Origine du projet
- 🔗 Sources et instances utilisées
- ✨ Fonctionnalités
- 🛠️ Technologies utilisées
- 📁 Structure du projet
- 📋 Prérequis
- 🚀 Installation et développement
- ⚙️ Configuration
- 🧭 Ordre des sections sur la page d’accueil
- 🛡️ Sécurité
- 📱 Progressive Web App (PWA)
- 🚀 Déploiement
- 👨💻 Développement et contribution
- 📜 Licence
- 📞 Contact
- 🇬🇧 English version
- 📖 Description
- 🌳 Project origin
- 🔗 Aggregated sources and instances
- ✨ Features
- 🛠️ Tech stack
- 📁 Project structure
- 📋 Requirements
- 🚀 Installation and development
- ⚙️ Configuration
- 🧭 Homepage section order
- 🛡️ Security
- 📱 Progressive Web App (PWA)
- 🚀 Deployment
- 👨💻 Development and contribution
- 📜 License
- 📞 Contact
🌍 Hub multimédia du podcast ANNU KUTE CED — version SvelteKit statique
🌍 Multimedia hub for the ANNU KUTE CED podcast — static SvelteKit version
Choisissez votre langue / Choose your language :
🇫🇷 Version française · 🇬🇧 English version
🇫🇷 Version française
📖 Description
ANNU KUTE CED est le hub multimédia du podcast du même nom. Il regroupe en un seul lieu :
-
🎥 la chaîne PeerTube du podcast (vidéos, shorts, directs) ;
-
🎙️ son compte Castopod (derniers épisodes, lecture audio intégrée) ;
-
🎵 une sélection aléatoire de morceaux Funkwhale (même lecteur intégré) ;
-
📡 sa timeline Mastodon (actualités et annonces).
Cette version est une application SvelteKit 100 % statique (Svelte 5 + TypeScript, adapter-static, prerender intégral) : toutes les données des instances sont récupérées au moment du build (« bake ») et le site généré peut être servi depuis n’importe quel hébergement statique — y compris un mutualisé comme o2switch — sans PHP, sans Node, sans base de données. C’est aussi une Progressive Web App installable, avec mode hors ligne, en français et en anglais.
🎯 Mission : offrir un point d’entrée unique, libre et décentralisé, pour découvrir, écouter et suivre le podcast ANNU KUTE CED sans dépendre des grandes plateformes propriétaires.
NOTE : La version historique PHP est conservée sur la branche main. Cette documentation décrit la branche svelte.
🌳 Origine du projet
Cette application est un fork de FEDIVERSE OKI, développée par l’ORGANISATION KA INTERNATIONALE (OKI), elle-même issue du projet kaubuntu.re du mouvement Ka-Ubuntu. La licence d’origine (GNU AGPL v3) est conservée et respectée. La refonte SvelteKit applique la charte OKI (tokens panafricains, thème sombre par défaut, iconographie SVG kréyòl, cadences de motion « gwoka »).
| Élément | Détail |
|---|---|
Dépôt upstream |
https://labola.o-k-i.net/ORGANISATION-KA-INTERNATIONALE/FEDIVERSE-OKI |
Dépôt de ce fork |
|
Domaine de production |
|
Gouvernance |
Application maintenue par OKI ; fork porté par le propriétaire du dépôt cedric (Cédric Famibelle-Pronzola) |
Licence |
GNU Affero General Public License v3 (AGPL-V3) ou ultérieure |
🔗 Sources et instances utilisées
Le hub agrège au build les sources du podcast ANNU KUTE CED :
| Service | Instance | Compte / chaîne |
|---|---|---|
🎥 PeerTube (vidéos, lives) |
GADE — |
|
🎙️ Castopod (épisodes audio) |
KUTE — |
|
🎵 Funkwhale (musique) |
MIZIK — |
morceaux aléatoires de l’instance |
📡 Mastodon (timeline) |
BOKANTE — |
Toutes ces sources sont modifiables dans src/lib/config.ts (voir Configuration).
✨ Fonctionnalités
🎥 Vidéos PeerTube
-
Vidéos récentes, tendances et par catégories (configurables)
-
Shorts : carrousel dédié aux vidéos portrait de moins de 3 minutes (défilement tactile)
-
Page vidéo complète : lecteur PeerTube embarqué, description Markdown, badge de licence Creative Commons, commentaires (lecture seule), vidéos suggérées
-
Téléchargement : fichiers directs et playlists HLS, avec résolution et taille
-
Partage : copie de lien, code d’intégration, e-mail, Facebook, X, WhatsApp, LinkedIn, Telegram
-
Recherche plein texte et par hashtag (préfixe
#) sur un index JSON généré au build
📺 Directs et annonces
-
Page Direct (
/direct) : embed du live PeerTube s’il est en cours, avec autoplay -
Annonce du prochain live : date et heure converties automatiquement pour 5 territoires (Ma’ohi Nui, Martinique/Guadeloupe, Guyane, France, Kanaky), image personnalisable
-
Section hero d’accueil configurable : live, vidéo unique, playlist ou masquée
🎙️ Podcast et musique
-
Castopod : derniers épisodes via flux RSS, avec lecteur audio intégré (lecture/pause, avance automatique à l’épisode suivant, Media Session API)
-
Funkwhale : sélection aléatoire de morceaux de l’instance, avec le même lecteur intégré
-
Un seul flux à la fois entre les deux lecteurs (bus audio partagé)
📡 Réseaux sociaux
-
Timeline Mastodon : les 10 derniers posts récupérés au build et rendus en HTML sémantique (plus de bibliothèque JS embarquée)
🌍 Internationalisation
-
Interface en français (
/) et en anglais (/en/) : routage par paramètre optionnel, bundles JSON,hreflang, attributlangpar route -
Kréyòl en signature : page 404 (« Paj la pa la »), page hors-ligne (« Pa gen koneksyon »), verrou maintenance (« An nou tann ! »)
🎨 Charte OKI
-
Thème sombre par défaut (identité de marque), thème clair en opt-in persisté en
localStorage, appliqué sans FOUC -
Tokens panafricains (noir/or/vert/rouge), flag-bar de signature, typographies Archivo et Inter self-hébergées en woff2
-
Zéro emoji en interface : sprite SVG maison (pictos kréyòl
ka,lambi,zetwal… + icônes UI), zéro Font Awesome, zéro CDN -
Motion « gwoka » : KineticText au scroll (
animation-timeline: view()), barre de progression, reveal syncopé (3+3+2), View Transitions — gate uniqueprefers-reduced-motion
📱 PWA et confort d’utilisation
-
Progressive Web App installable (bouton d’installation automatique)
-
Mode hors ligne : shell et pages en cache via Service Worker (Workbox), page
/offlinedédiée -
Indicateur visuel de perte de connexion
-
Interface entièrement responsive (mobile, tablette, desktop)
💝 Dons, compte à rebours et divers
-
Page de dons : LiberaPay, Ko-fi et Stripe (dons ponctuels et mensuels, montants suggérés)
-
Verrou maintenance : compte à rebours multi-fuseaux affiché à la place de tout le site quand activé au build, redirection automatique à l’échéance
-
Bloc « À propos » configurable (titre, deux paragraphes, image légendée)
🔍 SEO et données structurées
-
JSON-LD généré automatiquement :
WebSite(avecSearchAction),VideoObject,CollectionPage,BreadcrumbList,Organization -
Balises Open Graph complètes et Twitter Cards, canonical,
hreflangfr/en/x-default -
sitemap.xmlgénéré au build (toutes les pages, vidéos comprises) etrobots.txt
🛡️ Sécurité
-
CSP stricte : politique globale en en-tête (
_headersCloudflare /.htaccessApache) +script-srcdélivré par page en balise<meta>avec les empreintes SHA-256 des scripts inline (aucun'unsafe-inline'pour les scripts) -
Zéro requête tierce au chargement hors instances first-party de l’écosystème (GADE, KUTE, BOKANTE, MIZIK) : ni Google Fonts, ni CDN, ni analytics
-
HTML distant sanitisé au build (posts Mastodon, commentaires) ; descriptions Markdown échappées
-
HSTS,
X-Frame-Options: DENY,X-Content-Type-Options: nosniff,Referrer-Policy,Permissions-Policyrestrictive
⚡ Performance (mesurée, Lighthouse mobile)
-
97 / 100 / 100 / 100 (performance, accessibilité, bonnes pratiques, SEO)
-
JS initial ≈ 66 Ko gzip ; poids total de l’accueil ≈ 900 Ko ; LCP 2,4 s ; TBT 50 ms ; CLS 0
-
Images distantes optimisées au build (WebP via ImageMagick) : vignettes PeerTube, avatars, médias Mastodon, pochettes Castopod
🛠️ Technologies utilisées
-
⚡ SvelteKit 2 + Svelte 5 (runes) + TypeScript strict
-
📦
@sveltejs/adapter-static(prerender intégral,trailingSlash: 'always') -
📱
vite-plugin-pwa(Workbox, generateSW) -
🖼️ ImageMagick (optimisation des images au prebuild)
-
🎨 CSS vanilla : tokens OKI + styles scopés — pas de framework CSS
-
🔤
@fontsource/archivo+@fontsource/inter(woff2 copiés dansstatic/fonts/)
📁 Structure du projet
├── docs/
│ ├── wireframes/ # Maquettes d'origine (PNG desktop + mobile)
│ ├── DEPLOIEMENT-SVELTEKIT.md # Guide build + déploiement (o2switch, Cloudflare)
│ └── generate-readme-pdf.sh # Export de la doc : README.html / README.pdf
├── scripts/
│ ├── fetch-podcast-cover.mjs # Prebuild : pochettes Castopod optimisées (WebP)
│ ├── optimize-remote-images.mjs # Prebuild : vignettes/avatars/médias optimisés
│ └── postbuild-csp.mjs # Postbuild : CSP SHA-256 par page (balise meta)
├── src/
│ ├── app.html # %lang%, fonts, theme.js, registerSW.js
│ ├── hooks.server.ts # lang fr/en via transformPageChunk
│ ├── params/locale.ts # Matcher du paramètre [[locale]]
│ ├── lib/
│ │ ├── config.ts # ⭐ Toute la configuration d'instance
│ │ ├── types.ts # Types partagés (VideoSummary, Episode, Post…)
│ │ ├── audio-bus.svelte.ts # Un seul flux audio à la fois
│ │ ├── styles/ # oki-tokens.css (charte) + base.css (primitives)
│ │ ├── i18n/ # fr.json, en.json + helpers (locale, hrefFor…)
│ │ ├── server/ # Récupération des données au build :
│ │ │ ├── http.ts # fetch throttlé (429 → retry), sanitisation
│ │ │ ├── peertube.ts # API GADE (vidéos, shorts, live, commentaires)
│ │ │ ├── castopod.ts # Flux RSS KUTE (épisodes)
│ │ │ ├── funkwhale.ts # API MIZIK (morceaux aléatoires locaux)
│ │ │ ├── mastodon.ts # API BOKANTE (timeline)
│ │ │ ├── image-map.ts # Mapping images optimisées (mapImage)
│ │ │ └── image-map.json # (généré au prebuild)
│ │ ├── motion/ # tokens gwoka, action use:reveal
│ │ └── components/ # Seo, Nav, Sidebar, Footer, VideoCard,
│ │ # AudioPlayer, MastodonTimeline, HeroSection,
│ │ # CountdownLock, OfflineIndicator, motion/…
│ └── routes/
│ ├── +layout.ts # prerender = true, trailingSlash
│ ├── +error.svelte # 404 kréyòl (« Paj la pa la »)
│ ├── [[locale=locale]]/ # Toutes les pages, FR (/) et EN (/en/) :
│ │ ├── +page.svelte # accueil (agrégateur)
│ │ ├── video/[uuid]/ # page vidéo (embed, téléchargements, partage)
│ │ ├── categories/[id]/ # page catégorie
│ │ ├── recherche/ # recherche (index JSON + filtrage client)
│ │ ├── direct/ # live + annonce du prochain live
│ │ ├── dons/ # dons (LiberaPay, Ko-fi, Stripe)
│ │ ├── mentions-legales/ # mentions légales
│ │ └── offline/ # page hors-ligne (fallback du SW)
│ ├── search-index.json/ # Index de recherche (bake)
│ └── sitemap.xml/ # Sitemap (bake)
├── static/
│ ├── fonts/ # Archivo/Inter woff2 + fonts.css
│ ├── images/ # logo, favicons, pochettes, images optimisées
│ ├── icons.svg # Sprite SVG (pictos kréyòl + UI)
│ ├── theme.js # Anti-FOUC (thème sombre défaut, clair opt-in)
│ ├── registerSW.js # Enregistrement du Service Worker
│ ├── manifest.webmanifest # Manifest PWA
│ ├── _headers / .htaccess # Headers de sécurité (Cloudflare / Apache)
│ ├── 404.html # 404 statique autonome
│ ├── robots.txt / browserconfig.xml
├── uploads/ # Images d'annonces (non versionné, hors .gitkeep)
├── package.json / vite.config.ts # Adapter, PWA, paths.relative: false
└── README.adoc
📋 Prérequis
-
🟢 Node.js 20+ (recommandé : 22+)
-
🖼️ ImageMagick (
convert) — optimisation des images au prebuild -
🌐 Pour le développement : un accès réseau aux instances (les données sont fetchées au build)
# Ubuntu / Debian
sudo apt-get install imagemagick
# Fedora / RHEL / CentOS
sudo dnf install ImageMagick
🚀 Installation et développement
-
📥 Clonez le dépôt et basculez sur la branche :
git clone git@labola.o-k-i.net:cedric/annu-kute-ced.git cd annu-kute-ced git checkout svelte -
📦 Installez les dépendances :
npm ci -
🔧 Lancez le serveur de développement :
npm run dev -
🏗️ Buildez le site statique :
npm run build # prebuild (images) → prerender (données) → postbuild (CSP) npm run preview # vérification locale du build
Le dossier build/ contient le site complet, prêt à déposer sur n’importe quel hébergement statique.
✅ Vérifications qualité : npm run check (svelte-check, 0 erreur / 0 warning exigé).
🔄 Rafraîchissement du contenu : les données étant bakées au build, un nouveau build est nécessaire pour afficher les dernières vidéos/épisodes/posts — voir docs/DEPLOIEMENT-SVELTEKIT.md pour un exemple de cron + rsync.
⚙️ Configuration
Toute la configuration d’instance vit dans un seul fichier : src/lib/config.ts (versionné). C’est l’équivalent des anciennes constantes PHP — modifiez-le puis rebuildez.
| Export | Rôle |
|---|---|
|
Nom, domaine ( |
|
Coordonnées des mentions légales (webmaster, hébergeur o2Switch) |
|
URL de l’instance, chaîne, compte live, durée max des shorts, volumes par section, tag « indépendance » |
|
Bannière d’accueil : |
|
Activation, instance, slug(s) de podcast, nombre d’épisodes |
|
Activation, instance MIZIK, nombre de morceaux (pool de 50, sélection aléatoire) |
|
Instance, compte, nombre de posts de la timeline |
|
URLs des réseaux sociaux du header/footer (vide = icône masquée) |
|
Activation, URLs LiberaPay/Ko-fi, liens Stripe (ponctuels/mensuels), montants, devise |
|
Annonce du prochain live : activation, titre, description, date, image, 5 fuseaux horaires |
|
Catégories prioritaires (ordre d’affichage), noms FR/EN, pictos du sprite |
|
Hashtags de la sidebar/footer et de l’accueil |
|
Bloc « À propos » : activation, titre, deux paragraphes, image + légende (masqué tant que |
|
Verrou maintenance : quand |
NOTE : Les libellés FR/EN vivent dans src/lib/i18n/fr.json et en.json (pas dans config.ts).
🧭 Ordre des sections sur la page d’accueil
-
Hero — si
hero.type≠'none'(live, vidéo ou playlist) -
Derniers épisodes (Castopod) — si activé et épisodes disponibles
-
Morceaux (Funkwhale) — si activé et morceaux disponibles
-
Actualités (timeline Mastodon) — si posts disponibles
-
Shorts — carrousel
-
Vidéos récentes
-
Tendances
-
Une section par catégorie prioritaire (seulement si elle contient des vidéos) + lien « Voir plus »
-
Bloc « À propos » (si activé) + aside hashtags populaires
🛡️ Sécurité
Modèle CSP
-
En-tête global (
static/_headerspour Cloudflare Pages,static/.htaccesspour Apache/o2switch) :style-src,img-src,font-src 'self',connect-src 'self',media-src,frame-src https://gade.o-k-i.net,frame-ancestors 'none',base-uri 'self',form-action 'self',object-src 'none'. Seules origines externes : les instances de l’écosystème. -
script-srcpar page :scripts/postbuild-csp.mjscalcule les empreintes SHA-256 des scripts inline de chaque page (script de démarrage SvelteKit + JSON-LD) et les injecte dans une balise<meta http-equiv="Content-Security-Policy">complète. Aucun'unsafe-inline'pour les scripts. -
Anti-FOUC, redirection de langue et enregistrement du SW en fichiers externes (
theme.js,registerSW.js) : aucun script inline non hashé.
Redirections des anciennes URLs (Apache)
Le .htaccess redirige les URLs historiques PHP : index.php → /, video.php?uuid=X → /video/X/, categories.php?id=N → /categories/N/, recherche.php?q=… → /recherche/?q=…, direct.php → /direct/, dons.php → /dons/, mentions-legales.php → /mentions-legales/. Il force aussi HTTPS, sert 404.html et met en cache immutable /_app/ et /fonts/.
📱 Progressive Web App (PWA)
-
📲 Installation native : bouton « Installer » dans le header (événement
beforeinstallprompt) -
🌐 Mode hors ligne : précache du shell (Workbox
generateSW),navigateFallbackvers/offline/ -
📡 Détection de connexion : indicateur visuel en cas de perte réseau
-
🔄 Mise à jour :
autoUpdate+skipWaiting(mise à jour silencieuse au prochain chargement) -
L’enregistrement du SW se fait par le fichier statique
registerSW.js(chemins absolus), jamais par injection du plugin
NOTE : Les flux audio/vidéo des instances ne sont pas interceptés par le SW (requêtes Range) — seuls le shell et les assets locaux sont précachés.
🚀 Déploiement
Voir le guide complet : docs/DEPLOIEMENT-SVELTEKIT.md.
-
o2switch (Apache) : déposer
build/à la racine web ; le.htaccessinclus gère HTTPS, headers, redirections des anciennes URLs et 404 -
Cloudflare Pages : build command
npm run build, outputbuild; le_headersinclus applique la sécurité et le cache -
Rafraîchissement : rebuild planifié (cron + rsync, ou CI avec webhook après publication d’un épisode)
👨💻 Développement et contribution
-
🌿 Créez une branche :
git checkout -b ma-fonctionnalite -
💾 Committez avec des messages atomiques, au format conventionnel :
-
feat: add …,fix: prevent …,docs: update …,refactor: …
-
-
✅ Avant tout commit :
npm run check(0 erreur / 0 warning) etnpm run buildvert -
📤 Poussez :
git push origin ma-fonctionnalite -
🔀 Ouvrez une pull request
Bonnes pratiques du dépôt :
-
Les remontées de bugs et propositions sont les bienvenues sur le dépôt du fork ; les améliorations génériques peuvent être proposées en upstream (FEDIVERSE OKI)
-
src/lib/server/image-map.jsonetstatic/images/remote/sont régénérés au prebuild : pas d’édition manuelle -
Conventions Svelte 5 : runes uniquement,
{#each}à clé unique,onclick(jamaison:click), durées lues depuis les tokens CSS
📄 Exporter la documentation (HTML / PDF)
docs/generate-readme-pdf.sh # README.html + README.pdf
docs/generate-readme-pdf.sh --html # HTML seulement
docs/generate-readme-pdf.sh --pdf # PDF seulement
Prérequis : asciidoctor, chromium et le paquet fonts-noto-color-emoji. La feuille de style docs/docinfo.html est injectée automatiquement via le mécanisme docinfo d’Asciidoctor.
📜 Licence
Copyright © 2025 Cédric Famibelle-Pronzola & ORGANISATION KA INTERNATIONALE
Ce programme est un logiciel libre : vous pouvez le redistribuer et/ou le modifier selon les termes de la licence publique générale GNU Affero publiée par la Free Software Foundation, soit la version 3 de la licence, soit (à votre choix) toute version ultérieure.
Ce programme est distribué dans l’espoir qu’il sera utile, mais SANS AUCUNE GARANTIE ; sans même la garantie implicite de COMMERCIALISATION ou d’ADAPTATION À UN USAGE PARTICULIER. Voir la licence publique générale GNU Affero pour plus de détails (texte intégral dans LICENSE).
🇬🇧 English version
📖 Description
ANNU KUTE CED is the multimedia hub of the podcast of the same name. It brings together in a single place:
-
🎥 the podcast’s PeerTube channel (videos, shorts, live streams);
-
🎙️ its Castopod account (latest episodes, built-in audio playback);
-
🎵 a random selection of Funkwhale tracks (same built-in player);
-
📡 its Mastodon timeline (news and announcements).
This version is a 100% static SvelteKit application (Svelte 5 + TypeScript, adapter-static, full prerender): all instance data is fetched at build time ("baked") and the generated site can be served from any static hosting — including shared hosting like o2switch — with no PHP, no Node, no database. It is also an installable Progressive Web App with offline support, in French and English.
🎯 Mission: provide a single, free and decentralized entry point to discover, listen to and follow the ANNU KUTE CED podcast without relying on Big Tech platforms.
|
Note
|
The historical PHP version is kept on the main branch. This documentation covers the svelte branch.
|
🌳 Project origin
This application is a fork of FEDIVERSE OKI, developed by the ORGANISATION KA INTERNATIONALE (OKI), itself derived from the kaubuntu.re project by the Ka-Ubuntu movement. The original license (GNU AGPL v3) is preserved and respected. The SvelteKit rewrite applies the OKI design system (panafrican tokens, dark theme by default, Kréyòl SVG iconography, "gwoka" motion cadences).
| Item | Detail |
|---|---|
Upstream repository |
https://labola.o-k-i.net/ORGANISATION-KA-INTERNATIONALE/FEDIVERSE-OKI |
This fork’s repository |
|
Production domain |
|
Governance |
Application maintained by OKI; fork carried by the repository owner cedric (Cédric Famibelle-Pronzola) |
License |
GNU Affero General Public License v3 (AGPL-V3) or later |
🔗 Aggregated sources and instances
The hub aggregates the ANNU KUTE CED podcast’s sources at build time:
| Service | Instance | Account / channel |
|---|---|---|
🎥 PeerTube (videos, lives) |
GADE — |
|
🎙️ Castopod (audio episodes) |
KUTE — |
|
🎵 Funkwhale (music) |
MIZIK — |
random tracks from the instance |
📡 Mastodon (timeline) |
BOKANTE — |
All of these sources can be changed in src/lib/config.ts (see Configuration).
✨ Features
🎥 PeerTube videos
-
Recent, trending and per-category videos (configurable)
-
Shorts: dedicated carousel for portrait videos under 3 minutes (touch scrolling)
-
Full video page: embedded PeerTube player, Markdown description, Creative Commons licence badge, comments (read-only), suggested videos
-
Downloads: direct files and HLS playlists, with resolution and size
-
Sharing: copy link, embed code, e-mail, Facebook, X, WhatsApp, LinkedIn, Telegram
-
Full-text search and hashtag search (
#prefix) over a build-generated JSON index
📺 Live streams and announcements
-
Live page (
/direct): embeds the PeerTube live stream when one is running, with autoplay -
Next live announcement: date and time automatically converted for 5 territories (Ma’ohi Nui, Martinique/Guadeloupe, French Guiana, France, Kanaky), custom image
-
Configurable homepage hero section: live, single video, playlist or hidden
🎙️ Podcast and music
-
Castopod: latest episodes via RSS feed, with a built-in audio player (play/pause, auto-advance to the next episode, Media Session API)
-
Funkwhale: random track selection from the instance, using the same built-in player
-
One stream at a time across both players (shared audio bus)
📡 Social networks
-
Mastodon timeline: the 10 latest posts fetched at build time and rendered as semantic HTML (no embedded JS library anymore)
🌍 Internationalization
-
Interface in French (
/) and English (/en/): optional-parameter routing, JSON bundles,hreflang, per-routelangattribute -
Kréyòl as a signature: 404 page ("Paj la pa la"), offline page ("Pa gen koneksyon"), maintenance lock ("An nou tann !")
🎨 OKI design system
-
Dark theme by default (brand identity), light theme as an opt-in persisted in
localStorage, applied without FOUC -
Panafrican tokens (black/gold/green/red), signature flag-bar, self-hosted Archivo and Inter woff2 fonts
-
Zero emoji in the interface: custom SVG sprite (Kréyòl pictograms
ka,lambi,zetwal… + UI icons), no Font Awesome, no CDN -
"Gwoka" motion: scroll-driven KineticText (
animation-timeline: view()), progress bar, syncopated reveal (3+3+2), View Transitions — singleprefers-reduced-motiongate
📱 PWA and user experience
-
Installable Progressive Web App (automatic install button)
-
Offline mode: shell and pages cached via Service Worker (Workbox), dedicated
/offlinepage -
Visual indicator when the connection is lost
-
Fully responsive interface (mobile, tablet, desktop)
💝 Donations, countdown and more
-
Donation page: LiberaPay, Ko-fi and Stripe (one-time and monthly donations, suggested amounts)
-
Maintenance lock: multi-timezone countdown displayed instead of the whole site when enabled at build time, automatic redirect when it ends
-
Configurable "About" block (title, two paragraphs, captioned image)
🔍 SEO and structured data
-
Automatically generated JSON-LD:
WebSite(withSearchAction),VideoObject,CollectionPage,BreadcrumbList,Organization -
Full Open Graph and Twitter Cards meta tags, canonical,
hreflangfr/en/x-default -
Build-generated
sitemap.xml(every page, including videos) androbots.txt
🛡️ Security
-
Strict CSP: global policy in headers (
_headersfor Cloudflare /.htaccessfor Apache) + per-pagescript-srcdelivered in a<meta>tag with SHA-256 hashes of inline scripts (no'unsafe-inline'for scripts) -
Zero third-party requests at load time outside the first-party ecosystem instances (GADE, KUTE, BOKANTE, MIZIK): no Google Fonts, no CDN, no analytics
-
Remote HTML sanitized at build time (Mastodon posts, comments); Markdown descriptions escaped
-
HSTS,
X-Frame-Options: DENY,X-Content-Type-Options: nosniff,Referrer-Policy, restrictivePermissions-Policy
⚡ Performance (measured, mobile Lighthouse)
-
97 / 100 / 100 / 100 (performance, accessibility, best practices, SEO)
-
Initial JS ≈ 66 KB gzip; homepage total weight ≈ 900 KB; LCP 2.4 s; TBT 50 ms; CLS 0
-
Remote images optimized at build time (WebP via ImageMagick): PeerTube thumbnails, avatars, Mastodon media, Castopod covers
🛠️ Tech stack
-
⚡ SvelteKit 2 + Svelte 5 (runes) + strict TypeScript
-
📦
@sveltejs/adapter-static(full prerender,trailingSlash: 'always') -
📱
vite-plugin-pwa(Workbox, generateSW) -
🖼️ ImageMagick (build-time image optimization)
-
🎨 Vanilla CSS: OKI tokens + scoped styles — no CSS framework
-
🔤
@fontsource/archivo+@fontsource/inter(woff2 copied tostatic/fonts/)
📁 Project structure
├── docs/
│ ├── wireframes/ # Original mockups (desktop + mobile PNGs)
│ ├── DEPLOIEMENT-SVELTEKIT.md # Build + deployment guide (o2switch, Cloudflare)
│ └── generate-readme-pdf.sh # Docs export: README.html / README.pdf
├── scripts/
│ ├── fetch-podcast-cover.mjs # Prebuild: optimized Castopod covers (WebP)
│ ├── optimize-remote-images.mjs # Prebuild: optimized thumbnails/avatars/media
│ └── postbuild-csp.mjs # Postbuild: per-page SHA-256 CSP (meta tag)
├── src/
│ ├── app.html # %lang%, fonts, theme.js, registerSW.js
│ ├── hooks.server.ts # fr/en lang via transformPageChunk
│ ├── params/locale.ts # [[locale]] param matcher
│ ├── lib/
│ │ ├── config.ts # ⭐ All instance configuration
│ │ ├── types.ts # Shared types (VideoSummary, Episode, Post…)
│ │ ├── audio-bus.svelte.ts # One audio stream at a time
│ │ ├── styles/ # oki-tokens.css (brand) + base.css (primitives)
│ │ ├── i18n/ # fr.json, en.json + helpers (locale, hrefFor…)
│ │ ├── server/ # Build-time data fetching:
│ │ │ ├── http.ts # throttled fetch (429 → retry), sanitizing
│ │ │ ├── peertube.ts # GADE API (videos, shorts, live, comments)
│ │ │ ├── castopod.ts # KUTE RSS feed (episodes)
│ │ │ ├── funkwhale.ts # MIZIK API (random local tracks)
│ │ │ ├── mastodon.ts # BOKANTE API (timeline)
│ │ │ ├── image-map.ts # Optimized images mapping (mapImage)
│ │ │ └── image-map.json # (generated at prebuild)
│ │ ├── motion/ # gwoka tokens, use:reveal action
│ │ └── components/ # Seo, Nav, Sidebar, Footer, VideoCard,
│ │ # AudioPlayer, MastodonTimeline, HeroSection,
│ │ # CountdownLock, OfflineIndicator, motion/…
│ └── routes/
│ ├── +layout.ts # prerender = true, trailingSlash
│ ├── +error.svelte # Kréyòl 404 ("Paj la pa la")
│ ├── [[locale=locale]]/ # Every page, FR (/) and EN (/en/):
│ │ ├── +page.svelte # home (aggregator)
│ │ ├── video/[uuid]/ # video page (embed, downloads, sharing)
│ │ ├── categories/[id]/ # category page
│ │ ├── recherche/ # search (JSON index + client filtering)
│ │ ├── direct/ # live + next-live announcement
│ │ ├── dons/ # donations (LiberaPay, Ko-fi, Stripe)
│ │ ├── mentions-legales/ # legal notice
│ │ └── offline/ # offline page (SW fallback)
│ ├── search-index.json/ # Search index (baked)
│ └── sitemap.xml/ # Sitemap (baked)
├── static/
│ ├── fonts/ # Archivo/Inter woff2 + fonts.css
│ ├── images/ # logo, favicons, covers, optimized images
│ ├── icons.svg # SVG sprite (Kréyòl pictograms + UI)
│ ├── theme.js # Anti-FOUC (dark default, light opt-in)
│ ├── registerSW.js # Service Worker registration
│ ├── manifest.webmanifest # PWA manifest
│ ├── _headers / .htaccess # Security headers (Cloudflare / Apache)
│ ├── 404.html # Self-contained static 404
│ ├── robots.txt / browserconfig.xml
├── uploads/ # Announcement images (not versioned, except .gitkeep)
├── package.json / vite.config.ts # Adapter, PWA, paths.relative: false
└── README.adoc
📋 Requirements
-
🟢 Node.js 20+ (recommended: 22+)
-
🖼️ ImageMagick (
convert) — build-time image optimization -
🌐 For development: network access to the instances (data is fetched at build time)
# Ubuntu / Debian
sudo apt-get install imagemagick
# Fedora / RHEL / CentOS
sudo dnf install ImageMagick
🚀 Installation and development
-
📥 Clone the repository and switch to the branch:
git clone git@labola.o-k-i.net:cedric/annu-kute-ced.git cd annu-kute-ced git checkout svelte -
📦 Install dependencies:
npm ci -
🔧 Start the development server:
npm run dev -
🏗️ Build the static site:
npm run build # prebuild (images) → prerender (data) → postbuild (CSP) npm run preview # local check of the build
The build/ directory holds the complete site, ready to drop on any static hosting.
✅ Quality gates: npm run check (svelte-check, 0 errors / 0 warnings required).
🔄 Content refresh: since data is baked at build time, a new build is required to display the latest videos/episodes/posts — see docs/DEPLOIEMENT-SVELTEKIT.md for a cron + rsync example.
⚙️ Configuration
All instance configuration lives in a single file: src/lib/config.ts (versioned). It is the equivalent of the old PHP constants — edit it, then rebuild.
| Export | Role |
|---|---|
|
Name, domain ( |
|
Legal notice details (webmaster, o2Switch host) |
|
Instance URL, channel, live account, max shorts duration, per-section volumes, "independence" tag |
|
Homepage banner: |
|
Toggle, instance, podcast slug(s), episode count |
|
Toggle, MIZIK instance, track count (pool of 50, random selection) |
|
Instance, account, timeline post count |
|
Header/footer social network URLs (empty = icon hidden) |
|
Toggle, LiberaPay/Ko-fi URLs, Stripe links (one-time/monthly), amounts, currency |
|
Next live announcement: toggle, title, description, date, image, 5 timezones |
|
Priority categories (display order), FR/EN names, sprite pictograms |
|
Sidebar/footer and homepage hashtags |
|
"About" block: toggle, title, two paragraphs, image + caption (hidden while |
|
Maintenance lock: when |
|
Note
|
Interface texts
FR/EN labels live in src/lib/i18n/fr.json and en.json (not in config.ts).
|
🧭 Homepage section order
-
Hero — if
hero.type≠'none'(live, video or playlist) -
Latest episodes (Castopod) — if enabled and episodes available
-
Tracks (Funkwhale) — if enabled and tracks available
-
News (Mastodon timeline) — if posts available
-
Shorts — carousel
-
Recent videos
-
Trending
-
One section per priority category (only when it has videos) + "See more" link
-
"About" block (if enabled) + popular hashtags aside
🛡️ Security
CSP model
-
Global header (
static/_headersfor Cloudflare Pages,static/.htaccessfor Apache/o2switch):style-src,img-src,font-src 'self',connect-src 'self',media-src,frame-src https://gade.o-k-i.net,frame-ancestors 'none',base-uri 'self',form-action 'self',object-src 'none'. Only external origins: the ecosystem instances. -
Per-page
script-src:scripts/postbuild-csp.mjscomputes SHA-256 hashes of each page’s inline scripts (SvelteKit start script + JSON-LD) and injects them into a full<meta http-equiv="Content-Security-Policy">tag. No'unsafe-inline'for scripts. -
Anti-FOUC, language redirect and SW registration as external files (
theme.js,registerSW.js): no unhashed inline script.
Legacy URL redirects (Apache)
The .htaccess redirects historical PHP URLs: index.php → /, video.php?uuid=X → /video/X/, categories.php?id=N → /categories/N/, recherche.php?q=… → /recherche/?q=…, direct.php → /direct/, dons.php → /dons/, mentions-legales.php → /mentions-legales/. It also forces HTTPS, serves 404.html and sets immutable caching for /_app/ and /fonts/.
📱 Progressive Web App (PWA)
-
📲 Native installation: "Install" button in the header (
beforeinstallpromptevent) -
🌐 Offline mode: shell precache (Workbox
generateSW),navigateFallbackto/offline/ -
📡 Connection detection: visual indicator when the network is lost
-
🔄 Updates:
autoUpdate+skipWaiting(silent update on next load) -
SW registration is done by the static
registerSW.jsfile (absolute paths), never via plugin injection
|
Note
|
Instance audio/video streams are not intercepted by the SW (Range requests) — only the shell and local assets are precached.
|
🚀 Deployment
See the full guide: docs/DEPLOIEMENT-SVELTEKIT.md.
-
o2switch (Apache): drop
build/at the web root; the bundled.htaccesshandles HTTPS, headers, legacy URL redirects and 404 -
Cloudflare Pages: build command
npm run build, outputbuild; the bundled_headersapplies security and caching -
Refresh: scheduled rebuild (cron + rsync, or CI with a webhook after an episode is published)
👨💻 Development and contribution
-
🌿 Create a branch:
git checkout -b my-feature -
💾 Commit with atomic messages, conventional format:
-
feat: add …,fix: prevent …,docs: update …,refactor: …
-
-
✅ Before any commit:
npm run check(0 errors / 0 warnings) and a greennpm run build -
📤 Push:
git push origin my-feature -
🔀 Open a pull request
Repository best practices:
-
Bug reports and proposals are welcome on the fork’s repository; generic improvements can be proposed upstream (FEDIVERSE OKI)
-
src/lib/server/image-map.jsonandstatic/images/remote/are regenerated at prebuild: no manual editing -
Svelte 5 conventions: runes only, uniquely keyed
{#each},onclick(neveron:click), durations read from CSS tokens
📄 Exporting the documentation (HTML / PDF)
docs/generate-readme-pdf.sh # README.html + README.pdf
docs/generate-readme-pdf.sh --html # HTML only
docs/generate-readme-pdf.sh --pdf # PDF only
Requirements: asciidoctor, chromium and the fonts-noto-color-emoji package. The docs/docinfo.html stylesheet is automatically injected via Asciidoctor’s docinfo mechanism.
📜 License
Copyright © 2025 Cédric Famibelle-Pronzola & ORGANISATION KA INTERNATIONALE
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details (full text in LICENSE).