From db5a997e501dd8033b002efc8036ccc5cc8b313f Mon Sep 17 00:00:00 2001 From: sucupira Date: Fri, 10 Jul 2026 12:51:17 -0400 Subject: [PATCH] feat(oki): fondations de la migration vers la stack souveraine OKI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Architecture décidée et documentée dans ADR.md : SvelteKit natif en fichiers plats plutôt que Strapi (55 outils, données statiques, souveraineté et budget performance prioritaires — doctrine §1.2, §4.4). - frontend/ : SvelteKit 2 + Svelte 5 (runes), adapter-static, 58 pages prérendues, CSS natif en @layer (reset→tokens→base→layouts→components), tokens oklch(), container queries, typo fluide clamp(), 2 polices WOFF2 auto-hébergées (Atkinson Hyperlegible, Fraunces), zéro CDN tiers - i18n FR/gcf typé (clé manquante = erreur de build), bascule Kréyòl persistée en localStorage, micro-textes créoles (« Ou pa manké ayen ») - Composants : ToolCard, DifficultyBadge (forme+texte+couleur, jamais la couleur seule), CategoryNav (≤5 entrées, divulgation progressive), SearchBar (raccourci /), LanguageSwitcher, EndMarker - content/ : 55 fiches migrées depuis src/ (script frontend/scripts/ migrate-tools.mjs, idempotent), licences SPDX reprises de directory.json - docs/ : DESIGN_SYSTEM.md et CONTENT_GUIDE.md Budgets vérifiés : JS 41 Ko gzip, CSS 2,9 Ko gzip, svelte-check 0 erreur. Co-Authored-By: Claude Fable 5 --- .claude/launch.json | 11 + .gitignore | 4 + ADR.md | 83 ++ content/categories.json | 92 ++ content/outils/aegis.md | 42 + content/outils/alias.md | 36 + content/outils/backups.md | 42 + content/outils/bitchat.md | 34 + content/outils/bitcoin.md | 38 + content/outils/bitwarden.md | 43 + content/outils/brave.md | 44 + content/outils/briar.md | 42 + content/outils/cloudai.md | 28 + content/outils/cloudflare.md | 30 + content/outils/cryptomator.md | 41 + content/outils/databrokers.md | 41 + content/outils/element.md | 39 + content/outils/fediverse.md | 25 + content/outils/firefox.md | 42 + content/outils/grapheneos.md | 50 + content/outils/ivpn.md | 40 + content/outils/keepassxc.md | 38 + content/outils/linux.md | 53 + content/outils/localai.md | 46 + content/outils/mailbox.md | 33 + content/outils/maps.md | 37 + content/outils/mastodon.md | 41 + content/outils/molly.md | 39 + content/outils/monero.md | 39 + content/outils/mullvad.md | 62 + content/outils/mullvadbrowser.md | 41 + content/outils/mullvaddns.md | 40 + content/outils/nc-storage.md | 25 + content/outils/nextcloud.md | 29 + content/outils/nostr.md | 39 + content/outils/notes.md | 37 + content/outils/photos.md | 41 + content/outils/pihole.md | 39 + content/outils/pocketpal.md | 41 + content/outils/protondrive.md | 32 + content/outils/protonmail.md | 43 + content/outils/protonpass.md | 25 + content/outils/protonvpn.md | 40 + content/outils/quad9.md | 38 + content/outils/qubes.md | 27 + content/outils/search.md | 39 + content/outils/session.md | 40 + content/outils/signal.md | 43 + content/outils/simplex.md | 43 + content/outils/synapse.md | 27 + content/outils/tails.md | 36 + content/outils/tailscale.md | 43 + content/outils/tor.md | 45 + content/outils/tuta.md | 40 + content/outils/ublock.md | 36 + content/outils/visio.md | 38 + content/outils/website.md | 44 + content/outils/yubikey.md | 39 + content/outils/yunohost.md | 43 + docs/CONTENT_GUIDE.md | 76 ++ docs/DESIGN_SYSTEM.md | 72 ++ frontend/README.md | 40 + frontend/package.json | 31 + frontend/pnpm-lock.yaml | 1076 +++++++++++++++++ frontend/scripts/migrate-tools.mjs | 149 +++ frontend/src/app.css | 14 + frontend/src/app.d.ts | 12 + frontend/src/app.html | 15 + .../src/lib/components/CategoryNav.svelte | 101 ++ .../src/lib/components/DifficultyBadge.svelte | 36 + frontend/src/lib/components/EndMarker.svelte | 34 + .../lib/components/LanguageSwitcher.svelte | 40 + frontend/src/lib/components/SearchBar.svelte | 43 + frontend/src/lib/components/ToolCard.svelte | 99 ++ frontend/src/lib/content/tools.server.ts | 117 ++ frontend/src/lib/i18n/fr.json | 63 + frontend/src/lib/i18n/gcf.json | 63 + frontend/src/lib/i18n/index.svelte.ts | 54 + frontend/src/lib/i18n/locales.ts | 11 + frontend/src/routes/+layout.svelte | 108 ++ frontend/src/routes/+layout.ts | 3 + frontend/src/routes/+page.server.ts | 11 + frontend/src/routes/+page.svelte | 92 ++ frontend/src/routes/outils/+page.server.ts | 10 + frontend/src/routes/outils/+page.svelte | 136 +++ .../src/routes/outils/[slug]/+page.server.ts | 14 + .../src/routes/outils/[slug]/+page.svelte | 132 ++ frontend/src/styles/base.css | 99 ++ frontend/src/styles/fonts.css | 20 + frontend/src/styles/layouts.css | 63 + frontend/src/styles/reset.css | 54 + frontend/src/styles/tokens.css | 77 ++ frontend/static/favicon.png | Bin 0 -> 65703 bytes ...kinson-hyperlegible-latin-400-normal.woff2 | Bin 0 -> 17208 bytes .../fonts/fraunces-latin-700-normal.woff2 | Bin 0 -> 18212 bytes frontend/static/site.webmanifest | 18 + frontend/svelte.config.js | 19 + frontend/tsconfig.json | 14 + frontend/vite.config.ts | 12 + 99 files changed, 5356 insertions(+) create mode 100644 .claude/launch.json create mode 100644 ADR.md create mode 100644 content/categories.json create mode 100644 content/outils/aegis.md create mode 100644 content/outils/alias.md create mode 100644 content/outils/backups.md create mode 100644 content/outils/bitchat.md create mode 100644 content/outils/bitcoin.md create mode 100644 content/outils/bitwarden.md create mode 100644 content/outils/brave.md create mode 100644 content/outils/briar.md create mode 100644 content/outils/cloudai.md create mode 100644 content/outils/cloudflare.md create mode 100644 content/outils/cryptomator.md create mode 100644 content/outils/databrokers.md create mode 100644 content/outils/element.md create mode 100644 content/outils/fediverse.md create mode 100644 content/outils/firefox.md create mode 100644 content/outils/grapheneos.md create mode 100644 content/outils/ivpn.md create mode 100644 content/outils/keepassxc.md create mode 100644 content/outils/linux.md create mode 100644 content/outils/localai.md create mode 100644 content/outils/mailbox.md create mode 100644 content/outils/maps.md create mode 100644 content/outils/mastodon.md create mode 100644 content/outils/molly.md create mode 100644 content/outils/monero.md create mode 100644 content/outils/mullvad.md create mode 100644 content/outils/mullvadbrowser.md create mode 100644 content/outils/mullvaddns.md create mode 100644 content/outils/nc-storage.md create mode 100644 content/outils/nextcloud.md create mode 100644 content/outils/nostr.md create mode 100644 content/outils/notes.md create mode 100644 content/outils/photos.md create mode 100644 content/outils/pihole.md create mode 100644 content/outils/pocketpal.md create mode 100644 content/outils/protondrive.md create mode 100644 content/outils/protonmail.md create mode 100644 content/outils/protonpass.md create mode 100644 content/outils/protonvpn.md create mode 100644 content/outils/quad9.md create mode 100644 content/outils/qubes.md create mode 100644 content/outils/search.md create mode 100644 content/outils/session.md create mode 100644 content/outils/signal.md create mode 100644 content/outils/simplex.md create mode 100644 content/outils/synapse.md create mode 100644 content/outils/tails.md create mode 100644 content/outils/tailscale.md create mode 100644 content/outils/tor.md create mode 100644 content/outils/tuta.md create mode 100644 content/outils/ublock.md create mode 100644 content/outils/visio.md create mode 100644 content/outils/website.md create mode 100644 content/outils/yubikey.md create mode 100644 content/outils/yunohost.md create mode 100644 docs/CONTENT_GUIDE.md create mode 100644 docs/DESIGN_SYSTEM.md create mode 100644 frontend/README.md create mode 100644 frontend/package.json create mode 100644 frontend/pnpm-lock.yaml create mode 100644 frontend/scripts/migrate-tools.mjs create mode 100644 frontend/src/app.css create mode 100644 frontend/src/app.d.ts create mode 100644 frontend/src/app.html create mode 100644 frontend/src/lib/components/CategoryNav.svelte create mode 100644 frontend/src/lib/components/DifficultyBadge.svelte create mode 100644 frontend/src/lib/components/EndMarker.svelte create mode 100644 frontend/src/lib/components/LanguageSwitcher.svelte create mode 100644 frontend/src/lib/components/SearchBar.svelte create mode 100644 frontend/src/lib/components/ToolCard.svelte create mode 100644 frontend/src/lib/content/tools.server.ts create mode 100644 frontend/src/lib/i18n/fr.json create mode 100644 frontend/src/lib/i18n/gcf.json create mode 100644 frontend/src/lib/i18n/index.svelte.ts create mode 100644 frontend/src/lib/i18n/locales.ts create mode 100644 frontend/src/routes/+layout.svelte create mode 100644 frontend/src/routes/+layout.ts create mode 100644 frontend/src/routes/+page.server.ts create mode 100644 frontend/src/routes/+page.svelte create mode 100644 frontend/src/routes/outils/+page.server.ts create mode 100644 frontend/src/routes/outils/+page.svelte create mode 100644 frontend/src/routes/outils/[slug]/+page.server.ts create mode 100644 frontend/src/routes/outils/[slug]/+page.svelte create mode 100644 frontend/src/styles/base.css create mode 100644 frontend/src/styles/fonts.css create mode 100644 frontend/src/styles/layouts.css create mode 100644 frontend/src/styles/reset.css create mode 100644 frontend/src/styles/tokens.css create mode 100644 frontend/static/favicon.png create mode 100644 frontend/static/fonts/atkinson-hyperlegible-latin-400-normal.woff2 create mode 100644 frontend/static/fonts/fraunces-latin-700-normal.woff2 create mode 100644 frontend/static/site.webmanifest create mode 100644 frontend/svelte.config.js create mode 100644 frontend/tsconfig.json create mode 100644 frontend/vite.config.ts diff --git a/.claude/launch.json b/.claude/launch.json new file mode 100644 index 0000000..8c5b7b6 --- /dev/null +++ b/.claude/launch.json @@ -0,0 +1,11 @@ +{ + "version": "0.0.1", + "configurations": [ + { + "name": "oki-preview", + "runtimeExecutable": "pnpm", + "runtimeArgs": ["--dir", "frontend", "preview", "--port", "4173"], + "port": 4173 + } + ] +} diff --git a/.gitignore b/.gitignore index e879e9f..a752cad 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,7 @@ playwright-report/ test-results/ *.local .DS_Store + +# SvelteKit (frontend/) +.svelte-kit/ +frontend/build/ diff --git a/ADR.md b/ADR.md new file mode 100644 index 0000000..20abad0 --- /dev/null +++ b/ADR.md @@ -0,0 +1,83 @@ +# ADR-001 — Backend : SvelteKit natif (fichiers plats), pas de Strapi + +**Statut** : accepté · **Date** : 2026-07-10 · **Décideur** : migration OKI d'exitchatcontrol.org +**Doctrine de référence** : `ui-ux-souveraine-oki.md` (citée « doctrine §x.y » ci-dessous) + +## Contexte + +exitchatcontrol.org est un annuaire de ~55 outils libres répartis en 23 sections +thématiques, actuellement généré statiquement par Astro à partir de fichiers +versionnés (`src/data/*.json`, `src/content/sections/*/*.mdx`). Le déploiement +cible est un conteneur nginx auto-hébergé (YunoHost/Docker), sans backend. + +Deux options étaient sur la table (mission §2.1) : + +- **Option A** : Strapi v5 headless, base relationnelle, admin web, API, i18n plugin. +- **Option B** : SvelteKit full-stack, contenu en fichiers Markdown/JSON versionnés, + parsés au build, site prérendu. + +## Décision + +**Option B — SvelteKit 2 + Svelte 5, contenu en fichiers plats prérendus.** + +## Justification par la règle de décision + +La règle imposée : minimiser la charge cognitive **extrinsèque** (doctrine §1.2, +Sweller) pour l'utilisateur final ET l'administrateur, dans le budget performance +(doctrine §4.4). + +1. **Les données sont déjà statiques et versionnées.** 55 outils, mises à jour + par pull request espacées de plusieurs jours/semaines (historique git). Le seuil + « le volume de données justifie une base relationnelle » posé comme condition + de l'option A n'est pas atteint — 55 fiches tiennent dans un dossier de + fichiers Markdown lisibles à l'œil nu. +2. **Charge d'administration.** Strapi = un service Node persistant + une base + + des migrations de schéma + des mises à jour de sécurité + des sauvegardes. + Pour l'admin OKI, c'est de la charge extrinsèque pure : la tâche réelle + (« corriger une fiche ») devient « se connecter à un admin, naviguer un CMS, + publier, invalider un cache ». En fichiers plats : éditer un fichier, commit. + Le git devient l'historique éditorial, la revue par PR devient la modération. +3. **Budget performance (doctrine §4.4).** Site 100 % prérendu : premier rendu + = HTML servi par nginx, zéro requête API, JS initial minimal (hydratation + sélective). Strapi n'aiderait pas mais ajouterait un point de latence et de + panne si l'on cédait à la tentation du rendu à la demande. +4. **Souveraineté et surface d'attaque.** Un site statique n'a pas de CVE de CMS, + pas de compte admin à voler, pas de base à exfiltrer. C'est la posture + cohérente avec le sujet même du site (OPSEC, résistance à la censure) : + le site doit être **mirrorable** (un tarball du build suffit), ce qu'un + Strapi rend impossible. +5. **Loi de Tesler (doctrine §1.3)** : la complexité éditoriale n'est pas + supprimée, elle est déplacée vers le format de fichier. On l'absorbe par un + frontmatter strict + validation au build (le build échoue si une fiche est + invalide — équivalent des types du CMS, sans le CMS). + +### Ce qui ferait rebasculer vers Strapi (critères de réversibilité) + +- Une équipe éditoriale non-technique de plusieurs personnes publiant chaque semaine ; +- Des soumissions communautaires en volume nécessitant un workflow draft/publish ; +- Plus de ~500 fiches ou des relations réellement complexes. + +Dans ce cas, le modèle de données de la mission §3 s'applique tel quel ; le +frontend SvelteKit resterait inchangé (il consommerait l'API au build). +Étape intermédiaire possible sans backend : Decap CMS (git-based) sur le dossier +`content/` — écarté pour l'instant, « préférer la solution la plus simple ». + +## Décisions techniques associées + +- **adapter-static + prerender total** : parité avec le déploiement nginx actuel + (Dockerfile conservé dans l'esprit). Un lien fonctionne sans JS (doctrine §4.4). +- **Markdown + frontmatter maison** : parseur frontmatter minimal (~40 lignes, + zéro dépendance) + `marked` (MIT) pour le corps, exécuté **au build uniquement** + — rien de tout cela n'est expédié au client. +- **i18n maison typé** (pattern repris du projet source) plutôt que + Paraglide/inlang : ~40 lignes, dictionnaires JSON `fr`/`gcf` vérifiés par le + compilateur TypeScript (une clé manquante = erreur de build). Paraglide reste + la piste si le nombre de locales croît. +- **Langues** : `fr` (défaut) + `gcf` (créole guadeloupéen, code ISO 639-3 + enregistré BCP 47 valide). Micro-textes créoles d'abord (doctrine §4.2 : + impact affectif disproportionné pour un coût faible) ; traduction intégrale + des fiches = chantier éditorial séparé (voir `docs/CONTENT_GUIDE.md`). +- **CSS natif en `@layer`** : `reset → tokens → base → layouts → components → + utilities` (doctrine §5.2), tokens `oklch()`, container queries, `clamp()`. + Pas de Tailwind (doctrine §5.2 option 4 : écarté en connaissance de cause). +- **Licence du code** : MIT, comme le projet source (continuité). diff --git a/content/categories.json b/content/categories.json new file mode 100644 index 0000000..90039da --- /dev/null +++ b/content/categories.json @@ -0,0 +1,92 @@ +[ + { + "slug": "messagerie", + "title": "Messagerie chiffrée", + "order": 3, + "part": 1 + }, + { + "slug": "email", + "title": "E-mail chiffré & PGP", + "order": 4, + "part": 2 + }, + { + "slug": "navigateur", + "title": "Navigateur & recherche", + "order": 5, + "part": 3 + }, + { + "slug": "dns", + "title": "DNS chiffré & Cloudflare", + "order": 6, + "part": 3 + }, + { + "slug": "vpn", + "title": "VPN : la vérité", + "order": 7, + "part": 4 + }, + { + "slug": "stockage", + "title": "Stockage chiffré", + "order": 10, + "part": 6 + }, + { + "slug": "motsdepasse", + "title": "Gestionnaire de mots de passe", + "order": 11, + "part": 7 + }, + { + "slug": "deuxfa", + "title": "Double authentification & clés matérielles", + "order": 12, + "part": 7 + }, + { + "slug": "social", + "title": "Réseaux sociaux décentralisés", + "order": 13, + "part": 8 + }, + { + "slug": "argent", + "title": "Souveraineté financière", + "order": 14, + "part": 9 + }, + { + "slug": "ia", + "title": "\"L'IA conversationnelle\"", + "order": 15, + "part": 9 + }, + { + "slug": "boiteaoutils", + "title": "La boîte à outils du quotidien", + "order": 16, + "part": 9 + }, + { + "slug": "selfhost", + "title": "Auto-hébergement", + "order": 17, + "part": 10 + }, + { + "slug": "tor", + "title": "Tor", + "order": 18, + "part": 11 + }, + { + "slug": "os", + "title": "\"Systèmes d'exploitation libres\"", + "order": 19, + "part": 12 + } +] diff --git a/content/outils/aegis.md b/content/outils/aegis.md new file mode 100644 index 0000000..7460197 --- /dev/null +++ b/content/outils/aegis.md @@ -0,0 +1,42 @@ +--- +name: "Aegis · Ente Auth" +slug: "aegis" +category: "deuxfa" +difficulty: "beginner" +license: "GPL-3.0" +url: "https://getaegis.app" +color: "#2c5c9c" +monogram: "Ae" +iconSlug: "aegisauthenticator" +tags: ["Codes TOTP","🌍 · open-source"] +lang: "fr" +--- + +## what + +Des applications qui génèrent les codes à six chiffres qui changent toutes les 30 secondes, en + remplacement de Google Authenticator. + +## why + +Aegis (Android) garde vos codes dans un coffre chiffré local, sans cloud.{' '} + Ente Auth ajoute une synchronisation chiffrée de bout en bout entre appareils, et a été + audité. Bien plus sûrs que le SMS, et gratuits. + +## who + +Tout le monde : c'est le niveau de 2FA à adopter par défaut, avant les clés matérielles. + +## install + +
    +
  1. Installez Aegis (F-Droid) ou Ente Auth (toutes plateformes).
  2. +
  3. + Sur chaque compte, activez la 2FA « application d'authentification » et scannez le QR code + affiché. +
  4. +
  5. + Notez les codes de secours sur papier et faites une sauvegarde chiffrée de votre + coffre. +
  6. +
diff --git a/content/outils/alias.md b/content/outils/alias.md new file mode 100644 index 0000000..fdf8b21 --- /dev/null +++ b/content/outils/alias.md @@ -0,0 +1,36 @@ +--- +name: "SimpleLogin · addy.io" +slug: "alias" +category: "boiteaoutils" +difficulty: "beginner" +url: "https://simplelogin.io" +color: "#c0392b" +monogram: "@" +iconSlug: "simplelogin" +tags: ["Alias e-mail","🌍 · open-source"] +lang: "fr" +--- + +## what + +Des alias e-mail jetables : une adresse unique par site, qui redirige vers votre vraie boîte + sans jamais la révéler. + +## why + +Vous ne donnez plus votre vraie adresse partout. Si un site est piraté ou vous spamme, vous + coupez l'alias correspondant, et vous savez immédiatement qui a fait fuiter vos données.{' '} + SimpleLogin est intégré à Proton ; addy.io propose des alias illimités dès l'offre + gratuite. + +## who + +Tout le monde, et particulièrement les comptes pseudonymes qui veulent cloisonner leurs + inscriptions. + +## install + +
    +
  1. Créez un compte, installez l'extension de navigateur.
  2. +
  3. À chaque inscription, générez un nouvel alias au lieu de saisir votre vraie adresse.
  4. +
