Compare commits

17 Commits

Author SHA1 Message Date
sucupira 848e33d198 SEO : comble les régressions de la migration Astro → SvelteKit
- composant Seo.svelte partagé : canonical, Open Graph complet
  (type, url, title, description, image, locale fr_FR, site_name)
  et Twitter Cards sur toutes les pages
- branché sur l'accueil, /outils, /quiz, /contribuer et les fiches
  outils (les meta OG en dur des fiches sont remplacés par le
  composant, JSON-LD conservé)
- script prebuild gen-sitemap.mjs : régénère static/sitemap.xml
  (59 URLs, slugs relus depuis content/outils/) — remplace le
  sitemap qu'apportait @astrojs/sitemap
- robots.txt référence le sitemap en URL absolue
2026-07-21 13:50:03 -04:00
sucupira 3e60f48c5e feat: support du déploiement sous sous-chemin (paths.base)
- Liens et assets via {base}, polices déplacées vers src/lib/assets (hachage Vite)
- Service worker base-aware, précache sur les pages prérendues
- CSP hash documentée : l'hôte ne doit pas fixer script-src
- Docs : déploiement Apache/YunoHost (DEPLOYMENT, APACHE_AUTOINDEX)
2026-07-21 13:41:10 -04:00
sucupira 45fd4bd407 build html files 2026-07-10 14:54:09 -04:00
sucupira e091ec75f7 feat: migre le quiz, renomme en Lagé Chat Control, nettoie l'ère Astro
- Quiz de résistance (/quiz) migré d'Astro vers Svelte 5 : 12 questions,
  contenu fusionné dans content/quiz.json, score calculé localement
  (aucun envoi), barres par domaine, recommandations liées à l'annuaire
  réel (/outils?cat=…), persistance locale, partage natif/presse-papier.
  Audit axe-core : 0 violation.
- Renommage complet « Exit Chat Control » → « Lagé Chat Control » :
  i18n fr/gcf, manifest PWA, package.json, README, design system.
- README racine et frontend/README.md réécrits pour le projet Svelte :
  lancement local (pnpm dev) et déploiement VPS (renvoi vers
  docs/DEPLOYMENT.md), en français.
- CONTRIBUTING.md adapté à la nouvelle structure (content/outils/,
  forge labola) en conservant la politique éditoriale de confiance.
- Nettoyage : suppression de tout l'outillage Astro devenu inutile
  (src/, public/, scripts/, tests/, migration/, docker/, configs racine
  eslint/prettier/vitest/playwright/tsconfig, CI GitHub Actions, script
  de migration one-shot). robots.txt et security.txt recréés sous
  frontend/static/ avec les bonnes références (labola).
- Fix : pin pnpm via frontend/pnpm-workspace.yaml (onlyBuiltDependencies/
  allowBuilds pour esbuild) après suppression du package.json racine qui
  perturbait la résolution de version de corepack.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-10 14:02:54 -04:00
sucupira b13a1e7a31 feat(oki): intro publique, chimen, PWA offline, contribuer, kréyòl, ton OKI
- Landing : intro « Pourquoi ce site ? » (Chat Control expliqué au grand
  public) + sommaire-chimen en 3 arcs (Fondations 🟢 / Reprendre le
  contrôle 🟡 / Autodéfense avancée 🔴) avec compte d'outils par catégorie
- PWA : service worker offline-first (précache coquille, network-first
  avec repli cache pour les fiches consultées), badge hors-ligne discret
- Page /contribuer (3 voies + forge labola) + template d'issue Forgejo
  .gitea/ISSUE_TEMPLATE/nouvo-zouti.yaml ; liens GitHub → labola.o-k-i.net
- Fiches jumelles créoles : chargeur content/outils/gcf/, affichage selon
  la langue avec bandeaux « an chantyé » / « póko tradui », 6 pilotes
  (signal, protonmail, brave, bitwarden, ublock, quad9) — À FAIRE RELIRE
  par un locuteur natif
- Ton OKI : les 6 mêmes fiches réécrites (tutoiement, registre
  militant-chaleureux) ; le script de migration n'écrase plus les fiches
  retravaillées (--force pour tout régénérer)
- UI : View Transitions (respect reduced-motion), aide clavier « ? »
  (dialog natif, raccourcis / h a), progression locale « jès fèt »
  (localStorage uniquement, compteur visible après le premier geste)
- docs/DEPLOYMENT.md : guide VPS (Caddy, nginx, YunoHost, Docker, rsync)
- Audits : axe-core 0 violation (5 pages), Lighthouse 99-98/100/100/100 ;
  corrections contraste monogrammes + ordre des titres
- Budgets : JS 46 Ko gzip, CSS 3,5 Ko, 59 pages prérendues

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 13:38:43 -04:00
sucupira db5a997e50 feat(oki): fondations de la migration vers la stack souveraine OKI
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 <noreply@anthropic.com>
2026-07-10 12:51:17 -04:00
Aurealibe f4e09ca886 Add educational censorship-resistance quiz as a native Astro page
CI / ci (push) Failing after 6m53s
Reworks the standalone-HTML quiz from PR #7 into the site's real
architecture so it actually ships and respects every project rule.

- /quiz, /fr/quiz, /nl/quiz — prerendered per-locale routes with correct
  canonical + hreflang (new optional `path` prop on Base.astro).
- Progressive enhancement: the twelve questions and the full scoring key
  (bands + per-area guidance with deep links into the guide) are
  server-rendered and readable with JavaScript disabled; the client script
  only computes the live score and reveals the result panel.
- CSP-clean: bundled same-origin script (no inline handlers), bar widths
  set via a CSS custom property through the CSSOM (no inline styles), no
  third-party request. docker/csp.conf needs no new hash.
- i18n via the existing core+overlay model: src/data/quiz.json (neutral
  scoring) + src/i18n/content/<locale>/quiz.json (prose), loaded by a
  dedicated loadQuiz() that checks question/option/band parity in all three
  locales. Page chrome added to the `quiz` namespace (key parity tested).
- Share is copy-link + native Web Share + X (already allowlisted); the
  third-party mastodonshare.com redirector from PR #7 is dropped.
- TopBar "Contents" link made absolute (/#toc) so it works off the guide,
  plus a new "Quiz" nav link. New tests/e2e/quiz.spec.ts covers no-JS
  readability, scoring, the TOC link and zero third-party requests.

Co-Authored-By: arnaudom <1535627+arnaudom@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 13:24:41 +01:00
Aurealibe 4f583d8c88 Merge pull request #10 from depado/add-pocketpal
feat(content): add Pocketpal in the AI section
2026-07-10 12:40:54 +01:00
Aurealibe 6c46f35297 Reformate les renvois YunoHost (Prettier) après le raccourcissement du texte
Le déplacement de Tailscale a raccourci la ligne de renvoi FR/NL
(« section VPN » / « VPN-sectie »), ce qui a cassé le retour à la ligne
canonique attendu par `prettier --check` en CI. Applique prettier --write.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 12:35:41 +01:00
Aurealibe 80ad82310b Déplace la fiche Tailscale de l'auto-hébergement vers la section VPN
Porte l'intention de la PR #5 (gitickle) sur l'architecture Astro. La PR
éditait index.html, supprimé lors du rebuild Astro, donc inapplicable.

Tailscale/Headscale/WireGuard est avant tout un outil VPN/maillage, et
l'index (directory.json) le classe déjà en « vpn-reseau » : déplacer la
fiche aligne la carte sur l'index. La fiche <ToolCard t-tailscale> passe
de 17-selfhost à la fin de 07-vpn (après IVPN) dans les trois langues, et
le renvoi YunoHost « fiche ci-dessous » devient « section VPN ».

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 12:26:11 +01:00
depado 4279f8924a feat(content): add Pocketpal in the AI section 2026-07-10 11:08:10 +02:00
Aurealibe 710854510b Brand tile colors as generated stylesheet (CSP blocks inline styles), footer credit link 2026-07-10 04:31:15 +01:00
Aurealibe 5f3528cce1 Align production compose with the minimal Coolify pattern (no ports/expose) 2026-07-10 04:16:47 +01:00
Aurealibe dc7f1b3a7c nginx sur le port 80 : routage par defaut des reverse proxies (Coolify/Traefik) 2026-07-10 04:11:12 +01:00
Aurealibe 25cc54c2e6 Compose de prod pour Coolify : un seul service nginx sans port publie, compose de dev separe 2026-07-10 04:06:42 +01:00
Aurealibe b7b269eadf Merge rebuild/astro : site Astro multilingue, faits verifies, Docker, CI 2026-07-10 03:59:20 +01:00
Aurealibe 8ebc1da9e4 Rebuild du site sur Astro : i18n multilingue, faits verifies, Docker, CI, tests
- Astro statique, EN par defaut + /fr/ + /nl/, detection langue navigateur
- i18n par fichiers JSON, ajouter une langue = ajouter des traductions
- Contenu original porte a l'identique (diff d'inventaire par langue)
- Chronologie legislative corrigee sur sources primaires (PE, Conseil, votes)
- Timeline 23 precedents + observatoire 35 items + annuaire 66 outils FOSS,
  chaque affirmation verifiee et sourcee
- Zero requete tierce (teste), lisible JS coupe, axe WCAG AA x2 themes
- Version offline monofichier par langue a chaque build
- Docker multi-stage vers nginx + CSP stricte auto-generee
- CI GitHub Actions (SHA-pinnees) + verification hebdo des liens
- CONTRIBUTING : divulgation d'affiliation obligatoire, allowlist de
  domaines testee en CI
