= 🎙️ ANNU KUTE CED — Hub multimédia du podcast (SvelteKit)
:toc: left
:toc-title: Sommaire / Contents
:toclevels: 3

[[top]]
🌍 *Hub multimédia du podcast ANNU KUTE CED — version SvelteKit statique* +
🌍 *Multimedia hub for the ANNU KUTE CED podcast — static SvelteKit version*

[.text-center]
*Choisissez votre langue / Choose your language :* +
<<fr,🇫🇷 Version française>> · <<en,🇬🇧 English version>>

'''

[[fr]]
== 🇫🇷 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 https://labola.o-k-i.net/ORGANISATION-KA-INTERNATIONALE/FEDIVERSE-OKI[*FEDIVERSE OKI*^], développée par l'https://o-k-i.net[ORGANISATION KA INTERNATIONALE (OKI)^], elle-même issue du projet https://codeberg.org/Ka-Ubuntu/kaubuntu.re[kaubuntu.re^] du mouvement https://kaubuntu.com[Ka-Ubuntu^]. La licence d'origine (GNU AGPL v3) est conservée et respectée. La refonte SvelteKit applique la https://o-k-i.net[charte OKI] (tokens panafricains, thème sombre par défaut, iconographie SVG kréyòl, cadences de motion « gwoka »).

[cols="1,3",options="header"]
|===
| É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
| `git@labola.o-k-i.net:cedric/annu-kute-ced.git` (branche `svelte`)

| Domaine de production
| https://annukuteced.buzz

| Gouvernance
| Application maintenue par OKI ; fork porté par le propriétaire du dépôt *cedric* (Cédric Famibelle-Pronzola)

| Licence
| https://www.gnu.org/licenses/agpl-3.0.html[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 :

[cols="1,2,3",options="header"]
|===
| Service | Instance | Compte / chaîne

| 🎥 PeerTube (vidéos, lives)
| GADE — `https://gade.o-k-i.net`
| https://gade.o-k-i.net/c/annu_kute_ced/videos[`annu_kute_ced`^]

| 🎙️ Castopod (épisodes audio)
| KUTE — `https://kute.o-k-i.net`
| https://kute.o-k-i.net/@annu_kute_cedric[`@annu_kute_cedric`^]

| 🎵 Funkwhale (musique)
| MIZIK — `https://mizik.o-k-i.net`
| morceaux aléatoires de l'instance

| 📡 Mastodon (timeline)
| BOKANTE — `https://bokante.o-k-i.net`
| https://bokante.o-k-i.net/@cedric[`@cedric`^]
|===

Toutes ces sources sont modifiables dans `src/lib/config.ts` (voir <<fr-configuration,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`, attribut `lang` par 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 unique `prefers-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 `/offline` dé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` (avec `SearchAction`), `VideoObject`, `CollectionPage`, `BreadcrumbList`, `Organization`
- Balises *Open Graph* complètes et *Twitter Cards*, canonical, `hreflang` fr/en/x-default
- `sitemap.xml` *généré au build* (toutes les pages, vidéos comprises) et `robots.txt`

==== 🛡️ Sécurité

- *CSP stricte* : politique globale en en-tête (`_headers` Cloudflare / `.htaccess` Apache) + `script-src` dé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-Policy` restrictive

==== ⚡ 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 dans `static/fonts/`)

=== 📁 Structure du projet

[source]
----
├── 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)

.Installation d'ImageMagick
[source,bash]
----
# 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 :
+
[source,bash]
----
git clone git@labola.o-k-i.net:cedric/annu-kute-ced.git
cd annu-kute-ced
git checkout svelte
----
. 📦 Installez les dépendances :
+
[source,bash]
----
npm ci
----
. 🔧 Lancez le serveur de développement :
+
[source,bash]
----
npm run dev
----
. 🏗️ Buildez le site statique :
+
[source,bash]
----
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.

[[fr-configuration]]
=== ⚙️ 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.

[cols="1,3",options="header"]
|===
| Export | Rôle

| `site`
| Nom, domaine (`baseUrl`), description, e-mail de contact, URLs licence/source, fuseau par défaut

| `legal`
| Coordonnées des mentions légales (webmaster, hébergeur o2Switch)

| `peertube`
| URL de l'instance, chaîne, compte live, durée max des shorts, volumes par section, tag « indépendance »

| `hero`
| Bannière d'accueil : `type` (`'live'`, `'video'`, `'playlist'`, `'none'`), vidéo/playlist associée

| `castopod`
| Activation, instance, slug(s) de podcast, nombre d'épisodes

| `funkwhale`
| Activation, instance MIZIK, nombre de morceaux (pool de 50, sélection aléatoire)

| `mastodon`
| Instance, compte, nombre de posts de la timeline

| `social`
| URLs des réseaux sociaux du header/footer (vide = icône masquée)

| `donations`
| Activation, URLs LiberaPay/Ko-fi, liens Stripe (ponctuels/mensuels), montants, devise

| `nextLive`
| Annonce du prochain live : activation, titre, description, date, image, 5 fuseaux horaires

| `categories` / `categoryNames` / `categoryIcons`
| Catégories prioritaires (ordre d'affichage), noms FR/EN, pictos du sprite

| `importantTags` / `popularTags`
| Hashtags de la sidebar/footer et de l'accueil

| `about`
| Bloc « À propos » : activation, titre, deux paragraphes, image + légende (masqué tant que `enabled: false`)

| `countdown`
| *Verrou maintenance* : quand `enabled: true`, tout le site buildé affiche le compte à rebours multi-fuseaux + date cible
|===

.Les textes d'interface
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

[[fr-securite]]
=== 🛡️ Sécurité

==== Modèle CSP

- *En-tête global* (`static/_headers` pour Cloudflare Pages, `static/.htaccess` pour 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-src` par page* : `scripts/postbuild-csp.mjs` calcule 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`), `navigateFallback` vers `/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 `.htaccess` inclus gère HTTPS, headers, redirections des anciennes URLs et 404
- *Cloudflare Pages* : build command `npm run build`, output `build` ; le `_headers` inclus 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) et `npm run build` vert
. 📤 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.json` et `static/images/remote/` sont régénérés au prebuild : pas d'édition manuelle
- Conventions Svelte 5 : runes uniquement, `{#each}` à clé unique, `onclick` (jamais `on:click`), durées lues depuis les tokens CSS

==== 📄 Exporter la documentation (HTML / PDF)

[source,bash]
----
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 (C) 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 https://www.gnu.org/licenses/agpl-3.0.html[licence publique générale GNU Affero^] pour plus de détails (texte intégral dans `LICENSE`).

=== 📞 Contact

Pour toute question ou suggestion concernant cette plateforme : mailto:kontak@o-k-i.net[kontak@o-k-i.net]

<<top,⬆ Retour au sommaire>>

'''