diff --git a/content/outils/backups.md b/content/outils/backups.md new file mode 100644 index 0000000..ac5a351 --- /dev/null +++ b/content/outils/backups.md @@ -0,0 +1,42 @@ +--- +name: "Sauvegardes chiffrées" +slug: "backups" +category: "boiteaoutils" +difficulty: "intermediate" +url: "https://cryptomator.org" +color: "#5a6570" +monogram: "Bk" +iconSlug: "cryptomator" +tags: ["Cryptomator · restic","🌍 · open-source"] +lang: "fr" +--- + +## what + +De quoi sauvegarder votre téléphone et votre ordinateur en chiffrant les données avant{' '} + qu'elles ne partent dans un cloud. + +## why + +Les sauvegardes iCloud/Google sont souvent accessibles au fournisseur, donc à la justice.{' '} + Cryptomator chiffre vos fichiers avant de les envoyer sur n'importe quel cloud ;{' '} + restic fait des sauvegardes chiffrées automatiques ; Proton Drive est chiffré de + bout en bout. Sur iPhone, activez la « Protection avancée des données » pour chiffrer vos + sauvegardes iCloud. + +## who + +Tout le monde devrait chiffrer ses sauvegardes. Règle d'or : deux copies chiffrées, sur deux + supports, en deux lieux. + +## install + +
    +
  1. + iPhone : Réglages > votre nom > iCloud > Protection avancée des données : activez-la. +
  2. +
  3. + PC : installez Cryptomator, créez un coffre, placez-y vos fichiers avant de synchroniser vers + le cloud. +
  4. +
diff --git a/content/outils/bitchat.md b/content/outils/bitchat.md new file mode 100644 index 0000000..3e288a7 --- /dev/null +++ b/content/outils/bitchat.md @@ -0,0 +1,34 @@ +--- +name: "Bitchat" +slug: "bitchat" +category: "messagerie" +difficulty: "advanced" +url: "https://bitchat.free" +color: "#e8871e" +monogram: "bit" +tags: ["Mesh Bluetooth","🌍 · open-source"] +lang: "fr" +--- + +## what + +La messagerie mesh de Jack Dorsey : les téléphones proches se relaient les messages de proche en + proche, sans internet ni serveur ni compte. + +## why + +Chiffrement de bout en bout (AES-256-GCM) et un « mode panique » qui efface tout en trois + tapotements sur le logo. + +## who + +Manifestations et blackout, en complément de Briar. À utiliser avec prudence. + +## install + +
    +
  1. Installez depuis l'App Store (iOS) ou l'APK/GitHub (Android).
  2. +
  3. Ouvrez l'application et choisissez un pseudo. Aucun compte à créer.
  4. +
  5. Les appareils proches se découvrent automatiquement en Bluetooth.
  6. +
  7. En cas de danger, triple-tapez le logo pour tout effacer (mode panique).
  8. +