2026-07-10 03:52:49 +01:00
129 changed files with 7779 additions and 2041 deletions
+11
View File
@@ -0,0 +1,11 @@
{
"version": "0.0.1",
"configurations": [
{
"name": "oki-preview",
"runtimeExecutable": "pnpm",
"runtimeArgs": ["--dir", "frontend", "preview", "--port", "4173"],
"port": 4173
}
]
}
+51
View File
@@ -0,0 +1,51 @@
name: "Nouvo zouti — Proposer un outil"
description: "Pwopozé on zouti lib pou lis-la / Proposer un outil libre pour l'annuaire"
title: "[zouti] "
labels:
- "nouvo-zouti"
body:
- type: input
id: nom
attributes:
label: "Non a zouti-la / Nom de l'outil"
placeholder: "ex. Signal"
validations:
required: true
- type: input
id: url
attributes:
label: "Sit ofisyèl / Site officiel"
placeholder: "https://…"
validations:
required: true
- type: input
id: licence
attributes:
label: "Licence (identifiant SPDX si tu le connais)"
placeholder: "ex. AGPL-3.0"
- type: dropdown
id: difficulte
attributes:
label: "Difikilté / Difficulté d'installation"
options:
- "🟢 Débitan / Débutant"
- "🟡 Entèmédia / Intermédiaire"
- "🔴 Avansé / Avancé"
validations:
required: true
- type: textarea
id: pourquoi
attributes:
label: "Poukisa i konté ? / Pourquoi il compte ?"
description: "En quoi cet outil sert la souveraineté numérique ? Qui devrait s'en servir ?"
validations:
required: true
- type: checkboxes
id: verifs
attributes:
label: "Vérifications"
options:
- label: "L'outil est libre / open-source (code source publié)"
required: true
- label: "J'ai vérifié qu'il n'est pas déjà dans l'annuaire"
required: true
+10
View File
@@ -0,0 +1,10 @@
node_modules/
*.local
.DS_Store
# SvelteKit (frontend/)
frontend/.svelte-kit/
frontend/build/
# Brief de mission (document de travail local)
testoki_exit_chat_control.md
+83
View File
@@ -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é).
+44
View File
@@ -0,0 +1,44 @@
# Contribuer à Lagé Chat Control
Les contributions sont bienvenues — ce guide existe parce que les gens partagent ce qui marche. Mais c'est un projet où **la confiance prime** : les lecteurs et lectrices suivent nos recommandations pour se protéger de la surveillance. Cette confiance est le produit, donc les règles ci-dessous sont strictes et non négociables. Chaque soumission est relue personnellement.
Pour les gestes simples (proposer un outil, corriger une fiche), la page [/contribuer](frontend/src/routes/contribuer/+page.svelte) du site et le [guide de contenu](docs/CONTENT_GUIDE.md) suffisent. Ce document couvre la politique éditoriale et les changements plus larges.
## Ajouter un outil
Un outil doit remplir les trois règles :
1. **Traction** — un projet établi, activement maintenu, avec un vrai usage. Pas un outil obscur ou abandonné, pas ton projet vieux de trois mois.
2. **Open source** — un dépôt public sous licence OSI reconnue.
3. **Respectueux de la vie privée** — pas de traqueur, pas de monétisation des données, documentation honnête.
## Déclaration d'affiliation (obligatoire)
Si tu es autrice, mainteneur, salarié·e, investisseur ou autrement affilié·e à un outil que tu proposes, **dis-le dans la description de la demande**. Une auto-promotion non déclarée — même si le reste du travail est bon — entraîne la fermeture de la demande.
## Politique des liens
- Chaque outil référencé doit avoir une licence SPDX identifiable (voir [`content/outils/`](content/outils/)).
- Les mises en avant éditoriales (`is_featured`, ordre des catégories) restent une décision de maintenance — une demande qui met en avant un outil au détriment des autres sera priée de revenir en arrière.
## Contenu et faits
- Chaque affirmation datée a besoin d'une source, primaire si possible (texte de loi, décision de justice, communiqué institutionnel — pas un article de blog).
- Traductions : les micro-textes d'interface vivent dans `frontend/src/lib/i18n/<locale>.json` (clé manquante = erreur de build) ; le contenu des fiches dans `content/outils/` (FR) et `content/outils/gcf/` (jumelles créoles, voir [CONTENT_GUIDE.md](docs/CONTENT_GUIDE.md)).
## Changements de grande ampleur
Ouvre une discussion **avant** d'écrire une grosse demande (refontes, changement de stack, nouvelles sections). Les reconstructions complètes non sollicitées sont fermées par défaut — pas parce que le travail est mauvais, mais parce que le coût de relecture et de confiance croît avec la taille du diff.
## Développement
```sh
cd frontend
pnpm install
pnpm dev # serveur de développement
pnpm check # types + accessibilité (svelte-check)
pnpm build # build statique complet
pnpm audit:a11y # audit axe-core sur le build servi en local
```
Le site doit rester : statique, lisible sans JavaScript pour tout le contenu et les formulaires de base, sans requête vers un domaine tiers. Voir [docs/DEPLOYMENT.md](docs/DEPLOYMENT.md) pour la mise en production.
+40 -38
View File
@@ -1,57 +1,59 @@
# Exit Chat Control
# Lagé Chat Control
A free, trilingual (EN / FR / NL) guide to escape **Chat Control** and take back control of your digital privacy: encrypted messaging, email, VPN, DNS, 2FA, Linux, GrapheneOS, self-hosting, and more. Every tool is presented with **what it does**, **who it's for**, and **how to install it**.
Un guide de terrain, libre et bilingue (français / kréyòl), pour échapper à **Chat Control** et reprendre le contrôle de sa vie privée numérique : messagerie chiffrée, e-mail, VPN, DNS, 2FA, Linux, GrapheneOS, auto-hébergement, et plus. Chaque outil est présenté avec **à quoi il sert**, **pour qui** et **comment l'installer**.
🔗 **Live site: https://exitchatcontrol.org**
Projet OKI (Organisation Ka Internationale) — voir [`ADR.md`](ADR.md) pour la décision d'architecture et [`docs/`](docs/) pour la doctrine UI/UX, le design system et le guide de contenu.
## What is this?
## En bref
On 9 July 2026, the European Parliament let "Chat Control" through, opening the door to the scanning of private messages across the EU. This project is a practical, no-nonsense answer: a single, self-contained web page that references privacy-respecting tools anyone can use to opt out of mass surveillance, from the everyday citizen to the whistleblower.
- **Statique et auto-suffisant** — pas de traqueur, pas de cookie, aucune requête vers un domaine tiers, aucun CDN (polices et assets auto-hébergés).
- **Lisible sans JavaScript** — le contenu et les formulaires de base fonctionnent nus ; le script n'enrichit que le quiz, la recherche instantanée et les filtres.
- **Bilingue FR / kréyòl** — les micro-textes d'interface sont typés (une clé manquante = erreur de build) ; le contenu des fiches suit le même principe avec repli explicite quand une traduction manque.
- **Quiz éducatif** — `/quiz` évalue ta résistance à la censure sur 100 points et pointe vers des pistes concrètes dans l'annuaire. Calcul entièrement local, rien n'est envoyé ni enregistré.
- **PWA hors-ligne** — les fiches déjà consultées restent lisibles sans connexion.
It is intentionally simple: no tracking, no dependencies, no build step. Just open `index.html`.
## Stack
## Repository
**Svelte 5 + SvelteKit 2**, site 100 % prérendu (`adapter-static`) · **CSS natif** (`@layer`, `oklch()`, container queries, `clamp()` — pas de Tailwind) · contenu en fichiers Markdown/JSON versionnés dans [`content/`](content/), aucun CMS ni base de données. Gestionnaire de paquets : pnpm.
- **`index.html`** — the full guide (a standalone website).
- **`article-presse.md`** — a ready-to-publish, actionable article (for a blog, a newsletter, or an X Article).
- **`images/`** — screenshots used in the guide.
### Lancer en local
## Contributing
```sh
cd frontend
pnpm install
pnpm dev # http://localhost:5173
```
Contributions are very welcome. If you know a useful app that helps circumvent Chat Control or protect privacy, please **open a Pull Request** (or an Issue) to add it. **I review every submission personally.**
```sh
pnpm build # site statique dans frontend/build/
pnpm preview # sert ce build localement
```
To be accepted, an app must meet **three simple rules**:
### Déployer sur un VPS
1. **It has traction** — an established, actively maintained project with a real user base, not an obscure or abandoned tool.
2. **It is open source.**
3. **It is privacy-respecting.**
Le build est un dossier de fichiers statiques — n'importe quel serveur web
convient. Voir **[`docs/DEPLOYMENT.md`](docs/DEPLOYMENT.md)** pour la marche
à suivre complète (Caddy avec TLS automatique, nginx, YunoHost, Docker) et
la checklist de vérification post-déploiement.
That's it. If it ticks those three boxes and genuinely helps people escape surveillance, it belongs here.
## Contribuer
**How to contribute:**
- Fork the repo, add a tool card in `index.html` following the existing format (logo, "what it does", "why", "who it's for", install steps), then open a PR.
- Not comfortable with code? Just open an Issue with the app's name, its link, and why it qualifies (traction + open source + privacy), and I'll add it.
Les contributions sont bienvenues — un outil à ajouter, une traduction, une
correction. La page **[/contribuer](frontend/src/routes/contribuer/+page.svelte)**
du site couvre les trois voies (proposer un outil, corriger une fiche,
traduire) ; **[`CONTRIBUTING.md`](CONTRIBUTING.md)** détaille la politique
éditoriale (traction, open source, respect de la vie privée, déclaration
d'affiliation obligatoire) et **[`docs/CONTENT_GUIDE.md`](docs/CONTENT_GUIDE.md)**
le format des fiches et le ton.
## Roadmap
Pas à l'aise avec le code ? Ouvre une demande sur
[la forge](https://labola.o-k-i.net/cyber-mawonaj/lage-chat-control) avec le
nom de l'outil, son lien, et pourquoi il a sa place.
Right now, the goal is deliberately narrow: **reference useful apps to circumvent Chat Control**, on one clean, fast page.
## Licence
Later, the site will be refactored to support more:
- Full written guides
- Dedicated pages for each application
- Step-by-step tutorials
This single-page version is the foundation the rest will be built on.
## Running it yourself
It's a static site (one `index.html`, plus an `images/` folder). Host it on any static host (Netlify, Vercel, Cloudflare Pages) or serve it behind a reverse proxy. No build, no dependencies.
## License
See [`LICENSE`](./LICENSE).
Voir [`LICENSE`](LICENSE) (MIT).
---
*Privacy is a right, not a confession. To encrypt is to vote. To self-host is to disobey. To reclaim your tools is to become ungovernable.*
_La vie privée est un droit, pas un aveu. Chiffrer, c'est voter. S'auto-héberger, c'est désobéir. Reprendre ses outils, c'est devenir ingouvernable._
+92
View File
@@ -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
}
]
+42
View File
@@ -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
<b>Aegis</b> (Android) garde vos codes dans un coffre chiffré local, sans cloud.{' '}
<b>Ente Auth</b> 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
<ol>
<li>Installez Aegis (F-Droid) ou Ente Auth (toutes plateformes).</li>
<li>
Sur chaque compte, activez la 2FA « application d'authentification » et scannez le QR code
affiché.
</li>
<li>
Notez les <b>codes de secours</b> sur papier et faites une sauvegarde chiffrée de votre
coffre.
</li>
</ol>
+36
View File
@@ -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.{' '}
<b>SimpleLogin</b> est intégré à Proton ; <b>addy.io</b> 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
<ol>
<li>Créez un compte, installez l'extension de navigateur.</li>
<li>À chaque inscription, générez un nouvel alias au lieu de saisir votre vraie adresse.</li>
</ol>
+42
View File
@@ -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 <b>avant</b>{' '}
qu'elles ne partent dans un cloud.
## why
Les sauvegardes iCloud/Google sont souvent accessibles au fournisseur, donc à la justice.{' '}
<b>Cryptomator</b> chiffre vos fichiers avant de les envoyer sur n'importe quel cloud ;{' '}
<b>restic</b> fait des sauvegardes chiffrées automatiques ; <b>Proton Drive</b> 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
<ol>
<li>
iPhone : Réglages &gt; votre nom &gt; iCloud &gt; Protection avancée des données : activez-la.
</li>
<li>
PC : installez Cryptomator, créez un coffre, placez-y vos fichiers avant de synchroniser vers
le cloud.
</li>
</ol>
+34
View File
@@ -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
<ol>
<li>Installez depuis l'App Store (iOS) ou l'APK/GitHub (Android).</li>
<li>Ouvrez l'application et choisissez un pseudo. Aucun compte à créer.</li>
<li>Les appareils proches se découvrent automatiquement en Bluetooth.</li>
<li>En cas de danger, triple-tapez le logo pour tout effacer (mode panique).</li>
</ol>
+38
View File
@@ -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 <b>auto-conservation</b> (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
<ol>
<li>Procurez-vous des bitcoins (plateforme d'échange, ou entre particuliers).</li>
<li>
Transférez-les aussitôt vers un portefeuille que <b>vous</b> contrôlez : un portefeuille
matériel (Trezor, ColdCard) ou une application libre. Ne les laissez pas sur la plateforme.
</li>
<li>Sauvegardez votre phrase de récupération sur papier, jamais en photo ni en ligne.</li>
</ol>
+33
View File
@@ -0,0 +1,33 @@
---
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 toi, sur tous tes appareils.
## why
Gratuit, audité, multiplateforme, chiffré de bout en bout. Tu n'as plus qu'un seul mot de passe fort à retenir — et le jour où tu veux ne dépendre de personne, il est <b>auto-hébergeable</b> (via Vaultwarden).
## who
Tout le monde. C'est la fondation de toute ta sécurité : un mot de passe unique et fort par service, sans effort de mémoire.
## install
<ol>
<li>Crée un compte sur bitwarden.com et choisis un mot de passe maître <b>long</b> — une phrase entière. Ne l'oublie jamais : il n'est pas récupérable, et c'est voulu.</li>
<li>Installe l'extension de navigateur et l'application mobile.</li>
<li>Au fil de tes connexions, laisse Bitwarden enregistrer puis remplacer tes vieux mots de passe par des mots de passe générés.</li>
</ol>
+33
View File
@@ -0,0 +1,33 @@
---
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 le changement le plus indolore quand tu viens de Chrome : même ergonomie, sans le pistage. Il bloque les pubs YouTube nativement, propose une visio chiffrée (Brave Talk, basé sur Jitsi) et un accès Tor en un clic — pratique pour récupérer un site bloqué.
## who
Celles et ceux qui veulent quitter Chrome sans rien réapprendre. (Les fonctions crypto et pubs récompensées ? Ignore-les, elles se désactivent.)
## install
<ol>
<li>Installe Brave depuis <b>brave.com</b>.</li>
<li>Règle les « Boucliers » (Shields) sur « agressif », et désactive Brave Rewards si tu ne veux pas de pubs.</li>
<li>Pour Tor : menu &gt; « Nouvelle fenêtre privée avec Tor », puis attends « Tor connecté ».</li>
</ol>
+42
View File
@@ -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 <b>sans internet</b> : 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
<ol>
<li>
Installez depuis <b>briarproject.org</b> ou F-Droid (pas d'iOS).
</li>
<li>
Créez un compte <b>local</b> (pseudo + mot de passe), stocké seulement sur le téléphone.
</li>
<li>Ajoutez un contact proche en scannant son QR code, ou à distance via un lien.</li>
<li>
Quand le réseau est coupé, activez le Bluetooth : les messages passent d'appareil à appareil.
</li>
</ol>
+28
View File
@@ -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
<b>Duck.ai</b> (DuckDuckGo) donne un accès anonymisé à plusieurs modèles. <b>Lumo</b> (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.
+30
View File
@@ -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 à{' '}
<b>concentrer encore plus de vos traces</b> 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.
+41
View File
@@ -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 <b>avant</b> 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
<ol>
<li>Installez Cryptomator (PC et mobile) depuis cryptomator.org.</li>
<li>
Créez un « coffre » dans le dossier synchronisé de votre cloud, choisissez un mot de passe
fort.
</li>
<li>
Placez vos fichiers dans le coffre : ils sont chiffrés puis synchronisés automatiquement.
</li>
</ol>
+41
View File
@@ -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 <b>droit à l'effacement</b> : vous pouvez exiger,
gratuitement, qu'une entreprise supprime vos données, en principe sous un mois. Des services
comme <b>Incogni</b> 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
<ol>
<li>
Pour une demande manuelle : écrivez au courtier en invoquant l'article 17 du RGPD et demandez
la suppression.
</li>
<li>
Pour automatiser : souscrivez à un service de retrait et laissez-le envoyer les demandes en
votre nom.
</li>
</ol>
+39
View File
@@ -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
<ol>
<li>
Installez Element (element.io) et créez un compte sur un serveur public, ou sur le vôtre
(section 10).
</li>
<li>
Rejoignez ou créez des salons, et activez le chiffrement de bout en bout pour les
conversations privées.
</li>
</ol>
+25
View File
@@ -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.
+42
View File
@@ -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{' '}
<em>arkenfox</em>) et l'extension uBlock Origin, il devient très solide.
## who
Tout le monde, comme navigateur principal au quotidien.
## install
<ol>
<li>
Installez Firefox depuis <b>mozilla.org/firefox</b>.
</li>
<li>
Ajoutez l'extension <b>uBlock Origin</b> (blocage des pubs et traqueurs).
</li>
<li>
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.
</li>
</ol>
+25
View File
@@ -0,0 +1,25 @@
---
name: "Bitwarden"
slug: "bitwarden"
lang: "gcf"
---
## what
On kòf a modpas ki ka sonjé-yo, jénéré-yo é ranpli-yo pou vou, asi tout apawèy a-w.
## why
Gratis, odité, miltiplatfòwm, chifré bout-an-bout. Ou ni yenki on sèl modpas fò pou sonjé — é jou ou vlé pa dépann di pèsonn, ou pé <b>oto-ébèjé'y</b> (èvè Vaultwarden).
## who
Tout moun. Sé fondasyon a tout sékirité a-w : on modpas inik é fò pou chak sèwvis, san fòsé mémwa a-w.
## install
<ol>
<li>Kréyé on kont asi bitwarden.com é chwazi on modpas mèt <b>long</b> — on fraz antyé. Pa janmé oublié'y : i pa rékipérab, é sé fè èsprè.</li>
<li>Enstalé èkstansyon navigatè-la é aplikasyon mobil-la.</li>
<li>O fi a konèksyon a-w, kité Bitwarden anrèjistré é apré ranplasé vyé modpas a-w pa modpas jénéré.</li>
</ol>
+25
View File
@@ -0,0 +1,25 @@
---
name: "Brave"
slug: "brave"
lang: "gcf"
---
## what
On navigatè basé asi Chromium ki ka bloké piblisité é trakè pa défo — èvè an plis on fenèt privé konèkté asi Tor.
## why
Sé chanjman pi dous-la lè ou ka sòti an Chrome : menm èwgonomi, san pistaj-la. I ka bloké pib YouTube dirèk, i ka pwopozé on vizyo chifré (Brave Talk, basé asi Jitsi) é on aksè Tor an on klik — pratik pou ratrapé on sit bloké.
## who
Moun ki vlé kité Chrome san riaprann ayen. (Fonksyon crypto é pib rékonpansé ? Inyoré-yo, yo ka tenn.)
## install
<ol>
<li>Enstalé Brave dépi <b>brave.com</b>.</li>
<li>Réglé « Bouklyé »-la (Shields) asi « agrésif », é tenn Brave Rewards si ou pa vlé pib.</li>
<li>Pou Tor : mèni &gt; « Nouvo fenèt privé èvè Tor », apré atann « Tor konèkté ».</li>
</ol>
+26
View File
@@ -0,0 +1,26 @@
---
name: "Proton Mail"
slug: "protonmail"
lang: "gcf"
---
## what
On bwèt imèl chifré ki ka ranplasé Gmail oben Outlook — é kont pivo a tout ékosistèm Proton-la.
## why
Basé an Swis, andéwò a jiridiksyon « Five Eyes »-la. Konpatib OpenPGP : chifraj-la otomatik ant kont Proton é lòt sèwvis PGP. Kouryé a-w, sé korèspondans a-w — pa on matyè prèmyé pou piblisité.
## who
Tout moun. Sé pi bon pòt pou sòti an Google : louvè kont-la, rès a suit-la (ajanda, drive, VPN) ka suiv tousèl.
## install
<ol>
<li>Kréyé on kont asi <b>proton.me</b> (òf gratis-la ka sifi pou koumansé).</li>
<li>Rapatriyé vyé mésaj é kontak a-w èvè <b>Easy Switch</b> (entégré).</li>
<li>Mèt on rédirèksyon dépi Gmail é prévien kontak a-w di nouvo adrès a-w.</li>
<li>Enstalé aplikasyon mobil-la, limé <b>doub otantifikasyon-la</b> — é Proton Bridge si ou ka rété asi Thunderbird.</li>
</ol>
+24
View File
@@ -0,0 +1,24 @@
---
name: "Quad9"
slug: "quad9"
lang: "gcf"
---
## what
On rézolvè DNS chifré, swis é san bi likratif, ki ka bloké an plis domèn malfézan yo ja konnèt.
## why
Founisè aksè a-w ka vwè chak sit ou ka vizité gras a DNS-la. Quad9 ka chifré dimann-lasa yo, i pa ka gadé adrès IP a-w, é i ka pwotéjé-w kont lamèkantri an menm tan. On réglaj « fè'y é oublié'y ».
## who
Tout moun — sitou si ou vlé on vré gany san enstalé ayen anplis.
## install
<ol>
<li>Asi Android : Réglaj &gt; Rézo &gt; <b>DNS privé</b> &gt; matjé <b>dns.quad9.net</b>.</li>
<li>Asi iPhone/Mac : enstalé profil DoH-la dépi quad9.net. Asi PC : konfiguré DNS-over-HTTPS an navigatè-la oben an sistèm-la.</li>
</ol>
+26
View File
@@ -0,0 +1,26 @@
---
name: "Signal"
slug: "signal"
lang: "gcf"
---
## what
On mésajri chifré pou tèks, apèl, vizyo é group a-w. Sé ranplasan dirèk a WhatsApp — menm senplisité, san Meta dèyè.
## why
Chifraj bout-an-bout pa défo, san bi likratif, protokòl odité ki ka sèwvi référans toupatou. Prèské pon métadoné pa ka rété, é ékip-la ja di : yo ké kité Léwòp olyé yo enstalé on skannaj. Chak moun ou ka fè pasé, sé on konvèsasyon anplis ki ka sòti an survéyans-la.
## who
Tout moun, jòdi-la menm. Sé prèmyé jès-la, é pi itil-la : koumansé èvè group a fanmi a-w. Yenki on kontrent : on niméwo téléfòn — ou pé séré'y dèyè on non itilizatè.
## install
<ol>
<li>Enstalé dépi <b>signal.org</b> (App Store, Google Play, oben APK dirèk asi Android).</li>
<li>Konfirmé niméwo a-w pa SMS, apré chwazi on kòd PIN.</li>
<li>Réglaj &gt; Konfidansyalité : kréyé on <b>non itilizatè</b> pou séré niméwo a-w, é limé <b>vèwou enskripsyon-la</b>.</li>
<li>Envité moun a-w — group fanmi-la avan. Asi òwdinatè, lyanné aplikasyon biwo-la èvè téléfòn a-w.</li>
</ol>
+24
View File
@@ -0,0 +1,24 @@
---
name: "uBlock Origin"
slug: "ublock"
lang: "gcf"
---
## what
Èkstansyon-la ki ka bloké piblisité, trakè é èskrip èspyon. Jès pi rantab a tout gid-lasa : dis ségonn, éfè imedya.
## why
Piblisité an liny sé prensipal chimen a survéyans é profilaj. Bloké'y, sé koupé pi gwo moso a pistaj-la — é an menm balan paj a-w ka chajé pi vit é fòfé data a-w ka respiré.
## who
Tout moun, asi Firefox kon asi navigatè Chromium. Si ou ka fè yenki on sèl jès jòdi-la, fè ta-lasa.
## install
<ol>
<li>Louvè magazen èkstansyon a navigatè a-w é chèché « uBlock Origin » (orijinal-la, pa Raymond Hill).</li>
<li>Enstalé'y. I ka travay lamenm, san pon réglaj.</li>
</ol>
+50
View File
@@ -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,{' '}
<b>permissions réseau par application</b>, 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
<ol>
<li>
Procurez-vous un téléphone <b>Google Pixel</b> compatible (c'est le seul matériel supporté,
pour des raisons de sécurité).
</li>
<li>
Sur un ordinateur, allez sur <b>grapheneos.org/install</b> et suivez l'installeur web
(quelques clics, aucun terminal requis).
</li>
<li>
Pour vos applications, installez le magasin libre F-Droid ; ajoutez Google Play en bac à sable
seulement si nécessaire.
</li>
<li>
Réglez un long mot de passe (pas un code à 4 chiffres) et découvrez le mode « Lockdown ».
</li>
</ol>
+40
View File
@@ -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
<ol>
<li>
Sur <b>ivpn.net</b>, générez un compte (un identifiant est créé pour vous, sans e-mail).
</li>
<li>Payez en cryptomonnaie ou en espèces, comme pour Mullvad.</li>
<li>
Installez l'application, connectez-vous hors UE, activez le kill switch et le protocole
WireGuard.
</li>
</ol>
+38
View File
@@ -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 <b>local</b> : 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
<ol>
<li>
Installez KeePassXC (PC) et une application compatible sur mobile (KeePassDX sur Android,
Strongbox sur iOS).
</li>
<li>Créez un fichier de base de données protégé par un mot de passe maître.</li>
<li>Pour l'avoir sur tous vos appareils, synchronisez ce fichier via un cloud chiffré.</li>
</ol>
+53
View File
@@ -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 &amp; 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, <b>Linux Mint</b> (le plus proche de Windows)
ou <b>Fedora</b> ; <b>Debian</b> pour la stabilité. Pas besoin de tout casser : on peut d'abord
tester, puis installer à côté de son système actuel.
## install
<ol>
<li>
Pas sûr de la distribution ? Répondez au questionnaire <b>distrochooser.de/fr</b>, qui vous
oriente selon vos besoins.
</li>
<li>
Testez-la <b>sans rien installer</b> directement dans votre navigateur sur{' '}
<b>distrosea.com</b>.
</li>
<li>
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).
</li>
<li>
Démarrez sur la clé pour essayer le système « en live », puis lancez l'installation. Activez
le chiffrement du disque proposé.
</li>
</ol>
+46
View File
@@ -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. <b>Ollama</b> et{' '}
<b>LM Studio</b> sont les plus simples ; <b>Jan</b> et <b>GPT4All</b> 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
<ol>
<li>
Pour débuter en douceur, installez <b>LM Studio</b> ou <b>Jan</b> (interface graphique, comme
une application classique).
</li>
<li>
Téléchargez un modèle proposé dans l'application (par exemple Llama ou Mistral), adapté à la
puissance de votre machine.
</li>
<li>
Discutez : tout se passe en local. Ollama est la version en ligne de commande pour les plus
techniques.
</li>
</ol>
+33
View File
@@ -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
<ol>
<li>Créez un compte (payant, de l'ordre de 1 à 3 € par mois).</li>
<li>Activez le chiffrement PGP côté serveur (fonction « Guard » chez mailbox.org).</li>
<li>Configurez le compte dans Thunderbird ou l'application mobile de votre choix.</li>
</ol>
+37
View File
@@ -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. <b>Organic Maps</b> ne trace rien et n'affiche aucune publicité ; <b>OsmAnd</b> 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
<ol>
<li>Installez depuis F-Droid, l'App Store ou Google Play.</li>
<li>
Téléchargez à l'avance les cartes des régions qui vous intéressent pour l'usage hors ligne.
</li>
</ol>
+41
View File
@@ -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
<ol>
<li>
Sur joinmastodon.org, choisissez un serveur (ou « instance ») qui vous correspond, puis créez
un compte.
</li>
<li>
Installez une application (l'officielle, ou Ivory, Tusky…) et suivez des comptes : vous voyez
tout le fédiverse, quel que soit leur serveur.
</li>
</ol>
+39
View File
@@ -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
<ol>
<li>
Ajoutez le dépôt Molly dans F-Droid, ou récupérez l'APK sur <b>molly.im</b>.
</li>
<li>
Choisissez <b>Molly-FOSS</b> si vous voulez zéro dépendance Google.
</li>
<li>Enregistrez-vous avec votre numéro (même réseau que Signal).</li>
<li>Définissez un mot de passe de base de données et le verrouillage automatique.</li>
</ol>
+39
View File
@@ -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
<ol>
<li>Installez un portefeuille (l'officiel sur getmonero.org, ou Cake Wallet sur mobile).</li>
<li>
Obtenez des XMR via un service d'échange décentralisé (par exemple un « swap » depuis une
autre crypto).
</li>
<li>Sauvegardez votre phrase de récupération sur papier.</li>
</ol>
+62
View File
@@ -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
<ol>
<li>
Allez sur <b>mullvad.net</b>. Si le site est bloqué dans votre pays, ouvrez-le via le
navigateur Tor ou son adresse <b>.onion</b>. 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.
</li>
<li>
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.
</li>
<li>
<b>Paiement anonyme en crypto :</b> 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 <b>Monero</b> (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.
</li>
<li>
Téléchargez l'application sur <b>mullvad.net/download</b> (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.
</li>
<li>
Lancez l'application, entrez votre numéro de compte, puis connectez-vous à un serveur situé{' '}
<b>hors de votre pays</b>, 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.
</li>
</ol>
+41
View File
@@ -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{' '}
<b>même empreinte numérique</b>. 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
<ol>
<li>
Téléchargez-le sur <b>mullvad.net/browser</b> (Windows, macOS, Linux).
</li>
<li>
Utilisez-le tel quel, sans le personnaliser : chaque extension ou réglage ajouté vous rend à
nouveau identifiable.
</li>
<li>Associez-le à votre VPN pour masquer aussi votre adresse IP.</li>
</ol>
+40
View File
@@ -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
<b>Mullvad DNS</b> (Suède, no-log) est gratuit et propose des listes de blocage. <b>NextDNS</b>{' '}
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
<ol>
<li>
Mullvad DNS : utilisez l'adresse indiquée sur mullvad.net/help/dns comme DNS privé (même
méthode que Quad9).
</li>
<li>
NextDNS : créez un profil sur nextdns.io, <b>désactivez la journalisation</b>, puis appliquez
la configuration à vos appareils.
</li>
</ol>
+25
View File
@@ -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.
+29
View File
@@ -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.
+39
View File
@@ -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
<ol>
<li>Installez un client : Damus (iPhone), Amethyst ou Primal (Android), ou une version web.</li>
<li>
L'application génère votre paire de clés. <b>Sauvegardez votre clé privée (nsec)</b> comme une
phrase de récupération Bitcoin : sur papier, jamais partagée.
</li>
<li>Créez votre profil, suivez des gens, activez les zaps pour recevoir des sats.</li>
</ol>
+37
View File
@@ -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
<b>Standard Notes</b> (par Proton) chiffre vos notes par défaut. <b>Joplin</b> gère des carnets
en Markdown avec chiffrement optionnel et la synchronisation de votre choix. <b>CryptPad</b>{' '}
(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
<ol>
<li>
Standard Notes / Joplin : créez un compte, installez l'application, et activez le chiffrement
(par défaut sur Standard Notes, à activer dans Joplin).
</li>
<li>
CryptPad : utilisez cryptpad.fr ou une autre instance, aucun compte requis pour commencer.
</li>
</ol>
+41
View File
@@ -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
<b>Ente</b> est un service géré, chiffré de bout en bout, avec reconnaissance et recherche
faites sur l'appareil : simple et sûr. <b>Immich</b> est un clone de Google Photos que vous{' '}
<b>hébergez vous-même</b> (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
<ol>
<li>
Ente : installez l'application, créez un compte, activez la sauvegarde automatique de votre
pellicule.
</li>
<li>
Immich : déployez-le sur votre serveur (voir section 10) via Docker, puis connectez
l'application mobile.
</li>
</ol>
+39
View File
@@ -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 <b>tout votre réseau à la fois</b>,
installé sur un petit ordinateur comme un Raspberry Pi.
## why
Là où uBlock Origin protège un navigateur, Pi-hole filtre <b>chaque appareil</b> 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.{' '}
<b>AdGuard Home</b> 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
<ol>
<li>Sur un Raspberry Pi (ou un conteneur), lancez l'installeur officiel de Pi-hole.</li>
<li>
Dans les réglages de votre box/routeur, indiquez l'adresse du Pi-hole comme serveur DNS.
</li>
<li>Désormais, tous les appareils connectés à votre réseau sont filtrés automatiquement.</li>
</ol>
+41
View File
@@ -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 <b>GGUF</b> (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
<ol>
<li>
Installez <b>PocketPal</b> depuis l'App Store ou Google Play (ou compilez les sources).
</li>
<li>
Ouvrez <b>Models</b>, choisissez un modèle adapté à votre téléphone et téléchargez-le.
</li>
<li>Chargez-le et discutez : tout tourne hors ligne, sur l'appareil.</li>
</ol>
+32
View File
@@ -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
<ol>
<li>Activez Proton Drive dans votre compte Proton et installez l'application.</li>
<li>Activez la sauvegarde automatique de vos photos, puis désactivez celle de Google/Apple.</li>
</ol>
+33
View File
@@ -0,0 +1,33 @@
---
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ée en Suisse, hors juridiction des « Five Eyes ». Compatible OpenPGP : le chiffrement est automatique entre comptes Proton et vers les autres services PGP. Ton courrier, c'est ta correspondance — pas une matière première publicitaire.
## who
Tout le monde. C'est la meilleure porte de sortie de Google : ouvre le compte, le reste de la suite (agenda, drive, VPN) suit tout seul.
## install
<ol>
<li>Crée un compte sur <b>proton.me</b> (l'offre gratuite suffit largement pour commencer).</li>
<li>Rapatrie tes anciens messages et contacts avec <b>Easy Switch</b> (intégré).</li>
<li>Mets une redirection depuis Gmail et préviens tes contacts de ta nouvelle adresse.</li>
<li>Installe l'application mobile, active la <b>double authentification</b> — et Proton Bridge si tu tiens à Thunderbird.</li>
</ol>
+25
View File
@@ -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.
+40
View File
@@ -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
<ol>
<li>
Rendez-vous sur <b>protonvpn.com</b> et connectez-vous avec votre compte Proton (ou créez-en
un).
</li>
<li>Installez l'application (Windows, macOS, Linux, Android, iOS).</li>
<li>
Utilisez « Quick Connect » ou choisissez un pays, puis activez le kill switch et NetShield
(blocage des pubs et traqueurs).
</li>
</ol>
+31
View File
@@ -0,0 +1,31 @@
---
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
Ton fournisseur d'accès voit chaque site que tu visites via le DNS. Quad9 chiffre ces requêtes, ne conserve pas ton adresse IP, et te protège de l'hameçonnage au passage. Un réglage « fais-le et oublie-le ».
## who
Tout le monde — surtout si tu veux un gain réel sans rien installer de nouveau.
## install
<ol>
<li>Sur Android : Réglages &gt; Réseau &gt; <b>DNS privé</b> &gt; saisis <b>dns.quad9.net</b>.</li>
<li>Sur iPhone/Mac : installe le profil DoH depuis quad9.net. Sur PC : configure le DNS-over-HTTPS dans le navigateur ou le système.</li>
</ol>
+27
View File
@@ -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é.
+39
View File
@@ -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{' '}
<b>auto-hébergeable</b>, la souveraineté jusque dans la barre de recherche.
## who
Tout le monde. SearXNG vise ceux qui veulent héberger leur propre moteur.
## install
<ol>
<li>
Dans les réglages de votre navigateur, changez le moteur par défaut pour DuckDuckGo, Brave
Search ou Startpage.
</li>
<li>
Pour SearXNG : utilisez une instance publique (annuaire sur searx.space) ou hébergez la vôtre
(voir section 10).
</li>
</ol>
+40
View File
@@ -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
<ol>
<li>
Installez depuis <b>getsession.org</b> (toutes plateformes).
</li>
<li>
« Create account » génère votre Session ID et une <b>phrase de récupération</b>.
</li>
<li>Notez la phrase de récupération sur papier : c'est la seule clé de votre compte.</li>
<li>Partagez votre Session ID (ou QR) à vos contacts.</li>
</ol>
+34
View File
@@ -0,0 +1,34 @@
---
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 tes textos, appels, visios et groupes. C'est le remplaçant direct de WhatsApp — aussi simple, sans Meta derrière.
## 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 gardée, et l'équipe a promis de quitter l'UE plutôt que d'installer un scan. Chaque proche que tu fais basculer, c'est une conversation de plus qui sort de la surveillance.
## who
Tout le monde, dès aujourd'hui. C'est le premier geste, et le plus utile : commence par ton groupe de famille. Seule contrainte : un numéro de téléphone — que tu peux cacher derrière un nom d'utilisateur.
## install
<ol>
<li>Installe depuis <b>signal.org</b> (App Store, Google Play, ou l'APK direct sur Android).</li>
<li>Confirme ton numéro par SMS, puis choisis un code PIN.</li>
<li>Réglages &gt; Confidentialité : crée un <b>nom d'utilisateur</b> pour cacher ton numéro, et active le <b>verrou d'inscription</b>.</li>
<li>Invite tes proches — le groupe famille d'abord. Sur ordinateur, lie l'application de bureau à ton téléphone.</li>
</ol>
+43
View File
@@ -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
<ol>
<li>
Installez depuis <b>simplex.chat</b> (F-Droid, Google Play, App Store ou APK).
</li>
<li>
Choisissez un nom d'affichage <b>local</b> (jamais votre vrai nom : il ne quitte pas votre
appareil).
</li>
<li>
Pour ajouter un contact, partagez un <b>lien d'invitation à usage unique</b> ou un QR code.
</li>
<li>Activez le verrou par code, et (avancé) configurez vos propres serveurs SMP.</li>
</ol>
+27
View File
@@ -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 <b>métadonnées</b> (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.
+36
View File
@@ -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{' '}
<b>aucune trace</b> à 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
<ol>
<li>
Téléchargez Tails sur <b>tails.net</b> et suivez l'assistant pour créer la clé USB.
</li>
<li>Redémarrez l'ordinateur sur cette clé. Utilisez-le, puis éteignez : tout disparaît.</li>
</ol>
+43
View File
@@ -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), <b>Tailscale</b> crée un tunnel
WireGuard entre vos seuls appareils autorisés : votre serveur n'a aucune surface d'attaque
publique. <b>Headscale</b> est la version que vous hébergez vous-même, pour ne dépendre d'aucun
tiers. <b>WireGuard</b> 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
<ol>
<li>
Installez Tailscale sur votre serveur et sur votre téléphone/PC, connectez-les au même compte.
</li>
<li>
Vos appareils se voient aussitôt sur un réseau privé ; accédez à votre serveur par son adresse
Tailscale.
</li>
<li>Pour zéro dépendance, remplacez le serveur de coordination par Headscale, auto-hébergé.</li>
</ol>
+45
View File
@@ -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
<ol>
<li>
Téléchargez le Tor Browser sur <b>torproject.org</b> (Windows, macOS, Linux, Android).
</li>
<li>
Lancez-le et cliquez « Se connecter ». Si Tor est bloqué dans votre pays, activez un « pont »
(bridge).
</li>
<li>
Naviguez sans maximiser la fenêtre, sans installer d'extension, et{' '}
<b>sans vous connecter à vos vrais comptes</b>.
</li>
</ol>
+40
View File
@@ -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 <b>l'objet et une partie des métadonnées</b>, 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
<ol>
<li>
Créez un compte sur <b>tuta.com</b> et installez l'application (disponible sur F-Droid).
</li>
<li>
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.
</li>
<li>Activez la double authentification.</li>
</ol>
+31
View File
@@ -0,0 +1,31 @@
---
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 qui bloque publicités, traqueurs et scripts espions. Le geste le plus rentable de tout ce guide : dix secondes, effet immédiat.
## why
La publicité en ligne est le principal vecteur de surveillance et de profilage. La bloquer, c'est couper l'essentiel du pistage — et au passage tes pages chargent plus vite et ton forfait data respire.
## who
Tout le monde, sur Firefox comme sur les navigateurs Chromium. Si tu ne fais qu'un seul geste aujourd'hui, fais celui-là.
## install
<ol>
<li>Ouvre le magasin d'extensions de ton navigateur et cherche « uBlock Origin » (l'original, par Raymond Hill).</li>
<li>Installe-le. Il travaille aussitôt, sans aucun réglage.</li>
</ol>
+38
View File
@@ -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
<b>Jitsi Meet</b> se lance dans le navigateur, sans compte, et peut être auto-hébergé (c'est
aussi la base de Brave Talk). <b>Element Call</b> est chiffré de bout en bout et fédéré via
Matrix. Pour un appel simple et totalement chiffré, les appels <b>Signal</b> 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
<ol>
<li>
Jitsi : allez sur meet.jit.si, créez un nom de salon, partagez le lien. Rien à installer.
</li>
<li>Element Call : depuis l'application Element (Matrix), lancez un appel dans un salon.</li>
</ol>
+44
View File
@@ -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
<ol>
<li>
Achetez un nom de domaine (idéalement chez un registraire respectueux de la vie privée).
</li>
<li>
Choisissez un générateur de site statique (Astro, Hugo) et hébergez le résultat, ou installez
WordPress/Ghost sur votre serveur.
</li>
<li>
Renvoyez-y systématiquement depuis vos réseaux : votre audience vous retrouve même si un
compte tombe.
</li>
</ol>
+39
View File
@@ -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. <b>Nitrokey</b> 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 <b>deux</b> (une
principale, une de secours).
## install
<ol>
<li>Procurez-vous deux clés sur yubico.com ou nitrokey.com.</li>
<li>
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.
</li>
<li>Gardez la clé de secours dans un lieu sûr, séparé.</li>
</ol>
+43
View File
@@ -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. <b>YunoHost</b> gère même
votre propre e-mail et une authentification unique. <b>Umbrel</b> offre un magasin de plus de
300 applications, très prisé des amateurs de vie privée et de Bitcoin. <b>Start9</b> 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
<ol>
<li>
Récupérez un vieil ordinateur, un Raspberry Pi, ou louez un petit serveur (VPS) dans un pays
protecteur.
</li>
<li>Installez YunoHost, Umbrel ou Start9 en suivant leur guide officiel.</li>
<li>Depuis le catalogue, installez Nextcloud, un serveur Matrix, une galerie photo, etc.</li>
<li>
Accédez-y en privé avec Tailscale (section VPN) plutôt que d'exposer le serveur au public.
</li>
</ol>
+197
View File
@@ -0,0 +1,197 @@
{
"bands": [
{
"id": "exposed",
"min": 0,
"max": 24,
"title": "Exposé",
"summary": "Ta pile est encore très dépendante des grandes plateformes. Les premières étapes sont simples et à fort impact."
},
{
"id": "awakening",
"min": 25,
"max": 49,
"title": "Éveil",
"summary": "Tu as commencé à diversifier tes outils. Quelques changements bien choisis feraient une vraie différence."
},
{
"id": "resistant",
"min": 50,
"max": 74,
"title": "Résistant",
"summary": "Ta configuration est déjà sérieuse. Tu as une vraie base de résistance à la censure et à la surveillance."
},
{
"id": "hardened",
"min": 75,
"max": 89,
"title": "Durci",
"summary": "Tu as une pile plus dure et un socle pseudonyme solide. L'objectif devient la discipline et la séparation des identités."
},
{
"id": "sovereign",
"min": 90,
"max": 100,
"title": "Souverain",
"summary": "Ta posture est déjà très robuste. Tu es proche d'une forte souveraineté numérique, avec des choix techniques exigeants."
}
],
"questions": [
{
"id": "messaging",
"category": "messagerie",
"label": "Messagerie",
"prompt": "Comment échanges-tu le plus souvent avec tes proches ?",
"rec": "Bascule tes conversations sensibles vers Signal, SimpleX ou Session.",
"options": [
{ "points": 0, "label": "WhatsApp / Messenger / SMS", "hint": "Les métadonnées et les plateformes centralisées restent le point faible." },
{ "points": 2, "label": "Telegram pour la plupart des conversations", "hint": "Mieux que les grands réseaux, mais la confidentialité reste limitée." },
{ "points": 6, "label": "Signal ou Molly pour la plupart des échanges", "hint": "C'est déjà une bonne base pour la résistance à la censure." },
{ "points": 10, "label": "SimpleX / Session / Matrix pour les usages sensibles", "hint": "Moins de dépendance au numéro de téléphone et à la plateforme." }
]
},
{
"id": "email",
"category": "email",
"label": "E-mail",
"prompt": "Quel service utilises-tu le plus pour tes e-mails ?",
"rec": "Utilise Proton Mail ou Tuta, et ajoute PGP pour les contacts qui comptent.",
"options": [
{ "points": 0, "label": "Gmail / Outlook / iCloud Mail", "hint": "Le stockage et l'accès au contenu restent très centralisés." },
{ "points": 5, "label": "Proton Mail ou Tuta au quotidien", "hint": "Déjà une option plus sérieuse pour la confidentialité." },
{ "points": 7, "label": "Proton / Tuta + PGP pour certains contacts", "hint": "Un bon niveau de résistance si tu es régulier·ère." },
{ "points": 10, "label": "PGP / OpenPGP régulier et attention aux métadonnées", "hint": "Une posture beaucoup plus robuste." }
]
},
{
"id": "browser",
"category": "navigateur",
"label": "Navigateur & recherche",
"prompt": "Quels navigateur et moteur de recherche utilises-tu le plus ?",
"rec": "Quitte Chrome + Google pour un navigateur et un moteur respectueux de la vie privée.",
"options": [
{ "points": 0, "label": "Chrome + Google", "hint": "Confortable, mais fortement traçable." },
{ "points": 5, "label": "Firefox ou Brave avec un moteur privé", "hint": "Un vrai pas en avant pour la vie privée." },
{ "points": 7, "label": "Brave / Firefox + DuckDuckGo / Startpage / SearXNG", "hint": "C'est déjà une bonne base quotidienne." },
{ "points": 10, "label": "Mullvad Browser / LibreWolf / Tor Browser selon le contexte", "hint": "Une posture plus sérieuse face à la surveillance." }
]
},
{
"id": "dns",
"category": "dns",
"label": "DNS",
"prompt": "Comment résous-tu les noms de domaine ?",
"rec": "Passe à un DNS chiffré avec un résolveur minimaliste et respectueux.",
"options": [
{ "points": 0, "label": "Le DNS de ton FAI ou de ta box", "hint": "Le plus simple, mais souvent le moins privé." },
{ "points": 4, "label": "Un DNS chiffré chez un fournisseur grand public", "hint": "Un bon premier pas." },
{ "points": 7, "label": "Quad9 / Mullvad DNS / NextDNS", "hint": "Une vraie amélioration pour limiter la traçabilité." },
{ "points": 10, "label": "DNS chiffré + stratégie anti-tracking assumée", "hint": "Une posture plus consciente et plus robuste." }
]
},
{
"id": "vpn",
"category": "vpn",
"label": "VPN",
"prompt": "Quel type de VPN utilises-tu ?",
"rec": "Choisis un VPN honnête sans journalisation : Mullvad, Proton VPN ou IVPN.",
"options": [
{ "points": 0, "label": "Pas de VPN", "hint": "Le cas le plus exposé pour la traçabilité réseau." },
{ "points": 2, "label": "Un VPN gratuit ou douteux", "hint": "Parfois pire que rien si le fournisseur est opaque." },
{ "points": 7, "label": "Un VPN honnête, no-log, avec une vraie politique", "hint": "Une vraie amélioration pour la confidentialité." },
{ "points": 10, "label": "Mullvad / Proton VPN / IVPN au quotidien", "hint": "Une posture plus sérieuse et cohérente." }
]
},
{
"id": "passwords",
"category": "motsdepasse",
"label": "Mots de passe",
"prompt": "Comment gères-tu tes mots de passe ?",
"rec": "Adopte un gestionnaire de mots de passe avec des mots de passe uniques et longs.",
"options": [
{ "points": 0, "label": "Mots de passe réutilisés ou stockés dans le navigateur", "hint": "Risque élevé en cas de fuite ou de compromission." },
{ "points": 5, "label": "Un gestionnaire de mots de passe basique", "hint": "Déjà un très bon pas en avant." },
{ "points": 7, "label": "Bitwarden / KeePassXC / Proton Pass", "hint": "Une posture saine, simple à maintenir." },
{ "points": 10, "label": "Gestionnaire + mots de passe uniques et longs", "hint": "Le niveau le plus solide pour limiter les dégâts." }
]
},
{
"id": "twofa",
"category": "deuxfa",
"label": "Double authentification",
"prompt": "Quel niveau de double authentification utilises-tu ?",
"rec": "Passe la 2FA sur une application, ou mieux, une clé matérielle.",
"options": [
{ "points": 0, "label": "Pas de 2FA", "hint": "Le plus faible niveau de protection des comptes." },
{ "points": 2, "label": "SMS ou appel", "hint": "Mieux que rien, mais très fragile." },
{ "points": 6, "label": "TOTP sur une application (Aegis / Ente Auth)", "hint": "Une bonne protection au quotidien." },
{ "points": 10, "label": "Clé matérielle ou 2FA résistante au phishing", "hint": "Le meilleur niveau de résistance pratique." }
]
},
{
"id": "storage",
"category": "stockage",
"label": "Stockage",
"prompt": "Où stockes-tu tes fichiers sensibles ?",
"rec": "Déplace tes fichiers sensibles vers Proton Drive, Cryptomator ou un stockage local chiffré.",
"options": [
{ "points": 0, "label": "Google Drive / iCloud / OneDrive", "hint": "Très pratique, mais centralisé." },
{ "points": 5, "label": "Proton Drive ou un stockage chiffré de base", "hint": "Une vraie amélioration." },
{ "points": 7, "label": "Cryptomator ou un stockage local chiffré", "hint": "Une posture plus robuste." },
{ "points": 10, "label": "Stockage local + auto-hébergement / chiffrement avancé", "hint": "Le niveau le plus sérieux pour la souveraineté." }
]
},
{
"id": "social",
"category": "social",
"label": "Réseaux sociaux",
"prompt": "Où passes-tu l'essentiel de ta vie sociale en ligne ?",
"rec": "Ancre ta présence sur des réseaux décentralisés et réduis la dépendance aux plateformes.",
"options": [
{ "points": 0, "label": "Meta / X / grands réseaux uniquement", "hint": "Le plus simple, mais le plus exposé aux pressions." },
{ "points": 5, "label": "Mastodon / Matrix / Nostr de façon ponctuelle", "hint": "Une diversification utile." },
{ "points": 7, "label": "Un mix de réseaux décentralisés et de contacts directs", "hint": "Une posture plus indépendante." },
{ "points": 10, "label": "Réseaux alternatifs et faible dépendance aux plateformes", "hint": "C'est la vraie logique de résistance." }
]
},
{
"id": "os",
"category": "os",
"label": "Système d'exploitation",
"prompt": "Quel système d'exploitation utilises-tu le plus ?",
"rec": "Évolue vers GrapheneOS sur mobile et Linux sur l'ordinateur.",
"options": [
{ "points": 0, "label": "Android / iOS / Windows standard", "hint": "Très pratique, mais beaucoup moins respectueux de la vie privée." },
{ "points": 5, "label": "Un Android plus privé / Linux / macOS", "hint": "Déjà un bon progrès." },
{ "points": 8, "label": "GrapheneOS / Linux / BSD", "hint": "Une base plus sérieuse." },
{ "points": 10, "label": "GrapheneOS + Linux + discipline d'installation", "hint": "Déjà une posture très forte." }
]
},
{
"id": "tor",
"category": "tor",
"label": "Tor & anonymat",
"prompt": "Dans quelle mesure utilises-tu Tor / Tails / l'anonymat ?",
"rec": "Prends l'habitude de Tor (et de Tails au besoin) pour tes tâches les plus sensibles.",
"options": [
{ "points": 0, "label": "Jamais ou presque jamais", "hint": "Très exposé si la menace devient sérieuse." },
{ "points": 3, "label": "De temps en temps, sans discipline", "hint": "Un premier pas, mais encore fragile." },
{ "points": 6, "label": "Tor pour certaines tâches sensibles", "hint": "Déjà plus sérieux." },
{ "points": 10, "label": "Tor + Tails avec une discipline forte", "hint": "Le niveau le plus adapté à une menace élevée." }
]
},
{
"id": "money",
"category": "argent",
"label": "Argent",
"prompt": "Comment gères-tu ton argent numérique ?",
"rec": "Ajoute des moyens de paiement plus privés et sépare tes identités quand c'est possible.",
"options": [
{ "points": 0, "label": "Cartes bancaires et comptes standards uniquement", "hint": "La traçabilité est forte." },
{ "points": 4, "label": "Quelques outils de paiement plus privés", "hint": "Un premier effort de séparation." },
{ "points": 6, "label": "Un mix de comptes privés et de crypto limitée", "hint": "Déjà plus réfléchi." },
{ "points": 10, "label": "Pratiques de souveraineté financière plus fortes", "hint": "Une posture beaucoup plus sérieuse." }
]
}
]
}
+66
View File
@@ -0,0 +1,66 @@
# Piège Apache (o2switch) : « Index of /… » au lieu des pages
Symptôme : le site est déployé sur un hébergement mutualisé Apache (o2switch),
la page d'accueil s'affiche, mais **cliquer sur un lien interne affiche
« Index of /chatcontrol/quiz »** (un listing de dossier) au lieu de la page.
## Cause : conflit fichier / dossier
SvelteKit (adapter-static, `trailingSlash` par défaut = `'never'`) génère :
```
build/
├── quiz.html ← la page
├── quiz/ ← dossier créé pour __data.json
│ └── __data.json
├── outils.html
├── outils/
│ └── …
```
Quand le navigateur demande `/chatcontrol/quiz`, Apache voit qu'un **dossier**
`quiz/` existe et redirige (301) vers `/chatcontrol/quiz/`. Comme ce dossier
ne contient pas de `index.html`, Apache sert son **autoindex** (le listing).
Le fichier `quiz.html`, lui, n'est jamais trouvé.
Apache donne toujours la priorité au dossier quand les deux existent —
ce problème revient à chaque site statique posé sur du mutualisé Apache.
## Correctif (déjà appliqué dans ce dépôt)
`frontend/src/routes/+layout.ts` :
```ts
export const trailingSlash = 'always'
```
Chaque page est alors générée **dans** son dossier :
```
build/
├── quiz/
│ ├── index.html ← la page, servie par Apache
│ └── __data.json
├── outils/
│ ├── index.html
│ └── aegis/
│ └── index.html
```
Plus de conflit possible : le dossier existe, il contient un `index.html`,
Apache le sert.
## Checklist quand ça arrive
1. `export const trailingSlash = 'always'` dans le `+layout.ts` racine.
2. `pnpm build` → vérifier que `build/quiz/index.html` existe
(et non `build/quiz.html`).
3. **Vider le dossier distant avant de re-uploader** : les anciens
`quiz.html` / `outils.html` et les vieux assets `_app/immutable/` hashés
doivent partir, sinon ils traînent indéfiniment.
4. Tester `curl -s https://<site>/<page>/` → du HTML, pas « Index of ».
Note : les liens internes relatifs (`./outils`) provoquent une petite
redirection 301 vers l'URL avec `/` final. Normal, invisible pour
l'utilisateur. L'éviter exigerait `paths.relative: false`, au prix de la
consultation locale des fichiers du build.
+89
View File
@@ -0,0 +1,89 @@
# Guide de rédaction — fiches d'outils FR/créole
## Format d'une fiche (`content/outils/<slug>.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
<ol><li>Étapes concrètes, numérotées.</li></ol>
```
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).
### Fiches de référence pour le ton
Six fiches sont réécrites au registre OKI et servent de modèle : `signal`,
`protonmail`, `brave`, `bitwarden`, `ublock`, `quad9`. Avant d'écrire ou de
réviser une fiche, relis-en deux. Ce qui change par rapport au ton d'origine :
tutoiement systématique, le geste avant la technique (« dix secondes, effet
immédiat »), l'enjeu politique dit sans détour (« ta correspondance — pas une
matière première publicitaire »), et un ancrage collectif (« commence par ton
groupe de famille »).
## 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.
- Chaque fiche peut avoir sa **jumelle créole** : `content/outils/gcf/<slug>.md`
(frontmatter minimal : `name`, `slug`, `lang: "gcf"` ; mêmes sections
`## what/why/who/install`). Elle s'affiche automatiquement quand la personne
choisit Kréyòl, avec un bandeau « tradiksyon an chantyé » ; sans jumelle,
la version française s'affiche avec un bandeau « póko tradui ». Six jumelles
pilotes existent (mêmes slugs que les fiches de référence ci-dessus) —
**premier jet non relu**, à valider avant d'en faire des modèles.
- 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)
+206
View File
@@ -0,0 +1,206 @@
# Déployer lage-chat-control sur un VPS
Le site est **100 % statique** (ADR-001) : le déploiement consiste à poser le
contenu du dossier `frontend/build/` derrière un serveur web. Pas de base de
données, pas de service Node à maintenir, pas de tâche cron. Un miroir du site
= une copie du dossier.
## 0. Prérequis
- Un VPS (1 vCPU / 512 Mo suffisent très largement) sous Debian/Ubuntu
- Un nom de domaine pointant sur le VPS (enregistrement A/AAAA)
- En local : Node ≥ 22.12 et pnpm (le build se fait **sur ta machine**,
le VPS n'a besoin ni de Node ni de pnpm)
## 1. Construire le site
```sh
git clone https://labola.o-k-i.net/cyber-mawonaj/lage-chat-control.git
cd lage-chat-control/frontend
pnpm install
pnpm build # produit frontend/build/ (~60 pages statiques)
```
> **Racine de domaine** : le site est servi à la racine de
> `https://chatcontrol.o-k-i.net/` (YunoHost, voir §5) — aucun `paths.base`.
> Pour un déploiement sous un sous-chemin, ajoute `paths: { base: '/prefixe' }`
> dans `frontend/svelte.config.js` avant `pnpm build` et adapte les règles
> `try_files` côté serveur (ex. nginx : `location /prefixe/ { … }`).
> **Mutualisé Apache (o2switch)** : si les liens internes affichent
> « Index of /… » (listing de dossier) au lieu des pages, c'est le conflit
> fichier/dossier classique — voir [APACHE_AUTOINDEX.md](./APACHE_AUTOINDEX.md).
> **CSP et hydratation (critique)** : SvelteKit démarre côté navigateur via
> un script *inline* (qui embarque les données de la page). Un en-tête HTTP
> `Content-Security-Policy` contenant `script-src 'self'` — comme celui posé
> par le compte o2switch — **bloque ce script et tue toute l'interactivité**
> (recherche, filtres, sélecteur de langue), sans message visible. Chaque page
> prérendue porte donc sa propre `<meta>` CSP avec l'empreinte (`sha256-…`) de
> son script inline (`csp.mode: 'hash'` dans `frontend/svelte.config.js`).
> Règle côté serveur : **ne jamais fixer `script-src` dans l'en-tête HTTP** —
> le retirer de la config cPanel/`.htaccess`/reverse proxy (le reste de la CSP
> peut y rester). Si l'hébergeur impose un `script-src` en en-tête, le régler
> sur `script-src 'self' 'unsafe-inline'` (plus faible, mais fonctionnel).
## 2. Option A — Caddy (recommandé : TLS automatique)
```sh
# sur le VPS
sudo apt install caddy
sudo mkdir -p /srv/lage-chat-control
```
`/etc/caddy/Caddyfile` :
```caddy
chatcontrol.o-k-i.net {
root * /srv/lage-chat-control
file_server
encode zstd gzip
# SvelteKit adapter-static : pages pré-générées + fallback
try_files {path} {path}.html {path}/index.html /404.html
header {
# le service worker doit pouvoir se mettre à jour
Cache-Control "no-cache" /service-worker.js
# assets immuables (noms hachés)
Cache-Control "public, max-age=31536000, immutable" /_app/immutable/*
# en-têtes de sécurité
Strict-Transport-Security "max-age=31536000; includeSubDomains"
X-Content-Type-Options "nosniff"
Referrer-Policy "no-referrer"
Permissions-Policy "camera=(), microphone=(), geolocation=()"
# CSP stricte : le site n'a aucune dépendance externe.
# NE PAS remettre script-src ici : il est porté par la <meta> CSP de
# chaque page (empreinte du script inline de démarrage, différente
# d'une page à l'autre — inexprimable dans un en-tête statique).
Content-Security-Policy "default-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-ancestors 'none'; base-uri 'self'"
}
}
```
Le TLS (Let's Encrypt) est automatique, rien d'autre à faire.
## 3. Option B — nginx
```sh
sudo apt install nginx certbot python3-certbot-nginx
```
`/etc/nginx/sites-available/lage-chat-control` :
```nginx
server {
server_name chatcontrol.o-k-i.net;
root /srv/lage-chat-control;
index index.html;
# pages pré-générées : /outils/ → outils/index.html, fallback 404
location / {
try_files $uri $uri/ $uri.html $uri/index.html /404.html;
}
location /_app/immutable/ {
add_header Cache-Control "public, max-age=31536000, immutable";
}
location = /service-worker.js {
add_header Cache-Control "no-cache";
}
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "no-referrer" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
add_header Content-Security-Policy "default-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-ancestors 'none'; base-uri 'self'" always;
# NB : pas de script-src ici — voir le commentaire CSP de l'option A.
gzip on;
gzip_types text/html text/css application/javascript application/json image/svg+xml;
}
```
```sh
sudo ln -s /etc/nginx/sites-available/lage-chat-control /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
sudo certbot --nginx -d chatcontrol.o-k-i.net # TLS
```
## 4. Publier (et republier)
```sh
# depuis ta machine, après chaque `pnpm build`
rsync -avz --delete frontend/build/ vps:/srv/lage-chat-control/
```
C'est l'intégralité du « pipeline de déploiement ». Pour automatiser :
un runner Forgejo Actions sur labola peut exécuter build + rsync à chaque
push sur `main` (à ajouter quand le besoin se présente, pas avant).
## 5. Option C — YunoHost (cible de production)
Le site tourne en production sur **`chatcontrol.o-k-i.net`** via l'app
**`my_webapp`** (site statique) :
1. Installe `my_webapp` depuis le catalogue YunoHost avec le domaine
`chatcontrol.o-k-i.net`, le chemin `/`, un accès **public** (visiteurs
compris), sans PHP ni base de données.
2. Copie le contenu de `frontend/build/` dans le `www/` de l'app, avec
l'utilisateur SFTP dédié créé par l'app (son nom exact figure dans la
page de l'app, section « Accès SFTP ») :
```sh
rsync -avz --delete frontend/build/ my_webapp@chatcontrol.o-k-i.net:www/
```
3. C'est tout : la conf nginx générée sert déjà `<route>/index.html` (avec
redirection automatique vers le `/` final). YunoHost gère TLS, HSTS et
le renouvellement des certificats.
Durcissements optionnels (panneau de config de l'app, ou
`/etc/nginx/conf.d/chatcontrol.o-k-i.net.d/my_webapp.conf`) :
- `error_page 404 /404.html;` — notre page 404 plutôt que celle de nginx ;
- cache long sur les assets immuables :
`location /_app/immutable/ { add_header Cache-Control "public, max-age=31536000, immutable"; }`
- revalidation du service worker :
`location = /service-worker.js { add_header Cache-Control "no-cache"; }`
Ne pose **pas** d'en-tête CSP globale côté YunoHost : la `<meta>` CSP de
chaque page suffit (voir l'encadré CSP en §1). À noter : l'overlay du portail
YunoHost, injecté uniquement pour les visiteurs déjà connectés au SSO, est
bloqué par cette CSP — cosmétique, sans effet pour les visiteurs anonymes.
## 6. Option D — Docker
Le dépôt n'embarque volontairement aucun `Dockerfile` (un site 100 % statique
n'en a pas besoin — c'est un dossier de fichiers). Si le reste de l'infra OKI
tourne déjà en conteneurs, ce `Dockerfile` minimal (nginx durci, sans build
step) suffit :
```dockerfile
FROM nginx:alpine
COPY frontend/build/ /usr/share/nginx/html/
COPY docs/nginx.conf.example /etc/nginx/conf.d/default.conf
```
Reprends le bloc `server {}` de l'option B ci-dessus dans
`docs/nginx.conf.example` (à créer), en retirant `server_name`/`certbot`
(le TLS se termine en amont, sur un reverse proxy ou le load balancer).
Sinon, les options A/B sont plus simples et suffisent dans l'immense
majorité des cas.
## 7. Vérifier après mise en ligne
- [ ] `curl -s https://chatcontrol.o-k-i.net/outils/ | grep -o 'http-equiv="Content-Security-Policy"'`
→ la `<meta>` CSP par page est présente (elle porte `script-src` + empreinte)
- [ ] `curl -sI https://chatcontrol.o-k-i.net/outils/ | grep -i content-security`
→ l'en-tête HTTP éventuelle ne contient **pas** de `script-src` bloquant
- [ ] Ouvrir `/outils`, taper « signal » dans la recherche → la liste
se filtre sans rechargement (preuve que l'hydratation fonctionne) ;
cliquer une catégorie → l'URL gagne `?cat=…` et la liste se filtre
- [ ] DevTools > Console : aucune erreur CSP (« violates the following Content
Security Policy directive ») au chargement
- [ ] DevTools > Application > Service worker : enregistré ; passer hors-ligne
et recharger une fiche déjà visitée → elle s'affiche
- [ ] observatory.mozilla.org : note A/A+ attendue avec les en-têtes ci-dessus
- [ ] Aucune requête sortante vers un domaine tiers (onglet Réseau)
+72
View File
@@ -0,0 +1,72 @@
# Design system OKI — Lagé Chat Control
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 `<style>` Svelte (scopé à la compilation).
## Couleurs
En `oklch()` : la luminance perceptuelle rend les contrastes WCAG calculables
(méthode modus-vivendi, doctrine §4.3). `light-dark()` + `color-scheme` gèrent
le mode sombre sans duplication.
| Token | Rôle | Note contraste |
|---|---|---|
| `--oki-ink` / `--oki-surface` | texte / fond | ≥ 7:1 (AAA) dans les deux modes |
| `--oki-ink-2` | texte secondaire | ≥ 4.5:1 (AA) |
| `--oki-jaune` `--oki-vert` `--oki-rouge` `--oki-noir` | identité panafricaine | décoratif — jamais porteur d'information seul |
| `--accent` / `--accent-ink` | accent actif / accent texte | `--accent-ink` ≥ 4.5:1 sur `--oki-surface` |
| `--diff-*` / `--diff-*-ink` | difficulté (fond / texte) | les variantes `-ink` sont AA sur `--oki-surface` |
**Règle absolue** : la difficulté n'est jamais codée par la couleur seule —
`DifficultyBadge` = forme (●/◆/▲) + texte + couleur (doctrine §1.1, ~8 % de
déficience de vision des couleurs).
## Typographie
- Corps : **Atkinson Hyperlegible** 400 (SIL OFL, conçue pour la lisibilité).
- Display (titres, marque) : **Fraunces** 700 (SIL OFL).
- 2 fichiers WOFF2 auto-hébergés (`static/fonts/`), ~35 Ko au total,
`font-display: swap` — jamais de Google Fonts (doctrine §5.3).
- Échelle modulaire ratio 1.25, fluide par `clamp()` : `--text-0``--text-5`.
- `--measure: 65ch` sur `p, li` et `.prose` (doctrine §2.2 : 4575 caractères).
- Tout en `rem` : la taille utilisateur est respectée.
## Interaction
- `--tap-target: 48px` : tout élément interactif (boutons, liens de nav,
filtres, switcher) a une hauteur minimale de 48 px (Fitts, doctrine §1.3).
- `:focus-visible` global : anneau 3 px `--accent`, offset 2 px.
- Durées `--duration-1/2` + `--ease-out` ; annulées par `prefers-reduced-motion`.
## Composants
| Composant | Rôle | Principe doctrinal |
|---|---|---|
| `DifficultyBadge` | niveau (forme + texte + couleur) | §1.1 — jamais couleur seule |
| `ToolCard` | fiche condensée, vrai `<a>` couvrant | §4.3 — pas de div cliquable |
| `CategoryNav` | 4 catégories visibles + `<details>` | §1.3 Hick-Hyman, divulgation progressive, sans JS |
| `SearchBar` | recherche instantanée, raccourci `/` | §2.2 — le clavier est roi |
| `LanguageSwitcher` | FR / Kréyòl, persisté localStorage | §4.2 — bilinguisme |
| `EndMarker` | « Ou pa manké ayen » en fin de liste | §1.5, §3 — point d'arrêt explicite |
## Test des 50 ms (doctrine §1.1)
L'identité OKI est reconnaissable avant toute lecture : bandes panafricaines
vert/jaune/rouge dans la marque d'en-tête, soulignés et bordures jaune OKI,
titres Fraunces, créole visible dès le héros (« Chwazi zouti ou »).
+49
View File
@@ -0,0 +1,49 @@
# Lagé Chat Control × OKI — frontend
Annuaire souverain d'outils libres. SvelteKit 2 + Svelte 5, site 100 %
prérendu, zéro CDN tiers, zéro tracker. Voir [`ADR.md`](../ADR.md) pour la
décision d'architecture et `../docs/` pour le design system et le guide
de contenu.
## Prérequis
Node ≥ 22.12, pnpm.
## Commandes
```sh
pnpm install
pnpm dev # serveur de développement
pnpm build # build statique dans build/ (prérend toutes les pages)
pnpm preview # sert le build localement
pnpm check # svelte-check (types + a11y)
pnpm audit:a11y # audit axe-core sur le build servi en local
```
## Déploiement
`build/` est un site statique : n'importe quel serveur de fichiers convient.
Voir [`docs/DEPLOYMENT.md`](../docs/DEPLOYMENT.md) pour la mise en production
sur un VPS (Caddy, nginx, YunoHost, Docker). Aucune dépendance cloud, aucun
service à maintenir — un miroir = une copie du dossier.
## Arborescence
```
src/styles/ tokens, reset, base, layouts (cascade @layer)
src/lib/i18n/ fr.json + gcf.json typés, store de langue (runes)
src/lib/components/ ToolCard, DifficultyBadge, CategoryNav, SearchBar,
LanguageSwitcher, EndMarker, ShortcutsDialog,
OfflineBadge, AdoptButton
src/lib/content/ chargeurs build-time des fichiers ../content/
(tools.server.ts, quiz.server.ts)
src/routes/ / (landing), /outils (annuaire filtrable),
/outils/[slug], /quiz, /contribuer
src/service-worker.js précache + offline-first
static/fonts/ 2 WOFF2 auto-hébergés (Atkinson Hyperlegible, Fraunces)
scripts/audit-a11y.mjs audit axe-core (pnpm audit:a11y)
```
Le contenu éditorial (fiches d'outils, quiz, catégories) vit dans
[`../content/`](../content/) à la racine du dépôt, pas dans `frontend/`
voir [`docs/CONTENT_GUIDE.md`](../docs/CONTENT_GUIDE.md) pour l'éditer.
+34
View File
@@ -0,0 +1,34 @@
{
"name": "lage-chat-control",
"private": true,
"version": "4.0.0-alpha.1",
"type": "module",
"description": "Lagé Chat Control × OKI — annuaire souverain d'outils libres. SvelteKit statique, zéro tracker, zéro CDN.",
"license": "MIT",
"scripts": {
"dev": "vite dev",
"prebuild": "node scripts/gen-sitemap.mjs",
"build": "vite build",
"preview": "vite preview",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"audit:a11y": "node scripts/audit-a11y.mjs"
},
"engines": {
"node": ">=22.12"
},
"devDependencies": {
"@axe-core/playwright": "^4.12.1",
"@fontsource/atkinson-hyperlegible": "^5.2.8",
"@fontsource/fraunces": "^5.2.9",
"@playwright/test": "1.61.1",
"@sveltejs/adapter-static": "^3.0.0",
"@sveltejs/kit": "^2.0.0",
"@sveltejs/vite-plugin-svelte": "^6.0.0",
"@types/node": "^26.1.1",
"marked": "^16.0.0",
"svelte": "^5.0.0",
"svelte-check": "^4.0.0",
"typescript": "^5.6.0",
"vite": "^7.0.0"
}
}
+1133
View File
File diff suppressed because it is too large Load Diff
+4
View File
@@ -0,0 +1,4 @@
onlyBuiltDependencies:
- esbuild
allowBuilds:
esbuild: true
+24
View File
@@ -0,0 +1,24 @@
// Audit axe-core des pages clés servies sur localhost:4173
import { chromium } from '@playwright/test'
import { AxeBuilder } from '@axe-core/playwright'
const PAGES = ['/', '/outils', '/outils/signal', '/outils/tor', '/contribuer', '/quiz']
const browser = await chromium.launch()
const context = await browser.newContext()
const page = await context.newPage()
let total = 0
for (const path of PAGES) {
await page.goto(`http://localhost:4173${path}`, { waitUntil: 'networkidle' })
const results = await new AxeBuilder({ page }).withTags(['wcag2a', 'wcag2aa', 'wcag22aa']).analyze()
total += results.violations.length
console.log(`\n═══ ${path}${results.violations.length} violation(s)`)
for (const v of results.violations) {
console.log(` [${v.impact}] ${v.id}: ${v.help}`)
for (const n of v.nodes.slice(0, 3)) console.log(`${n.target.join(' ')}`)
}
}
await browser.close()
console.log(`\nTOTAL: ${total} violation(s)`)
process.exit(total > 0 ? 1 : 0)
+47
View File
@@ -0,0 +1,47 @@
// Génère static/sitemap.xml au build (script `prebuild`) — remplace le
// sitemap qu'apportait @astrojs/sitemap à l'ère Astro. Zéro dépendance :
// les slugs sont relus depuis les frontmatters de content/outils/.
import { readdirSync, readFileSync, writeFileSync } from 'node:fs'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
const SITE_URL = 'https://chatcontrol.o-k-i.net'
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
const OUTILS = path.resolve(ROOT, '..', 'content', 'outils')
// les valeurs du frontmatter sont encodées en JSON (cf. tools.server.ts)
function slugOf(file) {
const raw = readFileSync(path.join(OUTILS, file), 'utf8')
const m = raw.match(/^slug:\s*("(?:[^"\\]|\\.)*")/m)
return m ? JSON.parse(m[1]) : file.replace(/\.md$/, '')
}
const slugs = readdirSync(OUTILS)
.filter((f) => f.endsWith('.md'))
.map(slugOf)
.sort((a, b) => a.localeCompare(b, 'fr'))
const lastmod = new Date().toISOString().slice(0, 10)
const entry = (loc, priority) => ` <url>
<loc>${SITE_URL}${loc}</loc>
<lastmod>${lastmod}</lastmod>
<priority>${priority}</priority>
</url>`
const urls = [
entry('/', '1.0'),
entry('/outils/', '0.8'),
...slugs.map((slug) => entry(`/outils/${slug}/`, '0.6')),
entry('/quiz/', '0.5'),
entry('/contribuer/', '0.5')
]
const xml = `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${urls.join('\n')}
</urlset>
`
writeFileSync(path.join(ROOT, 'static', 'sitemap.xml'), xml)
console.log(`sitemap.xml : ${urls.length} URLs (lastmod ${lastmod})`)
+14
View File
@@ -0,0 +1,14 @@
/*
* app.css — ordre de cascade explicite et auditable (doctrine §5.2).
* Chaque couche est déclarée AVANT tout import : la position dans la
* cascade est ici, pas dans l'ordre des fichiers.
*/
@layer reset, tokens, base, layouts, components, utilities;
/* @font-face n'est pas concerné par les couches */
@import './styles/fonts.css';
@import './styles/reset.css' layer(reset);
@import './styles/tokens.css' layer(tokens);
@import './styles/base.css' layer(base);
@import './styles/layouts.css' layer(layouts);
+12
View File
@@ -0,0 +1,12 @@
// See https://svelte.dev/docs/kit/types#app.d.ts
declare global {
namespace App {
// interface Error {}
// interface Locals {}
// interface PageData {}
// interface PageState {}
// interface Platform {}
}
}
export {}
+15
View File
@@ -0,0 +1,15 @@
<!doctype html>
<html lang="fr">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="color-scheme" content="light dark" />
<meta name="theme-color" content="#17181f" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<link rel="manifest" href="%sveltekit.assets%/site.webmanifest" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>
+39
View File
@@ -0,0 +1,39 @@
import { browser } from '$app/environment'
/**
* « Jès fèt » — les outils que la personne a adoptés.
* Effet Zeigarnik utilisé positivement (doctrine §1.2) : une progression
* choisie, pas un streak. Stocké uniquement sur l'appareil, jamais envoyé
* nulle part (pas de compte, pas de télémétrie — doctrine §3.3 : aucune
* métrique anxiogène ; le compteur n'apparaît qu'après le premier geste).
*/
const STORAGE_KEY = 'oki-jes'
function initial(): string[] {
if (!browser) return []
try {
const raw = localStorage.getItem(STORAGE_KEY)
return raw ? (JSON.parse(raw) as string[]) : []
} catch {
return []
}
}
const state = $state({ slugs: initial() })
function persist() {
if (browser) localStorage.setItem(STORAGE_KEY, JSON.stringify(state.slugs))
}
export function isAdopted(slug: string): boolean {
return state.slugs.includes(slug)
}
export function adoptedCount(): number {
return state.slugs.length
}
export function toggleAdopted(slug: string) {
state.slugs = isAdopted(slug) ? state.slugs.filter((s) => s !== slug) : [...state.slugs, slug]
persist()
}
@@ -0,0 +1,28 @@
<script lang="ts">
import { isAdopted, toggleAdopted } from '$lib/adopted.svelte'
import { t } from '$lib/i18n/index.svelte'
let { slug }: { slug: string } = $props()
let adopted = $derived(isAdopted(slug))
</script>
<!-- Amélioration progressive : sans JS le bouton n'apparaît pas du tout
(rendu identique, aucune fonction cassée). -->
<button type="button" class:adopted aria-pressed={adopted} onclick={() => toggleAdopted(slug)}>
{adopted ? t('adopted.marked') : t('adopted.mark')}
<span class="visually-hidden">{t('adopted.srHint')}</span>
</button>
<style>
button {
border: 2px solid var(--oki-vert);
background: transparent;
color: var(--oki-ink);
font-weight: 600;
}
button.adopted {
background: var(--oki-vert);
color: oklch(98% 0 0);
}
</style>
@@ -0,0 +1,102 @@
<script lang="ts">
import { browser } from '$app/environment'
import { base } from '$app/paths'
import { page } from '$app/state'
import { t } from '$lib/i18n/index.svelte'
interface NavCategory {
slug: string
title: string
}
let { categories }: { categories: NavCategory[] } = $props()
// Hick-Hyman (doctrine §1.3) : ≤ 5 entrées visibles, le reste en
// divulgation progressive (<details>, fonctionne sans JS).
const VISIBLE = 4
let visible = $derived(categories.slice(0, VISIBLE))
let rest = $derived(categories.slice(VISIBLE))
// searchParams non lisibles au prerender : garde `browser`
let current = $derived(browser ? page.url.searchParams.get('cat') : null)
function href(slug: string | null): string {
return slug ? `${base}/outils?cat=${slug}` : `${base}/outils`
}
</script>
<nav aria-label={t('directory.filterCategory')} class="catnav">
<ul class="cluster">
<li><a href={href(null)} aria-current={current === null ? 'page' : undefined}>{t('directory.allCategories')}</a></li>
{#each visible as cat (cat.slug)}
<li>
<a href={href(cat.slug)} aria-current={current === cat.slug ? 'page' : undefined}>{cat.title}</a>
</li>
{/each}
{#if rest.length > 0}
<li>
<details>
<summary>+ {rest.length}</summary>
<ul class="stack">
{#each rest as cat (cat.slug)}
<li>
<a href={href(cat.slug)} aria-current={current === cat.slug ? 'page' : undefined}>{cat.title}</a>
</li>
{/each}
</ul>
</details>
</li>
{/if}
</ul>
</nav>
<style>
ul {
list-style: none;
padding: 0;
}
a {
display: inline-flex;
align-items: center;
min-height: var(--tap-target);
padding-inline: var(--space-3);
border: 1px solid var(--oki-line);
border-radius: var(--radius-2);
text-decoration: none;
color: var(--oki-ink);
}
a[aria-current='page'] {
background: var(--oki-jaune);
border-color: var(--oki-jaune);
color: var(--oki-noir);
font-weight: 700;
}
details {
position: relative;
}
summary {
display: inline-flex;
align-items: center;
min-height: var(--tap-target);
padding-inline: var(--space-3);
border: 1px dashed var(--oki-line);
border-radius: var(--radius-2);
cursor: pointer;
}
details ul {
position: absolute;
z-index: 2;
inset-inline-start: 0;
margin-block-start: var(--space-1);
padding: var(--space-2);
background: var(--oki-surface);
border: 1px solid var(--oki-line);
border-radius: var(--radius-2);
min-inline-size: 16rem;
box-shadow: 0 4px 16px oklch(0% 0 0 / 0.15);
}
</style>
@@ -0,0 +1,36 @@
<script lang="ts">
import { t } from '$lib/i18n/index.svelte'
// Jamais la couleur seule : forme + texte + couleur (doctrine §1.1 —
// ~8 % de déficience de vision des couleurs).
const SHAPES = { beginner: '●', intermediate: '◆', advanced: '▲' } as const
let { difficulty }: { difficulty: keyof typeof SHAPES } = $props()
</script>
<span class="badge badge-{difficulty}">
<span aria-hidden="true">{SHAPES[difficulty]}</span>
{t(`difficulty.${difficulty}`)}
</span>
<style>
.badge {
display: inline-flex;
align-items: center;
gap: var(--space-1);
font-size: var(--text-0);
font-weight: 600;
padding: 0.15em 0.6em;
border-radius: var(--radius-2);
border: 1px solid currentColor;
}
.badge-beginner {
color: var(--diff-beginner-ink);
}
.badge-intermediate {
color: var(--diff-intermediate-ink);
}
.badge-advanced {
color: var(--diff-advanced-ink);
}
</style>
@@ -0,0 +1,34 @@
<script lang="ts">
import { t } from '$lib/i18n/index.svelte'
</script>
<!-- Marqueur de fin explicite : restitue le point d'arrêt naturel que le
scroll infini supprime (doctrine §1.5 et §3.2). -->
<p class="end" role="status">
<strong>{t('directory.endOfList')}</strong>
<span>{t('directory.endOfListSub')}</span>
</p>
<style>
.end {
display: flex;
flex-direction: column;
align-items: center;
gap: var(--space-1);
text-align: center;
padding-block: var(--space-5);
border-top: 4px double var(--oki-line);
margin-block-start: var(--space-5);
}
strong {
font-family: var(--font-display);
font-size: var(--text-2);
color: var(--accent-ink);
}
span {
color: var(--oki-ink-2);
font-size: var(--text-0);
}
</style>
@@ -0,0 +1,40 @@
<script lang="ts">
import { getLocale, setLocale, locales, localeNames, t } from '$lib/i18n/index.svelte'
</script>
<!-- Bascule explicite FR / Kréyòl, persistée en localStorage (mission §4.3).
Amélioration progressive : sans JS, le site reste lisible en français. -->
<div class="switch" role="group" aria-label={t('lang.switchLabel')}>
{#each locales as locale (locale)}
<button
type="button"
aria-pressed={getLocale() === locale}
onclick={() => setLocale(locale)}
>
{localeNames[locale]}
</button>
{/each}
</div>
<style>
.switch {
display: inline-flex;
border: 1px solid var(--oki-line);
border-radius: var(--radius-2);
overflow: hidden;
}
button {
border: none;
border-radius: 0;
background: transparent;
min-height: var(--tap-target);
font-size: var(--text-0);
}
button[aria-pressed='true'] {
background: var(--oki-jaune);
color: var(--oki-noir);
font-weight: 700;
}
</style>
@@ -0,0 +1,31 @@
<script lang="ts">
import { browser } from '$app/environment'
import { t } from '$lib/i18n/index.svelte'
// Technologie périphérique (doctrine §3.5) : l'état réseau est une
// information non urgente — un badge discret, jamais une interruption.
let offline = $state(browser ? !navigator.onLine : false)
</script>
<svelte:window ononline={() => (offline = false)} onoffline={() => (offline = true)} />
{#if offline}
<p class="offline" role="status">
<strong>{t('offline.badge')}</strong>{t('offline.detail')}
</p>
{/if}
<style>
.offline {
font-size: var(--text-0);
color: var(--oki-ink-2);
background: var(--oki-surface-2);
border-block: 1px solid var(--oki-line);
padding: var(--space-2) var(--space-3);
text-align: center;
}
strong {
color: var(--accent-ink);
}
</style>
@@ -0,0 +1,44 @@
<script lang="ts">
import { t } from '$lib/i18n/index.svelte'
let { value = $bindable('') }: { value?: string } = $props()
let input: HTMLInputElement
// raccourci « / » desktop (doctrine §2.2 — le clavier est roi)
function onkeydown(e: KeyboardEvent) {
if (e.key === '/' && document.activeElement !== input) {
const target = e.target
if (target instanceof HTMLElement && target.closest('input, textarea, [contenteditable]'))
return
e.preventDefault()
input.focus()
}
}
</script>
<svelte:window {onkeydown} />
<p class="search">
<label>
<span class="visually-hidden">{t('search.label')}</span>
<input
bind:this={input}
bind:value
type="search"
placeholder={t('search.placeholder')}
autocomplete="off"
/>
</label>
</p>
<style>
input {
inline-size: 100%;
max-inline-size: 28rem;
min-height: var(--tap-target);
padding-inline: var(--space-3);
border: 1px solid var(--oki-line);
border-radius: var(--radius-2);
background: var(--oki-surface);
}
</style>
+43
View File
@@ -0,0 +1,43 @@
<script lang="ts">
import { t } from '$lib/i18n/index.svelte'
import { SITE_URL } from '$lib/site'
interface Props {
/** titre court de la page (« Annuaire des outils ») — omis sur l'accueil */
title?: string
description: string
/** chemin canonique avec slash final, ex. `/outils/` (trailingSlash: 'always') */
path: string
type?: 'website' | 'article'
}
let { title, description, path, type = 'website' }: Props = $props()
// même format que l'ère actuelle : « Titre — Lagé Chat Control »,
// et « Lagé Chat Control — accroche » sur l'accueil
let fullTitle = $derived(title ? `${title}${t('site.name')}` : `${t('site.name')}${t('site.tagline')}`)
let url = $derived(`${SITE_URL}${path}`)
// favicon.png servi en og:image — réellement 192×192 (vérifié au build)
const image = `${SITE_URL}/favicon.png`
</script>
<svelte:head>
<title>{fullTitle}</title>
<meta name="description" content={description} />
<link rel="canonical" href={url} />
<meta property="og:type" content={type} />
<meta property="og:url" content={url} />
<meta property="og:title" content={fullTitle} />
<meta property="og:description" content={description} />
<meta property="og:image" content={image} />
<meta property="og:image:width" content="192" />
<meta property="og:image:height" content="192" />
<meta property="og:image:alt" content={t('site.name')} />
<meta property="og:locale" content="fr_FR" />
<meta property="og:site_name" content={t('site.name')} />
<meta name="twitter:card" content="summary" />
<meta name="twitter:title" content={fullTitle} />
<meta name="twitter:description" content={description} />
<meta name="twitter:image" content={image} />
</svelte:head>
@@ -0,0 +1,100 @@
<script lang="ts">
import { goto } from '$app/navigation'
import { base } from '$app/paths'
import { t } from '$lib/i18n/index.svelte'
// Le clavier est roi sur desktop (doctrine §2.2.3) : raccourcis découvrables
// via « ? », jamais porteurs d'information exclusive (tout est aussi dans la nav).
let dialog: HTMLDialogElement
const KEYS = [
{ key: '/', label: 'shortcuts.search' },
{ key: 'h', label: 'shortcuts.home' },
{ key: 'a', label: 'shortcuts.directory' },
{ key: '?', label: 'shortcuts.help' },
{ key: 'Échap', label: 'shortcuts.close' }
]
function onkeydown(e: KeyboardEvent) {
if (e.ctrlKey || e.metaKey || e.altKey) return
const target = e.target
if (target instanceof HTMLElement && target.closest('input, textarea, select, [contenteditable]'))
return
if (e.key === '?') {
e.preventDefault()
dialog.open ? dialog.close() : dialog.showModal()
} else if (e.key === 'h') {
goto(`${base}/`)
} else if (e.key === 'a') {
goto(`${base}/outils`)
}
}
</script>
<svelte:window {onkeydown} />
<!-- <dialog> natif : focus piégé, Échap géré par le navigateur, zéro ARIA maison -->
<dialog bind:this={dialog}>
<h2>{t('shortcuts.title')}</h2>
<dl>
{#each KEYS as { key, label } (key)}
<div>
<dt><kbd>{key}</kbd></dt>
<dd>{t(label)}</dd>
</div>
{/each}
</dl>
<form method="dialog">
<button>{t('shortcuts.close')}</button>
</form>
</dialog>
<style>
dialog {
border: 1px solid var(--oki-line);
border-top: 4px solid var(--oki-jaune);
border-radius: var(--radius-2);
background: var(--oki-surface);
color: var(--oki-ink);
padding: var(--space-4);
max-inline-size: 24rem;
}
dialog::backdrop {
background: oklch(0% 0 0 / 0.5);
}
h2 {
font-size: var(--text-2);
margin-block-end: var(--space-3);
}
dl {
display: flex;
flex-direction: column;
gap: var(--space-2);
margin-block-end: var(--space-4);
}
dl div {
display: flex;
align-items: center;
gap: var(--space-3);
}
kbd {
display: inline-grid;
place-items: center;
min-inline-size: 2rem;
padding: 0.15em 0.5em;
border: 1px solid var(--oki-line);
border-bottom-width: 3px;
border-radius: var(--radius-1);
background: var(--oki-surface-2);
font-size: var(--text-0);
}
dd {
margin: 0;
}
</style>
+123
View File
@@ -0,0 +1,123 @@
<script lang="ts">
import { base } from '$app/paths'
import DifficultyBadge from './DifficultyBadge.svelte'
import { isAdopted } from '$lib/adopted.svelte'
import { t } from '$lib/i18n/index.svelte'
interface CardTool {
name: string
slug: string
difficulty: 'beginner' | 'intermediate' | 'advanced'
license?: string
color?: string
monogram?: string
tags: string[]
excerpt: string
}
let { tool }: { tool: CardTool } = $props()
</script>
<article class="card">
<header class="cluster">
<span class="monogram" style:--tool-color={tool.color} aria-hidden="true">
{tool.monogram ?? tool.name.slice(0, 2)}
</span>
<h3><a href="{base}/outils/{tool.slug}">{tool.name}</a></h3>
{#if isAdopted(tool.slug)}
<!-- information périphérique (doctrine §3.5) : discret, jamais bloquant -->
<span class="adopted" title={t('adopted.marked')}>
<span aria-hidden="true"></span>
<span class="visually-hidden">{t('adopted.marked')}</span>
</span>
{/if}
</header>
<p class="excerpt"><span class="visually-hidden">{t('tool.why')} : </span>{tool.excerpt}</p>
<footer class="cluster">
<DifficultyBadge difficulty={tool.difficulty} />
{#if tool.license}
<span class="meta">{tool.license}</span>
{/if}
{#each tool.tags as tag (tag)}
<span class="meta">{tag}</span>
{/each}
</footer>
</article>
<style>
.card {
position: relative;
display: flex;
flex-direction: column;
gap: var(--space-2);
padding: var(--space-3);
border: 1px solid var(--oki-line);
border-left: 4px solid var(--oki-jaune);
border-radius: var(--radius-2);
background: var(--oki-surface);
height: 100%;
}
/* la couleur de marque en anneau, jamais en fond de texte :
contraste AA garanti quel que soit l'outil (doctrine §4.3) */
.monogram {
display: grid;
place-items: center;
inline-size: 2.5rem;
block-size: 2.5rem;
border-radius: var(--radius-2);
background: var(--oki-surface-2);
border: 3px solid var(--tool-color, var(--oki-line));
color: var(--oki-ink);
font-weight: 700;
font-size: var(--text-0);
}
h3 {
font-size: var(--text-2);
}
/* toute la carte est cliquable via le pseudo-élément du lien —
un vrai <a>, pas de div cliquable (doctrine §4.3) */
h3 a {
text-decoration: none;
}
h3 a::after {
content: '';
position: absolute;
inset: 0;
}
h3 a:focus-visible::after {
outline: var(--focus-ring);
outline-offset: -3px;
border-radius: var(--radius-2);
}
.adopted {
display: grid;
place-items: center;
inline-size: 1.4rem;
block-size: 1.4rem;
border-radius: 50%;
background: var(--oki-vert);
color: oklch(98% 0 0);
font-size: var(--text-0);
margin-inline-start: auto;
}
.excerpt {
color: var(--oki-ink-2);
font-size: var(--text-0);
flex-grow: 1;
}
.meta {
font-size: var(--text-0);
color: var(--oki-ink-2);
border: 1px solid var(--oki-line);
border-radius: var(--radius-2);
padding: 0.1em 0.6em;
}
</style>
+42
View File
@@ -0,0 +1,42 @@
import { readFileSync } from 'node:fs'
import path from 'node:path'
export interface QuizOption {
points: number
label: string
hint: string
}
export interface QuizQuestion {
id: string
/** slug de content/categories.json — relie chaque question à l'annuaire */
category: string
label: string
prompt: string
rec: string
options: QuizOption[]
}
export interface QuizBand {
id: string
min: number
max: number
title: string
summary: string
}
export interface QuizContent {
bands: QuizBand[]
questions: QuizQuestion[]
}
const CONTENT = path.resolve(process.cwd(), '..', 'content')
let cache: QuizContent | null = null
export function loadQuiz(): QuizContent {
if (!cache) {
cache = JSON.parse(readFileSync(path.join(CONTENT, 'quiz.json'), 'utf8')) as QuizContent
}
return cache
}
+140
View File
@@ -0,0 +1,140 @@
/**
* 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 { existsSync, 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, unknown>, string] {
const m = raw.match(/^---\n([\s\S]*?)\n---\n?/)
if (!m) throw new Error('frontmatter manquant')
const meta: Record<string, unknown> = {}
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<string, string> = {
'&amp;': '&',
'&lt;': '<',
'&gt;': '>',
'&quot;': '"',
'&#39;': "'",
'&nbsp;': ' '
}
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<Tool, 'sections' | 'excerpt'>),
excerpt: why.length > 160 ? `${why.slice(0, 157).trimEnd()}` : why,
sections
}
}
export interface GcfTool {
sections: ToolSections
excerpt: string
}
/** Fiches jumelles créoles : content/outils/gcf/<slug>.md (CONTENT_GUIDE). */
function loadGcf(): Record<string, GcfTool> {
const dir = path.join(CONTENT, 'outils', 'gcf')
if (!existsSync(dir)) return {}
const out: Record<string, GcfTool> = {}
for (const file of readdirSync(dir).filter((f) => f.endsWith('.md'))) {
const raw = readFileSync(path.join(dir, file), 'utf8')
const [meta, body] = parseFrontmatter(raw)
const sections = parseSections(body)
const why = stripHtml(sections.why ?? sections.what ?? '')
out[(meta.slug as string) ?? file.replace(/\.md$/, '')] = {
excerpt: why.length > 160 ? `${why.slice(0, 157).trimEnd()}` : why,
sections
}
}
return out
}
let cache: { tools: Tool[]; categories: Category[]; gcf: Record<string, GcfTool> } | 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, gcf: loadGcf() }
return cache
}
+137
View File
@@ -0,0 +1,137 @@
{
"site": {
"name": "Lagé 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"
},
"intro": {
"title": "Pourquoi ce site ?",
"p1": "L'Union européenne veut imposer le scan de toutes tes conversations privées : c'est le projet « Chat Control ». Et même quand la loi recule, l'infrastructure de surveillance, elle, continue d'avancer.",
"p2": "Ce site est un guide de terrain. Des outils libres, vérifiés, expliqués pas à pas — pour sortir des plateformes qui te surveillent et reprendre la main : toi, ta famille, ton lakou.",
"p3": "Pas besoin d'être technicien·ne. Commence par les fondations, avance à ton rythme, le reste vient après."
},
"sommaire": {
"title": "Le chimen",
"sub": "Trois étapes, du premier geste à l'autodéfense complète. Chaque catégorie est une porte d'entrée."
},
"arc": {
"fondations": "Les fondations",
"controle": "Reprendre le contrôle",
"avance": "Autodéfense avancée"
},
"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",
"notTranslated": "Fich-lasa póko tradui an kréyòl — la voici en français.",
"draftTranslation": "Tradiksyon kréyòl an chantyé — un doute ? La version française fait foi."
},
"adopted": {
"mark": "Je m'en sers",
"marked": "Je m'en sers ✓",
"progressBefore": "Ou ja fè",
"progressAfter": "gestes — chaque outil adopté est une reprise de terrain.",
"srHint": "Enregistré uniquement sur cet appareil, jamais envoyé nulle part."
},
"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"
},
"shortcuts": {
"title": "Raccourcis clavier",
"search": "Rechercher",
"help": "Afficher cette aide",
"home": "Aller à l'accueil",
"directory": "Aller à l'annuaire",
"close": "Fermer"
},
"offline": {
"badge": "Hors-ligne",
"detail": "Tu lis la copie locale — les fiches déjà visitées restent disponibles."
},
"contrib": {
"title": "Pòté mannèv — Contribuer",
"intro": "Ce guide vit par celles et ceux qui s'en servent. Trois façons d'aider, de la plus simple à la plus complète.",
"suggest": "Proposer un outil",
"suggestBody": "Tu utilises un outil libre qui manque à l'annuaire ? Ouvre une demande avec le formulaire : nom, lien, pourquoi il compte. On s'occupe de la fiche.",
"suggestCta": "Ouvrir une demande",
"fix": "Corriger une fiche",
"fixBody": "Une étape d'installation qui a changé, un lien mort, une tournure plus claire ? Chaque fiche est un simple fichier texte dans le dossier content/outils/.",
"fixCta": "Parcourir les fiches",
"translate": "Traduire en kréyòl",
"translateBody": "Le créole est la langue du cœur de ce site. Les fiches attendent leur jumelle gcf — et les traductions existantes ont besoin de relecteurs et relectrices natifs.",
"translateCta": "Lire le guide de contenu",
"repo": "Tout se passe sur la forge",
"repoBody": "Le code, le contenu et les discussions vivent sur labola, la forge auto-hébergée d'OKI. Pas de compte Microsoft requis.",
"repoCta": "Voir le dépôt"
},
"footer": {
"license": "Contenu et code libres (MIT). Zéro tracker, zéro CDN, zéro cookie.",
"contribute": "Proposer un outil ou une correction"
},
"quiz": {
"nav": "Quiz",
"eyebrow": "Évaluer sa résistance à la censure",
"title": "Quiz de résistance",
"lede": "Une boussole, pas un classement : réponds à douze questions rapides pour repérer les points faibles de ta pile — sans jugement — puis suis des pistes concrètes vers l'annuaire.",
"privacyNote": "Le score est calculé localement dans ton navigateur. Rien n'est envoyé, rien n'est enregistré.",
"start": "Commencer le quiz",
"readDirectory": "Voir l'annuaire",
"progress": "{done} / {total} répondues",
"resultEyebrow": "Ton résultat",
"outOf": "/ 100",
"barsHeading": "Score par domaine",
"recHeading": "Tes 3 prochaines étapes",
"openDirectory": "Voir les outils →",
"restart": "Refaire le quiz",
"copy": "Copier le lien du résultat",
"copied": "Lien copié ✓",
"share": "Partager",
"shareText": "Mon score de résistance à la censure : {score}/100 — {band}. Évalue le tien :",
"bandsHeading": "Ce que le score signifie",
"categoriesHeading": "Les douze domaines, et où progresser"
}
}
+137
View File
@@ -0,0 +1,137 @@
{
"site": {
"name": "Lagé 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"
},
"intro": {
"title": "Poukisa sit-lasa ?",
"p1": "Linyon éwopéyèn vlé enposé skannaj a tout konvèsasyon privé a-w : sé pwojé « Chat Control ». É menm lè lwa-la ka fè bak, enfrastrikti a survéyans-la ka kontinyé vansé.",
"p2": "Sit-lasa sé on gid a téren. Zouti lib, vérifyé, èspliké pa a pa — pou sòti an platfòm ki ka survéyé-w é répran lanmen : vou, fanmi a-w, lakou a-w.",
"p3": "Pa bizwen sav enfòwmatik. Koumansé èvè fondasyon-la, vansé a vitès a-w, rès-la ka vin apré."
},
"sommaire": {
"title": "Chimen-la",
"sub": "Twa létap, dépi prèmyé jès jis otodéfans konplé. Chak katégori sé on pòt."
},
"arc": {
"fondations": "Fondasyon-la",
"controle": "Répran kontwòl",
"avance": "Otodéfans avansé"
},
"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",
"notTranslated": "Fich-lasa póko tradui an kréyòl — mi'y an fransé.",
"draftTranslation": "Tradiksyon kréyòl an chantyé — on dout ? Vèwsyon fransé-la ka fè fwa."
},
"adopted": {
"mark": "An ka sèvi épi'y",
"marked": "An ka sèvi épi'y ✓",
"progressBefore": "Ou ja fè",
"progressAfter": "jès — chak zouti adopté sé on moso téren ou répran.",
"srHint": "Anrèjistré asi apawèy-lasa sèlman, janmé voyé okenn koté."
},
"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"
},
"shortcuts": {
"title": "Rakousi klavyé",
"search": "Chèché",
"help": "Montré èd-lasa",
"home": "Alé an lakou-la",
"directory": "Alé an lis-la",
"close": "Fèmé"
},
"offline": {
"badge": "Dékonèkté",
"detail": "Ou ka li kopi lokal-la — fich ou ja vizité ka rété la."
},
"contrib": {
"title": "Pòté mannèv",
"intro": "Gid-lasa ka viv gras a moun ki ka sèvi épi'y. Twa mannyè pou édé, dépi pi senp jis pi konplé.",
"suggest": "Pwopozé on zouti",
"suggestBody": "Ou ka sèvi épi on zouti lib ki manké an lis-la ? Louvè on dimann èvè fòwmilè-la : non, lyen, poukisa i konté. Nou ka okipé fich-la.",
"suggestCta": "Louvè on dimann",
"fix": "Korijé on fich",
"fixBody": "On létap ki chanjé, on lyen mò, on fraz pi klè ? Chak fich sé on senp fichyé tèks an dosyé content/outils/.",
"fixCta": "Gadé fich-la yo",
"translate": "Tradui an kréyòl",
"translateBody": "Kréyòl sé lang a kè a sit-lasa. Fich-la yo ka atann jimèl gcf a-yo — é tradiksyon ki ja la bizwen lektè natif.",
"translateCta": "Li gid a kontni-la",
"repo": "Tout ka pasé asi fòwj-la",
"repoBody": "Kòd, kontni é diskisyon ka viv asi labola, fòwj oto-ébèjé a OKI. Pa bizwen kont Microsoft.",
"repoCta": "Gadé dépo-la"
},
"footer": {
"license": "Kontni é kòd lib (MIT). Pon trakè, pon CDN, pon cookie.",
"contribute": "Pwopozé on zouti oben on koreksyon"
},
"quiz": {
"nav": "Quiz",
"eyebrow": "Èvalyé rézistans a-w a sansi-la",
"title": "Quiz rézistans",
"lede": "On bousòl, pa on klasman : réponn douz kèsyon rapid pou trapé pwen fèb an pil a-w — san jijman — apré swiv chimen konkrè vè lis a zouti-la.",
"privacyNote": "Skò-la ka kalkilé lokalman an navigatè a-w. Ayen pa voyé, ayen pa anrèjistré.",
"start": "Koumansé quiz-la",
"readDirectory": "Gadé lis-la",
"progress": "{done} / {total} réponn",
"resultEyebrow": "Rézilta a-w",
"outOf": "/ 100",
"barsHeading": "Skò pa domèn",
"recHeading": "Twa pwochen étap a-w",
"openDirectory": "Gadé zouti yo →",
"restart": "Rifè quiz-la",
"copy": "Kopyé lyen rézilta-la",
"copied": "Lyen kopyé ✓",
"share": "Pataj",
"shareText": "Skò rézistans a sansi a mwen : {score}/100 — {band}. Èvalyé pa a-w :",
"bandsHeading": "Sa skò-la vlé di",
"categoriesHeading": "Douz domèn yo, é kotè pou pwogrésé"
}
}
+54
View File
@@ -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<Locale, Dict> = { 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<unknown>((node, key) => {
if (node && typeof node === 'object' && key in node) {
return (node as Record<string, unknown>)[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
}

Some files were not shown because too many files have changed in this diff Show More