[[en]]
== 🇬🇧 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 https://labola.o-k-i.net/ORGANISATION-KA-INTERNATIONALE/FEDIVERSE-OKI[*FEDIVERSE OKI*^], developed by the https://o-k-i.net[ORGANISATION KA INTERNATIONALE (OKI)^], itself derived from the https://codeberg.org/Ka-Ubuntu/kaubuntu.re[kaubuntu.re^] project by the https://kaubuntu.com[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).

[cols="1,3",options="header"]
|===
| Item | Detail

| Upstream repository
| https://labola.o-k-i.net/ORGANISATION-KA-INTERNATIONALE/FEDIVERSE-OKI

| This fork's repository
| `git@labola.o-k-i.net:cedric/annu-kute-ced.git` (`svelte` branch)

| Production domain
| https://annukuteced.buzz

| Governance
| Application maintained by OKI; fork carried by the repository owner *cedric* (Cédric Famibelle-Pronzola)

| License
| https://www.gnu.org/licenses/agpl-3.0.html[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:

[cols="1,2,3",options="header"]
|===
| Service | Instance | Account / channel

| 🎥 PeerTube (videos, lives)
| GADE — `https://gade.o-k-i.net`
| https://gade.o-k-i.net/c/annu_kute_ced/videos[`annu_kute_ced`^]

| 🎙️ Castopod (audio episodes)
| KUTE — `https://kute.o-k-i.net`
| https://kute.o-k-i.net/@annu_kute_cedric[`@annu_kute_cedric`^]

| 🎵 Funkwhale (music)
| MIZIK — `https://mizik.o-k-i.net`
| random tracks from the instance