diff --git a/content/outils/bitcoin.md b/content/outils/bitcoin.md new file mode 100644 index 0000000..e99aa2c --- /dev/null +++ b/content/outils/bitcoin.md @@ -0,0 +1,38 @@ +--- +name: "Bitcoin (auto-conservation)" +slug: "bitcoin" +category: "argent" +difficulty: "intermediate" +url: "https://bitcoin.org" +color: "#f7931a" +monogram: "₿" +iconSlug: "bitcoin" +tags: ["Résistant à la censure"] +lang: "fr" +--- + +## what + +Une monnaie numérique que vous détenez vous-même, sans passer par une banque. + +## why + +En auto-conservation (vos clés dans un portefeuille que vous seul contrôlez), personne ne + peut geler ni bloquer vos fonds. Sa confidentialité n'est pas parfaite (le registre est public), + mais il échappe à la censure bancaire, utile le jour où un paiement est refusé. + +## who + +Se prémunir contre le gel de compte ou la dé-bancarisation, et payer anonymement un service + comme un VPN. + +## install + +
    +
  1. Procurez-vous des bitcoins (plateforme d'échange, ou entre particuliers).
  2. +
  3. + Transférez-les aussitôt vers un portefeuille que vous contrôlez : un portefeuille + matériel (Trezor, ColdCard) ou une application libre. Ne les laissez pas sur la plateforme. +
  4. +
  5. Sauvegardez votre phrase de récupération sur papier, jamais en photo ni en ligne.
  6. +
diff --git a/content/outils/bitwarden.md b/content/outils/bitwarden.md new file mode 100644 index 0000000..f2fab57 --- /dev/null +++ b/content/outils/bitwarden.md @@ -0,0 +1,43 @@ +--- +name: "Bitwarden" +slug: "bitwarden" +category: "motsdepasse" +difficulty: "beginner" +license: "GPL-3.0" +url: "https://bitwarden.com" +color: "#175ddc" +monogram: "Bw" +iconSlug: "bitwarden" +tags: ["Le meilleur pour débuter","🌍 · open-source"] +lang: "fr" +--- + +## what + +Un coffre-fort à mots de passe qui les mémorise, les génère et les remplit pour vous, sur tous + vos appareils. + +## why + +Gratuit, audité, multiplateforme, et chiffré de bout en bout. Vous n'avez plus qu'un seul mot de + passe fort à retenir. Il est même auto-hébergeable (via Vaultwarden) pour ne dépendre de + personne. + +## who + +Tout le monde. C'est le point de départ de toute la sécurité : un mot de passe unique et fort + par service. + +## install + +
    +
  1. + Créez un compte sur bitwarden.com et choisissez un mot de passe maître long (une + phrase). Ne l'oubliez jamais : il n'est pas récupérable. +
  2. +
  3. Installez l'extension de navigateur et l'application mobile.
  4. +
  5. + Au fil de vos connexions, laissez Bitwarden enregistrer puis remplacer vos anciens mots de + passe par des mots de passe générés. +
  6. +
diff --git a/content/outils/brave.md b/content/outils/brave.md new file mode 100644 index 0000000..9cce23d --- /dev/null +++ b/content/outils/brave.md @@ -0,0 +1,44 @@ +--- +name: "Brave" +slug: "brave" +category: "navigateur" +difficulty: "beginner" +license: "MPL-2.0" +url: "https://brave.com" +color: "#fb542b" +monogram: "Bv" +iconSlug: "brave" +tags: ["Bloque tout par défaut","🌍 · open-source"] +lang: "fr" +--- + +## what + +Un navigateur basé sur Chromium qui bloque publicités et traqueurs par défaut, avec en prime une + fenêtre privée connectée à Tor. + +## why + +C'est l'option la plus simple pour quelqu'un qui vient de Chrome : même ergonomie, mais sans le + pistage. Il bloque les pubs YouTube nativement, propose une visio chiffrée (Brave Talk, basée + sur Jitsi) et un accès Tor en un clic, pratique pour récupérer un site bloqué. + +## who + +Les débutants qui veulent un changement indolore depuis Chrome. (Ignorez les fonctions + crypto/pubs récompensées si elles ne vous intéressent pas.) + +## install + +
    +
  1. + Installez Brave depuis brave.com. +
  2. +
  3. + Réglez les « Boucliers » (Shields) sur « agressif », et désactivez Brave Rewards si vous ne + voulez pas de pubs. +
  4. +
  5. + Pour Tor : menu > « Nouvelle fenêtre privée avec Tor », puis attendez « Tor connecté ». +
  6. +
diff --git a/content/outils/briar.md b/content/outils/briar.md new file mode 100644 index 0000000..cd93e98 --- /dev/null +++ b/content/outils/briar.md @@ -0,0 +1,42 @@ +--- +name: "Briar" +slug: "briar" +category: "messagerie" +difficulty: "advanced" +license: "GPL-3.0" +url: "https://briarproject.org" +color: "#c1272d" +monogram: "Br" +tags: ["Hors-ligne / P2P","Android · open-source"] +lang: "fr" +--- + +## what + +Une messagerie pair-à-pair qui fonctionne sans internet : par Bluetooth, Wi-Fi Direct ou + Tor. + +## why + +Aucun serveur, donc rien à couper ni à saisir. Résiste aux coupures d'internet et à la censure + réseau. + +## who + +Manifestations, catastrophes, zones isolées, blackout. Android uniquement, et les deux personnes + doivent avoir l'application. + +## install + +
    +
  1. + Installez depuis briarproject.org ou F-Droid (pas d'iOS). +
  2. +
  3. + Créez un compte local (pseudo + mot de passe), stocké seulement sur le téléphone. +
  4. +
  5. Ajoutez un contact proche en scannant son QR code, ou à distance via un lien.
  6. +
  7. + Quand le réseau est coupé, activez le Bluetooth : les messages passent d'appareil à appareil. +
  8. +
diff --git a/content/outils/cloudai.md b/content/outils/cloudai.md new file mode 100644 index 0000000..4ce7473 --- /dev/null +++ b/content/outils/cloudai.md @@ -0,0 +1,28 @@ +--- +name: "Duck.ai · Lumo (Proton)" +slug: "cloudai" +category: "ia" +difficulty: "beginner" +url: "https://duck.ai" +color: "#de5833" +monogram: "☁" +iconSlug: "duckduckgo" +tags: ["Cloud orienté vie privée"] +lang: "fr" +--- + +## what + +Des accès à l'IA dans le cloud, mais conçus pour la vie privée, quand une IA locale n'est pas + possible. + +## why + +Duck.ai (DuckDuckGo) donne un accès anonymisé à plusieurs modèles. Lumo (Proton) + chiffre les échanges et ne s'en sert pas pour entraîner. Bien mieux que ChatGPT grand public, + mais cela reste un serveur distant. + +## who + +Ceux qui veulent la commodité du cloud sans nourrir les géants. Gardez-y les sujets non + sensibles. diff --git a/content/outils/cloudflare.md b/content/outils/cloudflare.md new file mode 100644 index 0000000..bd9a1ea --- /dev/null +++ b/content/outils/cloudflare.md @@ -0,0 +1,30 @@ +--- +name: "Cloudflare 1.1.1.1 (à nuancer)" +slug: "cloudflare" +category: "dns" +difficulty: "beginner" +url: "https://one.one.one.one" +color: "#f38020" +monogram: "CF" +iconSlug: "cloudflare" +tags: ["Rapide mais centralisé","🇺🇸"] +lang: "fr" +--- + +## what + +Le DNS chiffré 1.1.1.1 et l'application WARP de Cloudflare : rapides, gratuits, et un vrai + progrès face au résolveur de votre FAI. + +## why + +C'est utile, avec un engagement de non-journalisation audité. Mais soyez lucide : Cloudflare se + trouve déjà devant une immense partie du web (comme intermédiaire technique, il peut voir le + trafic en clair vers ces sites). Y router aussi votre DNS revient à{' '} + concentrer encore plus de vos traces chez une seule entreprise américaine soumise au + droit américain. + +## who + +Bon pour un usage courant et le blocage des maliciels (1.1.1.1 for Families). À éviter ou à + diversifier pour un usage sensible : préférez alors Quad9 ou Mullvad DNS. diff --git a/content/outils/cryptomator.md b/content/outils/cryptomator.md new file mode 100644 index 0000000..5d71a80 --- /dev/null +++ b/content/outils/cryptomator.md @@ -0,0 +1,41 @@ +--- +name: "Cryptomator" +slug: "cryptomator" +category: "stockage" +difficulty: "intermediate" +license: "GPL-3.0" +url: "https://cryptomator.org" +color: "#26a69a" +monogram: "Cy" +iconSlug: "cryptomator" +tags: ["Chiffre avant l'envoi","🇩🇪 · open-source"] +lang: "fr" +--- + +## what + +Un coffre qui chiffre vos fichiers avant de les envoyer sur n'importe quel cloud, même + Google Drive ou Dropbox. + +## why + +L'astuce parfaite si vous devez garder un cloud existant : vous conservez le service pratique, + mais le fournisseur ne voit plus que du charabia chiffré. Vous gardez seul la clé. + +## who + +Ceux qui ne peuvent pas quitter un cloud grand public tout de suite, mais veulent protéger leurs + fichiers dès maintenant. + +## install + +
    +
  1. Installez Cryptomator (PC et mobile) depuis cryptomator.org.
  2. +
  3. + Créez un « coffre » dans le dossier synchronisé de votre cloud, choisissez un mot de passe + fort. +
  4. +
  5. + Placez vos fichiers dans le coffre : ils sont chiffrés puis synchronisés automatiquement. +
  6. +
diff --git a/content/outils/databrokers.md b/content/outils/databrokers.md new file mode 100644 index 0000000..fa8e67c --- /dev/null +++ b/content/outils/databrokers.md @@ -0,0 +1,41 @@ +--- +name: "Effacer ses données (RGPD)" +slug: "databrokers" +category: "boiteaoutils" +difficulty: "intermediate" +url: "https://www.cnil.fr" +color: "#b8860b" +monogram: "RG" +tags: ["Courtiers en données"] +lang: "fr" +--- + +## what + +Faire retirer vos informations personnelles des « courtiers en données » qui les collectent et + les revendent. + +## why + +En Europe, le RGPD (article 17) vous donne un droit à l'effacement : vous pouvez exiger, + gratuitement, qu'une entreprise supprime vos données, en principe sous un mois. Des services + comme Incogni automatisent ces demandes en masse, moyennant paiement. + +## who + +Ceux qui veulent réduire leur exposition. Attention : les données réapparaissent avec le temps, + et cela ne touche ni les registres publics ni les réseaux sociaux. Le faire soi-même est gratuit + mais fastidieux. + +## install + +
    +
  1. + Pour une demande manuelle : écrivez au courtier en invoquant l'article 17 du RGPD et demandez + la suppression. +
  2. +
  3. + Pour automatiser : souscrivez à un service de retrait et laissez-le envoyer les demandes en + votre nom. +
  4. +
diff --git a/content/outils/element.md b/content/outils/element.md new file mode 100644 index 0000000..5fe8185 --- /dev/null +++ b/content/outils/element.md @@ -0,0 +1,39 @@ +--- +name: "Matrix / Element" +slug: "element" +category: "social" +difficulty: "intermediate" +url: "https://element.io" +color: "#0dbd8b" +monogram: "El" +iconSlug: "element" +tags: ["Remplace Discord / Slack","🌍 · fédéré · E2EE"] +lang: "fr" +--- + +## what + +Une messagerie de communautés, dans l'esprit de Discord ou Slack, mais fédérée et chiffrable de + bout en bout. Element est l'application, Matrix le réseau. + +## why + +Vous pouvez héberger votre propre serveur (Synapse) et contrôler vos données. Parfait pour un + collectif, une rédaction, une association qui veut son espace indépendant. + +## who + +Communautés et groupes. Voir la nuance sur les métadonnées ci-dessous. + +## install + +
    +
  1. + Installez Element (element.io) et créez un compte sur un serveur public, ou sur le vôtre + (section 10). +
  2. +
  3. + Rejoignez ou créez des salons, et activez le chiffrement de bout en bout pour les + conversations privées. +
  4. +
diff --git a/content/outils/fediverse.md b/content/outils/fediverse.md new file mode 100644 index 0000000..e44ebb5 --- /dev/null +++ b/content/outils/fediverse.md @@ -0,0 +1,25 @@ +--- +name: "Pixelfed · PeerTube · Lemmy" +slug: "fediverse" +category: "social" +difficulty: "intermediate" +url: "https://pixelfed.org" +color: "#ea580c" +monogram: "Pi" +iconSlug: "pixelfed" +tags: ["Insta · YouTube · Reddit","🌍 · open-source"] +lang: "fr" +--- + +## what + +Les cousins fédérés d'Instagram (Pixelfed), de YouTube (PeerTube) et de Reddit (Lemmy). + +## why + +Même logique que Mastodon : pas de propriétaire unique, pas d'algorithme publicitaire, pas de + traçage. Vous publiez photos, vidéos ou discussions sans nourrir une machine à profiler. + +## who + +Ceux qui veulent quitter Instagram, YouTube ou Reddit sans renoncer au format. diff --git a/content/outils/firefox.md b/content/outils/firefox.md new file mode 100644 index 0000000..6f85150 --- /dev/null +++ b/content/outils/firefox.md @@ -0,0 +1,42 @@ +--- +name: "Firefox (durci)" +slug: "firefox" +category: "navigateur" +difficulty: "beginner" +license: "MPL-2.0" +url: "https://www.mozilla.org/firefox" +color: "#e66000" +monogram: "Fx" +iconSlug: "firefoxbrowser" +tags: ["Remplace Chrome","🌍 · open-source"] +lang: "fr" +--- + +## what + +Le navigateur libre de référence pour un usage quotidien respectueux de la vie privée. + +## why + +Contrairement à Chrome (un mouchard publicitaire de Google), Firefox est développé par une + fondation et n'a pas d'intérêt à vous profiler. Avec quelques réglages (le guide{' '} + arkenfox) et l'extension uBlock Origin, il devient très solide. + +## who + +Tout le monde, comme navigateur principal au quotidien. + +## install + +
    +
  1. + Installez Firefox depuis mozilla.org/firefox. +
  2. +
  3. + Ajoutez l'extension uBlock Origin (blocage des pubs et traqueurs). +
  4. +
  5. + Dans les réglages de vie privée, choisissez « Stricte », désactivez la télémétrie, et videz + les cookies à la fermeture. Pour aller plus loin, appliquez le guide arkenfox. +
  6. +
diff --git a/content/outils/grapheneos.md b/content/outils/grapheneos.md new file mode 100644 index 0000000..4dba926 --- /dev/null +++ b/content/outils/grapheneos.md @@ -0,0 +1,50 @@ +--- +name: "GrapheneOS" +slug: "grapheneos" +category: "os" +difficulty: "advanced" +license: "MIT" +url: "https://grapheneos.org" +color: "#1f8a70" +monogram: "Gr" +iconSlug: "grapheneos" +tags: ["Mobile dégooglisé","Android · Pixel · open-source"] +lang: "fr" +--- + +## what + +Un Android entièrement dégooglisé, installé sur un téléphone Pixel : la souveraineté sur mobile. + +## why + +C'est la défense structurelle par excellence contre un OS ou un magasin d'applications qui + voudrait vous scanner. Services Google en bac à sable optionnel,{' '} + permissions réseau par application, profils cloisonnés, redémarrage automatique qui purge + les clés de la mémoire, code PIN de contrainte qui efface le téléphone. Réputé pour sa sécurité + au point d'être utilisé par des cibles de logiciels espions. + +## who + +Profils exposés, mais aussi tout citoyen prêt à acheter un Pixel pour reprendre le contrôle de + son téléphone. + +## install + +
    +
  1. + Procurez-vous un téléphone Google Pixel compatible (c'est le seul matériel supporté, + pour des raisons de sécurité). +
  2. +
  3. + Sur un ordinateur, allez sur grapheneos.org/install et suivez l'installeur web + (quelques clics, aucun terminal requis). +
  4. +
  5. + Pour vos applications, installez le magasin libre F-Droid ; ajoutez Google Play en bac à sable + seulement si nécessaire. +
  6. +
  7. + Réglez un long mot de passe (pas un code à 4 chiffres) et découvrez le mode « Lockdown ». +
  8. +
diff --git a/content/outils/ivpn.md b/content/outils/ivpn.md new file mode 100644 index 0000000..d412dab --- /dev/null +++ b/content/outils/ivpn.md @@ -0,0 +1,40 @@ +--- +name: "IVPN" +slug: "ivpn" +category: "vpn" +difficulty: "intermediate" +url: "https://www.ivpn.net" +color: "#3b5bdb" +monogram: "iv" +tags: ["Sans e-mail · no-log","🇬🇮 · open-source"] +lang: "fr" +--- + +## what + +Un petit fournisseur de VPN particulièrement rigoureux et transparent, sur le même modèle que + Mullvad. + +## why + +Politique no-log auditée, pas d'e-mail obligatoire, paiement en crypto ou en espèces. Il propose + même des options anti-traçage (blocage des trackers au niveau réseau) et une inscription sans + aucun identifiant. + +## who + +Une alternative solide à Mullvad, pour les profils pseudonymes et à haut risque qui veulent + diversifier. + +## install + +
    +
  1. + Sur ivpn.net, générez un compte (un identifiant est créé pour vous, sans e-mail). +
  2. +
  3. Payez en cryptomonnaie ou en espèces, comme pour Mullvad.
  4. +
  5. + Installez l'application, connectez-vous hors UE, activez le kill switch et le protocole + WireGuard. +
  6. +
diff --git a/content/outils/keepassxc.md b/content/outils/keepassxc.md new file mode 100644 index 0000000..552185e --- /dev/null +++ b/content/outils/keepassxc.md @@ -0,0 +1,38 @@ +--- +name: "KeePassXC" +slug: "keepassxc" +category: "motsdepasse" +difficulty: "advanced" +license: "GPL-2.0/3.0" +url: "https://keepassxc.org" +color: "#2c6b9e" +monogram: "Kp" +iconSlug: "keepassxc" +tags: ["100% local","🌍 · open-source"] +lang: "fr" +--- + +## what + +Un coffre-fort local : un fichier chiffré que vous seul détenez, sans aucun cloud ni + compte. + +## why + +Rien ne quitte votre appareil, sauf si vous décidez de synchroniser le fichier vous-même (par + exemple via Cryptomator). C'est le choix des puristes qui ne veulent aucun intermédiaire. + +## who + +Les profils avancés qui veulent le contrôle total et zéro dépendance en ligne. + +## install + +
    +
  1. + Installez KeePassXC (PC) et une application compatible sur mobile (KeePassDX sur Android, + Strongbox sur iOS). +
  2. +
  3. Créez un fichier de base de données protégé par un mot de passe maître.
  4. +
  5. Pour l'avoir sur tous vos appareils, synchronisez ce fichier via un cloud chiffré.
  6. +
diff --git a/content/outils/linux.md b/content/outils/linux.md new file mode 100644 index 0000000..4e04d14 --- /dev/null +++ b/content/outils/linux.md @@ -0,0 +1,53 @@ +--- +name: "Linux (sur ordinateur)" +slug: "linux" +category: "os" +difficulty: "intermediate" +url: "https://distrochooser.de/fr" +color: "#2b2b2b" +monogram: "🐧" +iconSlug: "linux" +tags: ["Remplace Windows / macOS","🌍 · libre & gratuit"] +lang: "fr" +--- + +## what + +Un système d'exploitation libre et gratuit pour votre ordinateur, en remplacement de Windows ou + de macOS. Il en existe de nombreuses variantes, appelées « distributions ». + +## why + +C'est le point aveugle de beaucoup de gens : ils chiffrent leurs messages mais gardent un + Windows qui transmet en continu de la télémétrie à Microsoft (et dont la fonction « Recall » a + voulu photographier l'écran en permanence), ou un macOS qui signale les applications que vous + lancez. Linux, lui, ne vous espionne pas, il est gratuit, il ressuscite les vieux ordinateurs et + il vous rend enfin maître de votre machine. C'est plus facile que sa réputation : les + distributions modernes ressemblent à Windows ou macOS. + +## who + +Tout le monde peut franchir le pas. Pour débuter, Linux Mint (le plus proche de Windows) + ou Fedora ; Debian pour la stabilité. Pas besoin de tout casser : on peut d'abord + tester, puis installer à côté de son système actuel. + +## install + +
    +
  1. + Pas sûr de la distribution ? Répondez au questionnaire distrochooser.de/fr, qui vous + oriente selon vos besoins. +
  2. +
  3. + Testez-la sans rien installer directement dans votre navigateur sur{' '} + distrosea.com. +
  4. +
  5. + Une fois convaincu, téléchargez l'image de la distribution et créez une clé USB de démarrage + (avec Ventoy ou Balena Etcher). +
  6. +
  7. + Démarrez sur la clé pour essayer le système « en live », puis lancez l'installation. Activez + le chiffrement du disque proposé. +
  8. +
diff --git a/content/outils/localai.md b/content/outils/localai.md new file mode 100644 index 0000000..926d803 --- /dev/null +++ b/content/outils/localai.md @@ -0,0 +1,46 @@ +--- +name: "Ollama · LM Studio · Jan · GPT4All" +slug: "localai" +category: "ia" +difficulty: "intermediate" +url: "https://lmstudio.ai" +color: "#111111" +monogram: "Ai" +iconSlug: "ollama" +tags: ["Remplace ChatGPT","🌍 · 100% local"] +lang: "fr" +--- + +## what + +Des logiciels pour faire tourner un modèle d'intelligence artificielle directement sur votre + ordinateur. + +## why + +Vos questions ne quittent jamais l'appareil : aucun serveur, aucun compte, aucune télémétrie, et + ça marche hors ligne. La qualité dépend de votre matériel, mais rien ne fuit. Ollama et{' '} + LM Studio sont les plus simples ; Jan et GPT4All visent la vie privée + maximale. + +## who + +Tous ceux qui utilisent l'IA pour des sujets un tant soit peu personnels et ne veulent pas les + confier à un serveur américain. + +## install + +
    +
  1. + Pour débuter en douceur, installez LM Studio ou Jan (interface graphique, comme + une application classique). +
  2. +
  3. + Téléchargez un modèle proposé dans l'application (par exemple Llama ou Mistral), adapté à la + puissance de votre machine. +
  4. +
  5. + Discutez : tout se passe en local. Ollama est la version en ligne de commande pour les plus + techniques. +
  6. +
diff --git a/content/outils/mailbox.md b/content/outils/mailbox.md new file mode 100644 index 0000000..dd05b0f --- /dev/null +++ b/content/outils/mailbox.md @@ -0,0 +1,33 @@ +--- +name: "mailbox.org · Posteo" +slug: "mailbox" +category: "email" +difficulty: "beginner" +url: "https://mailbox.org" +color: "#2470d4" +monogram: "mx" +tags: ["Mail classique éthique","🇩🇪 · UE · PGP"] +lang: "fr" +--- + +## what + +Deux fournisseurs de mail européens, éthiques et peu coûteux, avec support PGP côté serveur. + +## why + +Respectueux de la vie privée et de l'écologie, sur des standards ouverts (IMAP, PGP). Ils + fonctionnent avec n'importe quel logiciel de messagerie. + +## who + +Ceux qui veulent un mail « classique » (relevé dans Thunderbird) mais éthique, plutôt qu'une + messagerie chiffrée fermée. + +## install + +
    +
  1. Créez un compte (payant, de l'ordre de 1 à 3 € par mois).
  2. +
  3. Activez le chiffrement PGP côté serveur (fonction « Guard » chez mailbox.org).
  4. +
  5. Configurez le compte dans Thunderbird ou l'application mobile de votre choix.
  6. +
diff --git a/content/outils/maps.md b/content/outils/maps.md new file mode 100644 index 0000000..d60bab9 --- /dev/null +++ b/content/outils/maps.md @@ -0,0 +1,37 @@ +--- +name: "Organic Maps · OsmAnd" +slug: "maps" +category: "boiteaoutils" +difficulty: "beginner" +url: "https://organicmaps.app" +color: "#0a7d33" +monogram: "Om" +iconSlug: "openstreetmap" +tags: ["Remplace Google Maps","🌍 · open-source"] +lang: "fr" +--- + +## what + +Des applications de cartes et de navigation, fondées sur OpenStreetMap, qui fonctionnent hors + ligne. + +## why + +Google Maps enregistre chacun de vos trajets et bâtit un historique de lieux d'une précision + redoutable. Organic Maps ne trace rien et n'affiche aucune publicité ; OsmAnd vise + les utilisateurs avancés (randonnée, courbes de niveau, navigation détaillée). + +## who + +Tout le monde. Le trafic en temps réel et les transports sont moins complets que chez Google, + mais la navigation quotidienne est excellente. + +## install + +
    +
  1. Installez depuis F-Droid, l'App Store ou Google Play.
  2. +
  3. + Téléchargez à l'avance les cartes des régions qui vous intéressent pour l'usage hors ligne. +
  4. +
diff --git a/content/outils/mastodon.md b/content/outils/mastodon.md new file mode 100644 index 0000000..0373690 --- /dev/null +++ b/content/outils/mastodon.md @@ -0,0 +1,41 @@ +--- +name: "Mastodon" +slug: "mastodon" +category: "social" +difficulty: "beginner" +license: "AGPL-3.0" +url: "https://joinmastodon.org" +color: "#6364ff" +monogram: "Ma" +iconSlug: "mastodon" +tags: ["Remplace X / Twitter","🌍 · open-source"] +lang: "fr" +--- + +## what + +L'alternative à X/Twitter : un réseau social fait de milliers de serveurs indépendants qui + communiquent entre eux (le « fédiverse »). + +## why + +Pas de patron unique, pas d'algorithme qui décide à votre place, pas de bannissement arbitraire + par une seule entreprise. Le fil est chronologique, sans publicité ni manipulation. + +## who + +Tout le monde. Idéal pour les comptes d'information qui veulent une présence qu'aucune + entreprise ne peut couper. + +## install + +
    +
  1. + Sur joinmastodon.org, choisissez un serveur (ou « instance ») qui vous correspond, puis créez + un compte. +
  2. +
  3. + Installez une application (l'officielle, ou Ivory, Tusky…) et suivez des comptes : vous voyez + tout le fédiverse, quel que soit leur serveur. +
  4. +
diff --git a/content/outils/molly.md b/content/outils/molly.md new file mode 100644 index 0000000..1445f7d --- /dev/null +++ b/content/outils/molly.md @@ -0,0 +1,39 @@ +--- +name: "Molly" +slug: "molly" +category: "messagerie" +difficulty: "advanced" +license: "GPL-3.0" +url: "https://molly.im" +color: "#6d4bb3" +monogram: "M" +tags: ["Signal durci","Android · open-source"] +lang: "fr" +--- + +## what + +Une version durcie de Signal pour Android, compatible avec le même réseau. + +## why + +Base de données chiffrée au repos, verrouillage automatique, et une variante « FOSS » + entièrement sans services Google. + +## who + +Utilisateurs avancés, notamment sur GrapheneOS, avec un modèle de menace élevé. Android + uniquement. + +## install + +
    +
  1. + Ajoutez le dépôt Molly dans F-Droid, ou récupérez l'APK sur molly.im. +
  2. +
  3. + Choisissez Molly-FOSS si vous voulez zéro dépendance Google. +
  4. +
  5. Enregistrez-vous avec votre numéro (même réseau que Signal).
  6. +
  7. Définissez un mot de passe de base de données et le verrouillage automatique.
  8. +
diff --git a/content/outils/monero.md b/content/outils/monero.md new file mode 100644 index 0000000..39a41e6 --- /dev/null +++ b/content/outils/monero.md @@ -0,0 +1,39 @@ +--- +name: "Monero (XMR)" +slug: "monero" +category: "argent" +difficulty: "advanced" +license: "BSD-3-Clause" +url: "https://www.getmonero.org" +color: "#ff6600" +monogram: "ɱ" +iconSlug: "monero" +tags: ["Confidentiel par défaut"] +lang: "fr" +--- + +## what + +La cryptomonnaie de la vie privée : montants, expéditeur et destinataire sont masqués par + défaut. + +## why + +C'est l'exact opposé de Bitcoin sur ce point : les transactions sont confidentielles par + conception. C'est le meilleur moyen de payer un service sans laisser de trace reliable à vous. + +## who + +Ceux qui veulent le maximum de confidentialité financière. Note : Monero est déjà retiré de + plusieurs plateformes en Europe, il faut souvent passer par un échange décentralisé. + +## install + +
    +
  1. Installez un portefeuille (l'officiel sur getmonero.org, ou Cake Wallet sur mobile).
  2. +
  3. + Obtenez des XMR via un service d'échange décentralisé (par exemple un « swap » depuis une + autre crypto). +
  4. +
  5. Sauvegardez votre phrase de récupération sur papier.
  6. +
diff --git a/content/outils/mullvad.md b/content/outils/mullvad.md new file mode 100644 index 0000000..71d2ca0 --- /dev/null +++ b/content/outils/mullvad.md @@ -0,0 +1,62 @@ +--- +name: "Mullvad VPN" +slug: "mullvad" +category: "vpn" +difficulty: "beginner" +url: "https://mullvad.net" +color: "#2b4b8f" +monogram: "Mv" +iconSlug: "mullvad" +tags: ["Sans compte · cash/Monero","🇸🇪 · open-source"] +lang: "fr" +--- + +## what + +Un VPN qui masque votre adresse IP et chiffre votre trafic jusqu'à ses serveurs, sans jamais + vous demander la moindre information personnelle. + +## why + +C'est l'étalon-or de la vie privée. Pas d'e-mail, pas de nom : à l'inscription, on vous attribue + un simple numéro de compte. Politique « no-log » auditée, prix unique et honnête (5 € par mois, + sans palier ni fausse promesse), et paiement possible en espèces ou en Monero. C'est précisément + ce qui compte le jour où un État tente de couper l'accès aux VPN : un compte impossible à relier + à votre identité, et un paiement que les banques ne peuvent pas bloquer. + +## who + +Tout le monde, du citoyen qui veut contourner une vérification d'identité au profil à haut + risque. C'est le VPN à recommander en priorité. + +## install + +
    +
  1. + Allez sur mullvad.net. Si le site est bloqué dans votre pays, ouvrez-le via le + navigateur Tor ou son adresse .onion. Cliquez sur « Générer un numéro de compte » : + aucun e-mail ni nom n'est demandé. Notez ce numéro dans votre gestionnaire de mots de passe et + ne le partagez jamais, c'est votre unique clé d'accès. +
  2. +
  3. + Choisissez la durée et le mode de paiement. Le prix est fixe. Trois voies : carte bancaire (le + plus simple si elle n'est pas bloquée), espèces envoyées par courrier, ou cryptomonnaie. +
  4. +
  5. + Paiement anonyme en crypto : cliquez sur « Créer une adresse de paiement à usage unique + ». Mullvad affiche une adresse (Bitcoin ou Monero) et un montant exact. Envoyez ce montant + depuis votre portefeuille. Pour l'anonymat maximal, préférez Monero (confidentiel par + défaut) ; Bitcoin est plus simple mais traçable. Voir la section 09 pour acheter de la crypto. + Comptez environ 30 minutes de confirmation. +
  6. +
  7. + Téléchargez l'application sur mullvad.net/download (via Tor si le site est filtré) et + installez-la. Sur mobile, passez par l'App Store, Google Play, ou l'APK officiel si + l'application a été retirée de votre magasin. +
  8. +
  9. + Lancez l'application, entrez votre numéro de compte, puis connectez-vous à un serveur situé{' '} + hors de votre pays, idéalement hors Union européenne (Suisse, États-Unis…). Activez le + « kill switch » (qui coupe Internet si le VPN tombe) et le DNS de Mullvad. +
  10. +
diff --git a/content/outils/mullvadbrowser.md b/content/outils/mullvadbrowser.md new file mode 100644 index 0000000..461fa75 --- /dev/null +++ b/content/outils/mullvadbrowser.md @@ -0,0 +1,41 @@ +--- +name: "Mullvad Browser" +slug: "mullvadbrowser" +category: "navigateur" +difficulty: "intermediate" +url: "https://mullvad.net/browser" +color: "#2b4b8f" +monogram: "Mb" +iconSlug: "mullvad" +tags: ["Anti-empreinte","🇸🇪 · open-source"] +lang: "fr" +--- + +## what + +Le navigateur du Tor Browser, mais sans le réseau Tor : à utiliser avec votre VPN pour un + ordinateur difficile à pister. + +## why + +Développé avec le Tor Project, il est conçu pour que tous ses utilisateurs présentent la{' '} + même empreinte numérique. Résultat : au lieu d'être unique et donc traçable, vous vous + fondez dans la masse. C'est le meilleur choix de vie privée sur ordinateur. + +## who + +Ceux qui veulent aller au-delà du simple blocage de pub, notamment les comptes pseudonymes et + les profils exposés. + +## install + +
    +
  1. + Téléchargez-le sur mullvad.net/browser (Windows, macOS, Linux). +
  2. +
  3. + Utilisez-le tel quel, sans le personnaliser : chaque extension ou réglage ajouté vous rend à + nouveau identifiable. +
  4. +
  5. Associez-le à votre VPN pour masquer aussi votre adresse IP.
  6. +
diff --git a/content/outils/mullvaddns.md b/content/outils/mullvaddns.md new file mode 100644 index 0000000..d055539 --- /dev/null +++ b/content/outils/mullvaddns.md @@ -0,0 +1,40 @@ +--- +name: "Mullvad DNS · NextDNS" +slug: "mullvaddns" +category: "dns" +difficulty: "intermediate" +url: "https://mullvad.net/help/dns-over-https-and-dns-over-tls" +color: "#2b4b8f" +monogram: "DNS" +iconSlug: "mullvad" +tags: ["Filtrage réseau"] +lang: "fr" +--- + +## what + +Deux résolveurs chiffrés qui bloquent en plus pubs et traqueurs pour tout l'appareil. + +## why + +Mullvad DNS (Suède, no-log) est gratuit et propose des listes de blocage. NextDNS{' '} + est très personnalisable, avec un tableau de bord et des profils par appareil, mais il est basé + aux États-Unis et journalise par défaut (à désactiver dans les réglages). + +## who + +Ceux qui veulent un blocage des pubs à l'échelle du système, sans installer d'application par + navigateur. + +## install + +
    +
  1. + Mullvad DNS : utilisez l'adresse indiquée sur mullvad.net/help/dns comme DNS privé (même + méthode que Quad9). +
  2. +
  3. + NextDNS : créez un profil sur nextdns.io, désactivez la journalisation, puis appliquez + la configuration à vos appareils. +
  4. +
diff --git a/content/outils/nc-storage.md b/content/outils/nc-storage.md new file mode 100644 index 0000000..31b50ca --- /dev/null +++ b/content/outils/nc-storage.md @@ -0,0 +1,25 @@ +--- +name: "Nextcloud" +slug: "nc-storage" +category: "stockage" +difficulty: "advanced" +url: "https://nextcloud.com" +color: "#0082c9" +monogram: "Nc" +iconSlug: "nextcloud" +tags: ["Votre propre cloud","🌍 · open-source"] +lang: "fr" +--- + +## what + +Votre propre nuage (fichiers, agenda, contacts, notes) hébergé sur votre serveur. + +## why + +Le degré ultime de contrôle : vos données ne quittent jamais votre matériel. Détaillé dans la + section 10 (Auto-hébergement). + +## who + +Les auto-hébergeurs. Voir la fiche complète en section 10. diff --git a/content/outils/nextcloud.md b/content/outils/nextcloud.md new file mode 100644 index 0000000..10bacf3 --- /dev/null +++ b/content/outils/nextcloud.md @@ -0,0 +1,29 @@ +--- +name: "Nextcloud" +slug: "nextcloud" +category: "selfhost" +difficulty: "advanced" +license: "AGPL-3.0" +url: "https://nextcloud.com" +color: "#0082c9" +monogram: "Nc" +iconSlug: "nextcloud" +tags: ["Remplace toute la suite Google","🇩🇪 · open-source"] +lang: "fr" +--- + +## what + +Votre propre nuage : fichiers, agenda, contacts, photos, notes et documents collaboratifs, sur + votre serveur. + +## why + +C'est le remplacement complet de Google Drive, Agenda et Contacts, mais chez vous. Vous décidez + de la juridiction et personne d'autre n'a la main sur vos fichiers. À savoir : c'est la + confidentialité « votre serveur », pas un chiffrement de bout en bout par défaut ; vous devez le + maintenir à jour. + +## who + +Les auto-hébergeurs. Le plus simple est de l'installer via YunoHost ou Umbrel. diff --git a/content/outils/nostr.md b/content/outils/nostr.md new file mode 100644 index 0000000..a507730 --- /dev/null +++ b/content/outils/nostr.md @@ -0,0 +1,39 @@ +--- +name: "Nostr" +slug: "nostr" +category: "social" +difficulty: "intermediate" +url: "https://nostr.com" +color: "#8e30eb" +monogram: "No" +tags: ["Incensurable","🌍 · protocole ouvert"] +lang: "fr" +--- + +## what + +Pas une application mais un protocole : un réseau social où votre identité est une simple paire + de clés, comme pour Bitcoin. + +## why + +Zéro e-mail, zéro mot de passe, aucun compte qu'on puisse supprimer. Vos messages voyagent sur + des relais décentralisés et vous possédez vraiment votre audience : personne ne peut vous bannir + durablement. Vous pouvez même recevoir des pourboires en Bitcoin (les « zaps ») et changer + d'application quand vous voulez sans perdre vos abonnés. + +## who + +Comptes d'information et créateurs qui craignent la censure de plateforme et veulent une + présence vraiment à eux. + +## install + +
    +
  1. Installez un client : Damus (iPhone), Amethyst ou Primal (Android), ou une version web.
  2. +
  3. + L'application génère votre paire de clés. Sauvegardez votre clé privée (nsec) comme une + phrase de récupération Bitcoin : sur papier, jamais partagée. +
  4. +
  5. Créez votre profil, suivez des gens, activez les zaps pour recevoir des sats.
  6. +
diff --git a/content/outils/notes.md b/content/outils/notes.md new file mode 100644 index 0000000..5968a82 --- /dev/null +++ b/content/outils/notes.md @@ -0,0 +1,37 @@ +--- +name: "Standard Notes · Joplin · CryptPad" +slug: "notes" +category: "boiteaoutils" +difficulty: "beginner" +url: "https://standardnotes.com" +color: "#2e7d6b" +monogram: "No" +tags: ["Remplace Docs / Notion","🌍 · open-source"] +lang: "fr" +--- + +## what + +Des notes et documents chiffrés de bout en bout, pour remplacer Google Docs, Notion ou Evernote. + +## why + +Standard Notes (par Proton) chiffre vos notes par défaut. Joplin gère des carnets + en Markdown avec chiffrement optionnel et la synchronisation de votre choix. CryptPad{' '} + (français) offre l'équivalent de Google Docs en temps réel, entièrement chiffré. + +## who + +Tout le monde pour les notes ; CryptPad pour la collaboration à plusieurs. + +## install + +
    +
  1. + Standard Notes / Joplin : créez un compte, installez l'application, et activez le chiffrement + (par défaut sur Standard Notes, à activer dans Joplin). +
  2. +
  3. + CryptPad : utilisez cryptpad.fr ou une autre instance, aucun compte requis pour commencer. +
  4. +
diff --git a/content/outils/photos.md b/content/outils/photos.md new file mode 100644 index 0000000..6f59248 --- /dev/null +++ b/content/outils/photos.md @@ -0,0 +1,41 @@ +--- +name: "Ente · Immich" +slug: "photos" +category: "boiteaoutils" +difficulty: "intermediate" +url: "https://ente.io" +color: "#7b3fbf" +monogram: "Ph" +iconSlug: "immich" +tags: ["Remplace Google Photos","🌍 · open-source"] +lang: "fr" +--- + +## what + +Deux façons de garder vos photos sans les confier à Google ou Apple. + +## why + +Ente est un service géré, chiffré de bout en bout, avec reconnaissance et recherche + faites sur l'appareil : simple et sûr. Immich est un clone de Google Photos que vous{' '} + hébergez vous-même (recherche par visages et objets incluse), à réserver à ceux qui + savent gérer un serveur. + +## who + +Ente pour tous ; Immich pour les auto-hébergeurs. Attention : Immich n'est pas chiffré de bout + en bout, sa sécurité dépend de votre serveur. + +## install + +
    +
  1. + Ente : installez l'application, créez un compte, activez la sauvegarde automatique de votre + pellicule. +
  2. +
  3. + Immich : déployez-le sur votre serveur (voir section 10) via Docker, puis connectez + l'application mobile. +
  4. +
diff --git a/content/outils/pihole.md b/content/outils/pihole.md new file mode 100644 index 0000000..73aedba --- /dev/null +++ b/content/outils/pihole.md @@ -0,0 +1,39 @@ +--- +name: "Pi-hole · AdGuard Home" +slug: "pihole" +category: "dns" +difficulty: "advanced" +url: "https://pi-hole.net" +color: "#96060c" +monogram: "π" +iconSlug: "pihole" +tags: ["Blocage tout le réseau","🌍 · open-source"] +lang: "fr" +--- + +## what + +Un bloqueur de publicités et de traqueurs qui protège tout votre réseau à la fois, + installé sur un petit ordinateur comme un Raspberry Pi. + +## why + +Là où uBlock Origin protège un navigateur, Pi-hole filtre chaque appareil de la maison, y + compris ceux où l'on ne peut pas installer d'extension : téléphones, téléviseurs connectés, + objets connectés. Les pubs et traqueurs sont bloqués au niveau du DNS, avant même de se charger.{' '} + AdGuard Home est une alternative à l'interface plus moderne. + +## who + +Les foyers qui veulent nettoyer tous leurs appareils d'un coup, et qui ont (ou veulent) un + Raspberry Pi ou un petit serveur. + +## install + +
    +
  1. Sur un Raspberry Pi (ou un conteneur), lancez l'installeur officiel de Pi-hole.
  2. +
  3. + Dans les réglages de votre box/routeur, indiquez l'adresse du Pi-hole comme serveur DNS. +
  4. +
  5. Désormais, tous les appareils connectés à votre réseau sont filtrés automatiquement.
  6. +
diff --git a/content/outils/pocketpal.md b/content/outils/pocketpal.md new file mode 100644 index 0000000..1295076 --- /dev/null +++ b/content/outils/pocketpal.md @@ -0,0 +1,41 @@ +--- +name: "PocketPal AI" +slug: "pocketpal" +category: "ia" +difficulty: "intermediate" +license: "MIT" +url: "https://pocketpal.dev" +color: "#6b4eff" +monogram: "Pp" +tags: ["Remplace ChatGPT sur mobile","📱 · 100% local"] +lang: "fr" +--- + +## what + +Une application pour faire tourner des modèles d'IA directement sur votre téléphone, entièrement + hors ligne. + +## why + +Vos requêtes ne quittent jamais le téléphone : aucun compte, aucun cloud, aucune télémétrie, et + ça marche en mode avion. Open source (MIT), l'appli télécharge des modèles GGUF (Gemma, + Qwen, Phi, Llama) depuis Hugging Face et les exécute sur le CPU, le GPU ou le NPU de l'appareil. + Le pendant mobile d'Ollama et LM Studio. + +## who + +Tous ceux qui dégainent ChatGPT sur leur téléphone pour des questions personnelles et ne veulent + pas les confier à un serveur américain. + +## install + +
    +
  1. + Installez PocketPal depuis l'App Store ou Google Play (ou compilez les sources). +
  2. +
  3. + Ouvrez Models, choisissez un modèle adapté à votre téléphone et téléchargez-le. +
  4. +
  5. Chargez-le et discutez : tout tourne hors ligne, sur l'appareil.
  6. +
diff --git a/content/outils/protondrive.md b/content/outils/protondrive.md new file mode 100644 index 0000000..4439e8d --- /dev/null +++ b/content/outils/protondrive.md @@ -0,0 +1,32 @@ +--- +name: "Proton Drive" +slug: "protondrive" +category: "stockage" +difficulty: "beginner" +url: "https://proton.me/drive" +color: "#6d4aff" +monogram: "Pd" +iconSlug: "protondrive" +tags: ["Remplace Google Drive / iCloud","🇨🇭 · E2EE"] +lang: "fr" +--- + +## what + +Un cloud chiffré de bout en bout pour vos fichiers et vos photos, intégré à Proton. + +## why + +Contrairement à Google Drive ou iCloud, Proton ne peut pas lire vos fichiers : ils sont chiffrés + avant de quitter votre appareil. Juridiction suisse, même compte que Proton Mail. + +## who + +Tout le monde, comme remplacement direct et simple du cloud de Google ou d'Apple. + +## install + +
    +
  1. Activez Proton Drive dans votre compte Proton et installez l'application.
  2. +
  3. Activez la sauvegarde automatique de vos photos, puis désactivez celle de Google/Apple.
  4. +
diff --git a/content/outils/protonmail.md b/content/outils/protonmail.md new file mode 100644 index 0000000..7f163ed --- /dev/null +++ b/content/outils/protonmail.md @@ -0,0 +1,43 @@ +--- +name: "Proton Mail" +slug: "protonmail" +category: "email" +difficulty: "beginner" +url: "https://proton.me/mail" +color: "#6d4aff" +monogram: "Pm" +iconSlug: "protonmail" +tags: ["Remplace Gmail","🇨🇭 · open-source · OpenPGP"] +lang: "fr" +--- + +## what + +Une boîte mail chiffrée qui remplace Gmail ou Outlook, et le compte pivot de tout l'écosystème + Proton. + +## why + +Basé en Suisse, hors juridiction des « Five Eyes ». Compatible OpenPGP : chiffrement automatique + entre comptes Proton et vers les autres services PGP. + +## who + +Tout le monde. C'est le meilleur point de départ pour quitter Google (voir section 05 pour le + reste de la suite). + +## install + +
    +
  1. + Créez un compte sur proton.me (offre gratuite suffisante pour commencer). +
  2. +
  3. + Importez vos anciens messages et contacts avec Easy Switch (intégré). +
  4. +
  5. Mettez une redirection depuis Gmail et prévenez vos contacts de votre nouvelle adresse.
  6. +
  7. + Installez l'application mobile, activez la double authentification, et utilisez Proton + Bridge si vous tenez à Thunderbird. +
  8. +
diff --git a/content/outils/protonpass.md b/content/outils/protonpass.md new file mode 100644 index 0000000..dd10cb2 --- /dev/null +++ b/content/outils/protonpass.md @@ -0,0 +1,25 @@ +--- +name: "Proton Pass" +slug: "protonpass" +category: "motsdepasse" +difficulty: "beginner" +url: "https://proton.me/pass" +color: "#6d4aff" +monogram: "Pp" +tags: ["Avec alias e-mail","🇨🇭 · open-source"] +lang: "fr" +--- + +## what + +Le gestionnaire de mots de passe de l'écosystème Proton, avec des alias e-mail jetables + intégrés. + +## why + +Idéal si vous êtes déjà chez Proton : un seul compte pour le mail, le cloud et les mots de + passe. Ses alias e-mail vous évitent de donner votre vraie adresse partout. + +## who + +Ceux qui veulent tout regrouper chez Proton. diff --git a/content/outils/protonvpn.md b/content/outils/protonvpn.md new file mode 100644 index 0000000..bdf2c65 --- /dev/null +++ b/content/outils/protonvpn.md @@ -0,0 +1,40 @@ +--- +name: "Proton VPN" +slug: "protonvpn" +category: "vpn" +difficulty: "beginner" +url: "https://protonvpn.com" +color: "#6d4aff" +monogram: "Pv" +iconSlug: "protonvpn" +tags: ["Offre gratuite honnête","🇨🇭 · open-source"] +lang: "fr" +--- + +## what + +Le VPN de l'écosystème Proton, avec une véritable offre gratuite (rare et honnête). + +## why + +Basé en Suisse, audité, open-source. Son offre gratuite est sans publicité, sans revente de + données et sans limite de volume (le nombre de pays est simplement réduit). C'est la porte + d'entrée idéale pour un débutant, surtout si vous utilisez déjà Proton Mail. + +## who + +Débutants, et toute personne déjà dans l'écosystème Proton qui veut un seul compte pour tout. + +## install + +
    +
  1. + Rendez-vous sur protonvpn.com et connectez-vous avec votre compte Proton (ou créez-en + un). +
  2. +
  3. Installez l'application (Windows, macOS, Linux, Android, iOS).
  4. +
  5. + Utilisez « Quick Connect » ou choisissez un pays, puis activez le kill switch et NetShield + (blocage des pubs et traqueurs). +
  6. +
diff --git a/content/outils/quad9.md b/content/outils/quad9.md new file mode 100644 index 0000000..512e6d0 --- /dev/null +++ b/content/outils/quad9.md @@ -0,0 +1,38 @@ +--- +name: "Quad9" +slug: "quad9" +category: "dns" +difficulty: "beginner" +url: "https://quad9.net" +color: "#1a5fb4" +monogram: "9" +iconSlug: "quad9" +tags: ["Anti-maliciel","🇨🇭 à but non lucratif"] +lang: "fr" +--- + +## what + +Un résolveur DNS chiffré, suisse et à but non lucratif, qui bloque en plus les domaines + malveillants connus. + +## why + +Juridiction suisse, pas de conservation de votre adresse IP, protection contre l'hameçonnage et + les logiciels malveillants. Un excellent réglage « installer et oublier ». + +## who + +Tout le monde, en particulier ceux qui veulent la simplicité et une bonne juridiction. + +## install + +
    +
  1. + Sur Android : Réglages > Réseau > DNS privé > saisissez dns.quad9.net. +
  2. +
  3. + Sur iPhone/Mac : installez le profil DoH depuis quad9.net. Sur PC : configurez le + DNS-over-HTTPS dans le navigateur ou le système. +
  4. +
diff --git a/content/outils/qubes.md b/content/outils/qubes.md new file mode 100644 index 0000000..1c608c1 --- /dev/null +++ b/content/outils/qubes.md @@ -0,0 +1,27 @@ +--- +name: "Qubes OS · postmarketOS" +slug: "qubes" +category: "os" +difficulty: "advanced" +url: "https://www.qubes-os.org" +color: "#3874d8" +monogram: "Qb" +iconSlug: "qubesos" +tags: ["Cloisonnement","🌍 · open-source"] +lang: "fr" +--- + +## what + +Deux systèmes de niche : Qubes cloisonne votre PC en machines virtuelles étanches ; postmarketOS + fait tourner du Linux sur d'anciens téléphones. + +## why + +Qubes isole chaque activité (travail, banque, navigation risquée) dans son propre compartiment : + une compromission n'atteint pas le reste. postmarketOS prolonge la vie de téléphones abandonnés + par leur constructeur. + +## who + +Utilisateurs experts avec un modèle de menace très élevé. diff --git a/content/outils/search.md b/content/outils/search.md new file mode 100644 index 0000000..8b4e9d3 --- /dev/null +++ b/content/outils/search.md @@ -0,0 +1,39 @@ +--- +name: "DuckDuckGo · Brave Search · SearXNG" +slug: "search" +category: "navigateur" +difficulty: "beginner" +url: "https://duckduckgo.com" +color: "#de5833" +monogram: "🔎" +iconSlug: "duckduckgo" +tags: ["Remplace Google Search"] +lang: "fr" +--- + +## what + +Des moteurs de recherche qui ne profilent pas chaque requête, contrairement à Google. + +## why + +DuckDuckGo et Brave Search ne conservent pas d'historique lié à votre identité. Startpage vous + sert des résultats Google sans le pistage. SearXNG va plus loin : c'est un métamoteur{' '} + auto-hébergeable, la souveraineté jusque dans la barre de recherche. + +## who + +Tout le monde. SearXNG vise ceux qui veulent héberger leur propre moteur. + +## install + +
    +
  1. + Dans les réglages de votre navigateur, changez le moteur par défaut pour DuckDuckGo, Brave + Search ou Startpage. +
  2. +
  3. + Pour SearXNG : utilisez une instance publique (annuaire sur searx.space) ou hébergez la vôtre + (voir section 10). +
  4. +
diff --git a/content/outils/session.md b/content/outils/session.md new file mode 100644 index 0000000..9323f4e --- /dev/null +++ b/content/outils/session.md @@ -0,0 +1,40 @@ +--- +name: "Session" +slug: "session" +category: "messagerie" +difficulty: "intermediate" +license: "GPL-3.0" +url: "https://getsession.org" +color: "#178a5a" +monogram: "Se" +iconSlug: "session" +tags: ["Sans numéro","🌍 · open-source"] +lang: "fr" +--- + +## what + +Un chiffrement à la Signal, mais sans numéro de téléphone et avec un routage en oignon. + +## why + +Votre identité est un « Session ID » aléatoire. Le trafic passe par un réseau décentralisé + (Lokinet) qui réduit fortement les métadonnées. + +## who + +Quand un numéro de téléphone est un risque. Écosystème plus petit, à réserver aux échanges qui + comptent. + +## install + +
    +
  1. + Installez depuis getsession.org (toutes plateformes). +
  2. +
  3. + « Create account » génère votre Session ID et une phrase de récupération. +
  4. +
  5. Notez la phrase de récupération sur papier : c'est la seule clé de votre compte.
  6. +
  7. Partagez votre Session ID (ou QR) à vos contacts.
  8. +
diff --git a/content/outils/signal.md b/content/outils/signal.md new file mode 100644 index 0000000..2ddcd48 --- /dev/null +++ b/content/outils/signal.md @@ -0,0 +1,43 @@ +--- +name: "Signal" +slug: "signal" +category: "messagerie" +difficulty: "beginner" +license: "AGPL-3.0" +url: "https://signal.org" +color: "#3a76f0" +monogram: "S" +iconSlug: "signal" +tags: ["Remplace WhatsApp","🇺🇸 · open-source"] +lang: "fr" +--- + +## what + +Une messagerie chiffrée pour vos textos, appels, visios et groupes. C'est le remplaçant direct + de WhatsApp, en aussi simple. + +## why + +Chiffrement de bout en bout par défaut, à but non lucratif, protocole audité qui sert de + référence mondiale. Presque aucune métadonnée conservée, et l'équipe a promis de quitter l'UE + plutôt que d'installer un scan. + +## who + +Tout le monde, dès aujourd'hui. C'est le premier geste et le plus utile. Seule contrainte : un + numéro de téléphone, que l'on peut masquer derrière un nom d'utilisateur. + +## install + +
    +
  1. + Installez depuis signal.org (App Store, Google Play, ou l'APK direct sur Android). +
  2. +
  3. Confirmez votre numéro par SMS, puis choisissez un code PIN.
  4. +
  5. + Réglages > Confidentialité : créez un nom d'utilisateur pour cacher votre numéro, et + activez le verrou d'inscription. +
  6. +
  7. Invitez vos proches. Sur ordinateur, liez l'application de bureau à votre téléphone.
  8. +
diff --git a/content/outils/simplex.md b/content/outils/simplex.md new file mode 100644 index 0000000..9bd530c --- /dev/null +++ b/content/outils/simplex.md @@ -0,0 +1,43 @@ +--- +name: "SimpleX Chat" +slug: "simplex" +category: "messagerie" +difficulty: "intermediate" +url: "https://simplex.chat" +color: "#1e63d0" +monogram: "SX" +iconSlug: "simplex" +tags: ["Sans identifiant","🌍 · open-source"] +lang: "fr" +--- + +## what + +Une messagerie chiffrée sans aucun compte, sans numéro et sans identifiant. On se connecte en + partageant un lien ou un QR code. + +## why + +Par conception, même les serveurs ignorent qui parle à qui. C'est aujourd'hui l'option la plus + protectrice contre la corrélation des métadonnées. + +## who + +Comptes pseudonymes, contacts sensibles, haut risque. Un peu plus jeune que Signal, avec + quelques aspérités. + +## install + +
    +
  1. + Installez depuis simplex.chat (F-Droid, Google Play, App Store ou APK). +
  2. +
  3. + Choisissez un nom d'affichage local (jamais votre vrai nom : il ne quitte pas votre + appareil). +
  4. +
  5. + Pour ajouter un contact, partagez un lien d'invitation à usage unique ou un QR code. +
  6. +
  7. Activez le verrou par code, et (avancé) configurez vos propres serveurs SMP.
  8. +
diff --git a/content/outils/synapse.md b/content/outils/synapse.md new file mode 100644 index 0000000..d5ae6ea --- /dev/null +++ b/content/outils/synapse.md @@ -0,0 +1,27 @@ +--- +name: "Matrix Synapse" +slug: "synapse" +category: "selfhost" +difficulty: "advanced" +license: "AGPL-3.0" +url: "https://element.io/server" +color: "#0dbd8b" +monogram: "Mx" +iconSlug: "matrix" +tags: ["Votre serveur de chat","🌍 · open-source"] +lang: "fr" +--- + +## what + +Votre propre serveur de messagerie Matrix, sur lequel tournent Element et les appels chiffrés. + +## why + +En hébergeant le serveur, vous contrôlez aussi les métadonnées (qui parle à qui, quand), + le point faible de Matrix en fédération. C'est la messagerie communautaire dont vous êtes le + seul maître. + +## who + +Communautés, collectifs, rédactions qui veulent leur espace fédéré indépendant. diff --git a/content/outils/tails.md b/content/outils/tails.md new file mode 100644 index 0000000..fea4f14 --- /dev/null +++ b/content/outils/tails.md @@ -0,0 +1,36 @@ +--- +name: "Tails" +slug: "tails" +category: "os" +difficulty: "advanced" +license: "GPL-3.0" +url: "https://tails.net" +color: "#56347c" +monogram: "Ta" +iconSlug: "tails" +tags: ["Clé USB amnésique","🌍 · tout via Tor"] +lang: "fr" +--- + +## what + +Un système qui démarre depuis une clé USB, fait passer tout le trafic par Tor et ne laisse{' '} + aucune trace à l'extinction. + +## why + +À l'arrêt, tout est oublié : c'est « amnésique ». Idéal pour un travail sensible et ponctuel sur + un ordinateur qui n'est pas le vôtre, sans rien y laisser. + +## who + +Lanceurs d'alerte, journalistes, sources. L'outil de référence pour les missions à haut risque. + +## install + +
    +
  1. + Téléchargez Tails sur tails.net et suivez l'assistant pour créer la clé USB. +
  2. +
  3. Redémarrez l'ordinateur sur cette clé. Utilisez-le, puis éteignez : tout disparaît.
  4. +
diff --git a/content/outils/tailscale.md b/content/outils/tailscale.md new file mode 100644 index 0000000..c591bd1 --- /dev/null +++ b/content/outils/tailscale.md @@ -0,0 +1,43 @@ +--- +name: "Tailscale · Headscale · WireGuard" +slug: "tailscale" +category: "vpn" +difficulty: "advanced" +license: "BSD-3-Clause" +url: "https://tailscale.com" +color: "#242424" +monogram: "Ts" +iconSlug: "tailscale" +tags: ["Accès privé à son serveur","🌍 · open-source"] +lang: "fr" +--- + +## what + +Un réseau privé chiffré qui relie vos appareils à votre serveur, sans jamais l'exposer sur + l'Internet public. + +## why + +Au lieu d'ouvrir des ports (et de vous faire attaquer), Tailscale crée un tunnel + WireGuard entre vos seuls appareils autorisés : votre serveur n'a aucune surface d'attaque + publique. Headscale est la version que vous hébergez vous-même, pour ne dépendre d'aucun + tiers. WireGuard seul offre le contrôle total, au prix d'une configuration manuelle. + +## who + +Tout auto-hébergeur. C'est la façon sûre d'atteindre son Nextcloud ou ses photos depuis + l'extérieur. + +## install + +
    +
  1. + Installez Tailscale sur votre serveur et sur votre téléphone/PC, connectez-les au même compte. +
  2. +
  3. + Vos appareils se voient aussitôt sur un réseau privé ; accédez à votre serveur par son adresse + Tailscale. +
  4. +
  5. Pour zéro dépendance, remplacez le serveur de coordination par Headscale, auto-hébergé.
  6. +
diff --git a/content/outils/tor.md b/content/outils/tor.md new file mode 100644 index 0000000..d481d25 --- /dev/null +++ b/content/outils/tor.md @@ -0,0 +1,45 @@ +--- +name: "Tor Browser" +slug: "tor" +category: "tor" +difficulty: "advanced" +license: "BSD-3-Clause" +url: "https://www.torproject.org" +color: "#7d4698" +monogram: "Tor" +iconSlug: "torbrowser" +tags: ["Anonymat réseau","🌍 · open-source"] +lang: "fr" +--- + +## what + +Un réseau qui sépare qui vous êtes de ce que vous faites en ligne, accessible via le Tor + Browser. + +## why + +Votre trafic rebondit à travers plusieurs relais chiffrés dans le monde : aucun ne connaît à la + fois votre identité et votre destination. C'est l'outil des journalistes et des sources, et le + moyen d'atteindre un site bloqué (par exemple pour télécharger un VPN censuré). + +## who + +Profils à haut risque, et quiconque doit dissocier son activité de son adresse IP réelle. À + utiliser ponctuellement, pas comme navigateur de tous les jours. + +## install + +
    +
  1. + Téléchargez le Tor Browser sur torproject.org (Windows, macOS, Linux, Android). +
  2. +
  3. + Lancez-le et cliquez « Se connecter ». Si Tor est bloqué dans votre pays, activez un « pont » + (bridge). +
  4. +
  5. + Naviguez sans maximiser la fenêtre, sans installer d'extension, et{' '} + sans vous connecter à vos vrais comptes. +
  6. +
diff --git a/content/outils/tuta.md b/content/outils/tuta.md new file mode 100644 index 0000000..cf20dec --- /dev/null +++ b/content/outils/tuta.md @@ -0,0 +1,40 @@ +--- +name: "Tuta" +slug: "tuta" +category: "email" +difficulty: "beginner" +license: "GPL-3.0" +url: "https://tuta.com" +color: "#b01e2e" +monogram: "Tu" +iconSlug: "tuta" +tags: ["Remplace Gmail","🇩🇪 · open-source · post-quantique"] +lang: "fr" +--- + +## what + +Une messagerie e-mail allemande, entièrement open-source et sans aucun service Google. + +## why + +Elle chiffre aussi l'objet et une partie des métadonnées, avec un chiffrement résistant à + l'informatique quantique. Juridiction UE. + +## who + +Ceux qui veulent le maximum de métadonnées chiffrées et du 100 % libre. Elle n'utilise pas PGP : + pour écrire chiffré à l'extérieur, on protège le message par mot de passe. + +## install + +
    +
  1. + Créez un compte sur tuta.com et installez l'application (disponible sur F-Droid). +
  2. +
  3. + Pour écrire à un non-utilisateur de façon chiffrée : cliquez sur le cadenas et définissez un + mot de passe, transmis à votre correspondant par un autre canal. +
  4. +
  5. Activez la double authentification.
  6. +
diff --git a/content/outils/ublock.md b/content/outils/ublock.md new file mode 100644 index 0000000..253a181 --- /dev/null +++ b/content/outils/ublock.md @@ -0,0 +1,36 @@ +--- +name: "uBlock Origin" +slug: "ublock" +category: "navigateur" +difficulty: "beginner" +url: "https://ublockorigin.com" +color: "#7a0d0d" +monogram: "uB" +iconSlug: "ublockorigin" +tags: ["Extension","🌍 · open-source"] +lang: "fr" +--- + +## what + +L'extension de navigateur qui bloque publicités, traqueurs et scripts espions. Le geste le plus + rentable, en dix secondes. + +## why + +La publicité en ligne est le principal vecteur de surveillance et de profilage. La bloquer coupe + l'essentiel du pistage, accélère les pages et réduit les risques de logiciels malveillants. + +## who + +Tout le monde, sur Firefox comme sur les navigateurs Chromium. + +## install + +
    +
  1. + Ouvrez le magasin d'extensions de votre navigateur et cherchez « uBlock Origin » (l'original, + par Raymond Hill). +
  2. +
  3. Installez-le. Il fonctionne aussitôt, sans réglage nécessaire.
  4. +
diff --git a/content/outils/visio.md b/content/outils/visio.md new file mode 100644 index 0000000..722767d --- /dev/null +++ b/content/outils/visio.md @@ -0,0 +1,38 @@ +--- +name: "Jitsi Meet · Element Call" +slug: "visio" +category: "boiteaoutils" +difficulty: "beginner" +url: "https://jitsi.org" +color: "#1d6fb8" +monogram: "Vi" +iconSlug: "jitsi" +tags: ["Remplace Zoom / Meet","🌍 · open-source"] +lang: "fr" +--- + +## what + +Des outils de visioconférence sans compte ni installation lourde, pour remplacer Zoom, Google + Meet ou Teams. + +## why + +Jitsi Meet se lance dans le navigateur, sans compte, et peut être auto-hébergé (c'est + aussi la base de Brave Talk). Element Call est chiffré de bout en bout et fédéré via + Matrix. Pour un appel simple et totalement chiffré, les appels Signal font aussi le + travail. + +## who + +Tout le monde. Sur un serveur Jitsi public, les appels à plusieurs sont chiffrés en transport ; + le chiffrement de bout en bout est en option. + +## install + +
    +
  1. + Jitsi : allez sur meet.jit.si, créez un nom de salon, partagez le lien. Rien à installer. +
  2. +
  3. Element Call : depuis l'application Element (Matrix), lancez un appel dans un salon.
  4. +
diff --git a/content/outils/website.md b/content/outils/website.md new file mode 100644 index 0000000..442de4d --- /dev/null +++ b/content/outils/website.md @@ -0,0 +1,44 @@ +--- +name: "Votre propre site web" +slug: "website" +category: "selfhost" +difficulty: "intermediate" +url: "https://astro.build" +color: "#7a4ddb" +monogram: "www" +tags: ["Résister au bannissement"] +lang: "fr" +--- + +## what + +Un site à vous, sur votre nom de domaine, où votre contenu ne dépend d'aucune plateforme. + +## why + +Le jour où un réseau supprime ou suspend votre compte, tout votre travail disparaît. Un site + personnel est votre point d'ancrage : vos articles y restent, archivés et à jour, quoi qu'il + arrive. C'est exactement ce que font les comptes d'information prudents. Un site statique + (généré par Hugo, Astro ou Jekyll) suffit et se publie presque gratuitement ; pour un blog + dynamique, un WordPress ou un Ghost auto-hébergé fait l'affaire. + +## who + +Comptes d'information, créateurs, militants, quiconque publie et craint la censure de + plateforme. + +## install + +
    +
  1. + Achetez un nom de domaine (idéalement chez un registraire respectueux de la vie privée). +
  2. +
  3. + Choisissez un générateur de site statique (Astro, Hugo) et hébergez le résultat, ou installez + WordPress/Ghost sur votre serveur. +
  4. +
  5. + Renvoyez-y systématiquement depuis vos réseaux : votre audience vous retrouve même si un + compte tombe. +
  6. +
diff --git a/content/outils/yubikey.md b/content/outils/yubikey.md new file mode 100644 index 0000000..6485075 --- /dev/null +++ b/content/outils/yubikey.md @@ -0,0 +1,39 @@ +--- +name: "YubiKey · Nitrokey" +slug: "yubikey" +category: "deuxfa" +difficulty: "intermediate" +url: "https://www.yubico.com" +color: "#84bd00" +monogram: "Yk" +iconSlug: "yubico" +tags: ["Clé matérielle FIDO2"] +lang: "fr" +--- + +## what + +Une petite clé physique (format USB, souvent avec NFC) qui prouve votre identité d'un simple + contact du doigt. + +## why + +C'est la 2FA la plus solide : elle résiste à l'hameçonnage, car la clé vérifie l'adresse réelle + du site avant de répondre. Nitrokey est l'alternative à matériel et micrologiciel + entièrement ouverts. Un faux site ne peut pas vous piéger. + +## who + +Ceux qui veulent le maximum de sécurité sur leurs comptes clés. Achetez-en deux (une + principale, une de secours). + +## install + +
    +
  1. Procurez-vous deux clés sur yubico.com ou nitrokey.com.
  2. +
  3. + Dans les réglages de sécurité de chaque compte (e-mail, gestionnaire de mots de passe…), + ajoutez une « clé de sécurité » ou « passkey ». Enregistrez vos deux clés. +
  4. +
  5. Gardez la clé de secours dans un lieu sûr, séparé.
  6. +
diff --git a/content/outils/yunohost.md b/content/outils/yunohost.md new file mode 100644 index 0000000..be31a40 --- /dev/null +++ b/content/outils/yunohost.md @@ -0,0 +1,43 @@ +--- +name: "YunoHost · Umbrel · Start9" +slug: "yunohost" +category: "selfhost" +difficulty: "advanced" +license: "AGPL-3.0" +url: "https://yunohost.org" +color: "#e29100" +monogram: "Yh" +tags: ["Serveur clé en main","🌍 · open-source"] +lang: "fr" +--- + +## what + +Des systèmes qui transforment un vieux PC ou un Raspberry Pi en serveur personnel, avec un + catalogue d'applications à installer en quelques clics. + +## why + +L'auto-hébergement fait peur, mais ces outils font le gros du travail. YunoHost gère même + votre propre e-mail et une authentification unique. Umbrel offre un magasin de plus de + 300 applications, très prisé des amateurs de vie privée et de Bitcoin. Start9 chiffre les + sauvegardes et donne une adresse Tor à chaque service par défaut. + +## who + +Les profils avancés prêts à consacrer un après-midi à monter leur propre cloud. C'est la parade + structurelle la plus radicale à Chat Control. + +## install + +
    +
  1. + Récupérez un vieil ordinateur, un Raspberry Pi, ou louez un petit serveur (VPS) dans un pays + protecteur. +
  2. +
  3. Installez YunoHost, Umbrel ou Start9 en suivant leur guide officiel.
  4. +
  5. Depuis le catalogue, installez Nextcloud, un serveur Matrix, une galerie photo, etc.
  6. +
  7. + Accédez-y en privé avec Tailscale (section VPN) plutôt que d'exposer le serveur au public. +
  8. +
diff --git a/docs/CONTENT_GUIDE.md b/docs/CONTENT_GUIDE.md new file mode 100644 index 0000000..3296150 --- /dev/null +++ b/docs/CONTENT_GUIDE.md @@ -0,0 +1,76 @@ +# Guide de rédaction — fiches d'outils FR/créole + +## Format d'une fiche (`content/outils/.md`) + +```markdown +--- +name: "Signal" +slug: "signal" +category: "messagerie" # slug d'une catégorie de content/categories.json +difficulty: "beginner" # beginner | intermediate | advanced +license: "AGPL-3.0" # identifiant SPDX exact (spdx.org/licenses) +url: "https://signal.org" +color: "#3a76f0" # couleur du monogramme (décorative) +monogram: "S" +tags: ["Remplace WhatsApp","🇺🇸 · open-source"] +lang: "fr" +--- + +## what +À quoi sert l'outil, en deux phrases maximum. + +## why +Pourquoi il compte pour la souveraineté numérique. + +## who +Pour qui, et à quel moment du parcours. + +## install +
  1. Étapes concrètes, numérotées.
+``` + +Les valeurs du frontmatter sont encodées en JSON (guillemets doubles). +Le corps accepte Markdown et HTML simple ; il est rendu au build, rien n'est +interprété côté client. **Le build échoue si une fiche est invalide** — c'est +le contrat de validation (ADR-001). + +## Ton éditorial + +- **Militant mais chaleureux, ancré Caraïbe.** Pas de langage corporate, pas + de peur gratuite : on explique le geste, on donne le pouvoir d'agir. +- Tutoiement. Phrases courtes. Le premier mot de chaque section porte le sens + (motif en F, doctrine §1.1). +- `what` dit le service rendu, `why` dit l'enjeu politique/technique, `who` + donne la porte d'entrée (« commence ici si… »), `install` est actionnable + sans autre lecture. +- Exemples et localisation ancrés territoire — jamais de Lorem ipsum + hexagonal (doctrine §4.2). + +## Créole guadeloupéen (gcf) + +- Les **micro-textes de l'interface** (boutons, filtres, états vides, marqueur + de fin) vivent dans `frontend/src/lib/i18n/gcf.json`. C'est la couche à + plus fort impact affectif pour le plus faible coût (doctrine §4.2) — elle + est traduite en premier. +- ⚠️ **Relecture par un locuteur natif requise** : les chaînes gcf actuelles + sont un premier jet à valider avant mise en production. La graphie de + référence à trancher (GEREC-F ou autre) doit être notée ici une fois choisie. +- La traduction intégrale des fiches est un chantier éditorial séparé : + le format prévu est un fichier jumeau `content/outils/gcf/.md` avec + `lang: "gcf"` (le chargeur l'ignorera tant que la locale de contenu n'est + pas activée). +- Termes fixés : « zouti » (outil), « Ou pa manké ayen » (fin de liste), + « Chwazi zouti ou » (héros), niveaux « Débitan / Entèmédia / Avansé ». + +## Ajouter une catégorie + +Ajouter une entrée à `content/categories.json` (`slug`, `title`, `order`, +`part`). La navigation n'affiche que 4 catégories avant la divulgation +progressive (Hick-Hyman, doctrine §1.3) : l'ordre `order` décide lesquelles. + +## Vérifications avant merge + +- [ ] `license` est un identifiant SPDX valide +- [ ] `difficulty` reflète l'expérience réelle d'installation (pas le prestige) +- [ ] Chaque section respecte sa question (what/why/who/install) +- [ ] `pnpm build` passe (validation du frontmatter incluse) diff --git a/docs/DESIGN_SYSTEM.md b/docs/DESIGN_SYSTEM.md new file mode 100644 index 0000000..eaa3b43 --- /dev/null +++ b/docs/DESIGN_SYSTEM.md @@ -0,0 +1,72 @@ +# Design system OKI — exitchatcontrol.org + +Toutes les valeurs visuelles vivent dans +[`frontend/src/styles/tokens.css`](../frontend/src/styles/tokens.css). +Les composants Svelte consomment les tokens ; **jamais de valeur brute** +(couleur, taille) dans un composant (doctrine §5.2). + +## Architecture CSS + +Cascade explicite par `@layer` ([app.css](../frontend/src/app.css)) : + +``` +reset → tokens → base → layouts → components → utilities +``` + +- `reset.css` — reset moderne minimal (~40 lignes), inclut `prefers-reduced-motion`. +- `tokens.css` — custom properties : couleurs `oklch()`, typo fluide `clamp()`, espacements, interaction. +- `base.css` — HTML sémantique nu : typo, liens, focus visible, boutons. +- `layouts.css` — primitives Every Layout : `.stack`, `.cluster`, `.center`, `.card-grid` (container queries, pas de media queries de largeur d'écran). +- Le style propre à chaque composant vit dans son ` diff --git a/frontend/src/lib/components/DifficultyBadge.svelte b/frontend/src/lib/components/DifficultyBadge.svelte new file mode 100644 index 0000000..8144eec --- /dev/null +++ b/frontend/src/lib/components/DifficultyBadge.svelte @@ -0,0 +1,36 @@ + + + + + {t(`difficulty.${difficulty}`)} + + + diff --git a/frontend/src/lib/components/EndMarker.svelte b/frontend/src/lib/components/EndMarker.svelte new file mode 100644 index 0000000..1dfa07f --- /dev/null +++ b/frontend/src/lib/components/EndMarker.svelte @@ -0,0 +1,34 @@ + + + +

+ {t('directory.endOfList')} + {t('directory.endOfListSub')} +

+ + diff --git a/frontend/src/lib/components/LanguageSwitcher.svelte b/frontend/src/lib/components/LanguageSwitcher.svelte new file mode 100644 index 0000000..f1d5204 --- /dev/null +++ b/frontend/src/lib/components/LanguageSwitcher.svelte @@ -0,0 +1,40 @@ + + + +
+ {#each locales as locale (locale)} + + {/each} +
+ + diff --git a/frontend/src/lib/components/SearchBar.svelte b/frontend/src/lib/components/SearchBar.svelte new file mode 100644 index 0000000..187abb0 --- /dev/null +++ b/frontend/src/lib/components/SearchBar.svelte @@ -0,0 +1,43 @@ + + + + + + + diff --git a/frontend/src/lib/components/ToolCard.svelte b/frontend/src/lib/components/ToolCard.svelte new file mode 100644 index 0000000..3664778 --- /dev/null +++ b/frontend/src/lib/components/ToolCard.svelte @@ -0,0 +1,99 @@ + + +
+
+ +

{tool.name}

+
+ +

{t('tool.why')} : {tool.excerpt}

+ +
+ + {#if tool.license} + {tool.license} + {/if} + {#each tool.tags as tag (tag)} + {tag} + {/each} +
+
+ + diff --git a/frontend/src/lib/content/tools.server.ts b/frontend/src/lib/content/tools.server.ts new file mode 100644 index 0000000..a5b12a2 --- /dev/null +++ b/frontend/src/lib/content/tools.server.ts @@ -0,0 +1,117 @@ +/** + * Chargeur de contenu — s'exécute uniquement au build (site prérendu, + * ADR-001) : ni fs ni marked ne sont expédiés au client. + */ +import { readFileSync, readdirSync } from 'node:fs' +import path from 'node:path' +import { marked } from 'marked' + +export type Difficulty = 'beginner' | 'intermediate' | 'advanced' + +export interface ToolSections { + what?: string + why?: string + who?: string + install?: string + extra?: string +} + +export interface Tool { + name: string + slug: string + category: string + difficulty: Difficulty + license?: string + url: string + color?: string + monogram?: string + iconSlug?: string + tags: string[] + /** extrait texte brut de « why », pour les cartes */ + excerpt: string + sections: ToolSections +} + +export interface Category { + slug: string + title: string + order: number + part: number +} + +const CONTENT = path.resolve(process.cwd(), '..', 'content') +const SECTION_KEYS = ['what', 'why', 'who', 'install', 'extra'] as const + +function parseFrontmatter(raw: string): [Record, string] { + const m = raw.match(/^---\n([\s\S]*?)\n---\n?/) + if (!m) throw new Error('frontmatter manquant') + const meta: Record = {} + for (const line of m[1].split('\n')) { + const i = line.indexOf(':') + if (i === -1) continue + // les valeurs sont encodées en JSON par le script de migration + meta[line.slice(0, i).trim()] = JSON.parse(line.slice(i + 1).trim()) + } + return [meta, raw.slice(m[0].length)] +} + +function parseSections(body: string): ToolSections { + const sections: ToolSections = {} + const parts = body.split(/^## (\w+)\s*$/m) + for (let i = 1; i < parts.length; i += 2) { + const key = parts[i] as (typeof SECTION_KEYS)[number] + if (SECTION_KEYS.includes(key)) { + sections[key] = marked.parse(parts[i + 1].trim(), { async: false }) + } + } + return sections +} + +const ENTITIES: Record = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + ''': "'", + ' ': ' ' +} + +function stripHtml(html: string): string { + return html + .replace(/<[^>]+>/g, ' ') + .replace(/&(?:amp|lt|gt|quot|#39|nbsp);/g, (e) => ENTITIES[e]) + .replace(/[ \t\r\n]+/g, ' ') + .trim() +} + +function loadTool(file: string): Tool { + const raw = readFileSync(path.join(CONTENT, 'outils', file), 'utf8') + const [meta, body] = parseFrontmatter(raw) + const sections = parseSections(body) + const why = stripHtml(sections.why ?? sections.what ?? '') + return { + ...(meta as unknown as Omit), + excerpt: why.length > 160 ? `${why.slice(0, 157).trimEnd()}…` : why, + sections + } +} + +let cache: { tools: Tool[]; categories: Category[] } | null = null + +export function loadContent() { + if (cache) return cache + const categories = ( + JSON.parse(readFileSync(path.join(CONTENT, 'categories.json'), 'utf8')) as Category[] + ).sort((a, b) => a.order - b.order) + const order = new Map(categories.map((c, i) => [c.slug, i])) + const tools = readdirSync(path.join(CONTENT, 'outils')) + .filter((f) => f.endsWith('.md')) + .map(loadTool) + .sort( + (a, b) => + (order.get(a.category) ?? 99) - (order.get(b.category) ?? 99) || + a.name.localeCompare(b.name, 'fr') + ) + cache = { tools, categories } + return cache +} diff --git a/frontend/src/lib/i18n/fr.json b/frontend/src/lib/i18n/fr.json new file mode 100644 index 0000000..29f9e44 --- /dev/null +++ b/frontend/src/lib/i18n/fr.json @@ -0,0 +1,63 @@ +{ + "site": { + "name": "Exit Chat Control", + "tagline": "Reprends le contrôle de ton internet", + "description": "Annuaire d'outils libres pour la souveraineté numérique — messagerie chiffrée, VPN, auto-hébergement, OS libres." + }, + "nav": { + "home": "Accueil", + "directory": "Annuaire", + "contribute": "Contribuer", + "skipToContent": "Aller au contenu" + }, + "hero": { + "title": "Chwazi zouti ou", + "subtitle": "Reprends le contrôle de ton internet : des outils libres, expliqués simplement, du premier geste au serveur auto-hébergé.", + "cta": "Voir l'annuaire" + }, + "directory": { + "title": "Annuaire des outils", + "intro": "Chaque outil est libre, vérifié, et classé par difficulté. Commence par le vert.", + "filterCategory": "Catégorie", + "filterDifficulty": "Difficulté", + "allCategories": "Toutes les catégories", + "allDifficulties": "Toutes", + "results": "outils", + "resultOne": "outil", + "empty": "Ayen pa maché èvè filtè-lasa — essaie d'élargir les filtres.", + "endOfList": "Ou pa manké ayen", + "endOfListSub": "Tu es au bout de la liste — rien ne se recharge dans ton dos.", + "prevPage": "Page précédente", + "nextPage": "Page suivante", + "page": "Page" + }, + "tool": { + "what": "À quoi ça sert", + "why": "Pourquoi c'est important", + "who": "Pour qui, quand", + "install": "Installer et utiliser", + "officialSite": "Site officiel", + "sourceCode": "Code source", + "license": "Licence", + "category": "Catégorie", + "backToDirectory": "Retour à l'annuaire" + }, + "difficulty": { + "beginner": "Débutant", + "intermediate": "Intermédiaire", + "advanced": "Avancé" + }, + "search": { + "label": "Rechercher un outil", + "placeholder": "Rechercher… (touche /)", + "noResults": "Aucun outil trouvé" + }, + "lang": { + "switchLabel": "Langue", + "switchTo": "Passer en" + }, + "footer": { + "license": "Contenu et code libres (MIT). Zéro tracker, zéro CDN, zéro cookie.", + "contribute": "Proposer un outil ou une correction" + } +} diff --git a/frontend/src/lib/i18n/gcf.json b/frontend/src/lib/i18n/gcf.json new file mode 100644 index 0000000..81f3aef --- /dev/null +++ b/frontend/src/lib/i18n/gcf.json @@ -0,0 +1,63 @@ +{ + "site": { + "name": "Exit Chat Control", + "tagline": "Pran kontwòl entènèt a-w", + "description": "Lis a zouti lib pou libèté niméwik — mésajri chifré, VPN, oto-ébèjman, sistèm lib." + }, + "nav": { + "home": "Lakou", + "directory": "Lis a zouti", + "contribute": "Pòté mannèv", + "skipToContent": "Alé dirèk asi kontni-la" + }, + "hero": { + "title": "Chwazi zouti ou", + "subtitle": "Pran kontwòl entènèt a-w : zouti lib, èspliké senp, dépi prèmyé jès jis sèwvè a-w.", + "cta": "Gadé lis-la" + }, + "directory": { + "title": "Lis a zouti", + "intro": "Chak zouti lib, vérifyé, é rangé pa difikilté. Koumansé èvè vèw-la.", + "filterCategory": "Katégori", + "filterDifficulty": "Difikilté", + "allCategories": "Tout katégori", + "allDifficulties": "Tout", + "results": "zouti", + "resultOne": "zouti", + "empty": "Ayen pa maché èvè filtè-lasa — èséyé louvri filtè a-w.", + "endOfList": "Ou pa manké ayen", + "endOfListSub": "Ou rivé an bout a lis-la — ayen pa ka richajé dèyè do a-w.", + "prevPage": "Paj avan", + "nextPage": "Paj apré", + "page": "Paj" + }, + "tool": { + "what": "Pou ki sa i sèwvi", + "why": "Poukisa sa enpòwtan", + "who": "Pou kimoun, kitan", + "install": "Enstalé é sèwvi", + "officialSite": "Sit ofisyèl", + "sourceCode": "Kòd sous", + "license": "Lisans", + "category": "Katégori", + "backToDirectory": "Viré an lis-la" + }, + "difficulty": { + "beginner": "Débitan", + "intermediate": "Entèmédia", + "advanced": "Avansé" + }, + "search": { + "label": "Chèché on zouti", + "placeholder": "Chèché… (touch /)", + "noResults": "Pon zouti pa touvé" + }, + "lang": { + "switchLabel": "Lang", + "switchTo": "Pasé an" + }, + "footer": { + "license": "Kontni é kòd lib (MIT). Pon trakè, pon CDN, pon cookie.", + "contribute": "Pwopozé on zouti oben on koreksyon" + } +} diff --git a/frontend/src/lib/i18n/index.svelte.ts b/frontend/src/lib/i18n/index.svelte.ts new file mode 100644 index 0000000..7d10574 --- /dev/null +++ b/frontend/src/lib/i18n/index.svelte.ts @@ -0,0 +1,54 @@ +import fr from './fr.json' +import gcf from './gcf.json' +import { browser } from '$app/environment' +import { defaultLocale, type Locale } from './locales' + +export { locales, localeNames, defaultLocale, type Locale } from './locales' + +type Dict = typeof fr +// gcf est vérifié par TypeScript contre la forme du français : une clé +// manquante est une erreur de build, pas un fallback silencieux (ADR-001). +const dicts: Record = { fr, gcf } + +const STORAGE_KEY = 'oki-lang' + +function initialLocale(): Locale { + if (!browser) return defaultLocale + const saved = localStorage.getItem(STORAGE_KEY) + return saved === 'gcf' || saved === 'fr' ? saved : defaultLocale +} + +// Rune d'état partagée (Svelte 5) : la langue de l'interface. +// Le HTML prérendu est en fr ; le créole est une amélioration progressive +// (persistance localStorage — mission §4.3, doctrine §4.2). +const state = $state({ locale: initialLocale() }) + +export function getLocale(): Locale { + return state.locale +} + +export function setLocale(locale: Locale) { + state.locale = locale + if (browser) { + localStorage.setItem(STORAGE_KEY, locale) + document.documentElement.lang = locale + } +} + +function lookup(dict: Dict, path: string): unknown { + return path.split('.').reduce((node, key) => { + if (node && typeof node === 'object' && key in node) { + return (node as Record)[key] + } + return undefined + }, dict) +} + +/** Chaîne d'interface pour `path` dans la langue courante (réactif). */ +export function t(path: string): string { + const hit = lookup(dicts[state.locale], path) ?? lookup(dicts[defaultLocale], path) + if (typeof hit !== 'string') { + throw new Error(`Clé i18n manquante « ${path} » (locale : ${state.locale})`) + } + return hit +} diff --git a/frontend/src/lib/i18n/locales.ts b/frontend/src/lib/i18n/locales.ts new file mode 100644 index 0000000..759991c --- /dev/null +++ b/frontend/src/lib/i18n/locales.ts @@ -0,0 +1,11 @@ +// gcf = créole guadeloupéen (ISO 639-3, valide BCP 47). +// La langue du cœur d'OKI : les micro-textes créoles activent les circuits +// de confiance avant toute lecture consciente (doctrine §4.2). +export const locales = ['fr', 'gcf'] as const +export type Locale = (typeof locales)[number] +export const defaultLocale: Locale = 'fr' + +export const localeNames: Record = { + fr: 'Français', + gcf: 'Kréyòl' +} diff --git a/frontend/src/routes/+layout.svelte b/frontend/src/routes/+layout.svelte new file mode 100644 index 0000000..d4f622c --- /dev/null +++ b/frontend/src/routes/+layout.svelte @@ -0,0 +1,108 @@ + + + + +
+ +
+ +
+ {@render children()} +
+ + + + diff --git a/frontend/src/routes/+layout.ts b/frontend/src/routes/+layout.ts new file mode 100644 index 0000000..f962865 --- /dev/null +++ b/frontend/src/routes/+layout.ts @@ -0,0 +1,3 @@ +// Tout le site est prérendu (ADR-001) : HTML statique servi par nginx, +// un lien fonctionne sans JS (doctrine §4.4). +export const prerender = true diff --git a/frontend/src/routes/+page.server.ts b/frontend/src/routes/+page.server.ts new file mode 100644 index 0000000..aee7f1d --- /dev/null +++ b/frontend/src/routes/+page.server.ts @@ -0,0 +1,11 @@ +import { loadContent } from '$lib/content/tools.server' + +export function load() { + const { tools, categories } = loadContent() + return { + categories, + toolCount: tools.length, + // les premiers gestes : outils débutant, un par catégorie + starters: tools.filter((t) => t.difficulty === 'beginner').slice(0, 3) + } +} diff --git a/frontend/src/routes/+page.svelte b/frontend/src/routes/+page.svelte new file mode 100644 index 0000000..69806be --- /dev/null +++ b/frontend/src/routes/+page.svelte @@ -0,0 +1,92 @@ + + + + {t('site.name')} — {t('site.tagline')} + + + + +
+

{t('hero.title')}

+

{t('hero.subtitle')}

+

{t('hero.cta')} · {data.toolCount} {t('directory.results')}

+
+ +
+

{t('directory.intro')}

+
+
    + {#each data.starters as tool (tool.slug)} +
  • + {/each} +
+
+
+ +
+

{t('directory.filterCategory')}

+
    + {#each data.categories as cat (cat.slug)} +
  • {cat.title}
  • + {/each} +
+
+ + diff --git a/frontend/src/routes/outils/+page.server.ts b/frontend/src/routes/outils/+page.server.ts new file mode 100644 index 0000000..bbea4ba --- /dev/null +++ b/frontend/src/routes/outils/+page.server.ts @@ -0,0 +1,10 @@ +import { loadContent } from '$lib/content/tools.server' + +export function load() { + const { tools, categories } = loadContent() + return { + categories, + // seules les données de carte partent au client (budget JS, doctrine §4.4) + tools: tools.map(({ sections: _sections, ...card }) => card) + } +} diff --git a/frontend/src/routes/outils/+page.svelte b/frontend/src/routes/outils/+page.svelte new file mode 100644 index 0000000..b7740b7 --- /dev/null +++ b/frontend/src/routes/outils/+page.svelte @@ -0,0 +1,136 @@ + + + + {t('directory.title')} — {t('site.name')} + + + +
+

{t('directory.title')}

+

{t('directory.intro')}

+
+ +
+ + + +
+ +
+

+ {filtered.length} + {filtered.length === 1 ? t('directory.resultOne') : t('directory.results')} +

+ + {#if filtered.length === 0} +

{t('directory.empty')}

+ {:else} +
+
    + {#each filtered as tool (tool.slug)} +
  • + {/each} +
+
+ {/if} + + +
+ + diff --git a/frontend/src/routes/outils/[slug]/+page.server.ts b/frontend/src/routes/outils/[slug]/+page.server.ts new file mode 100644 index 0000000..0eec6df --- /dev/null +++ b/frontend/src/routes/outils/[slug]/+page.server.ts @@ -0,0 +1,14 @@ +import { error } from '@sveltejs/kit' +import { loadContent } from '$lib/content/tools.server' + +export function entries() { + return loadContent().tools.map(({ slug }) => ({ slug })) +} + +export function load({ params }: { params: { slug: string } }) { + const { tools, categories } = loadContent() + const tool = tools.find((t) => t.slug === params.slug) + if (!tool) error(404) + const category = categories.find((c) => c.slug === tool.category) + return { tool, category } +} diff --git a/frontend/src/routes/outils/[slug]/+page.svelte b/frontend/src/routes/outils/[slug]/+page.svelte new file mode 100644 index 0000000..aefaa30 --- /dev/null +++ b/frontend/src/routes/outils/[slug]/+page.svelte @@ -0,0 +1,132 @@ + + + + {tool.name} — {t('site.name')} + + + + + + {@html jsonLd} + + + + +
+
+
+ +

{tool.name}

+
+
+ + {#if data.category} + {data.category.title} + {/if} + {#if tool.license} + {t('tool.license')} : {tool.license} + {/if} + {#each tool.tags as tag (tag)} + {tag} + {/each} +
+

+ {t('tool.officialSite')} ↗ +

+
+ + {#each SECTION_ORDER as key (key)} + {#if tool.sections[key]} +
+

{t(HEADINGS[key])}

+ + +
{@html tool.sections[key]}
+
+ {/if} + {/each} +
+ + diff --git a/frontend/src/styles/base.css b/frontend/src/styles/base.css new file mode 100644 index 0000000..41edc52 --- /dev/null +++ b/frontend/src/styles/base.css @@ -0,0 +1,99 @@ +/* + * base.css — HTML sémantique nu (doctrine §4.3 : l'accessibilité est + * d'abord un problème de HTML correct). + */ +body { + font-family: var(--font-text); + font-size: var(--text-1); + line-height: var(--leading); + color: var(--oki-ink); + background: var(--oki-surface); +} + +h1, +h2, +h3 { + font-family: var(--font-display); + line-height: 1.15; + text-wrap: balance; +} + +h1 { + font-size: var(--text-5); +} +h2 { + font-size: var(--text-4); +} +h3 { + font-size: var(--text-2); +} + +p, +li { + max-width: var(--measure); +} + +a { + color: var(--accent-ink); + text-decoration-thickness: 1px; + text-underline-offset: 0.2em; +} + +a:hover { + text-decoration-thickness: 2px; +} + +/* focus visible sur TOUT élément interactif (doctrine §4.3) */ +:focus-visible { + outline: var(--focus-ring); + outline-offset: 2px; + border-radius: var(--radius-1); +} + +button { + min-height: var(--tap-target); /* Fitts, doctrine §1.3 */ + padding-inline: var(--space-3); + border: 1px solid var(--oki-line); + border-radius: var(--radius-2); + background: var(--oki-surface-2); + cursor: pointer; +} + +code { + font-size: 0.9em; + background: var(--oki-surface-2); + padding: 0.1em 0.35em; + border-radius: var(--radius-1); +} + +pre { + overflow-x: auto; + padding: var(--space-3); + background: var(--oki-surface-2); + border-radius: var(--radius-2); +} + +pre code { + background: none; + padding: 0; +} + +::selection { + background: var(--oki-jaune); + color: var(--oki-noir); +} + +/* lien d'évitement clavier */ +.skip-link { + position: absolute; + inset-inline-start: var(--space-3); + inset-block-start: var(--space-3); + translate: 0 -300%; + background: var(--oki-surface); + padding: var(--space-2) var(--space-3); + z-index: 10; +} + +.skip-link:focus { + translate: 0 0; +} diff --git a/frontend/src/styles/fonts.css b/frontend/src/styles/fonts.css new file mode 100644 index 0000000..e30b6f0 --- /dev/null +++ b/frontend/src/styles/fonts.css @@ -0,0 +1,20 @@ +/* + * Polices auto-hébergées, servies depuis notre domaine — jamais de Google + * Fonts (doctrine §5.3 : performance, RGPD, souveraineté). + * Budget : 2 fichiers WOFF2 maximum. Le gras du corps est synthétisé. + */ +@font-face { + font-family: 'Atkinson Hyperlegible'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url('/fonts/atkinson-hyperlegible-latin-400-normal.woff2') format('woff2'); +} + +@font-face { + font-family: 'Fraunces'; + font-style: normal; + font-weight: 700; + font-display: swap; + src: url('/fonts/fraunces-latin-700-normal.woff2') format('woff2'); +} diff --git a/frontend/src/styles/layouts.css b/frontend/src/styles/layouts.css new file mode 100644 index 0000000..ef07f8c --- /dev/null +++ b/frontend/src/styles/layouts.css @@ -0,0 +1,63 @@ +/* + * layouts.css — primitives de composition (approche « Every Layout » : + * des compositions, pas des pages — doctrine §5.2). + * Le responsive passe par container queries, jamais par media queries + * de largeur d'écran (doctrine §2.2 point 6). + */ +.stack { + display: flex; + flex-direction: column; + gap: var(--gap, var(--space-3)); +} + +.cluster { + display: flex; + flex-wrap: wrap; + gap: var(--gap, var(--space-2)); + align-items: var(--align, center); +} + +.center { + box-sizing: content-box; + max-inline-size: var(--max, 70rem); + margin-inline: auto; + padding-inline: var(--space-3); +} + +.prose { + max-inline-size: var(--measure); +} + +/* grille de cartes : pilotée par le conteneur, pas par l'écran */ +.card-grid { + container-type: inline-size; +} + +.card-grid > ul { + list-style: none; + padding: 0; + display: grid; + gap: var(--space-3); + grid-template-columns: 1fr; +} + +@container (min-width: 44rem) { + .card-grid > ul { + grid-template-columns: repeat(2, 1fr); + } +} + +@container (min-width: 68rem) { + .card-grid > ul { + grid-template-columns: repeat(3, 1fr); + } +} + +.visually-hidden { + position: absolute; + width: 1px; + height: 1px; + overflow: hidden; + clip-path: inset(50%); + white-space: nowrap; +} diff --git a/frontend/src/styles/reset.css b/frontend/src/styles/reset.css new file mode 100644 index 0000000..1eff647 --- /dev/null +++ b/frontend/src/styles/reset.css @@ -0,0 +1,54 @@ +/* Reset moderne minimal (doctrine §5.2 — ~30 lignes, rien de plus). */ +*, +*::before, +*::after { + box-sizing: border-box; + margin: 0; +} + +html { + /* jamais de font-size en px sur html : respect de la taille utilisateur (doctrine §4.3) */ + -webkit-text-size-adjust: 100%; + text-size-adjust: 100%; +} + +body { + min-height: 100dvh; + line-height: 1.6; + -webkit-font-smoothing: antialiased; +} + +img, +picture, +svg, +video { + display: block; + max-width: 100%; +} + +input, +button, +textarea, +select { + font: inherit; + color: inherit; +} + +p, +h1, +h2, +h3, +h4 { + overflow-wrap: break-word; +} + +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + scroll-behavior: auto !important; + } +} diff --git a/frontend/src/styles/tokens.css b/frontend/src/styles/tokens.css new file mode 100644 index 0000000..81b45ae --- /dev/null +++ b/frontend/src/styles/tokens.css @@ -0,0 +1,77 @@ +/* + * tokens.css — design tokens OKI (doctrine §5.2). + * Toute valeur visuelle du site vit ici ; les composants Svelte consomment + * les tokens, jamais de valeur brute (couleur, taille) dans un composant. + * Couleurs en oklch() : luminance perceptuelle maîtrisée => contraste WCAG + * calculable (méthode modus-vivendi, doctrine §4.3). + */ +:root { + color-scheme: light dark; + + /* ── Identité panafricaine OKI ─────────────────────────────────────── */ + --oki-ink: light-dark(oklch(18% 0.02 260), oklch(94% 0.01 260)); + --oki-surface: light-dark(oklch(97% 0.005 90), oklch(14% 0.02 260)); + --oki-surface-2: light-dark(oklch(93% 0.008 90), oklch(20% 0.02 260)); + --oki-line: light-dark(oklch(85% 0.01 90), oklch(32% 0.02 260)); + --oki-ink-2: light-dark(oklch(38% 0.02 260), oklch(75% 0.01 260)); + + /* Couleurs identitaires (drapeau panafricain — jaune/vert/rouge/noir) */ + --oki-jaune: oklch(75% 0.18 85); + --oki-vert: oklch(55% 0.18 145); + --oki-rouge: oklch(55% 0.22 25); + --oki-noir: oklch(15% 0.01 260); + + /* Accent principal : le jaune OKI, décliné pour rester AA sur chaque fond */ + --accent: light-dark(oklch(52% 0.14 85), var(--oki-jaune)); + --accent-ink: light-dark(oklch(35% 0.1 85), oklch(85% 0.16 85)); + + /* ── Difficulté — jamais portée par la couleur seule (doctrine §1.1) ── + Chaque niveau = couleur + texte + icône dans DifficultyBadge. */ + --diff-beginner: oklch(65% 0.15 145); + --diff-intermediate: oklch(75% 0.16 85); + --diff-advanced: oklch(55% 0.18 25); + /* variantes texte, contrastées AA sur --oki-surface */ + --diff-beginner-ink: light-dark(oklch(42% 0.14 145), oklch(78% 0.15 145)); + --diff-intermediate-ink: light-dark(oklch(48% 0.13 85), oklch(80% 0.15 85)); + --diff-advanced-ink: light-dark(oklch(46% 0.18 25), oklch(72% 0.16 25)); + + /* ── Typographie fluide (clamp, zéro media query — doctrine §5.2) ──── + Échelle modulaire ratio 1.25, corps 16px mobile → 18px desktop. */ + --text-0: clamp(0.875rem, 0.85rem + 0.15vw, 0.9375rem); + --text-1: clamp(1rem, 0.95rem + 0.4vw, 1.125rem); + --text-2: clamp(1.25rem, 1.17rem + 0.5vw, 1.4rem); + --text-3: clamp(1.56rem, 1.42rem + 0.8vw, 1.75rem); + --text-4: clamp(1.95rem, 1.7rem + 1.4vw, 2.44rem); + --text-5: clamp(2.44rem, 2rem + 2.4vw, 3.4rem); + + --font-text: 'Atkinson Hyperlegible', system-ui, sans-serif; + --font-display: 'Fraunces', var(--font-text); + + --measure: 65ch; /* largeur de lecture 45-75ch (doctrine §2.2) */ + --leading: 1.6; + + /* ── Espacement modulaire ────────────────────────────────────────── */ + --space-1: 0.25rem; + --space-2: 0.5rem; + --space-3: 1rem; + --space-4: 1.5rem; + --space-5: 2.5rem; + --space-6: 4rem; + + /* ── Interaction ─────────────────────────────────────────────────── */ + --tap-target: 48px; /* Fitts (doctrine §1.3) */ + --radius-1: 4px; + --radius-2: 10px; + --duration-1: 120ms; + --duration-2: 240ms; + --ease-out: cubic-bezier(0.2, 0, 0, 1); + + --focus-ring: 3px solid var(--accent); +} + +@media (prefers-contrast: more) { + :root { + --oki-ink: light-dark(oklch(5% 0.01 260), oklch(99% 0 0)); + --oki-line: light-dark(oklch(40% 0.01 260), oklch(70% 0.01 260)); + } +} diff --git a/frontend/static/favicon.png b/frontend/static/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..0793b0a4b1f4f9cee393379309f2551e6c0e8f4b GIT binary patch literal 65703 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

LGsA^xrC$3ES z#0{-HJh+Upc&g3@v5+SEq>&*JOwf+-rk;L3pKI1F;zO5|^+mf2qYqW7-T-+M`XC&q zN8mS67P0>i-@(uVBb`xtR5-T!p~q8OgViQX`DCkoD$-NS#u#)zwff2OleC_!J3MEE z5a3{4m@5JtEdCZH=D4zCfhtv=isVo~lc0%TFKZOY`fyaFf-CME;+FNT>}Mef4FZEE zZ^CQDdzXbRs3LfLCYa5_8Sa}4lVH@_h{mxX!}F;Bvjd*RWo3*>Q1-Yzjauz7rF*s!mw+AkCvB^2$+apJi2$)tK-+ zv5o;VFg_NX7?o#_75GjV=<&kL{7>g`w3-luN2`BUb;cZBsb@vxFyt4O z+CjqdN(h|i0%3q0q;ld(u*qSH7-KNzDS*n8a9Eo)C%{JfWO6+veJ=W)NS_ms=sBZ= z;16m%X9CZopF literal 0 HcmV?d00001 diff --git a/frontend/static/fonts/atkinson-hyperlegible-latin-400-normal.woff2 b/frontend/static/fonts/atkinson-hyperlegible-latin-400-normal.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..2e43c90d5456bbf449b5c4f8e119ae83d129abae GIT binary patch literal 17208 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 LDf^jF)&SPc?#=oh literal 0 HcmV?d00001 diff --git a/frontend/static/fonts/fraunces-latin-700-normal.woff2 b/frontend/static/fonts/fraunces-latin-700-normal.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..71bbace60d84d20996214a3e68cf984ee3c0dabd GIT binary patch literal 18212 zcmV)4K+3;&Pew8T0RR9107oPM4*&oF0FA%^07k+90RR9100000000000000000000 z0000Ro?aY*C6QUo9cg+K>}Wef)! zz;+emn5DqR0mKBv=tzXHaR4YW&PP!~N%_hD9~0C#qAhN!@caO{2oWSo6`iKmK4q6k zYy`im1c&1>gZVLGk6*shsyg;DaI$R*1t#tGBK~t;k357B77&&jfx$%x9F{T+{_?tA z#_uyVque7j_DSv!{M>p!7WAT7G?g-P5*-Fev|LkNV$`5={52Z9sMP381og<9h78dkknI)(< zc<$ANM_tF8?Pjjg5w-U$Br5Tih-wnMnYVxn$Pzk~Fu=R5f2}LWWDR904sao&f-+Qoo2Is<#bMdtB^jgG3aQ|KFy)?Ry$&S6bAB(Axb9Vykck z)TL-rr_$V+H*ZiQu-5|%Ui*V&FUYWHFU(l>AIavBe}GZ8m*^967XavfQ>H7^l)xQO zl`2J7rOFBMpBHK}5094cgVnTvsoex9fSFFA6*|S1xbS^VXMgfS?G}^+;dTb+4qUP{ z3l4>fu_q-qhQ99+G8)^He($EB|KsGl|1_$OVh`SC)LGBfb`xsH+4@E^kckvB2bivb9+f<1*oYnkRA>oP zf)B!I&uI44Dd=~b20$>h1R|a4r})OP91vNrwz~mlAIus^Ji(l#ssFimo)TyRn8BXr z)Ya{=q4}jH0C1v$lDYC~zWoW?oOK41R~6-F8%##-Ge~jEY7!rD3WhP7%&F^VcS7gs z(@oA%P3mnvoK<;_1pTiqU!KrSUsm24>~DLfelz+LW(>R_-?;x-96AIdQ|Fm8fJj3S z^4g=KINLdDN{NmBNxNg3w0nhVbt1WgKAZy;Nt*)7^}j03G!Jyx;au7x7UvB#TTO%Z z7@*jvHJLi?8j@-bym+hHIl+0Dx~Z@+C%H3pfm2#w|LHyr{Irz5H+TjyZ@AHw|4Rv+ z-=9Ic2X)l)6v8ncuq%(O24SiMyPdz~G2xKm=uDVu;W^e6s8W-c`9l7whhGDT>{8R> z|Ikf6WObhc<@PY{pvwt=25ipgbrSVt0YY0gzJ}sI2JQh4MTTJ7+g81gx*f!i#n3$& zSU<_Qdu=K7S?^6D<@fe=2xF4v;`V-sXLaJQPp5IJ4?PY_FT?5Od)YZz?#Y9vIZ#9x z=cNC4>vZ^?1 zHZlcDyQ2`t zD(1KjL}xbUtgAhpuD|!Vh3q!yxPzb<>TcC=&wZ!|XdZ6dBhTvfTp#U!Z|ggs;lB9( z>xbV+1`N`2t|2*$Wa%JX5Rge}EFGF?*Z?8j7mXfDCa27V9?C5{yeL8qH>8FRffLgr zfs+!EVUW|MpiK#3K#w5-BU+5jj)15Ge*&jev~l?C*trU zp8O1l^M&uQBM92bRi4kTbN>Q@i>`L+vls4e4j?oXI<7ist77ij|?X+{^EL)xeh&%v9 zv*&AB^R4WeEf+xk{HS1#QZlp496BBVl+&EzJf~nB<{1M84RQqgao)KzZw^}ob|R|; zJKzC4C=-|s$63s`?B{4O2f?!+9so^BuD7rQ=tR()uAiL-j63EQ`2kfC3*r{Y5fO(B znSkB8oH0|@oP2^4RJ-+|Ay5+HpwadO%E)73l^(^Up1~X=Pa&p+Kni%R&-3A~K-jL@ z0c-`%e|_t8=W5EqY$<;@)1pp}1ZX7Qok?n0qW~IvW*R`A0|3}3y$paD1`+1~FeG1? z&d^(LD_(#AK*agpkq8RNz1L=zAPArrogW;4V1QkJS6U9jhVx{Jj|_!FgCoR&r89*x zmdkgLcydrEKsH0q1N2dV?Fs;eK!d)k!M%=;F_j^bmzPV&>7`K&$7jKTt9Yq0Eir{y zMMR#@YxyEyHUz^lvUoc&Pb?Hm#Y(Y_*iq~$K4|PNrAe7nQvk#)IdYRAO_s)Im>SLk z*5+<2=7{;v%UbNPOa-Q{UR%HLr<%dUldDIo^2!1eZ?EdVJ_`Wg)$v8$Ua^M$|NIHB zZ&qJ6Ae;fj0!Rli$7X?ZkY_*Nr3CEpu%4r=AJL8UPa_3mtPq zkDG3}t=$7JJom4c{?q4<|GoCg8*jb$&Icde^T}6VeADl{1@8OlhhKghaMz$CdNpa} z%TEArJ^~?-Iq3la7yN{0N7Rl0MHh%OkoWwq*g;v0E+PwLT6AN z=QAk6s1=Bm0ahgC<~#zyyc+?H#G`#8M?e&~Ga)J_0uv0xk_ol6BgQ62Dt=8(BZT1t z(*B4mfWD`rk$9RHqToTGzk@y9JZJ#Y*Mrlbsy_c|`Buf*y<&fF{8Xh9xuE^~py^@W zH+5#;r`IJ|W0ud^DH26BveIGtNGJqd+jf0W0+-`_*R&f>8xRUrsm=y3FTtZ_HfGJI zOtqx{BDs?1LPV!}S%OD*MET1%Sr+ESUz)5wXS<|kx?hA2lW(}EX1%t_(mYp<#v*gNfs62v$AL<2Ws?C_i>qXC7SLMzQ}mTxvq3mB z-6mdvR)3g=x_u{9MIAn=WVCkY*q5)%t#mRV>eTRzWfNFx-7OL$#LmSb|B&YbnL|HF z>Z(cwN>=_g9`Jn5LSuES6>XfG2>HM)n@O2PhI9<`>|nHwp*~f?|EFLP)U|eejci7Z z+Y1&{$$Yy4m@I^8XP`Z$a6{)e^YSD=80&qX9jzAbwqs zrstkN(Cp-Ef2VO;mYvlM;QIl`l&#%K#Rj>5qVrj?g6{or$DTgIm-&G)ryf;_!A36S2(%MhA8KhK3$L^tZ6)zd2}(v=R)=V9Y*_95Q0BNrIKA+l#NrfBL)Z9+QQq- z&Z*8>DCB4X#Ynj~OjBSEFZS(j>S2@9wjDME2?e7B>7;IsEYNs174lCth{F$i_;DFU zejg-OShqm&OD>aTR`c&P$`A`&?23=<#TIV|we z_*8(S6-7j^r9CWPYL!P7;F;hMQ({k!()}E&BA=XKs9}Xo@D+svLR*p6W-yRwFFxwa zBj8>Z^KxXI2Z{heCl#$+!Ah+3*9I-J3-J-9+ajWTkj7WcIW1ak*OTQpPPNyCe?MML zv8jKhwIfog;!wU;8b}rISa`1G=S8MIwYCm6PC4SBavGueps1!@MiDwHHzV@+fcO{t zc6-nyYRW=KLVzJ%p`<-U8&7nPowA*9k4y+tagsYV#wyTJ_Jv^rOllF{&(Jm$6n$EU z5AP>0N1d;e{>*LDwaW2>7=mqY67?jqm3r?!PIPn%IZ8+|$8AsO2hk0B(_Vv6pVG>- z{F)eB0*>*qSE_@MYVqqL@ijf`VOey~f2DhTCHb0+C{HM*^0dL;MPE z%*uGqI1Ky^qV7C`R?`oZTOcB=Ub6s`(iXZOLI9Cut$IcbraMU6N~#i3YB55Ha0dwc z+4CX~CP|x2)_flVI0cfRuF}W(+Y_*^>w`j|HcMuCz89`{c>mk~63Eu~f>Q2!QL~!| zdg;6kr4kqA^3J2-bVzJx6)JjOkz66PltQp4oz66u2(~LTM$QbdJ2FSjM6j$~FiySP zsWpHUF5uv>Vv4$=-hR&$oO@Hq<$-G^95KPE(7s?ow|(f-X%Ze6E;6n*H(EWB!Ib)V zB2WfZf4M*gi2ZRpM(fGO?kovf8+_o~+B&*t2Ja zSp$zgMmPTcWqN8DJPkdYuJq;ZZ3Kcp<8A3;2?0mLHK%`0BIZ-o2Orioo0_-_Uip;! zRSqXCJvM2}qwyIlZvBarDe=ab;G)}CwYP~zhVUNhs{cSo^K^psO-D((z~8gnw)g9N zA_t64v-ms*W}!2l${>a#D#&O56L1Me>3pdxQ6I=?ypp**wR=aXa@5m%iIk_M<9C&kE+OS& zZ*&%dMDj-A4^-I+*Tp4`wlwAQ2aaXX`uLP8BBHBA=A!ZIB=17T@Bu0g4$9~Of~5I) znZh`ejRb9MD)bXms}*sMhU6i}(52cxO`-f=mC}FM0?0#N$WA_+zm46L0n73|47Ott zH;mREypP>uU5fkqZb~#YUOWpHk=+v+FhAwSHVL(hs0W?@*E zT25!sVM|@S@Du5UR^`|pN3a`v%t%YYpdwXdDS|IfKQR#uc>e5opvs^Md|2a34ph?n z^)XyW=|Z;OB5Iq^R4<1xO;Ppss=T7CY)zh56Oz2yXQ+DOe(KZU8&oQKDhCIDUC1__ zTq^u6En2G_Gtx4{q^JF#A2n%Zs3DuFc~x3!#X)xCSxNUHe(veT4%NtRQ zLQ)C1B_=X#c}Kg`3FP(iNr}q|9Yse`pEqaoR@^ip}sPFg8iq&>S!kFYjv^0uRYHJ&71lU zkq-4*eU5Q!q?enZP&gnK7l5w`8njV9*FI*lN8FP8k3oc!->x@+x*p=7QR!D&4pdxX z1}^(KYSH$>9m~9J_c1Q^E9iRysM5yhDU|29!DC^LYJb(y70@9&=HauREwwv|MXaD) zr%Wl{wvSoxz=~8Un9@*G_##^RXYiKA!oK=|5UN&ko;U<#r&H{Yi z9mCK##3`g6PCB1m6Q(#>BwCuwd&gU{TBW15;*l!L;MM#D@()_lr$%J38#vqRK%2Hm z8oxgd-5CH%Z6yYx7voV{gH;I`L#;f>T44SKQ$xak6ztdJu+iEhyF(q%q+;aBeAf`| z*K%z1%xXCzmi+pnd$#KLUG`Rs@aUIUnf76o9w>BLM+NZ1t4M_v>13^QB`7#`1>GNs zey;#X7RA=tb)D)j2ZuX;rv<`6z$U?mF;Yklw$&%2Lp+N4G?tS*F94*kXDkF`c=mJz z2<+St`x^+`nRZ-KBC~GFoO)ktJ8G6~?r)QB#<_EQal3^WpQegB;bO>ACz0`~8Bw}> zyzs6~Z<37(e3tfh5V{E-*(^T9Q!zTC=WWdr_;bdN(?i-Wf=Rth$(Ee`*+mg~YSU+H z=Tx%4%A%BdMjav1@&-J!=2vCm4AQmNLqk47`CZCm9QUPBun1VTp+}1$2hs(AEz}vC z@5v$tjoW&^%iC(*?RI*k1K9XC|LFTKt-s(Q73WP{``*)$7<`2LURV-qX+xYDR)_-| zQ0CB`d+`ya#N~ZR$P>-smxe!_Dk$D{8QNu!cTCt|tCVn_%1b6X1}RGe^a3z~s>nD` zbzA^*q4)h{ycUcysj*0TF;Wdp#8TPckH(|)Iut(%XrITZ=M7bCphEs|wZTQuEZ+h6 zmRvgEl!hCSTcu5IPzSv2Et$zZym7~5-Trk+exj9=U2;nA8{i0Ox%P&kQm)&Q1`d+= z|IT9{SDOHpj|}oasjqHU36f^{x{cmQJRPltG15d5X_$&*RXtV?fa*cxso{ZS2ECDJ zWkf%3l~fktb}!fLb{{-|#jIWL2N5{v;~#5rfn|Z_04;we9UVJj3aF{mPqE-BwlkyXeoA{o5) zZ~PvOzaTS8;mLt7h7mK+x#-?8aO)^sFR*7Jxq;@exmWFr2##25CB>}Z0eFFXTm~=k zaMrpTU|EN=x1f5I1ucj>@Okms#WP0R@_2nn!JVJfe#*%8ttm~u{HX7kPRMRaRX@%G zn_YUQUW!u|mI_eL9PbrBzilFs&pSI!D=7_uyTXZMQc<34x@gkSbSqkCE?UH#W5sKC zW6GFxS zfn|1F3Dq$EYFE`5f;LZq?7k7nL{GM0`Q&)Fa$|IiFuRmLuno=4)N zYwm3A`COmduT^jwjOn0z~S3V z$4a=dOd3=ubDcT(Q-8bKoF1<4+8=kM@o;oOQ;iu{x7je+&ah}hlZwrDq#^J9YTlnL zGGgF!?&~2&CZlJfa~i%nGd|$(<*EJ|_ncE?qXH^@{33z*YwvFUsXe{)Mwj}TB)tfg z&0Tr3p1p!;`d^f_$@}!n;p?a`|F-z%7sBJ)AaFOt?_gr5b*RvTtv zD`Ql%ly_JoXwO~p3f*CDlOSI4Y#Kl><0~5Aii5Uvow-f(7Ac51n|iCzDMR`ant%0@ z0u!f^u-l^jauvUAgt#yF@i0`exhH!= zj5)`G1O+QI*G5K;L?yPa$F=!<&F%wOH`>ZTioF*ET8B-ZJRis^xgbm9Vy412j5>ff z$m2WJdz%F(@+n%0Q0=HjO1ti>zUARkzo45|4m)yx2GS$(?Lp3E7yy#?|9_--NftMR zZ|p`;Nhw4YGBB)o_;&?^7r12Az#*3dB@h&o6izynf0N>s8$_KzJfhgT=nVP0f{LI#{kE+d{*GO}ZQ&^eNg+wj z`OaE*sy?rB_^EL>VaFS7`%w4xWht-1OIeJ%%v{mNF1d{vlknChM4ZQ<6-p{LwgXX; z*7S_N{)RjL9go>Flj3_#v;Agz{-3ugrv@(|LQ%hq^r17G99gx~<5PF9(W;xeHY8>! z-RYL~G2bkjFYx?kWkvOAS3=1nu#v8S6cy#^_^N8ydvwwyCL=0`C|t)#S$WI6CHRnAyISu`3)~zZypPZUHNw2ymh~P z@Bf`;QtL@1i}cRO{YN3m_Z8_XZ_iB zrvx^;p_aH(o`ibI)-BIG^%#O+l3pp9a7aPTiQ`MRJ#qz(+ z&#wPGxXh4Kq!OBKCMAu`V5Bh_v5|UaZlv2nL6q)cd+n)5g^dV-&K-ypRfN|fb--AZ zCGruUn~h#yPy|J?NIOU@BI@y@6CPYI_UNk!%b6(E<%{x^LRw(=c^j*241k*EmdOyMi-M(3rS8>g-E zI6JQqmMB)xUUJxgNQI!Vd2ALUf{3~`JwBZ9tU1&f_Lzb|qA~c3`H>aZZMz5Ke#cPC zFeHn}Be9U-B-D47@d?Bef4M{yZ}3N8uwn)!;K}ER-ZQTBEiu3#N%9NFhP=h8QJ z|Gh(b9%s9~a@!_+Je<$kJ-$3NqKfM)%szxZ$r`IMrAkW-i4*k+h2R}wdGYDG4~Q8k zs#-JqG3!vp$rwR|Pi9;?PU{yP(I$|schndsN3sfN99Y?Ik=K0CJcYOkh@S6U+xETy zthVqp^>U4O?w$!>c0NCV6Uh^3*OM{F?Ngp!wUhQUR)2T@nPO9DqhYrryD?No(}dXk z7twTTE2D~kJnU#3<*nfN-t z%y4kn=1djCna+>rOrQ*rk2TDOzx%C;Sn#T!cv$nfI{3Xt5Hvkwh9amTH@q0W-G6;d zOb?&|BP|XV;2@A?{|+&^SXD{}S-tJ@?n$ST`qmbDnx)vRjE_-mmTim}h?-ulK33aU zGxxHnPgfBn$D!bVx(iV;*AvA}iit~mUZgf|?B2o>n;rS8xM=N0_1c(0`pIVH9w4`* z>gX_j;!ENB#BK*&ha zXA%WLZt(2bbvaP|WRX6l?H-avj10jbL7@jeiNESlBKB>mMv{m|z&}pAwukZ$%I?3S;oN zzzXSTOi3M(g3I$d1I+aIW!msJ69GKh5MIBdG^<$c(GfJgg7JFsmWhC0K4yw%+Wms-CGSy1TZi_01D^boNk&P`_NJ=XEjj^%U9146ryu3!n3ZycCJo2s$&+F-QllD7b;@0v)aLYxm~s0i zAKFh28dKOiZ)n}}ysG_v_cr(IH(k41&dBX6wKS!J6hg6ao5zGC}cTx>?pA?F@q#_v>EHyE>ci+8x$?yZQ=fpz{0!WTB&34 zIH^{niWbuW#8mY`^7<`i?>`cw)H))?zq=e-?;FtZBO^S z&3*uIIvuRqobR z<|``G#ENvbP@@)!B9UP+$b{%nX%c&;ju4}IssAf8_@Pf0@Nr(Je=Yo(wzgN<52ef6 zRSD`CTC!++TtBGJZ>^*O7xGgcGJD)@Q{yh~yf6ppDU+~GIgh5Di(*Z0R;o(Q@H}pl z1BtkRUu3w9>5U{hTh0;dGlYi0%GhdJhJDqJ2U&w-uWU_6UG;vPL)*pmZbFD1LV@j;&-5{1^0Czb~| zN|K3kycPIZzou$?!+eI;y?x*MXM{H;;5$va{ma1>*7ve0sZf#{qQk9PdP9hb0P&_Z zHwmu^&wUUREBpJ0$&5$;F7x4&G0$mB_uCIp;x$Z)9YFTE6yv&qiO_MyM{#gUa z@9c5;5jI?Ie9P?M?&?uqMkz<3%@7HcrgU!jB?HPIR_0MUftIh0GDZgx-Qa03B(xBB zv9rSEsQlHe!e8Ooz^iq~Z8_SNg0g46Ft|g5dGd=f9xT@6j8qr(=g-bar`<34%g)5& zK*$Py2jU^iuk-Kr*wnUL34pkDV-RHb;Z1W7;ftZ~-d-!94Spk@oeGNQZZ{&g5B**I zs8>1$yD->eS@2hQNFuJo`%O+CXOG|1$WNs6(vj-;*n$PrmYQjYvT4J9?={xH+RXlS z!xtU^dnnztfIA_Y+e98;?(dk+#ES_E5!{RTGLso-4&gQN><__)z$p+9PQ}(>I<>9g zJX_m3>^?+*>0=!EJ3iXdBq^avTS%K zEj!+pul%WR{rVr?SL$|_h*{Yy4s%=JP3b$Vfs8< zoK5(Say8}2=ClhJgL+D!VRbQ~F`SvkEz@eM-I0?=k;GKz*?pXag~iKu(XdfE^({C! zD(DE|HQ}?4hI#fz*3?=UMg=ca&!&pbXy7fkPjO*+s?1(gSy5^%5Q@aEB`2kR@C^VtbUHgZ5>?D634Hen zPvmmjR9kON-V*7c4hU@PI4tm+b&j(SckkP`Dk4&3H}Rv93+zIUW%Aoh z7VEl$(lWjhE`r11&sl+8+4z$USF`q4%Q^WQwSdT@kj-U-Cs>|a9lkAmR2>w0?-~>$ z9F1JvH4cg*$}^XSCri`QF?kaoVhC7++F|B*7%jGSlhSgr7VbbGmj7j@M^S`M)lIG) zSaX3n1~mY2z<-f5Aprh=eMiV5-_@hTuVCco_>GGKN2pycRZ5k}LmkV!1X-ITo^LLdB7wK2Ez)C4e0giDkPBq^a<+$x1f z;R{!t?fZa5Seu;oxA4ll#IN@nET?fi1^wz+kkGz-Keh3mg`D~%sR=*uZe|P!hD3V^ zG&z*_D^@`MT8C^VErdv{QLcP3(Rn;}y!4P#*pJ8JMSQrMxdSF!7qd|JZ#%Fi-vfqf>wG3evsgr<)61)? zJuloHoZGi~U1vk@gTKPoPJTQ%+uWiS7}dOpl#|J1rLFCb?7&6cy88NcYugNy{kpF< zC!4veSWVrkdZEK~B66?J!Yqp>h2<$tRJTf;d$Ah38D7$D)#==W>G^GE%JSysJ7O-R ze&bWfw)QukVft`C_Q!SqyIuawZd~LZPv_!@Q%m~w5By}Z~yfH~Y$ua2t#GJLJ>{Xj?DAyf$RZ=@sQB^Zp z5iud>rpg+=ZTu82AY@`u@2^%qHP+p34N6l-;B@GW9YvYE8qLVI?H>Q;mrYo+97C5Z zJwvw}GggU`Bls?sBrTU8->7B~ikT86f|<5@DnZESa?Y_K2_h(_9PZ0g6~7AgXJ3TI zK~QaTWH9DK3$3th_HR$UyCZ8gl+y{ZQW@#xhw5@4o}GB@JfqXWBVk* z9Fz{vw*BH@NuuX4lId9%Mj}&%TQoi^S<3OhM*ySyS*gRWL2h){JGU(t2;S{o=xp17 z)Hv;5%>OS_LUp3YWq}rqYPoI;2c{E-%LyPp2G2#))zyJK zM*Y~vLwr@Qg$>geY0-ei;NDHGA$@=0))oQS?RngdfIEgI>RFmRoK-;s4wW$(l?bb; zR2}cBN^o|S5d#GnK-aS^pkTyZlaiDfGERp;9U9QU6B>mo5q0q$Qr4IRDj4ZFQASQd^z@My33>Bl0a8 zxxO~A>9u*0QBVdiUTezM#f3bBV+D<>G6JCTv?X>i2U%mx0eV{wL<0I{NbCj8VK>XV zRsHf_uDimW(8(I2OkW8-WxXK^PP4p6xmCWI=LUW`{C)0FpX)phpcm8Wne@aJjoPPl zPLp>#r+eR_J-?ZO@tM77aePuhXPeEyi9kd>lVA{(Wbanm6skGV&2)MZo#E3;bxl|U_hmhe*wH@)J`m5b5r@TWFC%E6Diy$&vuX2+Fg$} zJV+M!#)PL(HF4ZL(&p?t7p&5DUD?Mfy?Cn#viJFUvu^Iymx1-Y8!yjHe5<*~&$zdO ztD2bQ*1Uf0_PBC#X~L_o{MPwHW{#hFns75C&evTXYk-J_L{VC5UrOBPsPKQ~u6(;x zn50kQO6(Pv+%}i*OU$m$yr^6bj_qsCCfEP^swwu=sU`jE|8?f})FXz0mEOBL_p0O3KWmjJ%QOElZy5*xd2_*Y@?P zGDH{z_VzdhqBSM{6SGUxr&$-#+-Tu*42HV9l2Q)i?Bf zg!L!;cov)7ho3=?mm`k=Zp2bW5{u1E7g|{fpJ=svLcYoM#2S5JEQG8}IDRPLk>2`^ zjxIPhoU(taQ;5FK&Yd0oeB%9xCM68CV2JhYIk7RY1fVRh?xy^YBVJgqobOz2Gu|Q) zM@c^jrS8Btj|V1%CJBaw2=wQ!$Dc_z0kvQ$4HE2G;Hfk8=dRS1R|_>Z@<>dNE&p5P&UM*hBICoU*G_S8NVo{5w1jAgfXgZw_L|#M@1MJ zwg}git3p`Qk}|g4=lY_sm~C*8Df#O@5l^PclVX3lgRk)_qgv+!3fr?51dg;R1GRu! zz`ywZdLh4nYM@R@3)`|D=PyY+8DRA;lhzPSB50mWrV;UOEDnv`1``tPckbl*l(Cx^ z3t51v*U%-{fnM>#>k(hJxMf`L*t_opDKrO6sVgE$$swnH0A-{|*G2 zGY;zU-en(N- z*1`0%bydVoX`E#yiJJLH4 zEx2~Ln6IS&bL}T3%ue>+9zDH5og=M`Fj@)f?ra3cU%X6gsZ)k9_(nS*dHx@`{U=1v zJ-usJMMuZQzjOERexxq#HtPxt6&j66YUc`dyhIg?(&`*7+fd9WV$smpiMibxvjm|- zO;D4;C+J=%4zDMgqad-T(V*FfK+Rgj+sU8r=3UigL?ag66wY>t!Kbf6kyMgZ#Go`8 z!TrP7@BaA`E+hr(?x%z&#On&9m9{FIDFmU)D9jM2Mx}WM#iEjhL}Dc-z;OiKL-PzTf*IoORy?^AMx_A>{&K&Bwq;Yg940fwt zpKVebS%+D11`&5B$*c3f#Q&#eFpbW!{UbGF)`Xl`j=Am=L0PhXPFKBVKBwqp+lJ1& zALi|!^J=S(2Of+zCq|wGYzAX~dK-k5p-K%b3_dUby#e&fh#wt}ha8puS1Hu%>nK`Z zwulxcNk|LVrf91ht1CBjtWKx(uRgP?708sHdS)yh%FD=hU)68-wqujGh`MsOSq;WA z8=tj! z&OS$L=mWQFT}`e{C;amPb7{{Nb-`MNF+WFV$Tq1>$xL<%zod;F3t)Zr?@}|8P0o?= zZ8bwV3~Lle|$@Dn5_LSyUqBUaIhx0TRWES%TNRKQ81rV_x;i_mD%T z<`$_^*_rH;Ihau?+O!pd!|?D28WeThBo_JgptuVXSdclft?yQKBy z`2yM90&_1#S=|ERv>oG2*vp?n9C*#ImjEz)5t|hvHoa5tgC$b9JfUJVq+fuF%$Jh3u z^9YrM8oUdgfApiloF&z`i{QZ6l1411$)Ds{;=-!&0+d`(yr>qdMhihnDaBw*@F4|D zs!4T(Vgi;zE?!)V)}RGQh_nO~RE!UCE~z2a5=*eKeInA1FxW zHuYlf7FtU>4%Ntdqzjhc8^Hd4IG}b5#*V8X*5WEL`F;Jmrz@xqeT;z(+{(Is*S*rC z#|yHjZ2h&aBPSfzBgb8LRl$&|UzWFibvv_drfe_UrBE-bW;?$JAn_;xmZWg4GeVrTRD`D{jMS{h?zJ{vGBE#LlFRne2f zlV!0*IVzq`<0!~t=f);jcq~RpUb{I3)beu>Uj-P2#4Ov&U&HR;fv57P6^-cGv)=E`Kbue}-InJv}@0iw)*ta>{ zuNPTnd)R~4kQ2O?>$Yos20Nrv9tG!N{g&*XF~}aT!2dfGkpT&ZV*`;5Wz47zEHLk> zKQz@)NO%(KP=raI8B&byt)|k&Dn|3Rj}MJA1`c)jPvbX30<(hyGy&h!H31=EPZdA^ z<_9jR3!!0bMd`$AoYZi?zE-Bu=I->Z)^khHyIt`~iP~-QRN(&^B zKa&<8rfB@X1Zn(3!p@R_*D<=~!wUA2C?|{FYA8&u5!BVse9FAi@-WZ7$DV0+sJY&4dP2syRRD+@>XTN*7{TUCjK} z#5{H}Inx%dJ)MJr?WZmBHZ>~4nJQH_M;PD+K4|%e$AHRr1T6K=4dnR~LV>7X3D_{c z0?$JYV&nlRcm^A^BpNTlId@|g>}eVLG+}zC zf|exZa1$-UgwEU=`K~o;oz5j#hfVK(3R;H8=5Lr)HFW=2vij=!jV(XFb~mo>~cBxWrE0m?@7O1q=Vt~A?oo;iU_v+p; z+)}w6`~{8j=8}dUswXh#nW$+RrO3>QN`TSn(A|RN-uutx6-cI z%E$*7aXT`Y6IHS56@BzVNCBraBMI0t`-NZZVrv99hAJ-MSc7-PoT--&W*6q~oG~}? zEr|bfm>*5p@PE65v0Zun|5@nn6M^0ve&Ay_* zrN#&@JyBZ2vILJtA8Qo%8uA=FW-T2lM(B7TyoLcmP8TyFvkSVi@foe!jRuFjFZx*sp{uDb&o6(DPj-}ZGh#@+(tg)xhGcY2NBqhupyMuNIpyJA+L&KYZa*+ty)A4 zc7^u`&-6LzFPD^`%T-nP4z48;v;J9Vj2wU=&cdWpwj_}x#r6X9oCe!s+rfNN7OFNJ zIRJ-WXW!;Xz=4GGT2zHUiKZxE0Q!UtEtlpGmS-QCFc%-caPjov8#iI@14-*>jD+>u zjwNkK!2P9P1&~@PB*>_`j&Ez109km(d2?HxUg%h=p_#L2)w7ogc0 z@nOC@pP-f!EvY8Fh!pzNh#mcYn(amFjNzu;!@X5maq+rpg*~F+G_Ok75Mu%>AtdbJ z4tdesyn-jtje2+%S9FfoOc4lv_4eFT z;5WZen*k9nFij`1WBbrS*z%)<=#&;qn}RAK{jo(5>{rlEq_SEp?Etp9EmBALB#VxS z)N7SkK$pRlnKVN2QcWf^YQzGg)+sZ4`-#k2nf#Y3Kx^@vxz?GTwa#I8b{1r2b{6Hc zLYRq(tPrxF%E22D?bwiQlWG4`%q-GKGMchoIWD$&iG%t>_9^mGzP~Yae>fU`Z4+qC z7|xI9U7bVI4fRP=Iq(@7gnQs%(1h zX(Upr)B`$$SbDrtC=@eYX0Q*!H>mTZ#+bmUlFBbje4`qm4vbirSq=KO9BXc?O{;6o zwG#2;wD<`;p^HZA2i(1CqU$s9>L+}7#`(pfP6z@Uq9jof!JR|13Bn7a-p&z%Wq;Fi zmDYa!g|v8)>3{l>&tV(q-o3f=(%6Mbc(Jsyiv6Rfqpc*0SR=XMo=R}s*%!$u6c$me z_q?`{jMCxH+-g@1OnYC3djedv;QO%Yu!%bfY$D%mkS9v4pXuFwVgLHmM*K0gaXRMF z^9!lDN1Vs6I~GqaC{V3YuR740{poVKe)!Udho9Vhx?SVr@BR-b=M@bQczl|jd2rdx zO=sOL8%``vYS%VG>|rdQxY<&I8Jtw)UN_n|I61iFxC`=<-=7Jqq3{Tj_l5g*Zfnop zk9^PD@9@5DjHLMAkH!c{SLUC7Jv_kmDt#~bG-NLbIu4lucVH`@do?z7n))2Ud;B<} zpx(1&qCAU9373db^LNNSF7MSB1f*k!mV6lvs1s<9pZl3ediHdu-$t!nH`dEoLYXj~ zFHfMG6JI*YmJQj{n;tTAtH|M`uY zwhSz1fp*ghyeOq`>|##)#Ix#}JFETZ4Kb7?oRpuV((@&$tdxTKt6{z6BeE#zUA5$h zuBhvRv+yX;nEOtZvEnGZ7Tze{ZsNo5+gm-3a2k*^eQ|Hu`is?JCPD~7SX(^C%o+Nv zx$VdP+K#6S?(m~sG%h_gQ?8=LxYdn`6j5^cxq7Jf3=HQVcfF6&ZsFU+%sZpFVwTS_wUka z-|t??yE3OHdCrp_x)O+7B0OE3pBT-h;jKN>doLZx#J^s@#l63PFw_Fv_5Qnd*W{vv zr5*wGKaGBTud)B|!G8v75!&8&`D`Nfan4`2aaNAj#;?8??gPHoX!WEP4BIXFTOXMY zU-Md1`F^*>$L)ep2#oSvOy7g0qsMrI=m;<(C?k?dcFZsOcAh4$s!$YCpKCND%f=vR z=Duv7jVX#jXTI3NjOHhz1dmVY@Hn^hm-27{EiV17u0K;pOtX(>nGONsh=CvRpZ-i6 zgayJp1g#hkHjT03^PdSk4f6%=!6XENwoSiQrq3V{uiMZ7dR(fYmVD4*r)TZ41axn1 z2|(foOhZ z3Nc53D*&xCO+jhhAR@Lo+(2Pn6Rd(e?E3x0Hs!!dH)nHbPOb1k0>Z)E(0uv9MEo|p zRf?|&{}F9GmKippiDq`wLMwY-Z89~)w0m<&bR)ViI&^<5&;zke4~FgPP&|WhXp3H8 zQWT^e5=k9WZVju0exjv&!&LW~t@}Bz4$cu%q|$AwfW<#?y+h@3hkD;<68C=IaMAT)du9`Ij0;=rBR@@z-VK1X_Ikn)}y<< z&{k_!dxCFK79m*@g_9i#{T)ej)?Aeq4}O#$GT*K-7w@!b^~YQD_Z(i`v3=_|)20fv zF_k(BXPa4^FxSTD)23i)v$wk2(5h97rv+lXGcC6TZ4PAB>M9P}n0#vvZ1sjabm;42 z|HE^PZYZbaQ$RGQ0q+J7`TpK=>TY|$VSTmiYnz<)cyjEin>0VfSbHpb-Io0J$h!Gq z(M4*+vaa>5UJ{$y(soHZ>Q8Vdf`<2Nv!$bz`;CDAu=FkK~#I6Ok^@9wB{N&5~ZM;3^1eqN1Fu^(w1vNmQon#xq*D_+Z z4#j@3hf^8}JN7h);8S*v%2lj~3D!{x8^HiQ_JUv?hZwfrueJ%0ig?@dBlY{C?`-dN z{)3E>?g4;*-^sIJ*G}zg)NMJoGHmW)z~SM_C_Hx3kP;ILHtgYwVRjj*Pa=9V^Zt!r z?;&R|fbuyF=fOOyy1lS_;B(k*QWxK$DRa^{fR z&CU68GEze3h{{+=Ss;38vaYD)CE$=cAWtojq#igVk84S8Y`@qfk8W^?rR10`N-m*rHys=tAYJn<+>>3UZ`G-R@O(;8(F*rp=7(|NX zD1L?!JP$`<4~xMLmUZ^C@=cKofz&%)254aYmHE6zj(CqZ@f_vshYHWkW zWfbcPpmi4jkT=}_WQJ@;bQ^Kda|A|hst43{N&^6PMQAsUpoD82MGpHo#ym9RIG&$z z0&5rJL_96~>9Ujea0mcLbQ}R~{&5s29OD>hvh-=k&LH$&NEv6LV8z+AY08{xdjR3` z zYG;9&eq7=dbF3OuD3+lJT!5=tbH#FwmGe7?Z#D}dyan=BBu%M28SyML?o{U-Gen!I zhzY_hF{`+0dC=KOf^;f&t%9XAwnXKma8tx?ej$p7NLylb0-hu;8p~H;zL<^{F>97b zmRO{VW_Bk;4n-{A95aDuxhb`3W-@@%VraH3v`{LYxj^nzi!gYGT9ocbFE&$&`3fpq zL`oS|k9(JspRwkYA+4hrE2pTQAe+p7vuP!p>EuqFx$xx0n@{=`rygoingwRHGz)c!mKWx(BCovI;me2*F z3rMzx9xS&ES(SRbtgtF5L9MuOxeA)|h71~3!^1Pry-@2veL)XKFoPBB-~=~#p&$GZ zgfK)QwqCn_pKWl%A=e$2E+d2Lty&PyZFj5K6lS^S;cOi-doMpJr&p%08XfiB7fn`s z8WNXX32De=nIl`S9C`Y~g-51XiBfaTQxkcGhb(HsF82O-6pC;%onET}@rB`&e0H zt-RZ8(IK19F=zMg`X=AXT0K|%ZsW>YmzKh<91yuqMjRf#!8e>JUwQB-?oI))Nu(mwSrC@c(N2cWQaQY5)KLS@%o! literal 0 HcmV?d00001 diff --git a/frontend/static/site.webmanifest b/frontend/static/site.webmanifest new file mode 100644 index 0000000..3bf4fdd --- /dev/null +++ b/frontend/static/site.webmanifest @@ -0,0 +1,18 @@ +{ + "name": "Exit Chat Control", + "short_name": "ExitCC", + "description": "Annuaire d'outils libres pour la souveraineté numérique", + "lang": "fr", + "start_url": "/", + "display": "standalone", + "background_color": "#17181f", + "theme_color": "#17181f", + "icons": [ + { + "src": "/favicon.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "any" + } + ] +} diff --git a/frontend/svelte.config.js b/frontend/svelte.config.js new file mode 100644 index 0000000..c4e11fa --- /dev/null +++ b/frontend/svelte.config.js @@ -0,0 +1,19 @@ +import adapter from '@sveltejs/adapter-static' +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' + +/** @type {import('@sveltejs/kit').Config} */ +const config = { + preprocess: vitePreprocess(), + kit: { + // Site 100 % prérendu : servi par nginx sur l'infra OKI, mirrorable + // (ADR-001 ; doctrine §4.4 — HTML rendu serveur, un lien marche sans JS). + adapter: adapter({ + fallback: '404.html' + }), + prerender: { + handleHttpError: 'fail' + } + } +} + +export default config diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 0000000..4344710 --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "./.svelte-kit/tsconfig.json", + "compilerOptions": { + "allowJs": true, + "checkJs": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "sourceMap": true, + "strict": true, + "moduleResolution": "bundler" + } +} diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts new file mode 100644 index 0000000..78873e7 --- /dev/null +++ b/frontend/vite.config.ts @@ -0,0 +1,12 @@ +import { sveltekit } from '@sveltejs/kit/vite' +import { defineConfig } from 'vite' + +export default defineConfig({ + plugins: [sveltekit()], + server: { + fs: { + // le contenu éditorial vit à la racine du repo, hors de frontend/ + allow: ['..'] + } + } +})