| 📡 Mastodon (timeline)
| BOKANTE — `https://bokante.o-k-i.net`
| https://bokante.o-k-i.net/@cedric[`@cedric`^]
|===

All of these sources can be changed in `src/lib/config.ts` (see <<en-configuration,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-route `lang` attribute
- 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 — single `prefers-reduced-motion` gate

==== 📱 PWA and user experience

- Installable *Progressive Web App* (automatic install button)
- Offline mode: shell and pages cached via Service Worker (Workbox), dedicated `/offline` page
- *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` (with `SearchAction`), `VideoObject`, `CollectionPage`, `BreadcrumbList`, `Organization`
- Full *Open Graph* and *Twitter Cards* meta tags, canonical, `hreflang` fr/en/x-default
- Build-generated `sitemap.xml` (every page, including videos) and `robots.txt`

==== 🛡️ Security

- *Strict CSP*: global policy in headers (`_headers` for Cloudflare / `.htaccess` for Apache) + per-page `script-src` delivered 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`, restrictive `Permissions-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 to `static/fonts/`)

=== 📁 Project structure

[source]
----
├── 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)

.Installing ImageMagick
[source,bash]
----
# 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:
+
[source,bash]
----
git clone git@labola.o-k-i.net:cedric/annu-kute-ced.git
cd annu-kute-ced
git checkout svelte
----
. 📦 Install dependencies:
+
[source,bash]
----
npm ci
----
. 🔧 Start the development server:
+
[source,bash]
----
npm run dev
----
. 🏗️ Build the static site:
+
[source,bash]
----
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.

[[en-configuration]]
=== ⚙️ 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.

[cols="1,3",options="header"]
|===
| Export | Role

| `site`
| Name, domain (`baseUrl`), description, contact e-mail, license/source URLs, default timezone

| `legal`
| Legal notice details (webmaster, o2Switch host)

| `peertube`
| Instance URL, channel, live account, max shorts duration, per-section volumes, "independence" tag

| `hero`
| Homepage banner: `type` (`'live'`, `'video'`, `'playlist'`, `'none'`), associated video/playlist

| `castopod`
| Toggle, instance, podcast slug(s), episode count

| `funkwhale`
| Toggle, MIZIK instance, track count (pool of 50, random selection)

| `mastodon`
| Instance, account, timeline post count

| `social`
| Header/footer social network URLs (empty = icon hidden)

| `donations`
| Toggle, LiberaPay/Ko-fi URLs, Stripe links (one-time/monthly), amounts, currency

| `nextLive`
| Next live announcement: toggle, title, description, date, image, 5 timezones

| `categories` / `categoryNames` / `categoryIcons`
| Priority categories (display order), FR/EN names, sprite pictograms

| `importantTags` / `popularTags`
| Sidebar/footer and homepage hashtags

| `about`
| "About" block: toggle, title, two paragraphs, image + caption (hidden while `enabled: false`)

| `countdown`
| *Maintenance lock*: when `enabled: true`, the whole built site shows the multi-timezone countdown + target date
|===

.Interface texts
NOTE: 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

[[en-securite]]
=== 🛡️ Security

==== CSP model

- *Global header* (`static/_headers` for Cloudflare Pages, `static/.htaccess` for 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.mjs` computes 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 (`beforeinstallprompt` event)
- 🌐 *Offline mode*: shell precache (Workbox `generateSW`), `navigateFallback` to `/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.js` file (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 `.htaccess` handles HTTPS, headers, legacy URL redirects and 404
- *Cloudflare Pages*: build command `npm run build`, output `build`; the bundled `_headers` applies 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 green `npm 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.json` and `static/images/remote/` are regenerated at prebuild: no manual editing
- Svelte 5 conventions: runes only, uniquely keyed `{#each}`, `onclick` (never `on:click`), durations read from CSS tokens

==== 📄 Exporting the documentation (HTML / PDF)

[source,bash]
----
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 (C) 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 https://www.gnu.org/licenses/agpl-3.0.html[GNU Affero General Public License^] for more details (full text in `LICENSE`).

=== 📞 Contact

For any question or suggestion about this platform: mailto:kontak@o-k-i.net[kontak@o-k-i.net]

<<top,⬆ Back to top>>
