Compare commits

...

10 Commits

Author SHA1 Message Date
Yvv
b9e6b4a96c Messages : types, réponses, sauts de ligne, data volume
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
- HomeMessages : type pill (Réaction/Question/Suggestion/Retour) + sélecteur dans le formulaire (sans Réaction)
- HomeMessages : white-space: pre-line sur les messages
- Page /messages : type pill + white-space: pre-line (idem home)
- Admin : badge type coloré + sélecteur d'édition + formulaire réponse
- API : type et reply dans PUT ; readDataYaml/writeDataYaml (data/ volume Docker)
- main.css : overrides light mode text-white/55, /75, /90

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 05:56:11 +01:00
Yvv
c52fa6007d Page /messages : afficher les réponses publiées
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
- Type badge + auteur + date comme sur la home
- Réponse avec liaison graphique (reply-thread/connector/block)
- Style adaptatif light/dark cohérent avec HomeMessages.vue

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 05:41:24 +01:00
Yvv
d4ff840e13 bookplayer.config.yml : slugs 06-produire et 07-echanger
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
- chapterSongs et chapterPages : 06-economie → 06-produire, 07-echange → 07-echanger
- Les liens "Lire dans le PDF" depuis les chapitres 6 et 7 s'ouvrent maintenant sur les bonnes pages

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 00:31:55 +01:00
Yvv
7691cc4139 Chapitres condensés, renommages, numéros de page
- 06-economie → 06-produire (titre "Produire"), 07-echange → 07-echanger
- Frontmatter : champ page: ajouté sur les 11 chapitres (p.9 à p.199)
- content.config.ts : page: z.number().optional() dans le schéma
- modele-eco/index.vue : numéro de page affiché sur chaque carte, hero réduit
- 11 chapitres condensés à ~moitié : voix de l'auteur conservée,
  répétitions et transitions secondaires supprimées, concepts clés préservés

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 00:28:09 +01:00
Yvv
088333e4d4 Monnaie libre : réécriture dans la voix du livre, liens forums, purge co-créer
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
- monnaie-libre.yml : texte entièrement réécrit (cavalerie, double symétrie, DU,
  toile de confiance, mesure de gratitude) + 3 nouveaux liens (duniter.org,
  forum.duniter.org, forum.monnaie-libre.fr)
- Suppression de "co-créer/co-créée/co-création" dans tous les fichiers :
  economique.yml, home.yml, authentification-wot.yml, cloud-libre.yml,
  content/book/05-trm.md, content/book/11-annexes.md

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-16 22:51:28 +01:00
Yvv
07449de187 Pages détail numérique : sommaire flottant, nav ctx, shadoks geek, contenu enrichi
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
- [slug].vue : sommaire sticky (overflow:clip sur parent), prev/next en haut, 6 shadoks geek (pinguin+USB, web-of-trust, rubber-duck, caféine, debugger loupe, rack serveur)
- Nouveaux types de sections : territoire (bouquet sweethomeCloud, 2 modèles éco, tableau matériel dépliable), projet (carte gestation)
- cloud-libre.yml : section sweethomeCloud complète avec infra 50 000 hab. (~2€/an/hab)
- authentification-wot.yml : trustWallet, correction WoT Duniter (Ed25519+Scrypt, sigQty=5, stepMax=3), DID/VC standards
- logiciel-libre.yml : carte projet wishBounty
- home.yml + numerique.yml : cloud-libre → sweethomeCloud, description RGPD/local-first
- AxisBlock.vue : bulles de présentation inline dans les cards (plus de tooltip absolu)
- Analytics : useTracking.ts (Umami), docker-compose.umami.yml, /api/stats fédération
- nuxt.config.ts : config Umami runtime

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-16 04:40:48 +01:00
Yvv
9d92c4a5b3 Fix typos blanches admin lightmode + hero audience
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
- Remplace color:white → hsl(var(--color-text)) dans tous les composants admin
  (AdminFieldText, AdminFieldTextarea, AdminFormSection, AdminMarkdownEditor,
  AdminMediaBrowser, AdminSidebar, book/index, book/[slug], login, messages, site, songs)
- Conserve color:white uniquement sur fond primary (AdminSaveButton, login-btn)
- Hero home : ajout bloc audience/addressees (clé distincte pour éviter conflit YAML)
- home.yml : réordonne axes (citoyenne en premier — effet triangle)
- TypewriterText : affiche le second bloc avec séparateur fin

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-15 16:08:03 +01:00
Yvv
8f548afb17 Palette dynamique dans BookPlayer et admin
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
- BookPlayer : toutes les couleurs HSL en dur remplacées par variables CSS palette
- Admin (sidebar, formulaires, pages, book, songs, messages, media, login) : idem
- L'ambiance graphique suit maintenant la palette active partout

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 16:41:23 +01:00
Yvv
9caf11c8ab Restructuration sections, contenu administrable, shadoks, palette été
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
- Structure par section : /numerique, /economique, /citoyenne (plus de /gestation)
- Chaque section a index + sous-pages avec contenu YAML administrable
- API content supporte les chemins imbriqués ([...path])
- Admin : liste des pages + éditeur par section
- Page /economique : monnaie libre (picto Ğ1), modèle éco, productions collectives, commande livre
- Page /citoyenne : decision (CTA Glibredecision), tarifs-eau (CTA SejeteralO)
- BookActions : composant partagé (player, PDF, chapitres, commande) sur home, eco et modele-eco
- GrateWizard remonté dans la section économique de la home
- Palette été par défaut, choix persisté en localStorage
- Fix lisibilité été (text-white/65 + variables CSS)
- Shadoks thématiques sur toutes les pages (8-10 par page, métiers variés)
- Redirections 301 : /gestation/*, /modele-eco/*, /decision, /lire/*
- README, CONTRIBUTING, CLAUDE.md mis à jour

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 16:13:46 +01:00
Yvv
c564e7be5f GrateWizard bloc dédié, messagerie libre, page numérique 3 piliers
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
- GrateWizard : lancement URL simple (plus de popup embed), bloc
  dédié violet sur la home entre axes et événement
- Messagerie : plus de champs obligatoires, plus de champ email
  séparé, hint email dans le message, remerciement onboarding
- Page /numerique : 3 piliers (Logiciel libre, WoT, Cloud libre)
  avec projets associés, remplace les extraits livre hors-sujet
- Admin : carte Messages ajoutée au dashboard
- Safelist icônes complétée

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 03:06:48 +01:00
94 changed files with 9026 additions and 1547 deletions

View File

@@ -14,26 +14,36 @@ Site vitrine du projet Le Librodrome — livre + chansons sur l'économie du don
```
app/
pages/ # Routes : index, lire/, ecouter/, autonomie, evenement, gratewizard, a-propos, admin/
components/ # admin, book, content, home, layout, player, song, ui
composables/ # useAudioPlayer, useBookData, useGrateWizard, usePlaylist, usePageContent, useSiteContent...
assets/css/ # main.css (UnoCSS)
data/
pages/ # Contenu YAML par page (home, lire, ecouter, autonomie, evenement...)
site.yml # Config globale du site
pages/
numerique/ # Autonomie numérique (index + [slug] détail)
economique/ # Autonomie économique (index, monnaie-libre, commande, productions-collectives)
modele-eco/ # Livre : sommaire + chapitres [slug]
citoyenne/ # Autonomie citoyenne (index + [slug] détail)
en-musique/ # Player audio
evenement.vue # Événement
admin/ # Back-office (pages/, book/, songs, messages, media)
components/
book/Actions.vue # Boutons partagés livre (player, PDF, chapitres, commande)
home/ # BookSection, AxisBlock, AxisGrid, HeroSection, Messages
admin/, player/, song/, ui/
composables/ # useAudioPlayer, useBookData, useGrateWizard, usePageContent...
stores/palette.ts # 4 palettes saisonnières (été par défaut, persisté localStorage)
assets/css/ # main.css (UnoCSS + overrides light mode)
site/
pages/ # Contenu YAML par section (numerique/, economique/, citoyenne/)
site.yml # Config globale (nav, footer, GrateWizard)
bookplayer.config.yml # Config player/chapitres
server/
api/ # Endpoints API (admin, health)
middleware/ # Auth middleware
api/content/pages/[...path].get.ts # GET pages YAML (chemins imbriqués)
api/admin/content/pages/[...path].put.ts # PUT pages YAML
api/admin/content/pages.get.ts # Liste toutes les pages
middleware/redirects.ts # 301 : /gestation, /modele-eco, /decision, /lire
docker/
Dockerfile # Build multi-stage (dev + prod)
docker-compose.yml # Production (Traefik)
docker-compose.dev.yml # Dev Docker
Dockerfile, docker-compose.yml, docker-compose.dev.yml
```
## Ports dev (CRITIQUE)
Chaque projet a un port fixe pour éviter les conflits Nuxt auto-increment :
| Projet | Port | Config |
|--------|------|--------|
| **librodrome** | **3000** | `nuxt.config.ts``devServer.port: 3000` |
@@ -41,32 +51,32 @@ Chaque projet a un port fixe pour éviter les conflits Nuxt auto-increment :
| **SejeteralO frontend** | **3009** | `frontend/nuxt.config.ts``devServer.port: 3009` |
| **SejeteralO backend** | **8000** | Makefile → `uvicorn --port 8000` |
Script de gestion : `/home/yvv/Documents/PROD/DEV/dev-ports.sh` (status/kill/clean/start)
**Ne jamais changer ces ports.** Si un port est occupé, tuer le process parasite plutôt que laisser Nuxt auto-incrémenter.
**Ne jamais changer ces ports.**
## Intégration GrateWizard
- URL dev configurée dans `app/app.config.ts``localhost:3001`
- URL dev : `app/app.config.ts``localhost:3001`
- URL prod : `https://gratewizard.axiom-team.fr`
- Ouverture en popup via `composables/useGrateWizard.ts`
- GrateWizard est un projet Next.js séparé (`/home/yvv/Documents/PROD/DEV/GrateWizard`)
- Bloc GrateWizard dans la section économique de la home
## Contenu
## Contenu administrable
Le contenu des pages est dans `data/pages/*.yml` et chargé via `composables/usePageContent.ts`. Le contenu riche (articles) utilise Nuxt Content avec des fichiers Markdown.
- YAML dans `site/pages/` organisé par section (sous-dossiers)
- API supporte les chemins imbriqués (`numerique/logiciel-libre`)
- Admin : `/admin/pages` liste toutes les pages, `/admin/pages/{path}` édite en YAML
- Git sync auto en prod (ADMIN_GIT_SYNC=true)
## Commandes
```bash
pnpm dev # Dev server sur :3000
pnpm build # Build production
pnpm generate # Génération statique
```
## Conventions
- Langue du site : français
- Commits en français, style concis
- CSS via UnoCSS (utility-first), pas de framework CSS externe
- CSS via UnoCSS (utility-first) + variables CSS palettes
- Composants Vue SFC avec `<script setup lang="ts">`
- Shadoks SVG inline thématiques sur chaque page (hidden mobile)

101
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,101 @@
# Contributing — Le Librodrome
## Architecture
```
app/
pages/
numerique/ # Section autonomie numérique
index.vue # Vue d'ensemble (3 piliers)
[slug].vue # Détail pilier (contenu YAML)
economique/ # Section autonomie économique
index.vue # Vue d'ensemble (monnaie libre, livre, productions)
monnaie-libre.vue
commande.vue # Commande du livre (Bookelis + librairie)
productions-collectives.vue
modele-eco/
index.vue # Couverture livre + sommaire chapitres
[slug].vue # Lecteur de chapitre (Nuxt Content)
citoyenne/ # Section autonomie citoyenne
index.vue # Vue d'ensemble (decision, tarifs-eau)
[slug].vue # Détail (contenu YAML)
en-musique/ # Player audio
evenement.vue
admin/ # Back-office protégé
pages/
index.vue # Liste toutes les pages YAML
[...path].vue # Éditeur YAML (supporte les sous-dossiers)
components/
book/Actions.vue # Boutons partagés du livre (player, PDF, chapitres, commande)
home/BookSection.vue # Bloc couverture + CTAs (utilise BookActions)
home/AxisBlock.vue # Bloc axe sur la home (supporte actions lien via `to`)
home/AxisGrid.vue # Grille 3 axes + GrateWizard
stores/palette.ts # 4 palettes saisonnières (été par défaut)
```
## Contenu YAML
API : `GET /api/content/pages/{path}` — supporte les chemins imbriqués (`numerique/logiciel-libre`).
Admin : `PUT /api/admin/content/pages/{path}` + `GET /api/admin/content/pages` (liste).
Les fichiers YAML sont dans `site/pages/`, organisés en sous-dossiers par section.
## Palettes
4 palettes dans `stores/palette.ts` : automne, hiver, printemps, été.
- Défaut : **été** (premier visiteur)
- Persisté en `localStorage('palette')` pour les visites suivantes
- CSS : variables `--color-primary`, `--color-accent`, etc. + classe `.palette-light`/`.palette-dark`
- Overrides light mode dans `main.css` (`.palette-light .text-white` → couleur adaptive)
- Admin : tous les `color: white` dans les composants admin sont remplacés par `hsl(var(--color-text))` sauf les boutons sur fond `hsl(var(--color-primary))` (`AdminSaveButton`, `.login-btn`)
## Navigation contextuelle — pages détail
`[slug].vue` (numérique, citoyenne) embarque :
- **Prev/next** (`ctx-nav`) en haut de page — liens vers les pages adjacentes dans la section
- **Sommaire flottant** (`.sommaire-sidebar`, `position: sticky`) dans un aside absolu à droite — visible ≥ 1300px, `overflow: clip` sur le parent pour ne pas casser le sticky
Les ancres sont des `<div :id="\`s${si}\`" class="section-anchor">` avec `scroll-margin-top: 5.5rem` (offset header fixe).
## Types de sections YAML
`[slug].vue` supporte : `arguments`, `fiche`, `equivalents`, `llm`, `atelier`, `links`, `insight`, `tiers`, `projet`, `territoire`.
`type: territoire` : économies alternatives (euro/Ğ1), grille bouquet de services, tableau matériel dépliable (`<details>`).
`type: projet` : carte gestation avec grille de features.
## Shadoks
Illustrations SVG inline sur chaque page, thématiques par section :
- Numérique index : métiers tech/industrie (codeuse, électricien, soudeuse, cryptographe...)
- Numérique détail : postures geek (pinguin+USB, web-of-trust, rubber duck, caféine, rack serveur, debugger loupe)
- Économique : artisanat/agriculture (boulangère, potier, forgeron...)
- Citoyenne : navigation/justice/théâtre (capitaine, avocate, comédien...)
- Modèle éco : livre/édition (typographe, calligraphe, conteuse...)
- Événement : spectacle (funambule, accordéoniste, jongleur...)
Règles : corps compact, longues pattes, grands pieds plats, bec pointu, postures variées.
CSS : `position: absolute`, `pointer-events: none`, `opacity 0.18-0.28`, animation float, `display: none` sur mobile.
Parent avec `overflow: clip` (pas `overflow: hidden` qui casserait `position: sticky`).
## Analytics
`app/composables/useTracking.ts` — wrapper Umami. Activé si `NUXT_PUBLIC_UMAMI_WEBSITE_ID` est défini.
`server/api/stats/index.get.ts` — endpoint public pour la fédération inter-instances (observatoires territoire).
`docker/docker-compose.umami.yml` — stack Umami + PostgreSQL avec labels Traefik.
## Redirections
`server/middleware/redirects.ts` :
- `/gestation/*` → section appropriée
- `/modele-eco/*` → `/economique/modele-eco/*`
- `/decision` → `/citoyenne/decision`
- `/lire/*` → `/economique/modele-eco/*`
- `/ecouter/*` → `/en-musique/*`
## Conventions
- Commits en français, concis
- Build prod vérifié avant push (`PORT=3099 node .output/server/index.mjs`)
- Port dev : 3000 (fixe, jamais de fallback)

124
README.md
View File

@@ -1,75 +1,73 @@
# Nuxt Minimal Starter
# Le Librodrome
Look at the [Nuxt documentation](https://nuxt.com/docs/getting-started/introduction) to learn more.
Site vitrine du projet Le Librodrome — livre + chansons sur l'économie du don.
## Setup
## Navigation
Make sure to install dependencies:
Le site est organisé en 3 grandes sections :
| Section | Route | Contenu |
|---------|-------|---------|
| **Numérique** | `/numerique` | Autonomie numérique : logiciel libre, authentification WoT, cloud libre |
| **Économique** | `/economique` | Création monétaire, monnaie libre, modèle économique (livre + chapitres), productions collectives |
| **Citoyenne** | `/citoyenne` | Décision collective (Glibredecision), tarifs de l'eau (SejeteralO) |
Autres pages : `/en-musique` (player audio), `/evenement`, `/a-propos`, `/messages`.
Chaque section a une page index et des sous-pages de détail (`/numerique/logiciel-libre`, `/economique/modele-eco`, etc.).
## Le livre
- **Lire** : lecteur PDF intégré, chapitres Markdown sous `/economique/modele-eco/[slug]`
- **Écouter** : player audio avec 9 morceaux sous `/en-musique`
- **Commander** : page `/economique/commande` (Bookelis + librairie)
## Stack
- **Nuxt 4** (Vue 3, TypeScript, Nitro)
- **UnoCSS** (utility-first) + palettes saisonnières (été par défaut)
- **Nuxt Content** pour les chapitres du livre
- **Pinia** pour l'état (palette, player)
- **pnpm** comme package manager
## Contenu administrable
Le contenu des pages est dans `site/pages/` en YAML, organisé par section :
```
site/pages/
home.yml # Page d'accueil
numerique.yml # Index numérique
numerique/*.yml # Sous-pages
economique.yml # Index économique
economique/*.yml # Sous-pages (modele-eco, monnaie-libre, commande...)
citoyenne.yml # Index citoyenne
citoyenne/*.yml # Sous-pages (decision, tarifs-eau)
en-musique.yml, evenement.yml, gratewizard.yml
```
Administration via `/admin/pages` (éditeur YAML, authentifié).
Le hero de la home (`home.yml`) supporte deux blocs dépliables :
- `approach` + `axes` : approche par dimension (numérique → code source, etc.)
- `audience` + `addressees` : à qui s'adresse le projet (collectifs, entreprises, collectivités)
## Développement
```bash
# npm
npm install
# pnpm
pnpm install
# yarn
yarn install
# bun
bun install
pnpm dev # Dev server sur :3000
pnpm build # Build production
```
## Development Server
Port réservé : **3000** (ne pas changer).
Start the development server on `http://localhost:3000`:
## Analytics
```bash
# npm
npm run dev
Umami self-hosted (optionnel). Configurer `NUXT_PUBLIC_UMAMI_WEBSITE_ID` et `NUXT_PUBLIC_UMAMI_URL` dans l'environnement.
Déploiement séparé : `docker/docker-compose.umami.yml``stats.librodrome.org`.
Stats publiques exposées via `/api/stats` pour la fédération inter-instances.
# pnpm
pnpm dev
## Déploiement
# yarn
yarn dev
# bun
bun run dev
```
## Production
Build the application for production:
```bash
# npm
npm run build
# pnpm
pnpm build
# yarn
yarn build
# bun
bun run build
```
Locally preview production build:
```bash
# npm
npm run preview
# pnpm
pnpm preview
# yarn
yarn preview
# bun
bun run preview
```
Check out the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information.
Docker + Traefik, CI via Woodpecker. Domaine : `librodrome.org`.

View File

@@ -17,6 +17,18 @@
const paletteStore = usePaletteStore()
onMounted(() => paletteStore.applyToDOM())
// Umami analytics — inject script only when configured
const runtimeConfig = useRuntimeConfig()
if (runtimeConfig.public.umamiWebsiteId && runtimeConfig.public.umamiUrl) {
useHead({
script: [{
src: `${runtimeConfig.public.umamiUrl}/script.js`,
defer: true,
'data-website-id': runtimeConfig.public.umamiWebsiteId,
}],
})
}
useHead({
titleTemplate: (title) => {
return title ? `${title} — Le Librodrome` : 'Le librodrome'

View File

@@ -104,9 +104,13 @@ a {
.palette-light .text-white\/45 { color: hsl(var(--color-text) / 0.52) !important; }
.palette-light .text-white\/50 { color: hsl(var(--color-text) / 0.58) !important; }
.palette-light .text-white\/60 { color: hsl(var(--color-text) / 0.68) !important; }
.palette-light .text-white\/65 { color: hsl(var(--color-text) / 0.73) !important; }
.palette-light .text-white\/70 { color: hsl(var(--color-text) / 0.78) !important; }
.palette-light .text-white\/55 { color: hsl(var(--color-text) / 0.63) !important; }
.palette-light .text-white\/75 { color: hsl(var(--color-text) / 0.83) !important; }
.palette-light .text-white\/80 { color: hsl(var(--color-text) / 0.88) !important; }
.palette-light .text-white\/85 { color: hsl(var(--color-text) / 0.92) !important; }
.palette-light .text-white\/90 { color: hsl(var(--color-text) / 0.95) !important; }
/* white backgrounds → surface tones with more contrast */
.palette-light .bg-white\/5 { background-color: hsl(var(--color-primary) / 0.05) !important; }

View File

@@ -58,7 +58,7 @@ function addItem() {
.field-label {
font-size: 0.8rem;
font-weight: 500;
color: hsl(20 8% 60%);
color: hsl(var(--color-text-muted));
}
.list-item {
@@ -66,9 +66,9 @@ function addItem() {
align-items: flex-start;
gap: 0.5rem;
padding: 0.5rem;
border: 1px solid hsl(20 8% 14%);
border: 1px solid hsl(var(--color-surface-light));
border-radius: 0.5rem;
background: hsl(20 8% 5%);
background: hsl(var(--color-bg));
}
.list-item > :deep(*:first-child) {
@@ -101,9 +101,9 @@ function addItem() {
gap: 0.375rem;
padding: 0.375rem 0.75rem;
border-radius: 0.5rem;
border: 1px dashed hsl(20 8% 22%);
border: 1px dashed hsl(var(--color-surface-light));
background: none;
color: hsl(20 8% 50%);
color: hsl(var(--color-text-muted));
font-size: 0.8rem;
cursor: pointer;
transition: all 0.2s;
@@ -111,7 +111,7 @@ function addItem() {
}
.list-add:hover {
border-color: hsl(12 76% 48% / 0.4);
color: hsl(12 76% 68%);
border-color: hsl(var(--color-primary) / 0.4);
color: hsl(var(--color-primary));
}
</style>

View File

@@ -36,21 +36,21 @@ const id = computed(() => `field-${props.label.toLowerCase().replace(/\s+/g, '-'
.field-label {
font-size: 0.8rem;
font-weight: 500;
color: hsl(20 8% 60%);
color: hsl(var(--color-text-muted));
}
.field-input {
padding: 0.5rem 0.75rem;
border-radius: 0.5rem;
border: 1px solid hsl(20 8% 18%);
background: hsl(20 8% 6%);
color: white;
border: 1px solid hsl(var(--color-surface-light));
background: hsl(var(--color-bg));
color: hsl(var(--color-text));
font-size: 0.875rem;
transition: border-color 0.2s;
}
.field-input:focus {
outline: none;
border-color: hsl(12 76% 48% / 0.5);
border-color: hsl(var(--color-primary) / 0.5);
}
</style>

View File

@@ -37,15 +37,15 @@ const id = computed(() => `field-${props.label.toLowerCase().replace(/\s+/g, '-'
.field-label {
font-size: 0.8rem;
font-weight: 500;
color: hsl(20 8% 60%);
color: hsl(var(--color-text-muted));
}
.field-textarea {
padding: 0.5rem 0.75rem;
border-radius: 0.5rem;
border: 1px solid hsl(20 8% 18%);
background: hsl(20 8% 6%);
color: white;
border: 1px solid hsl(var(--color-surface-light));
background: hsl(var(--color-bg));
color: hsl(var(--color-text));
font-size: 0.875rem;
resize: vertical;
min-height: 5rem;
@@ -55,6 +55,6 @@ const id = computed(() => `field-${props.label.toLowerCase().replace(/\s+/g, '-'
.field-textarea:focus {
outline: none;
border-color: hsl(12 76% 48% / 0.5);
border-color: hsl(var(--color-primary) / 0.5);
}
</style>

View File

@@ -19,7 +19,7 @@ defineProps<{
<style scoped>
.admin-section {
border: 1px solid hsl(20 8% 14%);
border: 1px solid hsl(var(--color-surface-light));
border-radius: 0.75rem;
overflow: hidden;
margin-bottom: 1rem;
@@ -32,14 +32,14 @@ defineProps<{
padding: 0.75rem 1rem;
font-weight: 600;
font-size: 0.9rem;
color: white;
color: hsl(var(--color-text));
cursor: pointer;
background: hsl(20 8% 6%);
background: hsl(var(--color-bg));
user-select: none;
}
.admin-section-header:hover {
background: hsl(20 8% 8%);
background: hsl(var(--color-surface));
}
.admin-section[open] .section-arrow {

View File

@@ -98,7 +98,7 @@ const renderedHtml = computed(() => {
<style scoped>
.md-editor {
border: 1px solid hsl(20 8% 18%);
border: 1px solid hsl(var(--color-surface-light));
border-radius: 0.5rem;
overflow: hidden;
}
@@ -107,8 +107,8 @@ const renderedHtml = computed(() => {
display: flex;
align-items: center;
justify-content: space-between;
background: hsl(20 8% 6%);
border-bottom: 1px solid hsl(20 8% 14%);
background: hsl(var(--color-bg));
border-bottom: 1px solid hsl(var(--color-surface-light));
}
.md-tabs {
@@ -122,24 +122,24 @@ const renderedHtml = computed(() => {
padding: 0.5rem 0.875rem;
border: none;
background: none;
color: hsl(20 8% 50%);
color: hsl(var(--color-text-muted));
font-size: 0.8rem;
cursor: pointer;
transition: all 0.2s;
}
.md-tab--active {
color: white;
background: hsl(20 8% 10%);
color: hsl(var(--color-text));
background: hsl(var(--color-surface));
}
.md-fullscreen {
padding: 0.5rem 0.75rem;
color: hsl(20 8% 40%);
color: hsl(var(--color-text-muted));
transition: color 0.2s;
}
.md-fullscreen:hover,
.md-fullscreen--active { color: white; }
.md-fullscreen--active { color: hsl(var(--color-text)); }
.md-body {
display: flex;
@@ -151,14 +151,14 @@ const renderedHtml = computed(() => {
}
.md-body--split .md-preview {
border-left: 1px solid hsl(20 8% 14%);
border-left: 1px solid hsl(var(--color-surface-light));
}
.md-body--fullscreen {
position: fixed;
inset: 0;
z-index: 50;
background: hsl(20 8% 4%);
background: hsl(var(--color-bg));
}
.md-body--fullscreen .md-textarea,
@@ -170,8 +170,8 @@ const renderedHtml = computed(() => {
width: 100%;
padding: 1rem;
border: none;
background: hsl(20 8% 4%);
color: white;
background: hsl(var(--color-bg));
color: hsl(var(--color-text));
font-family: var(--font-mono, monospace);
font-size: 0.85rem;
line-height: 1.7;
@@ -189,6 +189,6 @@ const renderedHtml = computed(() => {
min-height: 24rem;
max-height: 70vh;
overflow-y: auto;
background: hsl(20 8% 4%);
background: hsl(var(--color-bg));
}
</style>

View File

@@ -102,18 +102,18 @@ function formatSize(bytes: number): string {
.filter-btn {
padding: 0.25rem 0.625rem;
border-radius: 9999px;
border: 1px solid hsl(20 8% 18%);
border: 1px solid hsl(var(--color-surface-light));
background: none;
color: hsl(20 8% 55%);
color: hsl(var(--color-text-muted));
font-size: 0.75rem;
cursor: pointer;
transition: all 0.2s;
}
.filter-btn--active {
background: hsl(12 76% 48% / 0.15);
border-color: hsl(12 76% 48% / 0.3);
color: hsl(12 76% 68%);
background: hsl(var(--color-primary) / 0.15);
border-color: hsl(var(--color-primary) / 0.3);
color: hsl(var(--color-primary));
}
.media-grid {
@@ -123,7 +123,7 @@ function formatSize(bytes: number): string {
}
.media-card {
border: 1px solid hsl(20 8% 14%);
border: 1px solid hsl(var(--color-surface-light));
border-radius: 0.5rem;
overflow: hidden;
cursor: pointer;
@@ -131,18 +131,18 @@ function formatSize(bytes: number): string {
}
.media-card:hover {
border-color: hsl(20 8% 22%);
border-color: hsl(var(--color-surface-light));
}
.media-card--selected {
border-color: hsl(12 76% 48%);
box-shadow: 0 0 0 1px hsl(12 76% 48% / 0.3);
border-color: hsl(var(--color-primary));
box-shadow: 0 0 0 1px hsl(var(--color-primary) / 0.3);
}
.media-thumb {
aspect-ratio: 1;
overflow: hidden;
background: hsl(20 8% 6%);
background: hsl(var(--color-bg));
}
.media-thumb img {
@@ -156,8 +156,8 @@ function formatSize(bytes: number): string {
display: flex;
align-items: center;
justify-content: center;
background: hsl(20 8% 6%);
color: hsl(20 8% 40%);
background: hsl(var(--color-bg));
color: hsl(var(--color-text-muted));
}
.media-info {
@@ -169,7 +169,7 @@ function formatSize(bytes: number): string {
.media-name {
font-size: 0.72rem;
color: white;
color: hsl(var(--color-text));
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
@@ -177,7 +177,7 @@ function formatSize(bytes: number): string {
.media-size {
font-size: 0.65rem;
color: hsl(20 8% 40%);
color: hsl(var(--color-text-muted));
}
.media-actions {
@@ -186,9 +186,9 @@ function formatSize(bytes: number): string {
justify-content: space-between;
margin-top: 1rem;
padding: 0.75rem;
border: 1px solid hsl(20 8% 14%);
border: 1px solid hsl(var(--color-surface-light));
border-radius: 0.5rem;
background: hsl(20 8% 5%);
background: hsl(var(--color-bg));
}
.delete-btn {

View File

@@ -73,7 +73,7 @@ async function upload(files: FileList) {
<style scoped>
.upload-zone {
border: 2px dashed hsl(20 8% 22%);
border: 2px dashed hsl(var(--color-surface-light));
border-radius: 0.75rem;
padding: 2rem;
text-align: center;
@@ -82,12 +82,12 @@ async function upload(files: FileList) {
}
.upload-zone:hover {
border-color: hsl(12 76% 48% / 0.4);
border-color: hsl(var(--color-primary) / 0.4);
}
.upload-zone--active {
border-color: hsl(12 76% 48%);
background: hsl(12 76% 48% / 0.05);
border-color: hsl(var(--color-primary));
background: hsl(var(--color-primary) / 0.05);
}
.upload-progress {
@@ -95,7 +95,7 @@ async function upload(files: FileList) {
flex-direction: column;
align-items: center;
gap: 0.5rem;
color: hsl(20 8% 55%);
color: hsl(var(--color-text-muted));
font-size: 0.85rem;
}

View File

@@ -31,7 +31,7 @@ defineEmits<{
padding: 0.625rem 1.25rem;
border-radius: 0.5rem;
border: none;
background: hsl(12 76% 48%);
background: hsl(var(--color-primary));
color: white;
font-weight: 600;
font-size: 0.85rem;
@@ -40,7 +40,7 @@ defineEmits<{
}
.save-btn:hover:not(:disabled) {
background: hsl(12 76% 42%);
background: hsl(var(--color-primary));
}
.save-btn:disabled {

View File

@@ -23,13 +23,25 @@
</NuxtLink>
<p class="sidebar-section">Pages</p>
<NuxtLink to="/admin/pages" class="sidebar-link" exact-active-class="sidebar-link--active">
<div class="i-lucide-file-text h-4 w-4" />
Toutes les pages
</NuxtLink>
<NuxtLink to="/admin/pages/home" class="sidebar-link" active-class="sidebar-link--active">
<div class="i-lucide-home h-4 w-4" />
Accueil
</NuxtLink>
<NuxtLink to="/admin/pages/modele-eco" class="sidebar-link" active-class="sidebar-link--active">
<div class="i-lucide-book-open h-4 w-4" />
Modèle éco
<NuxtLink to="/admin/pages/numerique" class="sidebar-link" active-class="sidebar-link--active">
<div class="i-lucide-monitor h-4 w-4" />
Numérique
</NuxtLink>
<NuxtLink to="/admin/pages/economique" class="sidebar-link" active-class="sidebar-link--active">
<div class="i-lucide-coins h-4 w-4" />
Économique
</NuxtLink>
<NuxtLink to="/admin/pages/citoyenne" class="sidebar-link" active-class="sidebar-link--active">
<div class="i-lucide-landmark h-4 w-4" />
Citoyenne
</NuxtLink>
<NuxtLink to="/admin/pages/en-musique" class="sidebar-link" active-class="sidebar-link--active">
<div class="i-lucide-headphones h-4 w-4" />
@@ -79,8 +91,8 @@ async function logout() {
<style scoped>
.admin-sidebar {
background: hsl(20 8% 5%);
border-right: 1px solid hsl(20 8% 14%);
background: hsl(var(--color-bg));
border-right: 1px solid hsl(var(--color-surface-light));
display: flex;
flex-direction: column;
height: 100dvh;
@@ -90,7 +102,7 @@ async function logout() {
.sidebar-header {
padding: 1.25rem 1rem;
border-bottom: 1px solid hsl(20 8% 14%);
border-bottom: 1px solid hsl(var(--color-surface-light));
}
.sidebar-nav {
@@ -104,7 +116,7 @@ async function logout() {
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.1em;
color: hsl(20 8% 40%);
color: hsl(var(--color-text-muted));
padding: 1rem 0.75rem 0.375rem;
margin: 0;
}
@@ -116,7 +128,7 @@ async function logout() {
padding: 0.5rem 0.75rem;
border-radius: 0.5rem;
font-size: 0.85rem;
color: hsl(20 8% 60%);
color: hsl(var(--color-text-muted));
text-decoration: none;
transition: all 0.2s;
border: none;
@@ -126,18 +138,18 @@ async function logout() {
}
.sidebar-link:hover {
background: hsl(20 8% 10%);
color: white;
background: hsl(var(--color-surface));
color: hsl(var(--color-text));
}
.sidebar-link--active {
background: hsl(12 76% 48% / 0.12);
color: hsl(12 76% 68%);
background: hsl(var(--color-primary) / 0.12);
color: hsl(var(--color-primary));
}
.sidebar-footer {
padding: 0.5rem;
border-top: 1px solid hsl(20 8% 14%);
border-top: 1px solid hsl(var(--color-surface-light));
}
@media (max-width: 768px) {

View File

@@ -0,0 +1,33 @@
<template>
<div class="mt-8 flex flex-col gap-3 sm:flex-row sm:flex-wrap sm:gap-4">
<UiBaseButton @click="$emit('open-player')">
<div class="i-lucide-play mr-2 h-5 w-5" />
Présentation musicale
</UiBaseButton>
<UiBaseButton variant="accent" @click="$emit('open-pdf')">
<div class="i-lucide-book-open mr-2 h-5 w-5" />
Lire le livre
</UiBaseButton>
<UiBaseButton v-if="showChapters" variant="ghost" to="/economique/modele-eco">
<div class="i-lucide-list mr-2 h-5 w-5" />
Présentation des chapitres
</UiBaseButton>
<UiBaseButton variant="ghost" to="/economique/commande">
<div class="i-lucide-shopping-bag mr-2 h-5 w-5" />
Commandez le livre
</UiBaseButton>
</div>
</template>
<script setup lang="ts">
withDefaults(defineProps<{
showChapters?: boolean
}>(), {
showChapters: true,
})
defineEmits<{
'open-player': []
'open-pdf': []
}>()
</script>

View File

@@ -447,7 +447,7 @@ onUnmounted(() => {
position: fixed;
inset: 0;
z-index: 60;
background: hsl(20 8% 3%);
background: hsl(var(--color-bg));
display: flex;
flex-direction: column;
align-items: center;
@@ -538,7 +538,7 @@ onUnmounted(() => {
transform: translateX(-50%);
z-index: 10;
font-size: 0.68rem;
color: hsl(20 8% 28%);
color: hsl(var(--color-text-muted));
white-space: nowrap;
}
@@ -582,7 +582,7 @@ onUnmounted(() => {
width: 2.25rem; height: 2.25rem;
border-radius: 0.5rem;
background: transparent;
color: hsl(20 8% 45%);
color: hsl(var(--color-text-muted));
border: none;
cursor: pointer;
transition: all 0.3s;
@@ -617,7 +617,7 @@ onUnmounted(() => {
.reader-bar-pages {
font-family: var(--font-mono, monospace);
font-size: 0.75rem;
color: hsl(20 8% 40%);
color: hsl(var(--color-text-muted));
flex-shrink: 0;
}
@@ -626,16 +626,16 @@ onUnmounted(() => {
position: absolute;
inset: 0;
z-index: 72;
background: hsl(20 8% 3% / 0.6);
background: hsl(var(--color-bg) / 0.6);
backdrop-filter: blur(4px);
display: flex;
}
.sommaire-panel {
width: min(300px, 80vw);
height: 100%;
background: hsl(20 8% 6% / 0.95);
background: hsl(var(--color-surface) / 0.95);
backdrop-filter: blur(16px);
border-right: 1px solid hsl(20 8% 14%);
border-right: 1px solid hsl(var(--color-surface-light));
padding: 1.5rem;
overflow-y: auto;
display: flex;
@@ -646,7 +646,7 @@ onUnmounted(() => {
font-family: var(--font-display, 'Syne', sans-serif);
font-size: 0.85rem;
font-weight: 700;
color: hsl(20 8% 45%);
color: hsl(var(--color-text-muted));
text-transform: uppercase;
letter-spacing: 0.1em;
margin-bottom: 0.75rem;
@@ -658,7 +658,7 @@ onUnmounted(() => {
padding: 0.625rem 0.75rem;
border-radius: 0.5rem;
font-size: 0.85rem;
color: hsl(20 8% 55%);
color: hsl(var(--color-text-muted));
background: transparent;
border: none;
cursor: pointer;
@@ -667,7 +667,7 @@ onUnmounted(() => {
width: 100%;
}
.sommaire-item:hover {
background: hsl(20 8% 12%);
background: hsl(var(--color-surface));
color: white;
}
.sommaire-item--active {
@@ -683,8 +683,8 @@ onUnmounted(() => {
font-size: 0.65rem;
font-weight: 700;
font-family: var(--font-mono, monospace);
color: hsl(20 8% 40%);
background: hsl(20 8% 12%);
color: hsl(var(--color-text-muted));
background: hsl(var(--color-surface));
flex-shrink: 0;
}
.sommaire-item--active .sommaire-num {
@@ -713,7 +713,7 @@ onUnmounted(() => {
width: 100%;
overflow: hidden auto;
border-radius: 0.75rem;
background: hsl(20 8% 5% / 0.4);
background: hsl(var(--color-bg) / 0.4);
backdrop-filter: blur(16px);
-webkit-backdrop-filter: blur(16px);
}
@@ -809,7 +809,7 @@ onUnmounted(() => {
height: 100%;
pointer-events: none;
opacity: 0;
background: linear-gradient(to left, hsl(20 8% 3% / 0.4), transparent);
background: linear-gradient(to left, hsl(var(--color-bg) / 0.4), transparent);
transition: opacity 0.15s;
border-radius: 0 0.75rem 0.75rem 0;
}
@@ -833,7 +833,7 @@ onUnmounted(() => {
width: 2.5rem; height: 2.5rem;
border-radius: 50%;
background: transparent;
color: hsl(20 8% 45%);
color: hsl(var(--color-text-muted));
border: none;
cursor: pointer;
transition: all 0.3s;
@@ -861,7 +861,7 @@ onUnmounted(() => {
border-radius: 50%;
overflow: hidden;
flex-shrink: 0;
border: 2px solid hsl(20 8% 22%);
border: 2px solid hsl(var(--color-surface-light));
box-shadow: 0 0 10px hsl(var(--scene-h1) 50% 40% / 0.15);
}
.reader-disc.spinning {
@@ -878,12 +878,12 @@ onUnmounted(() => {
transform: translate(-50%, -50%);
width: 0.5rem; height: 0.5rem;
border-radius: 50%;
background: hsl(20 8% 5%);
border: 1.5px solid hsl(20 8% 18%);
background: hsl(var(--color-bg));
border: 1.5px solid hsl(var(--color-surface-light));
}
.reader-song-name {
font-size: 0.75rem;
color: hsl(20 8% 50%);
color: hsl(var(--color-text-muted));
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;

View File

@@ -6,7 +6,7 @@
<ul class="flex flex-col gap-1">
<li v-for="chapter in chapters" :key="chapter.path">
<NuxtLink
:to="`/modele-eco/${chapter.stem?.split('/').pop()}`"
:to="`/economique/modele-eco/${chapter.stem?.split('/').pop()}`"
class="flex items-center gap-3 rounded-lg px-3 py-2 text-sm transition-colors hover:bg-white/5"
active-class="bg-primary/10 text-primary font-medium"
>

View File

@@ -37,6 +37,15 @@
</h3>
<p class="text-sm text-white/60 leading-relaxed">{{ item.description }}</p>
<!-- Presentation inline (projet en gestation) -->
<div v-if="item.presentation" class="axis-presentation-inline">
<div class="axis-pi-header">
<div class="i-lucide-sparkles h-3 w-3" />
<span class="axis-pi-title">{{ item.presentation.title }}</span>
</div>
<p class="axis-pi-text">{{ item.presentation.text }}</p>
</div>
</component>
<!-- Actions zone (separate from card link) -->
@@ -56,15 +65,17 @@
</div>
<!-- Secondary row -->
<div v-if="secondaryActions(item.actions).length" class="axis-actions-secondary">
<button
<component
:is="action.to ? resolveComponent('NuxtLink') : 'button'"
v-for="action in secondaryActions(item.actions)"
:key="action.id"
:key="action.label"
:to="action.to"
class="axis-action-btn axis-action-btn--secondary"
@click.stop="handleAction(action.id)"
@click.stop="!action.to && handleAction(action.id)"
>
<div :class="iconClass(action.icon)" class="h-3.5 w-3.5" />
{{ action.label }}
</button>
</component>
</div>
</div>
</div>
@@ -79,6 +90,7 @@ interface AxisAction {
icon: string
highlight?: boolean
secondary?: boolean
to?: string
}
interface AxisItem {
@@ -170,7 +182,7 @@ function itemAttrs(item: AxisItem) {
border: 1px solid hsl(var(--color-text) / 0.08);
background: hsl(var(--color-surface));
transition: border-color 0.2s, box-shadow 0.2s;
overflow: hidden;
/* overflow: visible pour laisser le tooltip sortir du cadre */
}
.axis-item:hover {
@@ -245,6 +257,8 @@ function itemAttrs(item: AxisItem) {
gap: 0;
border-top: 1px solid hsl(var(--color-text) / 0.06);
background: hsl(var(--color-bg) / 0.4);
border-bottom-left-radius: 0.75rem;
border-bottom-right-radius: 0.75rem;
}
.axis-actions-row {
@@ -306,4 +320,34 @@ function itemAttrs(item: AxisItem) {
background: hsl(var(--color-accent) / 0.08);
border-color: hsl(var(--color-accent) / 0.2);
}
/* Presentation inline — projet en gestation, affiché dans la card */
.axis-presentation-inline {
margin-top: 0.75rem;
padding: 0.5rem 0.75rem;
border-radius: 8px;
background: hsl(var(--color-accent) / 0.07);
border: 1px solid hsl(var(--color-accent) / 0.18);
}
.axis-pi-header {
display: flex;
align-items: center;
gap: 0.3rem;
margin-bottom: 0.2rem;
color: hsl(var(--color-accent));
}
.axis-pi-title {
font-size: 0.75rem;
font-weight: 700;
letter-spacing: 0.02em;
}
.axis-pi-text {
font-size: 0.72rem;
color: hsl(var(--color-text) / 0.55);
line-height: 1.45;
margin: 0;
}
</style>

View File

@@ -25,6 +25,24 @@
@open-pdf="$emit('open-pdf')"
@launch-gratewizard="launchGW"
/>
<!-- Bloc GrateWizard dans la section économique -->
<button v-if="gw" class="gw-block mt-4" @click="launchGW">
<div class="gw-icon">
<div class="i-lucide-sparkles h-5 w-5" />
</div>
<div class="gw-text">
<h3 class="font-display text-base font-bold text-white sm:text-lg">
{{ gw.title }}
</h3>
<p class="text-white/55 text-xs sm:text-sm mt-0.5">
{{ gw.subtitle }}
</p>
</div>
<div class="gw-arrow">
<div class="i-lucide-arrow-up-right h-4 w-4" />
</div>
</button>
</div>
</UiScrollReveal>
@@ -41,7 +59,7 @@
</UiScrollReveal>
<!-- Bloc Événement -->
<UiScrollReveal v-if="evenement" :delay="300">
<UiScrollReveal v-if="evenement" :delay="350">
<NuxtLink :to="evenement.to" class="event-block">
<div class="event-content">
<div class="event-icon">
@@ -76,6 +94,7 @@ const { data: content } = await usePageContent('home')
const { launch } = useGrateWizard()
const axes = computed(() => (content.value as any)?.axes)
const gw = computed(() => (content.value as any)?.gratewizard)
const evenement = computed(() => (content.value as any)?.evenement)
function launchGW() {
@@ -135,11 +154,84 @@ function launchGW() {
flex-shrink: 0;
}
/* GrateWizard block */
.gw-block {
display: flex;
align-items: center;
gap: 1rem;
width: 100%;
padding: 1.25rem 1.5rem;
border-radius: 0.75rem;
border: none;
background: linear-gradient(135deg, hsl(280 50% 20% / 0.35), hsl(260 40% 15% / 0.25));
box-shadow: 0 0 40px hsl(280 60% 50% / 0.06), inset 0 1px 0 hsl(280 60% 70% / 0.08);
cursor: pointer;
transition: all 0.3s ease;
text-align: left;
color: inherit;
}
.gw-block:hover {
background: linear-gradient(135deg, hsl(280 50% 24% / 0.45), hsl(260 40% 18% / 0.35));
box-shadow: 0 0 60px hsl(280 60% 50% / 0.12), inset 0 1px 0 hsl(280 60% 70% / 0.12);
transform: translateY(-2px);
}
.gw-block:active {
transform: translateY(0);
}
.gw-icon {
display: flex;
align-items: center;
justify-content: center;
width: 2.5rem;
height: 2.5rem;
border-radius: 0.625rem;
background: hsl(280 60% 55% / 0.18);
color: hsl(280 60% 72%);
flex-shrink: 0;
box-shadow: 0 0 20px hsl(280 60% 50% / 0.15);
}
.gw-text {
flex: 1;
min-width: 0;
}
.gw-arrow {
display: flex;
align-items: center;
justify-content: center;
width: 2rem;
height: 2rem;
border-radius: 50%;
background: hsl(280 60% 55% / 0.1);
color: hsl(280 60% 65%);
flex-shrink: 0;
transition: all 0.2s;
}
.gw-block:hover .gw-arrow {
background: hsl(280 60% 55% / 0.2);
color: hsl(280 60% 80%);
transform: translate(2px, -2px);
}
@media (max-width: 640px) {
.event-block {
flex-direction: column;
align-items: flex-start;
padding: 1.5rem;
}
.gw-block {
padding: 1.25rem;
gap: 1rem;
}
.gw-arrow {
display: none;
}
}
</style>

View File

@@ -5,23 +5,6 @@
<!-- Book cover -->
<UiScrollReveal>
<div class="book-cover-wrapper relative">
<!-- Shadok pumper -->
<svg class="shadok-pumper" viewBox="0 0 200 240" fill="none" aria-hidden="true">
<ellipse cx="100" cy="130" rx="55" ry="65" fill="currentColor" opacity="0.9"/>
<ellipse cx="100" cy="60" rx="30" ry="28" fill="currentColor" opacity="0.85"/>
<circle cx="88" cy="54" r="6" fill="currentColor" opacity="0.2"/>
<circle cx="112" cy="54" r="6" fill="currentColor" opacity="0.2"/>
<circle cx="90" cy="53" r="2.5" fill="currentColor" opacity="0.5"/>
<circle cx="114" cy="53" r="2.5" fill="currentColor" opacity="0.5"/>
<polygon points="100,68 115,78 85,78" fill="currentColor" opacity="0.6"/>
<line x1="80" y1="192" x2="70" y2="230" stroke="currentColor" stroke-width="4" stroke-linecap="round" opacity="0.7"/>
<line x1="120" y1="192" x2="130" y2="230" stroke="currentColor" stroke-width="4" stroke-linecap="round" opacity="0.7"/>
<line x1="70" y1="230" x2="55" y2="232" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.5"/>
<line x1="130" y1="230" x2="145" y2="232" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.5"/>
<line x1="155" y1="110" x2="190" y2="90" stroke="currentColor" stroke-width="4" stroke-linecap="round" opacity="0.6"/>
<line x1="190" y1="90" x2="190" y2="120" stroke="currentColor" stroke-width="4" stroke-linecap="round" opacity="0.6"/>
<rect x="180" y="118" width="18" height="40" rx="3" fill="currentColor" opacity="0.4"/>
</svg>
<div class="book-cover-3d">
<img
:src="content?.book.coverImage"
@@ -48,16 +31,11 @@
</UiScrollReveal>
<UiScrollReveal :delay="200">
<div class="mt-8 flex flex-col gap-3 sm:flex-row sm:gap-4">
<UiBaseButton @click="$emit('open-player')">
<div class="i-lucide-play mr-2 h-5 w-5" />
{{ content?.book.cta.player }}
</UiBaseButton>
<UiBaseButton variant="accent" @click="$emit('open-pdf')">
<div class="i-lucide-book-open mr-2 h-5 w-5" />
{{ content?.book.cta.pdf }}
</UiBaseButton>
</div>
<BookActions
:show-chapters="showChapters"
@open-player="$emit('open-player')"
@open-pdf="$emit('open-pdf')"
/>
</UiScrollReveal>
</div>
</div>
@@ -66,6 +44,12 @@
</template>
<script setup lang="ts">
withDefaults(defineProps<{
showChapters?: boolean
}>(), {
showChapters: true,
})
defineEmits<{
'open-player': []
'open-pdf': []
@@ -111,25 +95,4 @@ const { data: content } = await usePageContent('home')
.heading-section {
font-size: clamp(1.625rem, 4vw, 2.125rem);
}
.shadok-pumper {
position: absolute;
right: 3%;
bottom: 8%;
width: clamp(90px, 12vw, 180px);
opacity: 0.28;
pointer-events: none;
color: hsl(var(--color-primary));
animation: shadok-float 10s ease-in-out infinite;
z-index: 1;
}
@keyframes shadok-float {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-10px); }
}
@media (max-width: 768px) {
.shadok-pumper { display: none; }
}
</style>

View File

@@ -65,6 +65,8 @@ const hero = computed(() => {
citations: Array.isArray(raw.citations) ? raw.citations : [],
approach: raw.approach || '',
axes: Array.isArray(raw.axes) ? raw.axes : [],
audience: raw.audience || '',
addressees: Array.isArray(raw.addressees) ? raw.addressees : [],
}
})
</script>

View File

@@ -1,46 +1,48 @@
<template>
<section class="section-padding">
<div class="container-content mx-auto max-w-3xl">
<!-- Formulaire -->
<UiScrollReveal>
<div class="message-form-card">
<h3 class="font-display text-lg font-bold text-white mb-4">Laisser un message</h3>
<h3 class="font-display text-lg font-bold form-title mb-4">Laisser un message</h3>
<form v-if="!submitted" class="space-y-3" @submit.prevent="send">
<div class="grid gap-3 sm:grid-cols-2">
<input
v-model="form.author"
type="text"
placeholder="Votre nom *"
required
placeholder="Votre nom"
class="msg-input"
/>
<input
v-model="form.email"
type="email"
placeholder="Email (optionnel)"
class="msg-input"
/>
</div>
<p class="hint-text text-xs -mt-1 px-1">Pour recevoir une réponse, laissez votre e-mail dans le message.</p>
<select v-model="form.type" class="msg-input msg-input--select">
<option value="question">Question</option>
<option value="suggestion">Suggestion</option>
<option value="retour">Retour d'expérience</option>
</select>
<textarea
v-model="form.text"
placeholder="Votre message *"
required
placeholder="Votre message"
rows="3"
class="msg-input resize-none"
/>
<div class="flex justify-end">
<button type="submit" class="btn-primary text-sm" :disabled="sending">
<button type="submit" class="btn-primary text-sm" :disabled="sending || !canSend">
<div v-if="sending" class="i-lucide-loader-2 h-4 w-4 animate-spin mr-2" />
Envoyer
</button>
</div>
</form>
<div v-else class="text-center py-4">
<div class="i-lucide-check-circle h-8 w-8 text-green-400 mx-auto mb-2" />
<p class="text-white/80">Merci pour votre message !</p>
<p class="text-white/40 text-sm mt-1">Il sera visible après modération.</p>
<div v-else class="text-center py-6">
<div class="i-lucide-heart-handshake h-10 w-10 text-primary mx-auto mb-3" />
<p class="msg-confirm-title font-display text-lg font-semibold">Merci pour votre message !</p>
<p class="msg-confirm-sub text-sm mt-2 max-w-md mx-auto leading-relaxed">
Il sera lu et traité dans un délai... humainement raisonnable.
</p>
<p class="msg-confirm-note text-xs mt-4 italic">
Chaque message est un premier pas dans l'aventure.
</p>
</div>
</div>
</UiScrollReveal>
@@ -48,13 +50,26 @@
<!-- 2 derniers messages publiés -->
<UiScrollReveal v-if="messages?.length" :delay="100">
<div class="mt-8 space-y-4">
<h3 class="font-display text-lg font-bold text-white/80 text-center">Derniers messages</h3>
<h3 class="font-display text-lg font-bold section-title text-center">Derniers messages</h3>
<div v-for="msg in messages.slice(0, 2)" :key="msg.id" class="message-card">
<p class="text-white/80 text-sm leading-relaxed">{{ msg.text }}</p>
<div class="mt-2 flex items-center gap-2 text-xs text-white/40">
<span class="font-semibold text-white/60">{{ msg.author }}</span>
<span>&middot;</span>
<span>{{ formatDate(msg.createdAt) }}</span>
<!-- En-tête auteur -->
<div class="flex items-center gap-2 mb-2">
<span class="msg-author font-semibold text-sm">{{ msg.author }}</span>
<span class="type-pill">{{ typeLabel(msg.type) }}</span>
<span class="msg-date text-xs ml-auto">{{ formatDate(msg.createdAt) }}</span>
</div>
<!-- Texte du message -->
<p class="msg-text text-sm leading-relaxed">{{ msg.text }}</p>
<!-- Réponse -->
<div v-if="msg.reply?.text" class="reply-thread">
<div class="reply-connector" aria-hidden="true" />
<div class="reply-block">
<div class="flex items-center gap-1.5 mb-1">
<div class="i-lucide-corner-down-right h-3 w-3 reply-icon" />
<span class="reply-author text-xs font-semibold">Le Librodrome</span>
</div>
<p class="reply-text text-sm leading-relaxed italic">{{ msg.reply.text }}</p>
</div>
</div>
</div>
<div class="text-center">
@@ -72,11 +87,25 @@
<script setup lang="ts">
const { data: messages } = await useFetch('/api/messages')
const form = reactive({ author: '', email: '', text: '' })
const form = reactive({ author: '', text: '', type: 'question' })
const sending = ref(false)
const submitted = ref(false)
const canSend = computed(() => form.text.trim().length > 0)
const TYPE_LABELS: Record<string, string> = {
reaction: 'Réaction',
question: 'Question',
suggestion: 'Suggestion',
retour: 'Retour',
}
function typeLabel(type: string) {
return TYPE_LABELS[type] ?? type
}
async function send() {
if (!canSend.value) return
sending.value = true
try {
await $fetch('/api/messages', { method: 'POST', body: form })
@@ -107,6 +136,21 @@ function formatDate(iso: string) {
</script>
<style scoped>
/* ── Couleurs adaptatives (dark + light) ── */
.form-title { color: hsl(var(--color-text)); }
.hint-text { color: hsl(var(--color-text) / 0.35); }
.section-title { color: hsl(var(--color-text) / 0.85); }
.msg-author { color: hsl(var(--color-text) / 0.75); }
.msg-date { color: hsl(var(--color-text) / 0.38); }
.msg-text { color: hsl(var(--color-text) / 0.78); white-space: pre-line; }
.msg-confirm-title { color: hsl(var(--color-text) / 0.95); }
.msg-confirm-sub { color: hsl(var(--color-text) / 0.6); }
.msg-confirm-note { color: hsl(var(--color-primary) / 0.65); }
.reply-icon { color: hsl(var(--color-primary) / 0.5); }
.reply-author { color: hsl(var(--color-primary) / 0.8); }
.reply-text { color: hsl(var(--color-text) / 0.62); }
/* ── Carte formulaire ── */
.message-form-card {
background: hsl(var(--color-surface));
border: 1px solid hsl(var(--color-text) / 0.1);
@@ -122,9 +166,15 @@ function formatDate(iso: string) {
background: hsl(var(--color-bg));
color: hsl(var(--color-text));
font-size: 0.875rem;
font-family: inherit;
transition: border-color 0.2s;
}
.msg-input--select {
width: auto;
cursor: pointer;
}
.msg-input::placeholder {
color: hsl(var(--color-text) / 0.35);
}
@@ -134,10 +184,53 @@ function formatDate(iso: string) {
border-color: hsl(var(--color-primary) / 0.5);
}
/* ── Carte message ── */
.message-card {
background: hsl(var(--color-surface));
border: 1px solid hsl(var(--color-text) / 0.1);
border-radius: 0.75rem;
padding: 1rem 1.25rem;
}
.type-pill {
font-size: 0.6rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
padding: 0.1rem 0.45rem;
border-radius: 9999px;
background: hsl(var(--color-primary) / 0.1);
color: hsl(var(--color-primary) / 0.7);
}
/* ── Réponse avec liaison graphique ── */
.reply-thread {
position: relative;
display: flex;
gap: 0;
margin-top: 0.75rem;
padding-left: 1.25rem;
}
.reply-connector {
position: absolute;
left: 0.5rem;
top: -0.5rem;
bottom: 0.5rem;
width: 2px;
background: linear-gradient(
to bottom,
hsl(var(--color-primary) / 0.35),
hsl(var(--color-primary) / 0.15)
);
border-radius: 2px;
}
.reply-block {
flex: 1;
background: hsl(var(--color-primary) / 0.05);
border-left: 2px solid hsl(var(--color-primary) / 0.25);
border-radius: 0 0.5rem 0.5rem 0;
padding: 0.6rem 0.875rem;
}
</style>

View File

@@ -34,6 +34,17 @@
</div>
</dl>
</div>
<!-- Audience + Addressees -->
<div v-if="hero.audience" class="hero-approach hero-audience">
<p class="hero-approach-text">{{ hero.audience }}</p>
<dl v-if="hero.addressees?.length" class="hero-axes">
<div v-for="(item, i) in hero.addressees" :key="i" class="hero-axis">
<dt>{{ item.label }}</dt>
<dd>{{ item.value }}</dd>
</div>
</dl>
</div>
</div>
</details>
</div>
@@ -50,6 +61,8 @@ interface HeroData {
citations: string[]
approach: string
axes: HeroAxis[]
audience: string
addressees: HeroAxis[]
}
defineProps<{
@@ -213,6 +226,12 @@ defineProps<{
text-align: center;
}
.hero-audience {
margin-top: 1rem;
padding-top: 0.75rem;
border-top: 1px solid hsl(var(--color-text) / 0.07);
}
.hero-approach-text {
font-family: var(--font-sans);
font-size: 0.82rem;

View File

@@ -1,19 +1,10 @@
export function useGrateWizard() {
const appConfig = useAppConfig()
const { url, popup } = appConfig.gratewizard as { url: string; popup: { width: number; height: number } }
const { url } = appConfig.gratewizard as { url: string; popup: { width: number; height: number } }
function launch(e?: Event) {
const w = popup.width
const h = popup.height
const left = Math.round((window.screen.width - w) / 2)
const top = Math.round((window.screen.height - h) / 2)
const embedUrl = `${url}?embed=true&hideTabBar=true&tab=mn`
const win = window.open(
embedUrl,
'grateWizard',
`width=${w},height=${h},left=${left},top=${top},menubar=no,toolbar=no,location=no,status=no,scrollbars=no,resizable=yes`,
)
if (win) e?.preventDefault()
window.open(url, '_blank', 'noopener,noreferrer')
e?.preventDefault()
}
return { url, launch }

View File

@@ -0,0 +1,20 @@
/**
* Umami analytics wrapper — safe server-side, no-op when not configured.
* Usage: const { track } = useTracking()
* track('player:open')
* track('axis:navigate', { axis: 'numerique' })
*/
export function useTracking() {
const runtimeConfig = useRuntimeConfig()
const enabled = !!runtimeConfig.public.umamiWebsiteId
function track(event: string, data?: Record<string, unknown>) {
if (!import.meta.client || !enabled) return
const umami = (window as Record<string, unknown>).umami as
| { track: (event: string, data?: unknown) => void }
| undefined
umami?.track(event, data)
}
return { track, enabled }
}

View File

@@ -209,7 +209,7 @@ async function save() {
.field-label {
display: block;
font-size: 0.75rem;
color: hsl(20 8% 50%);
color: hsl(var(--color-text-muted));
margin-bottom: 0.25rem;
}
@@ -217,15 +217,15 @@ async function save() {
width: 100%;
padding: 0.5rem 0.625rem;
border-radius: 0.375rem;
border: 1px solid hsl(20 8% 18%);
background: hsl(20 8% 6%);
color: white;
border: 1px solid hsl(var(--color-surface-light));
background: hsl(var(--color-bg));
color: hsl(var(--color-text));
font-size: 0.85rem;
}
.field-input:focus {
outline: none;
border-color: hsl(12 76% 48% / 0.5);
border-color: hsl(var(--color-primary) / 0.5);
}
/* ── Song tags ── */
@@ -233,45 +233,45 @@ async function save() {
display: inline-flex;
align-items: center;
border-radius: 9999px;
border: 1px solid hsl(20 8% 22%);
border: 1px solid hsl(var(--color-surface-light));
transition: all 0.15s;
overflow: hidden;
}
.song-tag:hover {
border-color: hsl(12 76% 48% / 0.4);
border-color: hsl(var(--color-primary) / 0.4);
}
.song-tag--active {
border-color: hsl(12 76% 48% / 0.6);
background: hsl(12 76% 48% / 0.08);
border-color: hsl(var(--color-primary) / 0.6);
background: hsl(var(--color-primary) / 0.08);
}
.song-tag--primary {
border-color: hsl(45 90% 55%);
background: hsl(45 90% 55% / 0.08);
border-color: hsl(var(--color-accent));
background: hsl(var(--color-accent) / 0.08);
}
.song-tag-label {
padding: 0.375rem 0.75rem;
background: none;
border: none;
color: hsl(20 8% 50%);
color: hsl(var(--color-text-muted));
font-size: 0.8rem;
cursor: pointer;
transition: color 0.15s;
}
.song-tag--active .song-tag-label {
color: hsl(12 76% 68%);
color: hsl(var(--color-primary));
}
.song-tag--primary .song-tag-label {
color: hsl(45 90% 65%);
color: hsl(var(--color-accent));
}
.song-tag-label:hover {
color: white;
color: hsl(var(--color-text));
}
.song-star {
@@ -281,16 +281,16 @@ async function save() {
padding: 0.375rem 0 0.375rem 0.625rem;
background: none;
border: none;
color: hsl(20 8% 30%);
color: hsl(var(--color-text-muted));
cursor: pointer;
transition: color 0.15s;
}
.song-star:hover {
color: hsl(45 90% 55%);
color: hsl(var(--color-accent));
}
.song-star--active {
color: hsl(45 90% 55%);
color: hsl(var(--color-accent));
}
</style>

View File

@@ -249,9 +249,9 @@ async function removeChapter(slug: string) {
.pdf-section {
margin-bottom: 1.5rem;
padding: 1rem;
border: 1px solid hsl(20 8% 14%);
border: 1px solid hsl(var(--color-surface-light));
border-radius: 0.5rem;
background: hsl(20 8% 5%);
background: hsl(var(--color-bg));
}
.save-pdf-btn {
@@ -260,9 +260,9 @@ async function removeChapter(slug: string) {
gap: 0.5rem;
padding: 0.5rem 1rem;
border-radius: 0.5rem;
border: 1px solid hsl(20 8% 25%);
border: 1px solid hsl(var(--color-surface-light));
background: none;
color: hsl(20 8% 55%);
color: hsl(var(--color-text-muted));
font-size: 0.8rem;
cursor: pointer;
transition: all 0.15s;
@@ -270,8 +270,8 @@ async function removeChapter(slug: string) {
}
.save-pdf-btn:hover:not(:disabled) {
border-color: hsl(12 76% 48% / 0.5);
color: hsl(12 76% 68%);
border-color: hsl(var(--color-primary) / 0.5);
color: hsl(var(--color-primary));
}
.save-pdf-btn:disabled {
@@ -284,14 +284,14 @@ async function removeChapter(slug: string) {
align-items: center;
gap: 0.75rem;
padding: 0.75rem 1rem;
border: 1px solid hsl(20 8% 14%);
border: 1px solid hsl(var(--color-surface-light));
border-radius: 0.5rem;
transition: all 0.2s;
}
.chapter-item:hover {
border-color: hsl(12 76% 48% / 0.3);
background: hsl(20 8% 6%);
border-color: hsl(var(--color-primary) / 0.3);
background: hsl(var(--color-bg));
}
.chapter-item--dragging {
@@ -299,13 +299,13 @@ async function removeChapter(slug: string) {
}
.chapter-item--over {
border-top: 2px solid hsl(12 76% 48%);
border-top: 2px solid hsl(var(--color-primary));
}
.drag-handle {
cursor: grab;
padding: 0.25rem;
color: hsl(20 8% 35%);
color: hsl(var(--color-text-muted));
flex-shrink: 0;
}
@@ -316,7 +316,7 @@ async function removeChapter(slug: string) {
.chapter-order {
font-family: var(--font-mono, monospace);
font-size: 0.85rem;
color: hsl(12 76% 48% / 0.5);
color: hsl(var(--color-primary) / 0.5);
font-weight: 600;
width: 1.75rem;
}
@@ -328,13 +328,13 @@ async function removeChapter(slug: string) {
.chapter-title {
display: block;
color: white;
color: hsl(var(--color-text));
font-weight: 500;
text-decoration: none;
}
.chapter-title:hover {
color: hsl(12 76% 68%);
color: hsl(var(--color-primary));
}
.chapter-songs {
@@ -348,9 +348,9 @@ async function removeChapter(slug: string) {
font-size: 0.65rem;
padding: 0.1rem 0.5rem;
border-radius: 9999px;
background: hsl(12 76% 48% / 0.1);
color: hsl(12 76% 60%);
border: 1px solid hsl(12 76% 48% / 0.2);
background: hsl(var(--color-primary) / 0.1);
color: hsl(var(--color-primary));
border: 1px solid hsl(var(--color-primary) / 0.2);
}
.page-select {
@@ -358,16 +358,16 @@ async function removeChapter(slug: string) {
max-width: 14rem;
padding: 0.25rem 0.4rem;
border-radius: 0.375rem;
border: 1px solid hsl(20 8% 18%);
background: hsl(20 8% 6%);
color: hsl(20 8% 65%);
border: 1px solid hsl(var(--color-surface-light));
background: hsl(var(--color-bg));
color: hsl(var(--color-text-muted));
font-size: 0.7rem;
cursor: pointer;
}
.page-select:focus {
outline: none;
border-color: hsl(12 76% 48% / 0.5);
border-color: hsl(var(--color-primary) / 0.5);
}
.delete-btn {
@@ -388,15 +388,15 @@ async function removeChapter(slug: string) {
.admin-input {
padding: 0.375rem 0.5rem;
border-radius: 0.375rem;
border: 1px solid hsl(20 8% 18%);
background: hsl(20 8% 6%);
color: white;
border: 1px solid hsl(var(--color-surface-light));
background: hsl(var(--color-bg));
color: hsl(var(--color-text));
font-size: 0.8rem;
}
.admin-input:focus {
outline: none;
border-color: hsl(12 76% 48% / 0.5);
border-color: hsl(var(--color-primary) / 0.5);
}
.add-btn {
@@ -405,9 +405,9 @@ async function removeChapter(slug: string) {
gap: 0.5rem;
padding: 0.5rem 1rem;
border-radius: 0.5rem;
border: 1px solid hsl(20 8% 25%);
border: 1px solid hsl(var(--color-surface-light));
background: none;
color: hsl(20 8% 55%);
color: hsl(var(--color-text-muted));
font-size: 0.85rem;
cursor: pointer;
transition: all 0.15s;
@@ -415,8 +415,8 @@ async function removeChapter(slug: string) {
}
.add-btn:hover:not(:disabled) {
border-color: hsl(12 76% 48% / 0.5);
color: hsl(12 76% 68%);
border-color: hsl(var(--color-primary) / 0.5);
color: hsl(var(--color-primary));
}
.add-btn:disabled {

View File

@@ -9,8 +9,8 @@
<p class="text-sm text-white/50">Identité, navigation, footer</p>
</NuxtLink>
<NuxtLink to="/admin/pages/home" class="dash-card">
<div class="i-lucide-home h-8 w-8 text-primary mb-2" />
<NuxtLink to="/admin/pages" class="dash-card">
<div class="i-lucide-file-text h-8 w-8 text-primary mb-2" />
<h2 class="text-lg font-semibold text-white">Pages</h2>
<p class="text-sm text-white/50">Contenus des pages publiques</p>
</NuxtLink>
@@ -27,6 +27,12 @@
<p class="text-sm text-white/50">Métadonnées des pistes</p>
</NuxtLink>
<NuxtLink to="/admin/messages" class="dash-card">
<div class="i-lucide-message-square h-8 w-8 text-accent mb-2" />
<h2 class="text-lg font-semibold text-white">Messages</h2>
<p class="text-sm text-white/50">Modération des messages visiteurs</p>
</NuxtLink>
<NuxtLink to="/admin/media" class="dash-card">
<div class="i-lucide-image h-8 w-8 text-accent mb-2" />
<h2 class="text-lg font-semibold text-white">Médias</h2>
@@ -47,14 +53,14 @@ definePageMeta({
.dash-card {
padding: 1.5rem;
border-radius: 0.75rem;
border: 1px solid hsl(20 8% 14%);
background: hsl(20 8% 6%);
border: 1px solid hsl(var(--color-surface-light));
background: hsl(var(--color-bg));
text-decoration: none;
transition: all 0.2s;
}
.dash-card:hover {
border-color: hsl(12 76% 48% / 0.3);
background: hsl(20 8% 8%);
border-color: hsl(var(--color-primary) / 0.3);
background: hsl(var(--color-surface));
}
</style>

View File

@@ -72,16 +72,16 @@ async function login() {
display: flex;
align-items: center;
justify-content: center;
background: hsl(20 8% 3.5%);
background: hsl(var(--color-bg));
}
.login-form {
width: 100%;
max-width: 24rem;
padding: 2.5rem;
border: 1px solid hsl(20 8% 14%);
border: 1px solid hsl(var(--color-surface-light));
border-radius: 1rem;
background: hsl(20 8% 6%);
background: hsl(var(--color-bg));
}
.login-error {
@@ -97,7 +97,7 @@ async function login() {
display: block;
font-size: 0.8rem;
font-weight: 500;
color: hsl(20 8% 60%);
color: hsl(var(--color-text-muted));
margin-bottom: 0.375rem;
}
@@ -105,16 +105,16 @@ async function login() {
width: 100%;
padding: 0.625rem 0.75rem;
border-radius: 0.5rem;
border: 1px solid hsl(20 8% 18%);
background: hsl(20 8% 4%);
color: white;
border: 1px solid hsl(var(--color-surface-light));
background: hsl(var(--color-bg));
color: hsl(var(--color-text));
font-size: 0.9rem;
margin-bottom: 1.25rem;
}
.login-input:focus {
outline: none;
border-color: hsl(12 76% 48% / 0.5);
border-color: hsl(var(--color-primary) / 0.5);
}
.login-btn {
@@ -126,7 +126,7 @@ async function login() {
padding: 0.625rem;
border-radius: 0.5rem;
border: none;
background: hsl(12 76% 48%);
background: hsl(var(--color-primary));
color: white;
font-weight: 600;
font-size: 0.9rem;
@@ -135,7 +135,7 @@ async function login() {
}
.login-btn:hover:not(:disabled) {
background: hsl(12 76% 42%);
background: hsl(var(--color-primary));
}
.login-btn:disabled {
@@ -147,12 +147,12 @@ async function login() {
margin-top: 1rem;
text-align: center;
font-size: 0.75rem;
color: hsl(20 8% 40%);
color: hsl(var(--color-text-muted));
}
.dev-hint code {
color: hsl(12 76% 60%);
background: hsl(20 8% 10%);
color: hsl(var(--color-primary));
background: hsl(var(--color-surface));
padding: 0.125rem 0.375rem;
border-radius: 0.25rem;
}

View File

@@ -5,7 +5,7 @@
<span class="text-sm text-white/40">{{ messages?.length || 0 }} message(s)</span>
</div>
<div v-if="messages?.length" class="space-y-3">
<div v-if="messages?.length" class="space-y-4">
<div
v-for="msg in messages"
:key="msg.id"
@@ -17,6 +17,9 @@
<div class="flex items-center gap-2 min-w-0">
<span class="font-semibold text-white text-sm truncate">{{ msg.author }}</span>
<span v-if="msg.email" class="text-white/30 text-xs truncate">{{ msg.email }}</span>
<span class="type-badge" :class="`type-badge--${msg.type || 'reaction'}`">
{{ typeLabel(msg.type) }}
</span>
</div>
<div class="flex items-center gap-2 shrink-0">
<span class="text-xs text-white/30">{{ formatDate(msg.createdAt) }}</span>
@@ -29,23 +32,54 @@
</div>
</div>
<!-- Texte éditable -->
<div v-if="editing === msg.id" class="mb-3">
<input
v-model="editForm.author"
class="admin-input mb-2 w-full"
placeholder="Auteur"
/>
<textarea
v-model="editForm.text"
class="admin-input w-full"
rows="3"
/>
<!-- Texte du message -->
<div v-if="editing === msg.id" class="mb-3 space-y-2">
<input v-model="editForm.author" class="admin-input w-full" placeholder="Auteur" />
<select v-model="editForm.type" class="admin-input w-full">
<option value="question">Question</option>
<option value="suggestion">Suggestion</option>
<option value="retour">Retour d'expérience</option>
</select>
<textarea v-model="editForm.text" class="admin-input w-full" rows="3" />
</div>
<p v-else class="msg-text text-sm leading-relaxed mb-3">{{ msg.text }}</p>
<!-- Réponse existante -->
<div v-if="msg.reply && replyEditing !== msg.id" class="reply-block mb-3">
<div class="flex items-center justify-between mb-1">
<span class="text-xs font-semibold text-primary/80">Réponse publiée</span>
<span class="text-xs text-white/30">{{ formatDate(msg.reply.publishedAt) }}</span>
</div>
<p class="text-white/60 text-sm leading-relaxed">{{ msg.reply.text }}</p>
<button class="action-btn mt-2 text-xs" @click="startReply(msg)">
<div class="i-lucide-pencil h-3 w-3" />
Modifier la réponse
</button>
</div>
<!-- Formulaire réponse -->
<div v-if="replyEditing === msg.id" class="reply-form mb-3">
<textarea
v-model="replyText"
class="admin-input w-full text-sm"
rows="3"
placeholder="Votre réponse..."
/>
<div class="flex gap-2 mt-2">
<button class="action-btn action-btn--save" @click="saveReply(msg)">
<div class="i-lucide-check h-3.5 w-3.5" />
Publier la réponse
</button>
<button v-if="msg.reply" class="action-btn action-btn--danger" @click="removeReply(msg)">
<div class="i-lucide-x h-3.5 w-3.5" />
Supprimer la réponse
</button>
<button class="action-btn" @click="replyEditing = null">Annuler</button>
</div>
</div>
<p v-else class="text-white/70 text-sm leading-relaxed mb-3">{{ msg.text }}</p>
<!-- Actions -->
<div class="flex items-center gap-2">
<div class="flex items-center gap-2 flex-wrap">
<button class="action-btn" @click="togglePublished(msg)">
<div :class="msg.published ? 'i-lucide-eye-off' : 'i-lucide-eye'" class="h-3.5 w-3.5" />
{{ msg.published ? 'Dépublier' : 'Publier' }}
@@ -56,15 +90,18 @@
<div class="i-lucide-check h-3.5 w-3.5" />
Valider
</button>
<button class="action-btn" @click="editing = null">
Annuler
</button>
<button class="action-btn" @click="editing = null">Annuler</button>
</template>
<button v-else class="action-btn" @click="startEdit(msg)">
<div class="i-lucide-pencil h-3.5 w-3.5" />
Modifier
</button>
<button v-if="replyEditing !== msg.id && !msg.reply" class="action-btn action-btn--reply" @click="startReply(msg)">
<div class="i-lucide-reply h-3.5 w-3.5" />
Répondre
</button>
<button class="action-btn action-btn--danger ml-auto" @click="remove(msg)">
<div class="i-lucide-trash-2 h-3.5 w-3.5" />
Supprimer
@@ -86,18 +123,33 @@ definePageMeta({
const { data: messages, refresh } = await useFetch<any[]>('/api/admin/messages')
const editing = ref<number | null>(null)
const editForm = reactive({ author: '', text: '' })
const editForm = reactive({ author: '', text: '', type: 'question' })
const replyEditing = ref<number | null>(null)
const replyText = ref('')
const TYPE_LABELS: Record<string, string> = {
reaction: 'Réaction',
question: 'Question',
suggestion: 'Suggestion',
retour: 'Retour',
}
function typeLabel(type: string) {
return TYPE_LABELS[type] ?? type
}
function startEdit(msg: any) {
editing.value = msg.id
editForm.author = msg.author
editForm.text = msg.text
editForm.type = TYPE_LABELS[msg.type] ? msg.type : 'question'
}
async function saveEdit(msg: any) {
await $fetch(`/api/admin/messages/${msg.id}`, {
method: 'PUT',
body: { author: editForm.author, text: editForm.text },
body: { author: editForm.author, text: editForm.text, type: editForm.type },
})
editing.value = null
await refresh()
@@ -111,6 +163,29 @@ async function togglePublished(msg: any) {
await refresh()
}
function startReply(msg: any) {
replyEditing.value = msg.id
replyText.value = msg.reply?.text ?? ''
}
async function saveReply(msg: any) {
await $fetch(`/api/admin/messages/${msg.id}`, {
method: 'PUT',
body: { reply: replyText.value.trim() || null },
})
replyEditing.value = null
await refresh()
}
async function removeReply(msg: any) {
await $fetch(`/api/admin/messages/${msg.id}`, {
method: 'PUT',
body: { reply: null },
})
replyEditing.value = null
await refresh()
}
async function remove(msg: any) {
if (!confirm(`Supprimer le message de "${msg.author}" ?`)) return
await $fetch(`/api/admin/messages/${msg.id}`, { method: 'DELETE' })
@@ -130,14 +205,14 @@ function formatDate(iso: string) {
<style scoped>
.msg-row {
background: hsl(20 8% 6%);
border: 1px solid hsl(20 8% 14%);
background: hsl(var(--color-bg));
border: 1px solid hsl(var(--color-surface-light));
border-radius: 0.75rem;
padding: 1rem 1.25rem;
}
.msg-row--draft {
border-left: 3px solid hsl(36 80% 52%);
border-left: 3px solid hsl(var(--color-accent));
}
.status-badge {
@@ -155,22 +230,61 @@ function formatDate(iso: string) {
}
.status-badge--draft {
background: hsl(36 80% 52% / 0.15);
color: hsl(36 80% 66%);
background: hsl(var(--color-accent) / 0.15);
color: hsl(var(--color-accent));
}
.type-badge {
font-size: 0.6rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
padding: 0.1rem 0.45rem;
border-radius: 9999px;
background: hsl(var(--color-primary) / 0.12);
color: hsl(var(--color-primary) / 0.7);
}
.type-badge--question {
background: hsl(220 70% 50% / 0.12);
color: hsl(220 70% 65%);
}
.type-badge--suggestion {
background: hsl(280 60% 55% / 0.12);
color: hsl(280 60% 70%);
}
.type-badge--retour {
background: hsl(35 70% 50% / 0.12);
color: hsl(35 70% 65%);
}
.reply-block {
background: hsl(var(--color-primary) / 0.06);
border-left: 2px solid hsl(var(--color-primary) / 0.3);
border-radius: 0 0.5rem 0.5rem 0;
padding: 0.75rem 1rem;
}
.reply-form {
background: hsl(var(--color-surface) / 0.5);
border-radius: 0.5rem;
padding: 0.75rem;
}
.admin-input {
padding: 0.375rem 0.5rem;
border-radius: 0.375rem;
border: 1px solid hsl(20 8% 18%);
background: hsl(20 8% 6%);
color: white;
border: 1px solid hsl(var(--color-surface-light));
background: hsl(var(--color-bg));
color: hsl(var(--color-text));
font-size: 0.8rem;
}
.admin-input:focus {
outline: none;
border-color: hsl(12 76% 48% / 0.5);
border-color: hsl(var(--color-primary) / 0.5);
}
.action-btn {
@@ -180,16 +294,16 @@ function formatDate(iso: string) {
padding: 0.3rem 0.6rem;
border-radius: 0.375rem;
font-size: 0.75rem;
color: hsl(20 8% 60%);
color: hsl(var(--color-text-muted));
background: none;
border: 1px solid hsl(20 8% 16%);
border: 1px solid hsl(var(--color-surface-light));
cursor: pointer;
transition: all 0.2s;
}
.action-btn:hover {
background: hsl(20 8% 10%);
color: white;
background: hsl(var(--color-surface));
color: hsl(var(--color-text));
}
.action-btn--save {
@@ -201,6 +315,20 @@ function formatDate(iso: string) {
background: hsl(142 70% 40% / 0.1);
}
.action-btn--reply {
border-color: hsl(var(--color-primary) / 0.3);
color: hsl(var(--color-primary) / 0.8);
}
.action-btn--reply:hover {
background: hsl(var(--color-primary) / 0.08);
}
.msg-text {
color: hsl(var(--color-text) / 0.72);
white-space: pre-line;
}
.action-btn--danger:hover {
background: hsl(0 70% 40% / 0.1);
border-color: hsl(0 70% 40% / 0.3);

View File

@@ -2,11 +2,11 @@
<div>
<div class="flex items-center justify-between mb-6">
<div>
<NuxtLink to="/admin" class="text-sm text-white/40 hover:text-white/60 transition-colors">
Dashboard
<NuxtLink to="/admin/pages" class="text-sm text-white/40 hover:text-white/60 transition-colors">
Pages
</NuxtLink>
<h1 class="font-display text-2xl font-bold text-white mt-1">
Page : {{ pageName }}
Page : {{ pagePath }}
</h1>
</div>
<AdminSaveButton :saving="saving" :saved="saved" @save="save" />
@@ -40,9 +40,12 @@ definePageMeta({
})
const route = useRoute()
const pageName = computed(() => route.params.name as string)
const pagePath = computed(() => {
const p = route.params.path
return Array.isArray(p) ? p.join('/') : p
})
const { data, error } = await useFetch(() => `/api/content/pages/${pageName.value}`)
const { data, error } = await useFetch(() => `/api/content/pages/${pagePath.value}`)
const yamlContent = ref('')
@@ -60,7 +63,7 @@ async function save() {
saved.value = false
try {
const parsed = yaml.parse(yamlContent.value)
await $fetch(`/api/admin/content/pages/${pageName.value}`, {
await $fetch(`/api/admin/content/pages/${pagePath.value}`, {
method: 'PUT',
body: parsed,
})
@@ -80,10 +83,10 @@ async function save() {
.yaml-textarea {
width: 100%;
padding: 1rem;
border: 1px solid hsl(20 8% 18%);
border: 1px solid hsl(var(--color-surface-light));
border-radius: 0.5rem;
background: hsl(20 8% 4%);
color: hsl(36 80% 76%);
background: hsl(var(--color-bg));
color: hsl(var(--color-accent));
font-family: var(--font-mono, monospace);
font-size: 0.85rem;
line-height: 1.7;
@@ -93,6 +96,6 @@ async function save() {
.yaml-textarea:focus {
outline: none;
border-color: hsl(12 76% 48% / 0.5);
border-color: hsl(var(--color-primary) / 0.5);
}
</style>

View File

@@ -0,0 +1,97 @@
<template>
<div>
<div class="flex items-center justify-between mb-6">
<div>
<NuxtLink to="/admin" class="text-sm text-white/40 hover:text-white/60 transition-colors">
Dashboard
</NuxtLink>
<h1 class="font-display text-2xl font-bold text-white mt-1">Pages</h1>
</div>
</div>
<div v-if="pages" class="flex flex-col gap-6">
<!-- Root pages -->
<div v-if="rootPages.length">
<h2 class="font-display text-lg font-semibold text-white/80 mb-3">Pages principales</h2>
<div class="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
<NuxtLink
v-for="page in rootPages"
:key="page.path"
:to="`/admin/pages/${page.path}`"
class="page-card"
>
<div class="i-lucide-file-text h-5 w-5 text-primary mb-1" />
<span class="font-mono text-sm text-white">{{ page.label }}</span>
</NuxtLink>
</div>
</div>
<!-- Section pages -->
<div v-for="[section, sectionPages] in sections" :key="section">
<h2 class="font-display text-lg font-semibold text-white/80 mb-3">
<span class="i-lucide-folder h-4 w-4 inline-block mr-1 text-accent" />
{{ section }}
</h2>
<div class="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
<NuxtLink
v-for="page in sectionPages"
:key="page.path"
:to="`/admin/pages/${page.path}`"
class="page-card"
>
<div class="i-lucide-file-text h-5 w-5 text-primary/60 mb-1" />
<span class="font-mono text-sm text-white">{{ page.label }}</span>
</NuxtLink>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
definePageMeta({
layout: 'admin',
middleware: 'admin',
})
interface PageEntry {
path: string
label: string
section?: string
}
const { data: pages } = await useFetch<PageEntry[]>('/api/admin/content/pages')
const rootPages = computed(() =>
(pages.value ?? []).filter(p => !p.section),
)
const sections = computed(() => {
const map = new Map<string, PageEntry[]>()
for (const p of pages.value ?? []) {
if (p.section) {
if (!map.has(p.section)) map.set(p.section, [])
map.get(p.section)!.push(p)
}
}
return Array.from(map.entries())
})
</script>
<style scoped>
.page-card {
display: flex;
flex-direction: column;
padding: 1rem;
border-radius: 0.5rem;
border: 1px solid hsl(var(--color-surface-light));
background: hsl(var(--color-bg));
text-decoration: none;
transition: all 0.2s;
}
.page-card:hover {
border-color: hsl(var(--color-primary) / 0.3);
background: hsl(var(--color-surface));
}
</style>

View File

@@ -103,14 +103,14 @@ async function save() {
.admin-input {
padding: 0.375rem 0.5rem;
border-radius: 0.375rem;
border: 1px solid hsl(20 8% 18%);
background: hsl(20 8% 6%);
color: white;
border: 1px solid hsl(var(--color-surface-light));
background: hsl(var(--color-bg));
color: hsl(var(--color-text));
font-size: 0.8rem;
}
.admin-input:focus {
outline: none;
border-color: hsl(12 76% 48% / 0.5);
border-color: hsl(var(--color-primary) / 0.5);
}
</style>

View File

@@ -163,7 +163,7 @@ async function save() {
align-items: flex-start;
gap: 0.75rem;
padding: 0.75rem 0.5rem;
border-bottom: 1px solid hsl(20 8% 10%);
border-bottom: 1px solid hsl(var(--color-surface));
transition: background 0.15s;
}
@@ -172,13 +172,13 @@ async function save() {
}
.song-row--over {
border-top: 2px solid hsl(12 76% 48%);
border-top: 2px solid hsl(var(--color-primary));
}
.drag-handle {
cursor: grab;
padding: 0.25rem;
color: hsl(20 8% 35%);
color: hsl(var(--color-text-muted));
flex-shrink: 0;
margin-top: 0.25rem;
}
@@ -190,7 +190,7 @@ async function save() {
.song-num {
font-family: var(--font-mono, monospace);
font-size: 0.8rem;
color: hsl(20 8% 40%);
color: hsl(var(--color-text-muted));
width: 1.25rem;
text-align: right;
flex-shrink: 0;
@@ -200,16 +200,16 @@ async function save() {
.admin-input {
padding: 0.375rem 0.5rem;
border-radius: 0.375rem;
border: 1px solid hsl(20 8% 18%);
background: hsl(20 8% 6%);
color: white;
border: 1px solid hsl(var(--color-surface-light));
background: hsl(var(--color-bg));
color: hsl(var(--color-text));
font-size: 0.8rem;
width: 100%;
}
.admin-input:focus {
outline: none;
border-color: hsl(12 76% 48% / 0.5);
border-color: hsl(var(--color-primary) / 0.5);
}
.lyrics-textarea {
@@ -240,16 +240,16 @@ async function save() {
margin-top: 1rem;
padding: 0.5rem 1rem;
border-radius: 0.5rem;
border: 1px dashed hsl(20 8% 25%);
border: 1px dashed hsl(var(--color-surface-light));
background: none;
color: hsl(20 8% 55%);
color: hsl(var(--color-text-muted));
font-size: 0.85rem;
cursor: pointer;
transition: all 0.15s;
}
.add-btn:hover {
border-color: hsl(12 76% 48% / 0.5);
color: hsl(12 76% 68%);
border-color: hsl(var(--color-primary) / 0.5);
color: hsl(var(--color-primary));
}
</style>

View File

@@ -0,0 +1,159 @@
<template>
<div class="section-padding">
<div class="container-content">
<div class="mx-auto max-w-2xl">
<div class="section-icon mx-auto mb-6">
<div :class="`i-lucide-${content?.icon ?? 'landmark'}`" class="h-12 w-12" />
</div>
<p class="mb-2 font-mono text-sm tracking-widest text-primary uppercase text-center">
{{ content?.kicker }}
</p>
<h1 class="font-display text-3xl font-bold mb-4 text-center" style="color: hsl(var(--color-text))">
{{ content?.title ?? slug }}
</h1>
<p class="text-lg leading-relaxed mb-8 text-center" style="color: hsl(var(--color-text-muted))">
{{ content?.description }}
</p>
<!-- Features grid (for decision page) -->
<div v-if="content?.features" class="grid gap-4 sm:grid-cols-2 mb-12">
<div v-for="feature in content.features" :key="feature.title" class="feature-card">
<div class="feature-icon">
<div :class="`i-lucide-${feature.icon} h-5 w-5`" />
</div>
<h3 class="font-display font-semibold mb-1" style="color: hsl(var(--color-text))">{{ feature.title }}</h3>
<p class="text-sm leading-relaxed" style="color: hsl(var(--color-text-muted))">{{ feature.text }}</p>
</div>
</div>
<!-- Project card -->
<div v-if="content?.project" class="project-card mb-8">
<div class="project-icon">
<div class="i-lucide-rocket h-5 w-5" />
</div>
<h2 class="font-display text-xl font-semibold mb-2" style="color: hsl(var(--color-text))">
{{ content.project.name }}
</h2>
<p style="color: hsl(var(--color-text-muted))" class="leading-relaxed">
{{ content.project.text }}
</p>
<span v-if="content?.gestation" class="gestation-badge mt-3">
<div class="i-lucide-flask-conical h-3 w-3" />
En gestation
</span>
</div>
<!-- Extended content -->
<div v-if="content?.content" class="prose-block mb-10">
<p class="leading-relaxed whitespace-pre-line" style="color: hsl(var(--color-text-muted))">
{{ content.content }}
</p>
</div>
<div class="text-center flex flex-col items-center gap-3 sm:flex-row sm:justify-center">
<UiBaseButton v-if="slug === 'decision'" :href="decisionUrl" target="_blank">
<div class="i-lucide-external-link mr-2 h-4 w-4" />
Ouvrir Glibredecision
</UiBaseButton>
<UiBaseButton v-if="slug === 'tarifs-eau'" :href="sejeteral0Url" target="_blank">
<div class="i-lucide-external-link mr-2 h-4 w-4" />
Lancer SejeteralO
</UiBaseButton>
<UiBaseButton variant="ghost" to="/citoyenne">
<div class="i-lucide-arrow-left mr-2 h-4 w-4" />
Autonomie citoyenne
</UiBaseButton>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
const route = useRoute()
const slug = route.params.slug as string
const { data: content } = await usePageContent(`citoyenne/${slug}`)
const appConfig = useAppConfig()
const decisionUrl = (appConfig.libredecision as { url: string })?.url ?? '#'
const sejeteral0Url = (appConfig.sejeteral0 as { url: string })?.url ?? '#'
useHead({
title: content.value?.meta?.title ?? slug,
})
</script>
<style scoped>
.section-icon {
display: flex;
align-items: center;
justify-content: center;
width: 5rem;
height: 5rem;
border-radius: 1rem;
background: hsl(var(--color-primary) / 0.1);
border: 1px solid hsl(var(--color-primary) / 0.2);
color: hsl(var(--color-primary));
}
.feature-card {
padding: 1.25rem;
border-radius: 0.75rem;
border: 1px solid hsl(var(--color-text) / 0.08);
background: hsl(var(--color-surface));
}
.feature-icon {
display: flex;
align-items: center;
justify-content: center;
width: 2.25rem;
height: 2.25rem;
border-radius: 0.5rem;
background: hsl(var(--color-primary) / 0.1);
color: hsl(var(--color-primary));
margin-bottom: 0.75rem;
}
.project-card {
padding: 1.5rem;
border-radius: 0.75rem;
border: 1px solid hsl(var(--color-primary) / 0.15);
background: hsl(var(--color-surface));
}
.project-icon {
display: flex;
align-items: center;
justify-content: center;
width: 2.5rem;
height: 2.5rem;
border-radius: 0.5rem;
background: hsl(var(--color-primary) / 0.1);
color: hsl(var(--color-primary));
margin-bottom: 1rem;
}
.gestation-badge {
display: inline-flex;
align-items: center;
gap: 0.25rem;
padding: 0.125rem 0.5rem;
border-radius: 9999px;
background: hsl(var(--color-accent) / 0.12);
color: hsl(var(--color-accent));
font-size: 0.7rem;
font-weight: 500;
font-family: var(--font-mono);
}
.prose-block {
padding: 1.5rem;
border-radius: 0.75rem;
background: hsl(var(--color-surface));
}
</style>

View File

@@ -0,0 +1,631 @@
<template>
<div class="relative overflow-hidden section-padding">
<!-- 1. Shadok Capitaine (top-left, profile at ship's wheel, telescope) -->
<svg class="shadok shadok-capitaine" viewBox="0 0 170 210" fill="none" xmlns="http://www.w3.org/2000/svg">
<g>
<!-- ship's wheel -->
<circle cx="50" cy="130" r="28" stroke="currentColor" stroke-width="3" fill="none" opacity="0.35"/>
<line x1="50" y1="102" x2="50" y2="158" stroke="currentColor" stroke-width="2.5" opacity="0.3"/>
<line x1="22" y1="130" x2="78" y2="130" stroke="currentColor" stroke-width="2.5" opacity="0.3"/>
<line x1="30" y1="110" x2="70" y2="150" stroke="currentColor" stroke-width="2.5" opacity="0.3"/>
<line x1="70" y1="110" x2="30" y2="150" stroke="currentColor" stroke-width="2.5" opacity="0.3"/>
<!-- wheel spokes knobs -->
<circle cx="50" cy="100" r="3" fill="currentColor" opacity="0.4"/>
<circle cx="50" cy="160" r="3" fill="currentColor" opacity="0.4"/>
<circle cx="20" cy="130" r="3" fill="currentColor" opacity="0.4"/>
<circle cx="80" cy="130" r="3" fill="currentColor" opacity="0.4"/>
<!-- body (small oval, profile facing right) -->
<ellipse cx="85" cy="110" rx="22" ry="28" fill="currentColor" opacity="0.25"/>
<!-- head -->
<circle cx="92" cy="68" r="16" fill="currentColor" opacity="0.3"/>
<!-- captain hat -->
<rect x="76" y="52" width="32" height="8" rx="4" fill="currentColor" opacity="0.4"/>
<rect x="80" y="46" width="24" height="8" rx="3" fill="currentColor" opacity="0.3"/>
<!-- hat brim detail -->
<ellipse cx="92" cy="60" rx="18" ry="3" fill="currentColor" opacity="0.2"/>
<!-- eyes (looking right through telescope) -->
<circle cx="98" cy="65" r="2.5" fill="currentColor" opacity="0.6"/>
<circle cx="90" cy="66" r="2" fill="currentColor" opacity="0.5"/>
<!-- beak (pointy, profile right) -->
<polygon points="108,68 120,65 108,72" fill="currentColor" opacity="0.35"/>
<!-- left arm on wheel -->
<line x1="65" y1="100" x2="55" y2="115" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<!-- right arm holding telescope -->
<line x1="105" y1="95" x2="130" y2="72" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<!-- telescope -->
<rect x="128" y="66" width="30" height="7" rx="3.5" fill="currentColor" opacity="0.4"/>
<rect x="155" y="64" width="10" height="11" rx="3" fill="currentColor" opacity="0.3"/>
<!-- left leg (long, forward) -->
<line x1="75" y1="135" x2="60" y2="190" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.35"/>
<!-- left foot -->
<ellipse cx="55" cy="193" rx="9" ry="4" fill="currentColor" opacity="0.3"/>
<!-- right leg (long, back) -->
<line x1="92" y1="136" x2="100" y2="192" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.35"/>
<!-- right foot -->
<ellipse cx="103" cy="195" rx="9" ry="4" fill="currentColor" opacity="0.3"/>
</g>
</svg>
<!-- 2. Shadok Avocate (top-right robe, scroll, arm up) -->
<svg class="shadok shadok-avocate" viewBox="0 0 160 210" fill="none" xmlns="http://www.w3.org/2000/svg">
<g>
<!-- body (small oval) -->
<ellipse cx="80" cy="115" rx="22" ry="30" fill="currentColor" opacity="0.25"/>
<!-- robe/gown flowing -->
<path d="M58 100 Q55 145 48 175 L112 175 Q105 145 102 100 Z" fill="currentColor" opacity="0.18"/>
<!-- robe collar -->
<path d="M68 88 Q80 96 92 88" stroke="currentColor" stroke-width="2.5" fill="none" opacity="0.35"/>
<!-- head -->
<circle cx="80" cy="70" r="16" fill="currentColor" opacity="0.3"/>
<!-- eyes (looking up-left) -->
<circle cx="74" cy="67" r="2.5" fill="currentColor" opacity="0.6"/>
<circle cx="83" cy="68" r="2" fill="currentColor" opacity="0.5"/>
<!-- beak (3/4 view, slight left) -->
<polygon points="68,72 56,69 66,76" fill="currentColor" opacity="0.35"/>
<!-- left arm up dramatically -->
<line x1="60" y1="100" x2="30" y2="55" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<!-- open hand at top -->
<line x1="30" y1="55" x2="24" y2="46" stroke="currentColor" stroke-width="2" stroke-linecap="round" opacity="0.35"/>
<line x1="30" y1="55" x2="28" y2="44" stroke="currentColor" stroke-width="2" stroke-linecap="round" opacity="0.35"/>
<line x1="30" y1="55" x2="34" y2="46" stroke="currentColor" stroke-width="2" stroke-linecap="round" opacity="0.35"/>
<!-- right arm holding scroll -->
<line x1="100" y1="102" x2="125" y2="90" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<!-- scroll document (big) -->
<rect x="120" y="72" width="28" height="40" rx="3" fill="currentColor" opacity="0.25"/>
<!-- scroll curl top -->
<ellipse cx="134" cy="72" rx="14" ry="5" fill="currentColor" opacity="0.2"/>
<!-- scroll curl bottom -->
<ellipse cx="134" cy="112" rx="12" ry="4" fill="currentColor" opacity="0.2"/>
<!-- scroll text lines -->
<line x1="126" y1="82" x2="142" y2="82" stroke="currentColor" stroke-width="1.5" opacity="0.2"/>
<line x1="126" y1="88" x2="140" y2="88" stroke="currentColor" stroke-width="1.5" opacity="0.2"/>
<line x1="126" y1="94" x2="143" y2="94" stroke="currentColor" stroke-width="1.5" opacity="0.2"/>
<line x1="126" y1="100" x2="138" y2="100" stroke="currentColor" stroke-width="1.5" opacity="0.2"/>
<!-- left leg (long) -->
<line x1="72" y1="142" x2="62" y2="195" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.35"/>
<ellipse cx="58" cy="198" rx="9" ry="4" fill="currentColor" opacity="0.3"/>
<!-- right leg -->
<line x1="88" y1="142" x2="95" y2="196" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.35"/>
<ellipse cx="99" cy="199" rx="9" ry="4" fill="currentColor" opacity="0.3"/>
</g>
</svg>
<!-- 3. Shadok Vigie (left crow's nest, looking far) -->
<svg class="shadok shadok-vigie" viewBox="0 0 140 220" fill="none" xmlns="http://www.w3.org/2000/svg">
<g>
<!-- mast pole -->
<line x1="70" y1="90" x2="70" y2="218" stroke="currentColor" stroke-width="4" opacity="0.3"/>
<!-- crow's nest platform -->
<path d="M38 88 L42 100 L98 100 L102 88 Z" fill="currentColor" opacity="0.25"/>
<line x1="36" y1="88" x2="104" y2="88" stroke="currentColor" stroke-width="3" opacity="0.35"/>
<!-- nest railing -->
<line x1="40" y1="78" x2="40" y2="100" stroke="currentColor" stroke-width="2.5" opacity="0.3"/>
<line x1="100" y1="78" x2="100" y2="100" stroke="currentColor" stroke-width="2.5" opacity="0.3"/>
<line x1="40" y1="78" x2="100" y2="78" stroke="currentColor" stroke-width="2" opacity="0.25"/>
<!-- body (small, inside nest) -->
<ellipse cx="70" cy="72" rx="20" ry="22" fill="currentColor" opacity="0.25"/>
<!-- head -->
<circle cx="70" cy="38" r="15" fill="currentColor" opacity="0.3"/>
<!-- eyes (squinting, looking right far) -->
<circle cx="78" cy="35" r="2.5" fill="currentColor" opacity="0.6"/>
<circle cx="70" cy="36" r="2" fill="currentColor" opacity="0.4"/>
<!-- beak (profile right) -->
<polygon points="83,38 96,35 84,42" fill="currentColor" opacity="0.35"/>
<!-- right hand shielding eyes -->
<line x1="88" y1="55" x2="98" y2="35" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<line x1="96" y1="33" x2="108" y2="30" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" opacity="0.35"/>
<!-- left arm resting on railing -->
<line x1="52" y1="60" x2="42" y2="78" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<!-- legs dangling below nest (long!) -->
<line x1="60" y1="98" x2="52" y2="160" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.35"/>
<ellipse cx="48" cy="163" rx="8" ry="4" fill="currentColor" opacity="0.3"/>
<line x1="80" y1="98" x2="86" y2="158" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.35"/>
<ellipse cx="90" cy="161" rx="8" ry="4" fill="currentColor" opacity="0.3"/>
</g>
</svg>
<!-- 4. Shadok Comedien (right theater masks, cape, leg lifted) -->
<svg class="shadok shadok-comedien" viewBox="0 0 170 210" fill="none" xmlns="http://www.w3.org/2000/svg">
<g>
<!-- dramatic cape -->
<path d="M65 85 Q40 120 35 180 L115 180 Q110 120 95 85 Z" fill="currentColor" opacity="0.15"/>
<path d="M95 85 Q120 100 135 160 Q130 165 115 180" fill="currentColor" opacity="0.1"/>
<!-- body (small oval) -->
<ellipse cx="80" cy="105" rx="20" ry="26" fill="currentColor" opacity="0.25"/>
<!-- head -->
<circle cx="80" cy="62" r="16" fill="currentColor" opacity="0.3"/>
<!-- eyes (different directions dramatic!) -->
<circle cx="74" cy="59" r="2.5" fill="currentColor" opacity="0.6"/>
<circle cx="86" cy="61" r="2.5" fill="currentColor" opacity="0.55"/>
<!-- beak -->
<polygon points="72,66 60,63 70,70" fill="currentColor" opacity="0.35"/>
<!-- left arm up holding comedy mask -->
<line x1="62" y1="92" x2="32" y2="55" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<!-- comedy mask (big, smiling) -->
<circle cx="25" cy="42" r="16" fill="currentColor" opacity="0.2"/>
<path d="M17 46 Q25 56 33 46" stroke="currentColor" stroke-width="2" fill="none" opacity="0.4"/>
<circle cx="20" cy="38" r="2" fill="currentColor" opacity="0.5"/>
<circle cx="30" cy="38" r="2" fill="currentColor" opacity="0.5"/>
<!-- right arm out holding tragedy mask -->
<line x1="98" y1="92" x2="135" y2="65" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<!-- tragedy mask (big, frowning) -->
<circle cx="145" cy="55" r="16" fill="currentColor" opacity="0.2"/>
<path d="M137 62 Q145 54 153 62" stroke="currentColor" stroke-width="2" fill="none" opacity="0.4"/>
<circle cx="140" cy="50" r="2" fill="currentColor" opacity="0.5"/>
<circle cx="150" cy="50" r="2" fill="currentColor" opacity="0.5"/>
<!-- left leg (long, planted) -->
<line x1="72" y1="128" x2="62" y2="190" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.35"/>
<ellipse cx="58" cy="193" rx="9" ry="4" fill="currentColor" opacity="0.3"/>
<!-- right leg (lifted dramatically!) -->
<line x1="88" y1="126" x2="115" y2="165" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.35"/>
<ellipse cx="120" cy="167" rx="8" ry="4" transform="rotate(-25 120 167)" fill="currentColor" opacity="0.3"/>
</g>
</svg>
<!-- 5. Shadok Cartographe (bottom-left sitting, map, compass) -->
<svg class="shadok shadok-cartographe" viewBox="0 0 180 190" fill="none" xmlns="http://www.w3.org/2000/svg">
<g>
<!-- table -->
<rect x="20" y="125" width="140" height="6" rx="2" fill="currentColor" opacity="0.3"/>
<line x1="35" y1="131" x2="30" y2="185" stroke="currentColor" stroke-width="3" opacity="0.25"/>
<line x1="145" y1="131" x2="150" y2="185" stroke="currentColor" stroke-width="3" opacity="0.25"/>
<!-- big map spread on table -->
<rect x="30" y="108" width="110" height="18" rx="2" fill="currentColor" opacity="0.2"/>
<!-- map details -->
<path d="M45 112 Q65 108 85 115 Q105 110 125 114" stroke="currentColor" stroke-width="1" fill="none" opacity="0.25"/>
<circle cx="75" cy="115" r="3" stroke="currentColor" stroke-width="1" fill="none" opacity="0.2"/>
<line x1="95" y1="110" x2="95" y2="122" stroke="currentColor" stroke-width="0.8" opacity="0.2"/>
<!-- body (small, hunched over map) -->
<ellipse cx="90" cy="88" rx="22" ry="25" fill="currentColor" opacity="0.25" transform="rotate(10 90 88)"/>
<!-- head (bent down looking at map) -->
<circle cx="78" cy="60" r="15" fill="currentColor" opacity="0.3"/>
<!-- eyes (looking down at map) -->
<circle cx="74" cy="64" r="2" fill="currentColor" opacity="0.6"/>
<circle cx="83" cy="65" r="2" fill="currentColor" opacity="0.5"/>
<!-- beak (pointing down) -->
<polygon points="72,70 65,78 78,72" fill="currentColor" opacity="0.35"/>
<!-- right arm holding compass tool -->
<line x1="110" y1="92" x2="130" y2="105" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<!-- compass/divider tool (big V shape) -->
<line x1="130" y1="105" x2="125" y2="125" stroke="currentColor" stroke-width="2.5" opacity="0.4"/>
<line x1="130" y1="105" x2="140" y2="125" stroke="currentColor" stroke-width="2.5" opacity="0.4"/>
<circle cx="130" cy="103" r="3" fill="currentColor" opacity="0.3"/>
<!-- left arm holding magnifying glass -->
<line x1="70" y1="90" x2="48" y2="100" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<!-- magnifying glass (big) -->
<circle cx="38" cy="96" r="12" stroke="currentColor" stroke-width="2.5" fill="none" opacity="0.35"/>
<line x1="48" y1="103" x2="55" y2="112" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.35"/>
<!-- legs (sitting, bent under table) -->
<line x1="78" y1="110" x2="65" y2="145" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.35"/>
<line x1="65" y1="145" x2="50" y2="148" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.3"/>
<ellipse cx="45" cy="150" rx="8" ry="3.5" fill="currentColor" opacity="0.3"/>
<line x1="100" y1="110" x2="110" y2="145" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.35"/>
<line x1="110" y1="145" x2="122" y2="148" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.3"/>
<ellipse cx="127" cy="150" rx="8" ry="3.5" fill="currentColor" opacity="0.3"/>
</g>
</svg>
<!-- 6. Shadok Juge (bottom-right on bench, gavel, wig) -->
<svg class="shadok shadok-juge" viewBox="0 0 160 210" fill="none" xmlns="http://www.w3.org/2000/svg">
<g>
<!-- elevated bench/podium -->
<rect x="30" y="145" width="100" height="55" rx="4" fill="currentColor" opacity="0.2"/>
<rect x="25" y="138" width="110" height="10" rx="3" fill="currentColor" opacity="0.28"/>
<!-- bench front panel detail -->
<rect x="55" y="155" width="50" height="35" rx="3" fill="currentColor" opacity="0.1"/>
<!-- body (small, seated) -->
<ellipse cx="80" cy="112" rx="22" ry="28" fill="currentColor" opacity="0.25"/>
<!-- head -->
<circle cx="80" cy="68" r="16" fill="currentColor" opacity="0.3"/>
<!-- judge wig (curly puffs) -->
<circle cx="66" cy="60" r="6" fill="currentColor" opacity="0.2"/>
<circle cx="94" cy="60" r="6" fill="currentColor" opacity="0.2"/>
<circle cx="72" cy="54" r="5" fill="currentColor" opacity="0.18"/>
<circle cx="88" cy="54" r="5" fill="currentColor" opacity="0.18"/>
<circle cx="80" cy="52" r="5.5" fill="currentColor" opacity="0.2"/>
<!-- wig curls hanging -->
<circle cx="63" cy="70" r="4.5" fill="currentColor" opacity="0.15"/>
<circle cx="97" cy="70" r="4.5" fill="currentColor" opacity="0.15"/>
<!-- eyes (stern, both forward) -->
<circle cx="74" cy="66" r="2.5" fill="currentColor" opacity="0.65"/>
<circle cx="86" cy="66" r="2.5" fill="currentColor" opacity="0.65"/>
<!-- stern eyebrows -->
<line x1="71" y1="62" x2="77" y2="63" stroke="currentColor" stroke-width="2" stroke-linecap="round" opacity="0.4"/>
<line x1="89" y1="63" x2="83" y2="62" stroke="currentColor" stroke-width="2" stroke-linecap="round" opacity="0.4"/>
<!-- beak (small, stern) -->
<polygon points="80,74 72,78 80,80" fill="currentColor" opacity="0.35"/>
<!-- right arm raising gavel -->
<line x1="100" y1="98" x2="130" y2="60" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<!-- gavel (big!) -->
<rect x="120" y="44" width="26" height="12" rx="4" fill="currentColor" opacity="0.4"/>
<line x1="133" y1="56" x2="133" y2="38" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.35"/>
<!-- left arm on bench -->
<line x1="60" y1="100" x2="40" y2="130" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<!-- legs (dangling from bench, long) -->
<line x1="72" y1="138" x2="62" y2="200" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.35"/>
<ellipse cx="58" cy="203" rx="9" ry="4" fill="currentColor" opacity="0.3"/>
<line x1="88" y1="138" x2="96" y2="198" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.35"/>
<ellipse cx="100" cy="201" rx="9" ry="4" fill="currentColor" opacity="0.3"/>
</g>
</svg>
<!-- 7. Shadok Matelot (center-bottom pulling rope, leaning back, anchor) -->
<svg class="shadok shadok-matelot" viewBox="0 0 160 200" fill="none" xmlns="http://www.w3.org/2000/svg">
<g>
<!-- rope going up and off -->
<path d="M30 20 Q35 50 55 70 Q65 78 68 85" stroke="currentColor" stroke-width="3" fill="none" opacity="0.3"/>
<!-- body (leaning back, tilted) -->
<ellipse cx="85" cy="108" rx="20" ry="27" fill="currentColor" opacity="0.25" transform="rotate(-15 85 108)"/>
<!-- head -->
<circle cx="95" cy="68" r="15" fill="currentColor" opacity="0.3"/>
<!-- bandana -->
<path d="M80 62 Q95 55 110 62" stroke="currentColor" stroke-width="3" fill="currentColor" opacity="0.2"/>
<line x1="108" y1="60" x2="118" y2="55" stroke="currentColor" stroke-width="2" stroke-linecap="round" opacity="0.25"/>
<line x1="108" y1="62" x2="116" y2="60" stroke="currentColor" stroke-width="2" stroke-linecap="round" opacity="0.2"/>
<!-- eyes (straining, squinting) -->
<circle cx="90" cy="66" r="2" fill="currentColor" opacity="0.6"/>
<circle cx="100" cy="65" r="2.5" fill="currentColor" opacity="0.55"/>
<!-- beak -->
<polygon points="104,70 115,67 105,74" fill="currentColor" opacity="0.35"/>
<!-- both arms pulling rope -->
<line x1="68" y1="95" x2="55" y2="78" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<line x1="75" y1="98" x2="62" y2="82" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<!-- left leg (long, bracing forward) -->
<line x1="72" y1="130" x2="48" y2="185" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.35"/>
<ellipse cx="43" cy="188" rx="10" ry="4" fill="currentColor" opacity="0.3"/>
<!-- right leg (long, back) -->
<line x1="92" y1="132" x2="110" y2="188" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.35"/>
<ellipse cx="114" cy="191" rx="10" ry="4" fill="currentColor" opacity="0.3"/>
<!-- anchor nearby -->
<line x1="135" y1="140" x2="135" y2="185" stroke="currentColor" stroke-width="3" opacity="0.3"/>
<circle cx="135" cy="138" r="5" stroke="currentColor" stroke-width="2" fill="none" opacity="0.25"/>
<path d="M120 180 Q135 195 150 180" stroke="currentColor" stroke-width="3" fill="none" opacity="0.3"/>
<line x1="120" y1="180" x2="117" y2="174" stroke="currentColor" stroke-width="2.5" opacity="0.3"/>
<line x1="150" y1="180" x2="153" y2="174" stroke="currentColor" stroke-width="2.5" opacity="0.3"/>
</g>
</svg>
<!-- 8. Shadok Mime (right pushing invisible wall, striped shirt, beret) -->
<svg class="shadok shadok-mime" viewBox="0 0 150 210" fill="none" xmlns="http://www.w3.org/2000/svg">
<g>
<!-- body (small oval) -->
<ellipse cx="70" cy="112" rx="22" ry="28" fill="currentColor" opacity="0.2"/>
<!-- striped shirt lines -->
<line x1="52" y1="96" x2="88" y2="96" stroke="currentColor" stroke-width="2" opacity="0.2"/>
<line x1="50" y1="102" x2="90" y2="102" stroke="currentColor" stroke-width="2" opacity="0.2"/>
<line x1="50" y1="108" x2="90" y2="108" stroke="currentColor" stroke-width="2" opacity="0.2"/>
<line x1="50" y1="114" x2="90" y2="114" stroke="currentColor" stroke-width="2" opacity="0.2"/>
<line x1="52" y1="120" x2="88" y2="120" stroke="currentColor" stroke-width="2" opacity="0.2"/>
<line x1="54" y1="126" x2="86" y2="126" stroke="currentColor" stroke-width="2" opacity="0.2"/>
<!-- head (white face = lighter) -->
<circle cx="75" cy="65" r="16" fill="currentColor" opacity="0.2"/>
<!-- white face highlight -->
<circle cx="75" cy="65" r="13" fill="currentColor" opacity="0.08"/>
<!-- beret -->
<ellipse cx="75" cy="50" rx="18" ry="6" fill="currentColor" opacity="0.3"/>
<circle cx="75" cy="46" r="3" fill="currentColor" opacity="0.25"/>
<!-- eyes (expressive, wide) -->
<circle cx="69" cy="62" r="3" fill="currentColor" opacity="0.55"/>
<circle cx="81" cy="62" r="3" fill="currentColor" opacity="0.55"/>
<!-- raised eyebrows (surprised) -->
<path d="M66 57 Q69 54 72 57" stroke="currentColor" stroke-width="1.5" fill="none" opacity="0.4"/>
<path d="M78 57 Q81 54 84 57" stroke="currentColor" stroke-width="1.5" fill="none" opacity="0.4"/>
<!-- beak (small) -->
<polygon points="75,72 68,76 75,78" fill="currentColor" opacity="0.3"/>
<!-- both arms pushing against invisible wall (to the right) -->
<line x1="90" y1="100" x2="125" y2="85" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<line x1="90" y1="115" x2="125" y2="110" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<!-- flat hands pressed against wall -->
<rect x="124" y="78" width="12" height="15" rx="3" fill="currentColor" opacity="0.2"/>
<rect x="124" y="103" width="12" height="15" rx="3" fill="currentColor" opacity="0.2"/>
<!-- invisible wall hint (faint line) -->
<line x1="138" y1="50" x2="138" y2="180" stroke="currentColor" stroke-width="1" stroke-dasharray="4 6" opacity="0.12"/>
<!-- left leg (long, leaning forward) -->
<line x1="60" y1="138" x2="45" y2="195" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.35"/>
<ellipse cx="40" cy="198" rx="9" ry="4" fill="currentColor" opacity="0.3"/>
<!-- right leg (long, back) -->
<line x1="80" y1="138" x2="95" y2="194" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.35"/>
<ellipse cx="99" cy="197" rx="9" ry="4" fill="currentColor" opacity="0.3"/>
</g>
</svg>
<div class="container-content">
<header class="mb-12 text-center">
<p class="mb-2 font-mono text-sm tracking-widest text-primary uppercase">{{ content?.kicker }}</p>
<h1 class="page-title font-display font-bold tracking-tight" style="color: hsl(var(--color-text))">
{{ content?.title }}
</h1>
<p class="mt-4 mx-auto max-w-2xl leading-relaxed" style="color: hsl(var(--color-text-muted))">
{{ content?.description }}
</p>
</header>
<div class="mx-auto max-w-3xl flex flex-col gap-6">
<!-- Decision collective -->
<div class="item-card">
<NuxtLink to="/citoyenne/decision" class="item-body group">
<div class="item-header">
<div class="item-icon">
<div class="i-lucide-gavel h-5 w-5" />
</div>
<h2 class="font-display text-xl font-bold" style="color: hsl(var(--color-text))">
Décision collective
</h2>
</div>
<p class="leading-relaxed mt-3" style="color: hsl(var(--color-text-muted))">
Se donner les moyens de la décision collective.
</p>
<div class="mt-3 inline-flex items-center gap-1 text-sm text-primary group-hover:text-primary/80 transition-colors">
En savoir plus
<div class="i-lucide-arrow-right h-3.5 w-3.5" />
</div>
</NuxtLink>
<div class="item-actions">
<a :href="decisionUrl" target="_blank" rel="noopener" class="action-btn action-btn--primary">
<div class="i-lucide-external-link h-3.5 w-3.5" />
Ouvrir Glibredecision
</a>
</div>
</div>
<!-- Tarifs de l'eau -->
<div class="item-card">
<NuxtLink to="/citoyenne/tarifs-eau" class="item-body group">
<div class="item-header">
<div class="item-icon">
<div class="i-lucide-droplets h-5 w-5" />
</div>
<h2 class="font-display text-xl font-bold" style="color: hsl(var(--color-text))">
Tarifs de l'eau
</h2>
<span class="gestation-badge">
<div class="i-lucide-flask-conical h-3 w-3" />
En gestation
</span>
</div>
<p class="leading-relaxed mt-3" style="color: hsl(var(--color-text-muted))">
Application pour obtenir justice sociale et incitation dynamique à la réduction.
Permet de confier la décision à la population des communes.
</p>
<div class="mt-3 inline-flex items-center gap-1 text-sm text-primary group-hover:text-primary/80 transition-colors">
En savoir plus
<div class="i-lucide-arrow-right h-3.5 w-3.5" />
</div>
</NuxtLink>
<div class="item-actions">
<a :href="sejeteral0Url" target="_blank" rel="noopener" class="action-btn action-btn--primary">
<div class="i-lucide-external-link h-3.5 w-3.5" />
Lancer SejeteralO
</a>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
definePageMeta({
layout: 'default',
})
const { data: content } = await usePageContent('citoyenne')
const appConfig = useAppConfig()
const decisionUrl = (appConfig.libredecision as { url: string })?.url ?? '#'
const sejeteral0Url = (appConfig.sejeteral0 as { url: string })?.url ?? '#'
useHead({
title: content.value?.meta?.title ?? 'Autonomie citoyenne',
})
</script>
<style scoped>
.page-title {
font-size: clamp(2rem, 5vw, 2.75rem);
}
.item-card {
border-radius: 0.75rem;
border: 1px solid hsl(var(--color-text) / 0.08);
background: hsl(var(--color-surface));
transition: border-color 0.2s;
overflow: hidden;
}
.item-card:hover {
border-color: hsl(var(--color-primary) / 0.2);
}
.item-body {
display: block;
padding: 1.5rem;
text-decoration: none;
color: inherit;
}
.item-header {
display: flex;
align-items: center;
gap: 0.75rem;
}
.item-icon {
display: flex;
align-items: center;
justify-content: center;
width: 2.25rem;
height: 2.25rem;
border-radius: 0.5rem;
background: hsl(var(--color-primary) / 0.15);
color: hsl(var(--color-primary));
box-shadow: 0 0 12px hsl(var(--color-primary) / 0.12);
flex-shrink: 0;
}
.gestation-badge {
display: inline-flex;
align-items: center;
gap: 0.25rem;
margin-left: auto;
padding: 0.125rem 0.5rem;
border-radius: 9999px;
background: hsl(var(--color-accent) / 0.12);
color: hsl(var(--color-accent));
font-size: 0.7rem;
font-weight: 500;
font-family: var(--font-mono);
}
.item-actions {
display: flex;
flex-wrap: wrap;
gap: 0.375rem;
padding: 0.75rem 1.5rem;
border-top: 1px solid hsl(var(--color-text) / 0.06);
background: hsl(var(--color-bg) / 0.4);
}
.action-btn {
display: inline-flex;
align-items: center;
gap: 0.375rem;
padding: 0.375rem 0.75rem;
border-radius: 0.5rem;
font-size: 0.8rem;
font-weight: 500;
text-decoration: none;
transition: all 0.2s;
cursor: pointer;
}
.action-btn--primary {
color: hsl(var(--color-primary));
background: hsl(var(--color-primary) / 0.12);
border: 1px solid hsl(var(--color-primary) / 0.25);
}
.action-btn--primary:hover {
background: hsl(var(--color-primary) / 0.2);
border-color: hsl(var(--color-primary) / 0.4);
}
/* Shadok illustrations */
.shadok {
position: absolute;
pointer-events: none;
width: clamp(70px, 10vw, 140px);
z-index: 0;
}
.shadok-capitaine {
top: 2%;
left: 2%;
color: hsl(var(--color-primary));
opacity: 0.22;
animation: shadok-float-1 9s ease-in-out infinite;
}
.shadok-avocate {
top: 1%;
right: 2%;
color: hsl(var(--color-accent));
opacity: 0.2;
animation: shadok-float-2 11s ease-in-out infinite;
}
.shadok-vigie {
left: 2%;
top: 38%;
color: hsl(var(--color-primary));
opacity: 0.24;
animation: shadok-float-3 10s ease-in-out infinite;
}
.shadok-comedien {
right: 3%;
top: 35%;
color: hsl(var(--color-accent));
opacity: 0.2;
animation: shadok-float-4 8s ease-in-out infinite;
}
.shadok-cartographe {
bottom: 10%;
left: 1%;
color: hsl(var(--color-accent));
opacity: 0.22;
animation: shadok-float-5 12s ease-in-out infinite;
}
.shadok-juge {
bottom: 6%;
right: 1%;
color: hsl(var(--color-primary));
opacity: 0.24;
animation: shadok-float-6 9.5s ease-in-out infinite;
}
.shadok-matelot {
bottom: 2%;
left: 50%;
transform: translateX(-50%);
color: hsl(var(--color-primary));
opacity: 0.18;
animation: shadok-float-7 7s ease-in-out infinite;
}
.shadok-mime {
right: 2%;
top: 55%;
color: hsl(var(--color-accent));
opacity: 0.28;
animation: shadok-float-8 10.5s ease-in-out infinite;
}
@keyframes shadok-float-1 {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-8px); }
}
@keyframes shadok-float-2 {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-10px); }
}
@keyframes shadok-float-3 {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-7px); }
}
@keyframes shadok-float-4 {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-9px); }
}
@keyframes shadok-float-5 {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-6px); }
}
@keyframes shadok-float-6 {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-8px); }
}
@keyframes shadok-float-7 {
0%, 100% { transform: translateX(-50%) translateY(0); }
50% { transform: translateX(-50%) translateY(-7px); }
}
@keyframes shadok-float-8 {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-10px); }
}
@media (max-width: 768px) {
.shadok {
display: none;
}
}
</style>

View File

@@ -1,104 +0,0 @@
<template>
<div class="section-padding">
<div class="container-content">
<div class="mx-auto max-w-3xl">
<!-- Header -->
<div class="text-center mb-12">
<div class="decision-icon mx-auto mb-6">
<div class="i-lucide-gavel h-10 w-10" />
</div>
<h1 class="font-display text-4xl font-bold text-white mb-4">Plateforme Décision</h1>
<p class="text-lg text-white/60 leading-relaxed">
Se donner les moyens de la décision collective.
</p>
</div>
<!-- Features -->
<div class="grid gap-4 sm:grid-cols-2 mb-12">
<div v-for="feature in features" :key="feature.title" class="feature-card">
<div class="feature-icon">
<div :class="`i-lucide-${feature.icon} h-5 w-5`" />
</div>
<h3 class="font-display font-semibold text-white mb-1">{{ feature.title }}</h3>
<p class="text-sm text-white/50 leading-relaxed">{{ feature.text }}</p>
</div>
</div>
<!-- CTA -->
<div class="text-center flex flex-col items-center gap-3 sm:flex-row sm:justify-center">
<UiBaseButton :href="decisionUrl" target="_blank">
<div class="i-lucide-external-link mr-2 h-4 w-4" />
Ouvrir Glibredecision
</UiBaseButton>
<UiBaseButton variant="ghost" to="/">
<div class="i-lucide-arrow-left mr-2 h-4 w-4" />
Retour à l'accueil
</UiBaseButton>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
useHead({ title: 'Décision collective' })
const appConfig = useAppConfig()
const decisionUrl = (appConfig.libredecision as { url: string }).url
const features = [
{
icon: 'vote',
title: 'Décisions on-chain',
text: 'Des décisions transparentes et vérifiables, inscrites sur la blockchain.',
},
{
icon: 'scroll-text',
title: 'Les Mandats',
text: 'Formaliser et suivre les mandats confiés aux personnes désignées.',
},
{
icon: 'scroll-text',
title: 'Documents de référence',
text: 'Les textes fondateurs et documents qui encadrent la prise de décision.',
},
{
icon: 'git-branch',
title: 'Les Protocoles',
text: 'Les règles et processus qui structurent la décision collective.',
},
]
</script>
<style scoped>
.decision-icon {
display: flex;
align-items: center;
justify-content: center;
width: 5rem;
height: 5rem;
border-radius: 1rem;
background: hsl(var(--color-primary) / 0.1);
border: 1px solid hsl(var(--color-primary) / 0.2);
color: hsl(var(--color-primary));
}
.feature-card {
padding: 1.25rem;
border-radius: 0.75rem;
border: 1px solid hsl(var(--color-text) / 0.08);
background: hsl(var(--color-surface));
}
.feature-icon {
display: flex;
align-items: center;
justify-content: center;
width: 2.25rem;
height: 2.25rem;
border-radius: 0.5rem;
background: hsl(var(--color-primary) / 0.1);
color: hsl(var(--color-primary));
margin-bottom: 0.75rem;
}
</style>

View File

@@ -0,0 +1,453 @@
<template>
<div class="relative overflow-hidden section-padding">
<!-- Shadok 1 : Libraire behind counter, 3/4 view, glasses, recommending a book -->
<svg class="shadok-libraire" viewBox="0 0 160 210" fill="none" aria-hidden="true">
<!-- Body (small oval) -->
<ellipse cx="75" cy="95" rx="22" ry="28" fill="currentColor" opacity="0.25"/>
<!-- Head -->
<circle cx="75" cy="58" r="16" fill="currentColor" opacity="0.3"/>
<!-- Eyes looking right (recommending) -->
<circle cx="80" cy="55" r="2" fill="currentColor" opacity="0.6"/>
<circle cx="86" cy="54" r="2" fill="currentColor" opacity="0.6"/>
<!-- Glasses on beak -->
<circle cx="80" cy="55" r="5" stroke="currentColor" stroke-width="1.2" fill="none" opacity="0.4"/>
<circle cx="86" cy="54" r="5" stroke="currentColor" stroke-width="1.2" fill="none" opacity="0.4"/>
<line x1="85" y1="55" x2="81" y2="54" stroke="currentColor" stroke-width="1" opacity="0.3"/>
<!-- Beak (pointy, triangular) -->
<polygon points="90,58 102,55 90,52" fill="currentColor" opacity="0.35"/>
<!-- Arm holding book out to customer -->
<line x1="97" y1="88" x2="125" y2="78" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<!-- Book in hand (big, detailed) -->
<rect x="120" y="70" width="18" height="24" rx="2" fill="currentColor" opacity="0.35"/>
<rect x="122" y="73" width="14" height="3" rx="1" fill="currentColor" opacity="0.2"/>
<line x1="129" y1="70" x2="129" y2="94" stroke="currentColor" stroke-width="1" opacity="0.2"/>
<!-- Other arm resting on counter -->
<line x1="53" y1="90" x2="35" y2="108" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<!-- Counter (big) -->
<rect x="10" y="120" width="140" height="10" rx="2" fill="currentColor" opacity="0.3"/>
<rect x="15" y="130" width="130" height="6" rx="1" fill="currentColor" opacity="0.15"/>
<!-- Stack of books on counter -->
<rect x="25" y="105" width="22" height="15" rx="2" fill="currentColor" opacity="0.3"/>
<rect x="28" y="98" width="18" height="7" rx="1" fill="currentColor" opacity="0.25"/>
<rect x="30" y="93" width="14" height="5" rx="1" fill="currentColor" opacity="0.2"/>
<!-- Legs (long!) -->
<line x1="65" y1="123" x2="55" y2="185" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.35"/>
<line x1="85" y1="123" x2="95" y2="185" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.35"/>
<!-- Big flat feet -->
<ellipse cx="50" cy="188" rx="10" ry="4" fill="currentColor" opacity="0.3"/>
<ellipse cx="100" cy="188" rx="10" ry="4" fill="currentColor" opacity="0.3"/>
</svg>
<!-- Shadok 2 : Factrice running profile, letter carrier bag, letters flying, cap -->
<svg class="shadok-factrice" viewBox="0 0 180 200" fill="none" aria-hidden="true">
<!-- Body (small oval, leaning forward) -->
<ellipse cx="80" cy="80" rx="20" ry="26" fill="currentColor" opacity="0.25" transform="rotate(-15 80 80)"/>
<!-- Head -->
<circle cx="95" cy="48" r="15" fill="currentColor" opacity="0.3"/>
<!-- Cap -->
<rect x="82" y="34" width="28" height="7" rx="3" fill="currentColor" opacity="0.4"/>
<rect x="106" y="36" width="10" height="5" rx="2" fill="currentColor" opacity="0.3"/>
<!-- Eyes (determined, looking forward) -->
<circle cx="101" cy="46" r="1.8" fill="currentColor" opacity="0.6"/>
<circle cx="106" cy="45" r="1.8" fill="currentColor" opacity="0.6"/>
<!-- Beak (pointy, profile) -->
<polygon points="110,48 125,44 110,42" fill="currentColor" opacity="0.35"/>
<!-- Carrier bag (big, on shoulder) -->
<rect x="55" y="62" width="28" height="35" rx="4" fill="currentColor" opacity="0.3"/>
<line x1="60" y1="62" x2="75" y2="55" stroke="currentColor" stroke-width="2" stroke-linecap="round" opacity="0.3"/>
<!-- Letters flying out of bag -->
<rect x="42" y="55" width="12" height="9" rx="1" fill="currentColor" opacity="0.2" transform="rotate(-20 48 59)"/>
<rect x="35" y="45" width="10" height="7" rx="1" fill="currentColor" opacity="0.15" transform="rotate(-35 40 48)"/>
<rect x="48" y="42" width="11" height="8" rx="1" fill="currentColor" opacity="0.18" transform="rotate(10 53 46)"/>
<!-- Arm swinging back -->
<line x1="65" y1="72" x2="45" y2="90" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.35"/>
<!-- Running legs (long strides!) -->
<line x1="72" y1="106" x2="40" y2="170" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.35"/>
<line x1="88" y1="106" x2="130" y2="165" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.35"/>
<!-- Big flat feet -->
<ellipse cx="35" cy="174" rx="11" ry="4" fill="currentColor" opacity="0.3"/>
<ellipse cx="135" cy="168" rx="11" ry="4" fill="currentColor" opacity="0.3"/>
</svg>
<!-- Shadok 3 : Cartonnier assembling cardboard box, tape gun, flat boxes nearby -->
<svg class="shadok-cartonnier" viewBox="0 0 170 220" fill="none" aria-hidden="true">
<!-- Body (small, leaning over work) -->
<ellipse cx="70" cy="100" rx="22" ry="27" fill="currentColor" opacity="0.25" transform="rotate(8 70 100)"/>
<!-- Head (looking down at box) -->
<circle cx="78" cy="65" r="15" fill="currentColor" opacity="0.3"/>
<!-- Eyes (focused, looking down) -->
<circle cx="83" cy="67" r="1.8" fill="currentColor" opacity="0.6"/>
<circle cx="88" cy="68" r="1.8" fill="currentColor" opacity="0.6"/>
<!-- Beak pointing down -->
<polygon points="88,72 98,78 86,76" fill="currentColor" opacity="0.35"/>
<!-- Arm holding tape gun -->
<line x1="90" y1="95" x2="130" y2="105" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.35"/>
<!-- Tape gun (big, detailed) -->
<circle cx="138" cy="102" r="8" fill="currentColor" opacity="0.25"/>
<rect x="132" y="99" width="18" height="6" rx="1" fill="currentColor" opacity="0.35"/>
<polygon points="150,100 158,102 150,104" fill="currentColor" opacity="0.3"/>
<!-- Other arm holding box flap -->
<line x1="50" y1="92" x2="35" y2="118" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.35"/>
<!-- Box being assembled (big, 3D perspective) -->
<rect x="25" y="118" width="45" height="35" rx="2" fill="currentColor" opacity="0.2"/>
<polygon points="25,118 15,108 60,108 70,118" fill="currentColor" opacity="0.15"/>
<polygon points="70,118 60,108 60,143 70,153" fill="currentColor" opacity="0.12"/>
<!-- Flap open -->
<polygon points="25,118 15,108 15,98 25,108" fill="currentColor" opacity="0.18"/>
<!-- Flat boxes nearby -->
<rect x="90" y="145" width="35" height="4" rx="1" fill="currentColor" opacity="0.2"/>
<rect x="88" y="150" width="38" height="4" rx="1" fill="currentColor" opacity="0.15"/>
<rect x="92" y="155" width="32" height="4" rx="1" fill="currentColor" opacity="0.12"/>
<!-- Legs (long!) -->
<line x1="60" y1="127" x2="48" y2="192" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.35"/>
<line x1="80" y1="127" x2="92" y2="192" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.35"/>
<!-- Big flat feet -->
<ellipse cx="43" cy="196" rx="10" ry="4" fill="currentColor" opacity="0.3"/>
<ellipse cx="97" cy="196" rx="10" ry="4" fill="currentColor" opacity="0.3"/>
</svg>
<!-- Shadok 4 : Cycliste livreur on bicycle, package on rack, profile leaning forward -->
<svg class="shadok-cycliste" viewBox="0 0 180 200" fill="none" aria-hidden="true">
<!-- Back wheel -->
<circle cx="35" cy="160" r="22" stroke="currentColor" stroke-width="2.5" fill="none" opacity="0.25"/>
<circle cx="35" cy="160" r="3" fill="currentColor" opacity="0.3"/>
<!-- Front wheel -->
<circle cx="145" cy="160" r="22" stroke="currentColor" stroke-width="2.5" fill="none" opacity="0.25"/>
<circle cx="145" cy="160" r="3" fill="currentColor" opacity="0.3"/>
<!-- Frame -->
<line x1="35" y1="160" x2="80" y2="120" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" opacity="0.3"/>
<line x1="80" y1="120" x2="120" y2="120" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" opacity="0.3"/>
<line x1="120" y1="120" x2="145" y2="160" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" opacity="0.3"/>
<line x1="35" y1="160" x2="80" y2="140" stroke="currentColor" stroke-width="2" stroke-linecap="round" opacity="0.2"/>
<line x1="80" y1="140" x2="120" y2="120" stroke="currentColor" stroke-width="2" stroke-linecap="round" opacity="0.2"/>
<!-- Handlebars -->
<line x1="120" y1="120" x2="130" y2="105" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" opacity="0.3"/>
<line x1="125" y1="105" x2="135" y2="105" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" opacity="0.3"/>
<!-- Saddle -->
<rect x="72" y="116" width="16" height="5" rx="2" fill="currentColor" opacity="0.3"/>
<!-- Package on rear rack (big!) -->
<rect x="15" y="125" width="30" height="22" rx="3" fill="currentColor" opacity="0.3"/>
<line x1="30" y1="125" x2="30" y2="147" stroke="currentColor" stroke-width="1" opacity="0.2"/>
<line x1="15" y1="136" x2="45" y2="136" stroke="currentColor" stroke-width="1" opacity="0.2"/>
<!-- Rear rack -->
<line x1="35" y1="147" x2="35" y2="155" stroke="currentColor" stroke-width="2" stroke-linecap="round" opacity="0.25"/>
<line x1="15" y1="147" x2="55" y2="147" stroke="currentColor" stroke-width="2" stroke-linecap="round" opacity="0.25"/>
<!-- Body (small, leaning forward on bike) -->
<ellipse cx="90" cy="100" rx="18" ry="24" fill="currentColor" opacity="0.25" transform="rotate(-20 90 100)"/>
<!-- Head -->
<circle cx="108" cy="68" r="14" fill="currentColor" opacity="0.3"/>
<!-- Eyes (focused ahead) -->
<circle cx="115" cy="65" r="1.8" fill="currentColor" opacity="0.6"/>
<circle cx="120" cy="64" r="1.8" fill="currentColor" opacity="0.6"/>
<!-- Beak -->
<polygon points="122,68 134,64 122,62" fill="currentColor" opacity="0.35"/>
<!-- Arms to handlebars -->
<line x1="100" y1="90" x2="130" y2="105" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.35"/>
<!-- Legs pedaling -->
<line x1="82" y1="118" x2="72" y2="145" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.35"/>
<line x1="88" y1="120" x2="95" y2="148" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.35"/>
<!-- Feet on pedals -->
<ellipse cx="70" cy="148" rx="8" ry="3" fill="currentColor" opacity="0.3"/>
<ellipse cx="97" cy="150" rx="8" ry="3" fill="currentColor" opacity="0.3"/>
</svg>
<!-- Shadok 5 : Lectrice satisfaite sitting in armchair, open book, wrapping paper on floor -->
<svg class="shadok-lectrice" viewBox="0 0 160 210" fill="none" aria-hidden="true">
<!-- Armchair (big!) -->
<path d="M20 100 Q20 80 35 80 L115 80 Q130 80 130 100 L130 150 L20 150 Z" fill="currentColor" opacity="0.15"/>
<!-- Armrests -->
<rect x="10" y="90" width="16" height="55" rx="6" fill="currentColor" opacity="0.2"/>
<rect x="124" y="90" width="16" height="55" rx="6" fill="currentColor" opacity="0.2"/>
<!-- Chair legs -->
<line x1="25" y1="150" x2="22" y2="165" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.2"/>
<line x1="125" y1="150" x2="128" y2="165" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.2"/>
<!-- Body (sitting, small) -->
<ellipse cx="75" cy="120" rx="20" ry="25" fill="currentColor" opacity="0.25"/>
<!-- Head (tilted, reading happily) -->
<circle cx="75" cy="82" r="16" fill="currentColor" opacity="0.3"/>
<!-- Happy eyes (curved) -->
<path d="M66 79 Q69 76 72 79" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" fill="none" opacity="0.5"/>
<path d="M80 78 Q83 75 86 78" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" fill="none" opacity="0.5"/>
<!-- Small smile -->
<path d="M70 88 Q75 93 80 88" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" fill="none" opacity="0.3"/>
<!-- Beak (small, happy) -->
<polygon points="88,83 98,80 88,78" fill="currentColor" opacity="0.3"/>
<!-- Arms holding open book -->
<line x1="55" y1="112" x2="40" y2="125" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.35"/>
<line x1="95" y1="112" x2="110" y2="125" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.35"/>
<!-- Open book (big, in lap) -->
<rect x="38" y="125" width="28" height="20" rx="1" fill="currentColor" opacity="0.3"/>
<rect x="66" y="125" width="28" height="20" rx="1" fill="currentColor" opacity="0.25"/>
<line x1="66" y1="125" x2="66" y2="145" stroke="currentColor" stroke-width="1.5" opacity="0.35"/>
<!-- Text lines on book pages -->
<line x1="42" y1="131" x2="62" y2="131" stroke="currentColor" stroke-width="0.8" opacity="0.15"/>
<line x1="42" y1="135" x2="60" y2="135" stroke="currentColor" stroke-width="0.8" opacity="0.15"/>
<line x1="70" y1="131" x2="90" y2="131" stroke="currentColor" stroke-width="0.8" opacity="0.15"/>
<line x1="70" y1="135" x2="88" y2="135" stroke="currentColor" stroke-width="0.8" opacity="0.15"/>
<!-- Legs (long, dangling from chair) -->
<line x1="62" y1="145" x2="50" y2="195" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.35"/>
<line x1="88" y1="145" x2="100" y2="195" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.35"/>
<!-- Big flat feet -->
<ellipse cx="45" cy="198" rx="10" ry="4" fill="currentColor" opacity="0.3"/>
<ellipse cx="105" cy="198" rx="10" ry="4" fill="currentColor" opacity="0.3"/>
<!-- Wrapping paper on floor -->
<path d="M30 175 Q45 170 55 178 Q60 182 50 185" stroke="currentColor" stroke-width="1.5" fill="currentColor" opacity="0.12"/>
<path d="M100 180 Q115 172 125 180 Q120 188 108 185" stroke="currentColor" stroke-width="1.5" fill="currentColor" opacity="0.1"/>
</svg>
<!-- Shadok 6 : Empileur balancing tower of books on head, arms out, worried eyes -->
<svg class="shadok-empileur" viewBox="0 0 140 220" fill="none" aria-hidden="true">
<!-- Precarious tower of books on head (big, detailed!) -->
<rect x="48" y="8" width="24" height="7" rx="1" fill="currentColor" opacity="0.2" transform="rotate(3 60 11)"/>
<rect x="46" y="16" width="28" height="7" rx="1" fill="currentColor" opacity="0.25" transform="rotate(-2 60 19)"/>
<rect x="44" y="24" width="32" height="7" rx="1" fill="currentColor" opacity="0.2" transform="rotate(4 60 27)"/>
<rect x="47" y="32" width="26" height="7" rx="1" fill="currentColor" opacity="0.28" transform="rotate(-3 60 35)"/>
<rect x="50" y="40" width="22" height="6" rx="1" fill="currentColor" opacity="0.22" transform="rotate(2 61 43)"/>
<!-- Head -->
<circle cx="65" cy="58" r="14" fill="currentColor" opacity="0.3"/>
<!-- Worried eyes (looking up at books) -->
<circle cx="60" cy="54" r="2.2" fill="currentColor" opacity="0.6"/>
<circle cx="70" cy="54" r="2.2" fill="currentColor" opacity="0.6"/>
<!-- Tiny worried pupils (looking up) -->
<circle cx="60" cy="53" r="1" fill="currentColor" opacity="0.35"/>
<circle cx="70" cy="53" r="1" fill="currentColor" opacity="0.35"/>
<!-- Worried eyebrows -->
<line x1="56" y1="50" x2="63" y2="51" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" opacity="0.4"/>
<line x1="74" y1="51" x2="67" y2="50" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" opacity="0.4"/>
<!-- Beak (slightly open, worried) -->
<polygon points="74,60 86,57 74,55" fill="currentColor" opacity="0.3"/>
<line x1="76" y1="58" x2="83" y2="58" stroke="currentColor" stroke-width="0.8" opacity="0.2"/>
<!-- Body (small oval, upright, tense) -->
<ellipse cx="65" cy="95" rx="20" ry="26" fill="currentColor" opacity="0.25"/>
<!-- Arms out wide for balance -->
<line x1="45" y1="88" x2="10" y2="82" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.35"/>
<line x1="85" y1="88" x2="120" y2="82" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.35"/>
<!-- Hands (small circles at arm ends) -->
<circle cx="8" cy="82" r="3" fill="currentColor" opacity="0.25"/>
<circle cx="122" cy="82" r="3" fill="currentColor" opacity="0.25"/>
<!-- Legs (long!) -->
<line x1="55" y1="121" x2="45" y2="192" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.35"/>
<line x1="75" y1="121" x2="85" y2="192" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.35"/>
<!-- Big flat feet -->
<ellipse cx="40" cy="196" rx="10" ry="4" fill="currentColor" opacity="0.3"/>
<ellipse cx="90" cy="196" rx="10" ry="4" fill="currentColor" opacity="0.3"/>
</svg>
<div class="container-content">
<div class="mx-auto max-w-2xl">
<div class="section-icon mx-auto mb-6">
<div class="i-lucide-shopping-bag h-12 w-12" />
</div>
<p class="mb-2 font-mono text-sm tracking-widest text-primary uppercase text-center">
{{ content?.kicker }}
</p>
<h1 class="font-display text-3xl font-bold mb-4 text-center" style="color: hsl(var(--color-text))">
{{ content?.title }}
</h1>
<p class="text-lg leading-relaxed mb-10 text-center" style="color: hsl(var(--color-text-muted))">
{{ content?.description }}
</p>
<!-- Bookelis CTA -->
<div class="cta-block mb-8">
<div class="cta-icon">
<div class="i-lucide-globe h-5 w-5" />
</div>
<div class="flex-1">
<h2 class="font-display text-lg font-semibold mb-1" style="color: hsl(var(--color-text))">
Commander en ligne
</h2>
<p class="text-sm mb-3" style="color: hsl(var(--color-text-muted))">
Impression à la demande, livraison chez vous.
</p>
<a
:href="content?.bookelis?.url"
target="_blank"
rel="noopener"
class="order-btn"
>
<div class="i-lucide-external-link h-4 w-4" />
{{ content?.bookelis?.label }}
</a>
</div>
</div>
<!-- Librairie -->
<div class="cta-block mb-10">
<div class="cta-icon cta-icon--accent">
<div class="i-lucide-store h-5 w-5" />
</div>
<div class="flex-1">
<h2 class="font-display text-lg font-semibold mb-2" style="color: hsl(var(--color-text))">
{{ content?.librairie?.title }}
</h2>
<p class="leading-relaxed whitespace-pre-line" style="color: hsl(var(--color-text-muted))">
{{ content?.librairie?.text }}
</p>
</div>
</div>
<div class="text-center">
<UiBaseButton variant="ghost" to="/economique">
<div class="i-lucide-arrow-left mr-2 h-4 w-4" />
Autonomie économique
</UiBaseButton>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
const { data: content } = await usePageContent('economique/commande')
useHead({
title: content.value?.meta?.title ?? 'Commander le livre',
})
</script>
<style scoped>
.section-icon {
display: flex;
align-items: center;
justify-content: center;
width: 5rem;
height: 5rem;
border-radius: 1rem;
background: hsl(var(--color-primary) / 0.1);
border: 1px solid hsl(var(--color-primary) / 0.2);
color: hsl(var(--color-primary));
}
.cta-block {
display: flex;
gap: 1rem;
padding: 1.5rem;
border-radius: 0.75rem;
border: 1px solid hsl(var(--color-text) / 0.08);
background: hsl(var(--color-surface));
}
.cta-icon {
display: flex;
align-items: center;
justify-content: center;
width: 2.5rem;
height: 2.5rem;
border-radius: 0.5rem;
background: hsl(var(--color-primary) / 0.12);
color: hsl(var(--color-primary));
flex-shrink: 0;
margin-top: 0.125rem;
}
.cta-icon--accent {
background: hsl(var(--color-accent) / 0.12);
color: hsl(var(--color-accent));
}
.order-btn {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
border-radius: 20px;
font-size: 0.85rem;
font-weight: 600;
text-decoration: none;
background: hsl(var(--color-primary));
color: white;
transition: all 0.2s;
}
.order-btn:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px hsl(var(--color-primary) / 0.3);
}
.shadok-libraire {
position: absolute;
left: 2%;
top: 5%;
width: clamp(70px, 10vw, 140px);
opacity: 0.22;
pointer-events: none;
color: hsl(var(--color-primary));
animation: shadok-float-1 9s ease-in-out infinite;
}
.shadok-factrice {
position: absolute;
right: 2%;
top: 4%;
width: clamp(70px, 10vw, 140px);
opacity: 0.24;
pointer-events: none;
color: hsl(var(--color-accent));
animation: shadok-float-2 11s ease-in-out infinite;
}
.shadok-cartonnier {
position: absolute;
left: 3%;
top: 45%;
width: clamp(70px, 10vw, 130px);
opacity: 0.2;
pointer-events: none;
color: hsl(var(--color-primary));
animation: shadok-float-3 10s ease-in-out infinite;
}
.shadok-cycliste {
position: absolute;
left: 3%;
bottom: 8%;
width: clamp(75px, 10vw, 140px);
opacity: 0.18;
pointer-events: none;
color: hsl(var(--color-accent));
animation: shadok-float-4 8s ease-in-out infinite;
}
.shadok-lectrice {
position: absolute;
right: 2%;
bottom: 10%;
width: clamp(70px, 10vw, 135px);
opacity: 0.28;
pointer-events: none;
color: hsl(var(--color-primary));
animation: shadok-float-5 12s ease-in-out infinite;
}
.shadok-empileur {
position: absolute;
left: 50%;
bottom: 2%;
transform: translateX(-50%);
width: clamp(70px, 10vw, 130px);
opacity: 0.2;
pointer-events: none;
color: hsl(var(--color-accent));
animation: shadok-float-6 7s ease-in-out infinite;
}
@keyframes shadok-float-1 { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-8px); } }
@keyframes shadok-float-2 { 0%, 100% { transform: translateY(0) rotate(0deg); } 50% { transform: translateY(-10px) rotate(2deg); } }
@keyframes shadok-float-3 { 0%, 100% { transform: translateY(0) rotate(0deg); } 50% { transform: translateY(-12px) rotate(-1deg); } }
@keyframes shadok-float-4 { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-7px) rotate(1deg); } }
@keyframes shadok-float-5 { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-6px); } }
@keyframes shadok-float-6 { 0%, 100% { transform: translateX(-50%) translateY(0); } 50% { transform: translateX(-50%) translateY(-9px); } }
@media (max-width: 768px) {
.shadok-libraire,
.shadok-factrice,
.shadok-cartonnier,
.shadok-cycliste,
.shadok-lectrice,
.shadok-empileur { display: none; }
}
</style>

View File

@@ -0,0 +1,647 @@
<template>
<div class="relative overflow-hidden section-padding">
<!-- Shadok boulangère (top-left, walking profile, carrying bread tray) -->
<svg class="shadok shadok-boulangere" viewBox="0 0 160 200" fill="none" xmlns="http://www.w3.org/2000/svg">
<g stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<!-- body tilted forward walking -->
<ellipse cx="85" cy="105" rx="22" ry="28" fill="currentColor" opacity="0.25" transform="rotate(-8 85 105)" />
<!-- head -->
<circle cx="80" cy="65" r="16" fill="currentColor" opacity="0.3" />
<!-- eyes looking forward -->
<circle cx="88" cy="62" r="2" fill="currentColor" />
<circle cx="88" cy="68" r="1.5" fill="currentColor" />
<!-- beak pointing right -->
<path d="M94 64 L108 62 L94 68" stroke="currentColor" stroke-width="2" fill="currentColor" opacity="0.2" />
<!-- arms up holding tray -->
<line x1="75" y1="88" x2="60" y2="42" stroke-width="3" />
<line x1="95" y1="88" x2="110" y2="42" stroke-width="3" />
<!-- big bread tray -->
<rect x="48" y="32" width="74" height="12" rx="3" fill="currentColor" opacity="0.25" />
<!-- bread loaves on tray -->
<ellipse cx="62" cy="30" rx="10" ry="6" fill="currentColor" opacity="0.3" />
<ellipse cx="85" cy="28" rx="11" ry="7" fill="currentColor" opacity="0.3" />
<ellipse cx="108" cy="30" rx="9" ry="6" fill="currentColor" opacity="0.3" />
<!-- score marks on loaves -->
<line x1="58" y1="28" x2="60" y2="32" opacity="0.4" />
<line x1="64" y1="27" x2="66" y2="31" opacity="0.4" />
<line x1="82" y1="26" x2="84" y2="30" opacity="0.4" />
<line x1="88" y1="25" x2="90" y2="29" opacity="0.4" />
<!-- flour apron -->
<path d="M70 95 Q85 92 100 95 L97 125 Q85 128 73 125 Z" fill="currentColor" opacity="0.1" />
<!-- flour dots -->
<circle cx="78" cy="100" r="1" fill="currentColor" opacity="0.3" />
<circle cx="92" cy="108" r="1.2" fill="currentColor" opacity="0.25" />
<circle cx="83" cy="115" r="0.8" fill="currentColor" opacity="0.3" />
<!-- long legs walking stride -->
<line x1="78" y1="132" x2="58" y2="185" stroke-width="3" />
<line x1="92" y1="132" x2="112" y2="180" stroke-width="3" />
<!-- big flat feet -->
<ellipse cx="52" cy="188" rx="12" ry="4" fill="currentColor" opacity="0.3" />
<ellipse cx="118" cy="183" rx="12" ry="4" fill="currentColor" opacity="0.3" />
</g>
</svg>
<!-- Shadok potier (top-right, sitting at wheel) -->
<svg class="shadok shadok-potier" viewBox="0 0 170 200" fill="none" xmlns="http://www.w3.org/2000/svg">
<g stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<!-- body sitting, slightly hunched -->
<ellipse cx="70" cy="100" rx="23" ry="26" fill="currentColor" opacity="0.25" transform="rotate(5 70 100)" />
<!-- head looking down at wheel -->
<circle cx="65" cy="65" r="15" fill="currentColor" opacity="0.3" />
<!-- eyes looking down -->
<circle cx="60" cy="68" r="2" fill="currentColor" />
<circle cx="72" cy="70" r="1.8" fill="currentColor" />
<!-- beak pointing down-right -->
<path d="M68 76 L80 82 L66 82" stroke="currentColor" stroke-width="2" fill="currentColor" opacity="0.2" />
<!-- arms reaching to vase -->
<line x1="50" y1="92" x2="110" y2="115" stroke-width="3" />
<line x1="90" y1="90" x2="120" y2="110" stroke-width="3" />
<!-- hands on vase -->
<circle cx="112" cy="113" r="4" fill="currentColor" opacity="0.2" />
<circle cx="122" cy="108" r="4" fill="currentColor" opacity="0.2" />
<!-- vase being shaped -->
<path d="M108 95 Q102 108 106 125 Q115 135 124 125 Q128 108 122 95" fill="currentColor" opacity="0.2" stroke="currentColor" stroke-width="2" />
<!-- vase opening -->
<ellipse cx="115" cy="95" rx="8" ry="3" fill="currentColor" opacity="0.15" />
<!-- pottery wheel -->
<ellipse cx="115" cy="140" rx="28" ry="8" fill="currentColor" opacity="0.2" />
<line x1="115" y1="148" x2="115" y2="170" stroke-width="3" />
<line x1="100" y1="170" x2="130" y2="170" stroke-width="3" />
<!-- spinning motion lines -->
<path d="M88 138 Q85 135 88 132" fill="none" opacity="0.35" />
<path d="M142 138 Q145 135 142 132" fill="none" opacity="0.35" />
<path d="M90 145 Q86 143 90 140" fill="none" opacity="0.25" />
<!-- legs folded sitting -->
<line x1="60" y1="124" x2="40" y2="165" stroke-width="3" />
<line x1="80" y1="124" x2="75" y2="170" stroke-width="3" />
<!-- flat feet -->
<ellipse cx="34" cy="168" rx="11" ry="4" fill="currentColor" opacity="0.3" />
<ellipse cx="70" cy="173" rx="11" ry="4" fill="currentColor" opacity="0.3" />
</g>
</svg>
<!-- Shadok apicultrice (left, 40% down, with smoker and bees) -->
<svg class="shadok shadok-apicultrice" viewBox="0 0 160 210" fill="none" xmlns="http://www.w3.org/2000/svg">
<g stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<!-- body upright -->
<ellipse cx="75" cy="110" rx="20" ry="27" fill="currentColor" opacity="0.25" />
<!-- head -->
<circle cx="75" cy="70" r="15" fill="currentColor" opacity="0.3" />
<!-- beekeeping hat brim -->
<ellipse cx="75" cy="56" rx="22" ry="5" fill="currentColor" opacity="0.2" />
<!-- hat top -->
<path d="M58 56 Q58 42 75 40 Q92 42 92 56" fill="currentColor" opacity="0.15" />
<!-- veil hanging down -->
<path d="M53 56 L53 82 Q75 88 97 82 L97 56" fill="none" stroke-dasharray="3 2" opacity="0.3" />
<!-- eyes behind veil -->
<circle cx="70" cy="68" r="2" fill="currentColor" />
<circle cx="82" cy="66" r="1.8" fill="currentColor" />
<!-- beak -->
<path d="M80 74 L94 72 L82 78" stroke="currentColor" stroke-width="2" fill="currentColor" opacity="0.2" />
<!-- arm left holding smoker -->
<line x1="56" y1="100" x2="28" y2="85" stroke-width="3" />
<!-- smoker device (big) -->
<rect x="10" y="72" width="22" height="20" rx="4" fill="currentColor" opacity="0.25" />
<path d="M14 72 L14 62 Q21 58 28 62 L28 72" fill="currentColor" opacity="0.15" />
<!-- smoke puffs -->
<circle cx="18" cy="55" r="5" fill="currentColor" opacity="0.12" />
<circle cx="24" cy="48" r="4" fill="currentColor" opacity="0.08" />
<circle cx="16" cy="42" r="3" fill="currentColor" opacity="0.06" />
<!-- arm right -->
<line x1="94" y1="100" x2="110" y2="90" stroke-width="3" />
<!-- buzzing bees (small detailed dots) -->
<circle cx="120" cy="60" r="2.5" fill="currentColor" opacity="0.35" />
<line x1="118" y1="58" x2="115" y2="56" opacity="0.3" />
<line x1="122" y1="58" x2="125" y2="55" opacity="0.3" />
<circle cx="135" cy="75" r="2" fill="currentColor" opacity="0.3" />
<line x1="133" y1="73" x2="131" y2="71" opacity="0.25" />
<line x1="137" y1="73" x2="139" y2="71" opacity="0.25" />
<circle cx="110" cy="48" r="2.2" fill="currentColor" opacity="0.25" />
<line x1="108" y1="46" x2="106" y2="44" opacity="0.2" />
<line x1="112" y1="46" x2="114" y2="43" opacity="0.2" />
<circle cx="140" cy="55" r="1.8" fill="currentColor" opacity="0.2" />
<circle cx="128" cy="42" r="2" fill="currentColor" opacity="0.22" />
<!-- long legs -->
<line x1="66" y1="136" x2="55" y2="192" stroke-width="3" />
<line x1="84" y1="136" x2="95" y2="192" stroke-width="3" />
<!-- flat feet -->
<ellipse cx="49" cy="195" rx="11" ry="4" fill="currentColor" opacity="0.3" />
<ellipse cx="101" cy="195" rx="11" ry="4" fill="currentColor" opacity="0.3" />
</g>
</svg>
<!-- Shadok forgeron (right, 35%, swinging hammer on anvil) -->
<svg class="shadok shadok-forgeron" viewBox="0 0 180 210" fill="none" xmlns="http://www.w3.org/2000/svg">
<g stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<!-- body leaning forward, muscular feel -->
<ellipse cx="90" cy="105" rx="24" ry="30" fill="currentColor" opacity="0.3" transform="rotate(-12 90 105)" />
<!-- head in profile -->
<circle cx="100" cy="65" r="16" fill="currentColor" opacity="0.3" />
<!-- eyes focused, intense -->
<circle cx="108" cy="62" r="2.5" fill="currentColor" />
<circle cx="108" cy="70" r="2" fill="currentColor" />
<!-- beak pointing right -->
<path d="M114 65 L130 62 L114 70" stroke="currentColor" stroke-width="2" fill="currentColor" opacity="0.2" />
<!-- big arm swinging hammer UP -->
<line x1="72" y1="90" x2="40" y2="40" stroke-width="3.5" />
<!-- hammer head (big!) -->
<rect x="22" y="22" width="38" height="18" rx="3" fill="currentColor" opacity="0.3" />
<!-- hammer handle end -->
<line x1="40" y1="40" x2="42" y2="30" stroke-width="4" />
<!-- other arm resting on anvil -->
<line x1="110" y1="92" x2="140" y2="130" stroke-width="3" />
<!-- anvil (big detailed) -->
<path d="M120 135 L160 135 L168 145 L112 145 Z" fill="currentColor" opacity="0.25" />
<rect x="128" y="145" width="24" height="20" fill="currentColor" opacity="0.2" />
<rect x="122" y="165" width="36" height="6" rx="2" fill="currentColor" opacity="0.25" />
<!-- hot metal on anvil -->
<rect x="132" y="130" width="20" height="5" rx="1" fill="currentColor" opacity="0.35" />
<!-- sparks flying -->
<line x1="138" y1="128" x2="132" y2="118" opacity="0.4" />
<line x1="145" y1="126" x2="150" y2="116" opacity="0.35" />
<line x1="152" y1="128" x2="160" y2="120" opacity="0.3" />
<circle cx="130" cy="115" r="1.5" fill="currentColor" opacity="0.35" />
<circle cx="155" cy="112" r="1.2" fill="currentColor" opacity="0.3" />
<circle cx="162" cy="118" r="1" fill="currentColor" opacity="0.25" />
<!-- long legs wide stance -->
<line x1="78" y1="133" x2="55" y2="190" stroke-width="3" />
<line x1="100" y1="133" x2="120" y2="192" stroke-width="3" />
<!-- flat feet -->
<ellipse cx="48" cy="193" rx="13" ry="4" fill="currentColor" opacity="0.3" />
<ellipse cx="127" cy="195" rx="13" ry="4" fill="currentColor" opacity="0.3" />
</g>
</svg>
<!-- Shadok maraîchère (bottom-left, pushing wheelbarrow) -->
<svg class="shadok shadok-maraichere" viewBox="0 0 180 200" fill="none" xmlns="http://www.w3.org/2000/svg">
<g stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<!-- body leaning forward pushing -->
<ellipse cx="120" cy="95" rx="21" ry="26" fill="currentColor" opacity="0.25" transform="rotate(-20 120 95)" />
<!-- head tilted forward -->
<circle cx="130" cy="58" r="14" fill="currentColor" opacity="0.3" />
<!-- eyes looking down at path -->
<circle cx="136" cy="56" r="2" fill="currentColor" />
<circle cx="136" cy="63" r="1.5" fill="currentColor" />
<!-- beak pointing right-down -->
<path d="M140 60 L154 64 L140 66" stroke="currentColor" stroke-width="2" fill="currentColor" opacity="0.2" />
<!-- arms pushing wheelbarrow handles -->
<line x1="104" y1="86" x2="72" y2="110" stroke-width="3" />
<line x1="108" y1="80" x2="72" y2="100" stroke-width="3" />
<!-- wheelbarrow body (big!) -->
<path d="M10 80 L70 80 L75 120 L5 120 Z" fill="currentColor" opacity="0.2" />
<path d="M10 80 L70 80 L75 120 L5 120 Z" />
<!-- wheelbarrow handles -->
<line x1="70" y1="90" x2="72" y2="110" stroke-width="3" />
<line x1="70" y1="100" x2="72" y2="100" stroke-width="2" />
<!-- wheel -->
<circle cx="12" cy="128" r="12" fill="currentColor" opacity="0.15" />
<circle cx="12" cy="128" r="12" />
<circle cx="12" cy="128" r="3" fill="currentColor" opacity="0.3" />
<!-- vegetables in wheelbarrow -->
<circle cx="22" cy="72" r="7" fill="currentColor" opacity="0.3" />
<circle cx="38" cy="70" r="8" fill="currentColor" opacity="0.25" />
<circle cx="55" cy="73" r="6" fill="currentColor" opacity="0.3" />
<ellipse cx="30" cy="76" rx="5" ry="8" fill="currentColor" opacity="0.2" transform="rotate(15 30 76)" />
<!-- carrot tops -->
<line x1="22" y1="66" x2="18" y2="58" opacity="0.35" />
<line x1="22" y1="66" x2="25" y2="57" opacity="0.35" />
<line x1="55" y1="67" x2="52" y2="60" opacity="0.3" />
<line x1="55" y1="67" x2="58" y2="59" opacity="0.3" />
<!-- long legs striding -->
<line x1="112" y1="118" x2="100" y2="180" stroke-width="3" />
<line x1="128" y1="118" x2="150" y2="178" stroke-width="3" />
<!-- flat feet -->
<ellipse cx="94" cy="183" rx="12" ry="4" fill="currentColor" opacity="0.3" />
<ellipse cx="156" cy="181" rx="12" ry="4" fill="currentColor" opacity="0.3" />
</g>
</svg>
<!-- Shadok tisserand (bottom-right, sitting at loom) -->
<svg class="shadok shadok-tisserand" viewBox="0 0 170 200" fill="none" xmlns="http://www.w3.org/2000/svg">
<g stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<!-- body sitting upright -->
<ellipse cx="55" cy="100" rx="20" ry="25" fill="currentColor" opacity="0.25" />
<!-- head looking down at loom -->
<circle cx="55" cy="65" r="15" fill="currentColor" opacity="0.3" />
<!-- eyes focused downward -->
<circle cx="62" cy="66" r="2" fill="currentColor" />
<circle cx="60" cy="72" r="1.5" fill="currentColor" />
<!-- beak pointing right-down -->
<path d="M66 70 L78 74 L64 76" stroke="currentColor" stroke-width="2" fill="currentColor" opacity="0.2" />
<!-- arms reaching to loom -->
<line x1="72" y1="92" x2="100" y2="80" stroke-width="3" />
<line x1="74" y1="100" x2="105" y2="100" stroke-width="3" />
<!-- loom frame (big detailed) -->
<rect x="95" y="55" width="60" height="80" rx="3" fill="none" />
<!-- vertical loom posts -->
<line x1="95" y1="55" x2="95" y2="135" stroke-width="3" />
<line x1="155" y1="55" x2="155" y2="135" stroke-width="3" />
<!-- top beam -->
<line x1="95" y1="55" x2="155" y2="55" stroke-width="3" />
<!-- warp threads (vertical) -->
<line x1="105" y1="55" x2="105" y2="135" opacity="0.3" />
<line x1="115" y1="55" x2="115" y2="135" opacity="0.3" />
<line x1="125" y1="55" x2="125" y2="135" opacity="0.3" />
<line x1="135" y1="55" x2="135" y2="135" opacity="0.3" />
<line x1="145" y1="55" x2="145" y2="135" opacity="0.3" />
<!-- weft threads (horizontal, partial = work in progress) -->
<line x1="95" y1="70" x2="155" y2="70" opacity="0.25" />
<line x1="95" y1="80" x2="155" y2="80" opacity="0.25" />
<line x1="95" y1="90" x2="145" y2="90" opacity="0.25" />
<line x1="95" y1="100" x2="135" y2="100" opacity="0.2" />
<!-- shuttle in hand -->
<ellipse cx="105" cy="100" rx="8" ry="3" fill="currentColor" opacity="0.3" transform="rotate(-5 105 100)" />
<!-- woven fabric area -->
<rect x="97" y="62" width="56" height="28" fill="currentColor" opacity="0.08" />
<!-- legs folded sitting on stool -->
<line x1="45" y1="123" x2="30" y2="178" stroke-width="3" />
<line x1="65" y1="123" x2="70" y2="180" stroke-width="3" />
<!-- flat feet -->
<ellipse cx="24" cy="181" rx="11" ry="4" fill="currentColor" opacity="0.3" />
<ellipse cx="76" cy="183" rx="11" ry="4" fill="currentColor" opacity="0.3" />
<!-- stool -->
<rect x="38" y="124" width="30" height="6" rx="2" fill="currentColor" opacity="0.15" />
<line x1="42" y1="130" x2="40" y2="148" stroke-width="2" opacity="0.3" />
<line x1="64" y1="130" x2="66" y2="148" stroke-width="2" opacity="0.3" />
</g>
</svg>
<!-- Shadok berger (center-bottom, walking with sheep) -->
<svg class="shadok shadok-berger" viewBox="0 0 180 200" fill="none" xmlns="http://www.w3.org/2000/svg">
<g stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<!-- body walking, slight lean -->
<ellipse cx="85" cy="90" rx="20" ry="26" fill="currentColor" opacity="0.25" transform="rotate(-5 85 90)" />
<!-- head 3/4 view -->
<circle cx="88" cy="52" r="16" fill="currentColor" opacity="0.3" />
<!-- eyes looking ahead, slightly different directions -->
<circle cx="94" cy="48" r="2" fill="currentColor" />
<circle cx="96" cy="56" r="1.8" fill="currentColor" />
<!-- beak -->
<path d="M100 52 L114 50 L100 56" stroke="currentColor" stroke-width="2" fill="currentColor" opacity="0.2" />
<!-- arm holding staff -->
<line x1="68" y1="82" x2="50" y2="60" stroke-width="3" />
<!-- shepherd's crook (big, detailed) -->
<line x1="50" y1="60" x2="44" y2="10" stroke-width="3" />
<path d="M44 10 Q44 0 54 0 Q62 0 62 10 Q62 18 54 18" fill="none" stroke-width="3" />
<!-- other arm relaxed -->
<line x1="104" y1="84" x2="118" y2="95" stroke-width="3" />
<!-- long legs walking -->
<line x1="76" y1="114" x2="60" y2="175" stroke-width="3" />
<line x1="94" y1="114" x2="110" y2="172" stroke-width="3" />
<!-- flat feet -->
<ellipse cx="54" cy="178" rx="12" ry="4" fill="currentColor" opacity="0.3" />
<ellipse cx="116" cy="175" rx="12" ry="4" fill="currentColor" opacity="0.3" />
<!-- sheep 1 (following) -->
<ellipse cx="140" cy="160" rx="14" ry="10" fill="currentColor" opacity="0.18" />
<circle cx="150" cy="152" r="6" fill="currentColor" opacity="0.15" />
<circle cx="153" cy="150" r="1" fill="currentColor" />
<line x1="132" y1="170" x2="132" y2="180" stroke-width="2" opacity="0.3" />
<line x1="148" y1="170" x2="148" y2="180" stroke-width="2" opacity="0.3" />
<!-- sheep 2 -->
<ellipse cx="158" cy="172" rx="12" ry="8" fill="currentColor" opacity="0.14" />
<circle cx="166" cy="166" r="5" fill="currentColor" opacity="0.12" />
<circle cx="168" cy="164" r="0.8" fill="currentColor" />
<line x1="152" y1="180" x2="152" y2="188" stroke-width="1.5" opacity="0.25" />
<line x1="164" y1="180" x2="164" y2="188" stroke-width="1.5" opacity="0.25" />
<!-- sheep 3 (smaller, behind) -->
<ellipse cx="170" cy="178" rx="9" ry="6" fill="currentColor" opacity="0.1" />
<circle cx="176" cy="174" r="4" fill="currentColor" opacity="0.08" />
<!-- dog at side -->
<ellipse cx="125" cy="170" rx="8" ry="5" fill="currentColor" opacity="0.2" />
<circle cx="130" cy="164" r="4" fill="currentColor" opacity="0.18" />
<circle cx="132" cy="163" r="1" fill="currentColor" />
<line x1="118" y1="168" x2="112" y2="165" stroke-width="1.5" opacity="0.3" />
<line x1="120" y1="175" x2="120" y2="182" stroke-width="1.5" opacity="0.25" />
<line x1="130" y1="175" x2="130" y2="182" stroke-width="1.5" opacity="0.25" />
</g>
</svg>
<!-- Shadok vigneronne (right, 55%, carrying grape basket) -->
<svg class="shadok shadok-vigneronne" viewBox="0 0 170 210" fill="none" xmlns="http://www.w3.org/2000/svg">
<g stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<!-- body seen from 3/4 back, leaning under weight -->
<ellipse cx="80" cy="105" rx="22" ry="28" fill="currentColor" opacity="0.25" transform="rotate(10 80 105)" />
<!-- head turned slightly -->
<circle cx="72" cy="65" r="15" fill="currentColor" opacity="0.3" />
<!-- eyes looking to the side -->
<circle cx="64" cy="62" r="2" fill="currentColor" />
<circle cx="66" cy="69" r="1.5" fill="currentColor" />
<!-- beak pointing left -->
<path d="M58 65 L44 62 L58 69" stroke="currentColor" stroke-width="2" fill="currentColor" opacity="0.2" />
<!-- arm back holding basket strap -->
<line x1="98" y1="92" x2="115" y2="75" stroke-width="3" />
<!-- arm front with pruning shears -->
<line x1="62" y1="95" x2="38" y2="80" stroke-width="3" />
<!-- pruning shears (big) -->
<line x1="38" y1="80" x2="26" y2="68" stroke-width="3" />
<line x1="38" y1="80" x2="28" y2="78" stroke-width="3" />
<circle cx="36" cy="78" r="3" fill="currentColor" opacity="0.2" />
<!-- blade shapes -->
<path d="M26 68 Q22 72 28 74" fill="none" stroke-width="2" />
<path d="M28 78 Q22 76 24 72" fill="none" stroke-width="2" />
<!-- big basket on back with grapes -->
<path d="M100 60 L130 60 L135 120 L95 120 Z" fill="currentColor" opacity="0.2" />
<path d="M100 60 L130 60 L135 120 L95 120 Z" />
<!-- basket weave texture -->
<line x1="100" y1="75" x2="133" y2="75" opacity="0.2" />
<line x1="98" y1="90" x2="134" y2="90" opacity="0.2" />
<line x1="97" y1="105" x2="135" y2="105" opacity="0.2" />
<!-- grapes overflowing -->
<circle cx="108" cy="55" r="4" fill="currentColor" opacity="0.3" />
<circle cx="116" cy="53" r="4.5" fill="currentColor" opacity="0.3" />
<circle cx="124" cy="55" r="4" fill="currentColor" opacity="0.25" />
<circle cx="112" cy="48" r="3.5" fill="currentColor" opacity="0.25" />
<circle cx="120" cy="47" r="3.5" fill="currentColor" opacity="0.2" />
<circle cx="105" cy="52" r="3" fill="currentColor" opacity="0.2" />
<!-- grape stems -->
<line x1="115" y1="43" x2="115" y2="36" opacity="0.3" />
<line x1="113" y1="36" x2="117" y2="36" opacity="0.3" />
<!-- grapevine nearby -->
<line x1="148" y1="30" x2="148" y2="130" stroke-width="2" opacity="0.2" />
<path d="M148 50 Q158 45 162 55" fill="none" opacity="0.2" />
<path d="M148 75 Q160 70 165 80" fill="none" opacity="0.2" />
<circle cx="160" cy="58" r="3" fill="currentColor" opacity="0.12" />
<circle cx="163" cy="83" r="3" fill="currentColor" opacity="0.1" />
<!-- grape leaf -->
<path d="M148 90 Q155 85 158 90 Q160 95 155 98 Q150 95 148 90" fill="currentColor" opacity="0.12" />
<!-- long legs -->
<line x1="70" y1="132" x2="55" y2="192" stroke-width="3" />
<line x1="90" y1="132" x2="105" y2="190" stroke-width="3" />
<!-- flat feet -->
<ellipse cx="49" cy="195" rx="12" ry="4" fill="currentColor" opacity="0.3" />
<ellipse cx="111" cy="193" rx="12" ry="4" fill="currentColor" opacity="0.3" />
</g>
</svg>
<div class="container-content">
<!-- Header -->
<header class="mb-12 text-center">
<p class="mb-2 font-mono text-sm tracking-widest text-primary uppercase">{{ content?.kicker }}</p>
<h1 class="page-title font-display font-bold tracking-tight" style="color: hsl(var(--color-text))">
{{ content?.title }}
</h1>
<p class="mt-4 mx-auto max-w-2xl leading-relaxed" style="color: hsl(var(--color-text-muted))">
{{ content?.description }}
</p>
</header>
<div class="mx-auto max-w-3xl flex flex-col gap-8">
<!-- Monnaie libre -->
<NuxtLink to="/economique/monnaie-libre" class="item-card group">
<div class="item-header">
<div class="item-icon">
<span class="g1-icon">Ğ1</span>
</div>
<h2 class="font-display text-xl font-bold" style="color: hsl(var(--color-text))">
Monnaie libre
</h2>
</div>
<p class="leading-relaxed mt-3" style="color: hsl(var(--color-text-muted))">
La Ğ1 (June) : une monnaie co-créée par ses membres, sans dette ni intérêt. Le dividende universel comme base.
</p>
<div class="mt-3 inline-flex items-center gap-1 text-sm text-primary group-hover:text-primary/80 transition-colors">
En savoir plus
<div class="i-lucide-arrow-right h-3.5 w-3.5" />
</div>
</NuxtLink>
<!-- Modèle économique — bloc livre -->
<div class="book-block">
<HomeBookSection
@open-player="showBookPlayer = true"
@open-pdf="showPdfReader = true"
/>
</div>
<!-- Productions collectives -->
<NuxtLink to="/economique/productions-collectives" class="item-card group">
<div class="item-header">
<div class="item-icon">
<div class="i-lucide-users h-5 w-5" />
</div>
<h2 class="font-display text-xl font-bold" style="color: hsl(var(--color-text))">
Productions collectives
</h2>
<span class="gestation-badge">
<div class="i-lucide-flask-conical h-3 w-3" />
En gestation
</span>
</div>
<p class="leading-relaxed mt-3" style="color: hsl(var(--color-text-muted))">
Une plateforme pour faciliter la création d'équipes et la réalisation de productions à l'échelle des bassins de vie. Passer la seconde.
</p>
<div class="mt-3 inline-flex items-center gap-1 text-sm text-primary group-hover:text-primary/80 transition-colors">
En savoir plus
<div class="i-lucide-arrow-right h-3.5 w-3.5" />
</div>
</NuxtLink>
</div>
</div>
<BookPlayer v-model="showBookPlayer" />
<BookPdfReader v-model="showPdfReader" />
</div>
</template>
<script setup lang="ts">
definePageMeta({
layout: 'default',
})
const { data: content } = await usePageContent('economique')
useHead({
title: content.value?.meta?.title ?? 'Autonomie économique',
})
const showBookPlayer = ref(false)
const showPdfReader = ref(false)
</script>
<style scoped>
.page-title {
font-size: clamp(2rem, 5vw, 2.75rem);
}
.item-card {
display: block;
padding: 1.5rem;
border-radius: 0.75rem;
border: 1px solid hsl(var(--color-text) / 0.08);
background: hsl(var(--color-surface));
transition: border-color 0.2s, transform 0.12s ease;
text-decoration: none;
}
.item-card:hover {
border-color: hsl(var(--color-primary) / 0.2);
transform: translateY(-3px);
box-shadow: 0 8px 24px hsl(var(--color-primary) / 0.08);
}
.item-header {
display: flex;
align-items: center;
gap: 0.75rem;
}
.g1-icon {
font-family: var(--font-display);
font-weight: 700;
font-size: 1.1rem;
line-height: 1;
}
.item-icon {
display: flex;
align-items: center;
justify-content: center;
width: 2.25rem;
height: 2.25rem;
border-radius: 0.5rem;
background: hsl(var(--color-primary) / 0.15);
color: hsl(var(--color-primary));
box-shadow: 0 0 12px hsl(var(--color-primary) / 0.12);
flex-shrink: 0;
}
.gestation-badge {
display: inline-flex;
align-items: center;
gap: 0.25rem;
margin-left: auto;
padding: 0.125rem 0.5rem;
border-radius: 9999px;
background: hsl(var(--color-accent) / 0.12);
color: hsl(var(--color-accent));
font-size: 0.7rem;
font-weight: 500;
font-family: var(--font-mono);
}
.book-block {
padding: 1.5rem;
border-radius: 0.75rem;
border: 1px solid hsl(var(--color-text) / 0.08);
background: hsl(var(--color-surface));
}
/* Shadok illustrations */
.shadok {
position: absolute;
pointer-events: none;
width: clamp(70px, 10vw, 140px);
z-index: 0;
}
.shadok-boulangere {
top: 1%;
left: 2%;
opacity: 0.22;
color: hsl(var(--color-primary));
animation: shadok-float-1 9s ease-in-out infinite;
}
.shadok-potier {
top: 1%;
right: 2%;
opacity: 0.2;
color: hsl(var(--color-accent));
animation: shadok-float-2 11s ease-in-out infinite;
}
.shadok-apicultrice {
left: 2%;
top: 40%;
opacity: 0.2;
color: hsl(var(--color-accent));
animation: shadok-float-3 10s ease-in-out infinite;
}
.shadok-forgeron {
right: 3%;
top: 35%;
opacity: 0.24;
color: hsl(var(--color-primary));
animation: shadok-float-4 8s ease-in-out infinite;
}
.shadok-maraichere {
bottom: 14%;
left: 1%;
opacity: 0.18;
color: hsl(var(--color-primary));
animation: shadok-float-5 12s ease-in-out infinite;
}
.shadok-tisserand {
bottom: 12%;
right: 1%;
opacity: 0.22;
color: hsl(var(--color-accent));
animation: shadok-float-6 9.5s ease-in-out infinite;
}
.shadok-berger {
bottom: 2%;
left: 50%;
transform: translateX(-50%);
opacity: 0.28;
color: hsl(var(--color-primary));
animation: shadok-float-7 11s ease-in-out infinite;
}
.shadok-vigneronne {
right: 2%;
top: 55%;
opacity: 0.2;
color: hsl(var(--color-accent));
animation: shadok-float-8 7.5s ease-in-out infinite;
}
@keyframes shadok-float-1 {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-10px); }
}
@keyframes shadok-float-2 {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-8px); }
}
@keyframes shadok-float-3 {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-12px); }
}
@keyframes shadok-float-4 {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-9px); }
}
@keyframes shadok-float-5 {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-11px); }
}
@keyframes shadok-float-6 {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-8px); }
}
@keyframes shadok-float-7 {
0%, 100% { transform: translateX(-50%) translateY(0); }
50% { transform: translateX(-50%) translateY(-10px); }
}
@keyframes shadok-float-8 {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-7px); }
}
@media (max-width: 768px) {
.shadok {
display: none;
}
}
</style>

View File

@@ -14,7 +14,7 @@
<nav class="mt-16 flex items-center justify-between border-t border-white/8 pt-8">
<NuxtLink
v-if="prevChapter"
:to="`/modele-eco/${prevChapter.stem?.split('/').pop()}`"
:to="`/economique/modele-eco/${prevChapter.stem?.split('/').pop()}`"
class="btn-ghost gap-2"
>
<div class="i-lucide-arrow-left h-4 w-4" />
@@ -24,7 +24,7 @@
<NuxtLink
v-if="nextChapter"
:to="`/modele-eco/${nextChapter.stem?.split('/').pop()}`"
:to="`/economique/modele-eco/${nextChapter.stem?.split('/').pop()}`"
class="btn-ghost gap-2"
>
<span class="text-sm">{{ nextChapter.title }}</span>

View File

@@ -0,0 +1,583 @@
<template>
<div class="relative overflow-hidden section-padding">
<!-- Shadok illustrations -->
<!-- 1. Typographe placing movable type in composing stick, profile view -->
<svg class="shadok shadok-typographe" viewBox="0 0 170 210" fill="none" xmlns="http://www.w3.org/2000/svg">
<!-- body (small oval, profile facing right) -->
<ellipse cx="70" cy="72" rx="22" ry="28" fill="currentColor" opacity="0.28"/>
<!-- head -->
<circle cx="78" cy="36" r="16" fill="currentColor" opacity="0.3"/>
<!-- beak (pointy, profile right) -->
<polygon points="94,34 110,38 94,42" fill="currentColor" opacity="0.35"/>
<!-- eyes (profile one visible) -->
<circle cx="84" cy="33" r="2.5" fill="currentColor" opacity="0.6"/>
<!-- apron -->
<path d="M52 60 L88 60 L85 100 L55 100 Z" fill="currentColor" opacity="0.15"/>
<line x1="70" y1="60" x2="70" y2="100" stroke="currentColor" stroke-width="1" opacity="0.2"/>
<!-- arm reaching to composing stick -->
<line x1="88" y1="65" x2="120" y2="55" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.3"/>
<!-- other arm holding type block -->
<line x1="52" y1="68" x2="35" y2="58" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.3"/>
<!-- composing stick (big, detailed) -->
<rect x="110" y="40" width="50" height="14" rx="2" fill="currentColor" opacity="0.2"/>
<rect x="110" y="42" width="50" height="10" rx="1" stroke="currentColor" stroke-width="1" fill="none" opacity="0.25"/>
<!-- type blocks in stick -->
<rect x="114" y="44" width="6" height="7" rx="0.5" fill="currentColor" opacity="0.3"/>
<rect x="122" y="44" width="5" height="7" rx="0.5" fill="currentColor" opacity="0.25"/>
<rect x="129" y="44" width="7" height="7" rx="0.5" fill="currentColor" opacity="0.3"/>
<rect x="138" y="44" width="5" height="7" rx="0.5" fill="currentColor" opacity="0.22"/>
<rect x="145" y="44" width="6" height="7" rx="0.5" fill="currentColor" opacity="0.28"/>
<!-- tiny letter on held block -->
<rect x="30" y="52" width="8" height="10" rx="1" fill="currentColor" opacity="0.25"/>
<text x="32" y="60" font-size="6" fill="currentColor" opacity="0.5" font-family="serif">A</text>
<!-- long legs -->
<line x1="62" y1="98" x2="50" y2="165" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.25"/>
<line x1="78" y1="98" x2="90" y2="165" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.25"/>
<!-- big flat feet -->
<ellipse cx="45" cy="168" rx="10" ry="4" fill="currentColor" opacity="0.25"/>
<ellipse cx="95" cy="168" rx="10" ry="4" fill="currentColor" opacity="0.25"/>
</svg>
<!-- 2. Lectrice sitting in armchair, legs crossed, book on lap, reading glasses -->
<svg class="shadok shadok-lectrice" viewBox="0 0 180 210" fill="none" xmlns="http://www.w3.org/2000/svg">
<!-- armchair back -->
<path d="M30 55 Q25 50 28 30 L85 28 Q88 50 83 55" fill="currentColor" opacity="0.12"/>
<!-- armchair seat -->
<path d="M25 55 L88 55 L92 90 L20 90 Z" fill="currentColor" opacity="0.15"/>
<!-- armchair arms -->
<rect x="15" y="45" width="12" height="45" rx="5" fill="currentColor" opacity="0.15"/>
<rect x="85" y="45" width="12" height="45" rx="5" fill="currentColor" opacity="0.15"/>
<!-- body (seated, small) -->
<ellipse cx="58" cy="68" rx="20" ry="25" fill="currentColor" opacity="0.28"/>
<!-- head -->
<circle cx="58" cy="32" r="15" fill="currentColor" opacity="0.3"/>
<!-- beak (small, facing right-down toward book) -->
<polygon points="70,35 80,40 70,42" fill="currentColor" opacity="0.3"/>
<!-- reading glasses -->
<circle cx="52" cy="30" r="6" stroke="currentColor" stroke-width="1.5" fill="none" opacity="0.4"/>
<circle cx="65" cy="30" r="6" stroke="currentColor" stroke-width="1.5" fill="none" opacity="0.4"/>
<line x1="58" y1="30" x2="59" y2="30" stroke="currentColor" stroke-width="1.5" opacity="0.35"/>
<!-- eyes behind glasses (looking down) -->
<circle cx="53" cy="31" r="2" fill="currentColor" opacity="0.55"/>
<circle cx="64" cy="31" r="2" fill="currentColor" opacity="0.55"/>
<!-- arms holding open book on lap -->
<line x1="40" y1="60" x2="35" y2="82" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.25"/>
<line x1="76" y1="60" x2="80" y2="82" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.25"/>
<!-- open book on lap -->
<path d="M32 80 L58 88 L84 80 L84 95 L58 103 L32 95 Z" fill="currentColor" opacity="0.18"/>
<line x1="58" y1="88" x2="58" y2="103" stroke="currentColor" stroke-width="1" opacity="0.25"/>
<!-- text lines on pages -->
<line x1="37" y1="86" x2="54" y2="91" stroke="currentColor" stroke-width="0.7" opacity="0.2"/>
<line x1="37" y1="89" x2="54" y2="94" stroke="currentColor" stroke-width="0.7" opacity="0.2"/>
<line x1="62" y1="91" x2="79" y2="86" stroke="currentColor" stroke-width="0.7" opacity="0.2"/>
<line x1="62" y1="94" x2="79" y2="89" stroke="currentColor" stroke-width="0.7" opacity="0.2"/>
<!-- crossed legs (long!) -->
<line x1="48" y1="90" x2="30" y2="165" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.25"/>
<line x1="68" y1="90" x2="55" y2="150" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.25"/>
<!-- the crossed leg goes over -->
<line x1="55" y1="150" x2="75" y2="140" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.22"/>
<!-- big flat feet -->
<ellipse cx="25" cy="168" rx="11" ry="4" fill="currentColor" opacity="0.25"/>
<ellipse cx="79" cy="142" rx="9" ry="3.5" fill="currentColor" opacity="0.22"/>
<!-- cup of tea nearby -->
<rect x="130" y="78" width="14" height="16" rx="3" fill="currentColor" opacity="0.2"/>
<ellipse cx="137" cy="78" rx="7" ry="2.5" fill="currentColor" opacity="0.25"/>
<!-- tea handle -->
<path d="M144 83 Q152 86 144 92" stroke="currentColor" stroke-width="1.5" fill="none" opacity="0.2"/>
<!-- steam -->
<path d="M134 73 Q132 68 135 64" stroke="currentColor" stroke-width="0.8" fill="none" opacity="0.15"/>
<path d="M139 74 Q141 69 138 65" stroke="currentColor" stroke-width="0.8" fill="none" opacity="0.15"/>
</svg>
<!-- 3. Calligraphe standing at tilted drafting table, large quill, ink flourishes -->
<svg class="shadok shadok-calligraphe" viewBox="0 0 180 220" fill="none" xmlns="http://www.w3.org/2000/svg">
<!-- drafting table (tilted, big) -->
<rect x="75" y="50" width="65" height="85" rx="3" fill="currentColor" opacity="0.12" transform="rotate(-15 108 92)"/>
<!-- table legs -->
<line x1="85" y1="130" x2="78" y2="195" stroke="currentColor" stroke-width="2.5" opacity="0.18"/>
<line x1="140" y1="115" x2="150" y2="195" stroke="currentColor" stroke-width="2.5" opacity="0.18"/>
<!-- paper on table -->
<rect x="85" y="58" width="48" height="65" rx="1" fill="currentColor" opacity="0.08" transform="rotate(-15 109 90)"/>
<!-- ink flourishes on paper -->
<path d="M95 80 Q105 70 115 82 Q120 90 110 95" stroke="currentColor" stroke-width="1.2" fill="none" opacity="0.25" transform="rotate(-15 105 85)"/>
<path d="M100 95 Q108 88 118 98" stroke="currentColor" stroke-width="0.8" fill="none" opacity="0.2" transform="rotate(-15 109 93)"/>
<!-- body (3/4 view, facing table) -->
<ellipse cx="55" cy="95" rx="21" ry="27" fill="currentColor" opacity="0.28"/>
<!-- head (turned toward table) -->
<circle cx="62" cy="58" r="16" fill="currentColor" opacity="0.3"/>
<!-- beak pointing at paper -->
<polygon points="75,55 90,52 78,60" fill="currentColor" opacity="0.32"/>
<!-- eyes (looking at paper, slightly different directions) -->
<circle cx="66" cy="55" r="2.5" fill="currentColor" opacity="0.6"/>
<circle cx="58" cy="54" r="2" fill="currentColor" opacity="0.5"/>
<!-- arm holding large quill -->
<line x1="72" y1="85" x2="105" y2="65" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.3"/>
<!-- large quill -->
<line x1="105" y1="65" x2="100" y2="80" stroke="currentColor" stroke-width="1.5" opacity="0.35"/>
<path d="M105 65 L115 30 Q108 45 100 40 Q105 55 105 65" fill="currentColor" opacity="0.2"/>
<!-- other arm resting on table edge -->
<line x1="40" y1="88" x2="80" y2="85" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.25"/>
<!-- ink trailing from quill tip -->
<path d="M100 80 Q95 90 98 100 Q102 108 96 115" stroke="currentColor" stroke-width="0.8" fill="none" opacity="0.2"/>
<!-- long legs -->
<line x1="45" y1="120" x2="35" y2="190" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.25"/>
<line x1="65" y1="120" x2="72" y2="190" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.25"/>
<!-- big flat feet -->
<ellipse cx="30" cy="193" rx="11" ry="4" fill="currentColor" opacity="0.25"/>
<ellipse cx="77" cy="193" rx="11" ry="4" fill="currentColor" opacity="0.25"/>
</svg>
<!-- 4. Relieur sewing book spine with needle and thread, stack of signatures -->
<svg class="shadok shadok-relieur" viewBox="0 0 170 210" fill="none" xmlns="http://www.w3.org/2000/svg">
<!-- body (leaning forward over work) -->
<ellipse cx="85" cy="80" rx="22" ry="26" fill="currentColor" opacity="0.28" transform="rotate(10 85 80)"/>
<!-- head (tilted down, focused) -->
<circle cx="95" cy="46" r="15" fill="currentColor" opacity="0.3"/>
<!-- beak (pointing down at work) -->
<polygon points="105,52 115,62 103,58" fill="currentColor" opacity="0.32"/>
<!-- eyes (both looking down at different angles) -->
<circle cx="92" cy="44" r="2" fill="currentColor" opacity="0.55"/>
<circle cx="100" cy="46" r="2.5" fill="currentColor" opacity="0.6"/>
<!-- arm holding needle high -->
<line x1="100" y1="70" x2="130" y2="40" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.3"/>
<!-- needle -->
<line x1="130" y1="40" x2="135" y2="32" stroke="currentColor" stroke-width="2" opacity="0.45"/>
<!-- thread from needle down to book -->
<path d="M132 38 Q140 55 125 75 Q115 90 120 100" stroke="currentColor" stroke-width="1" fill="none" stroke-dasharray="4 3" opacity="0.3"/>
<!-- other arm holding book spine -->
<line x1="68" y1="75" x2="50" y2="95" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.3"/>
<!-- book being bound (open spine view, big) -->
<path d="M40 90 L60 85 L60 125 L40 130 Z" fill="currentColor" opacity="0.18"/>
<path d="M60 85 L80 90 L80 130 L60 125 Z" fill="currentColor" opacity="0.14"/>
<!-- stitching holes along spine -->
<circle cx="60" cy="92" r="1.2" fill="currentColor" opacity="0.35"/>
<circle cx="60" cy="100" r="1.2" fill="currentColor" opacity="0.35"/>
<circle cx="60" cy="108" r="1.2" fill="currentColor" opacity="0.35"/>
<circle cx="60" cy="116" r="1.2" fill="currentColor" opacity="0.35"/>
<!-- stack of folded signatures nearby -->
<rect x="10" y="125" width="30" height="5" rx="1" fill="currentColor" opacity="0.18"/>
<rect x="12" y="119" width="28" height="5" rx="1" fill="currentColor" opacity="0.15"/>
<rect x="11" y="113" width="29" height="5" rx="1" fill="currentColor" opacity="0.2"/>
<rect x="13" y="107" width="27" height="5" rx="1" fill="currentColor" opacity="0.16"/>
<!-- long legs -->
<line x1="75" y1="104" x2="65" y2="172" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.25"/>
<line x1="95" y1="104" x2="105" y2="172" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.25"/>
<!-- big flat feet -->
<ellipse cx="60" cy="175" rx="10" ry="4" fill="currentColor" opacity="0.25"/>
<ellipse cx="110" cy="175" rx="10" ry="4" fill="currentColor" opacity="0.25"/>
</svg>
<!-- 5. Conteuse on small stage, arms dramatically wide, 3 tiny shadoks below -->
<svg class="shadok shadok-conteuse" viewBox="0 0 180 220" fill="none" xmlns="http://www.w3.org/2000/svg">
<!-- small stage/box -->
<rect x="55" y="110" width="60" height="20" rx="3" fill="currentColor" opacity="0.18"/>
<rect x="58" y="108" width="54" height="4" rx="1.5" fill="currentColor" opacity="0.22"/>
<!-- body (on stage, upright, dramatic) -->
<ellipse cx="85" cy="85" rx="20" ry="26" fill="currentColor" opacity="0.28"/>
<!-- head (thrown back slightly) -->
<circle cx="85" cy="50" r="16" fill="currentColor" opacity="0.3"/>
<!-- beak (open, telling story) -->
<polygon points="98,46 112,42 100,52" fill="currentColor" opacity="0.3"/>
<polygon points="100,52 112,55 98,54" fill="currentColor" opacity="0.22"/>
<!-- wide expressive eyes -->
<circle cx="82" cy="46" r="3" fill="currentColor" opacity="0.6"/>
<circle cx="92" cy="45" r="2.5" fill="currentColor" opacity="0.55"/>
<!-- arms dramatically wide -->
<line x1="65" y1="75" x2="15" y2="50" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.3"/>
<line x1="105" y1="75" x2="160" y2="50" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.3"/>
<!-- gesture sparkles -->
<circle cx="10" cy="45" r="2" fill="currentColor" opacity="0.15"/>
<circle cx="165" cy="45" r="2" fill="currentColor" opacity="0.15"/>
<circle cx="18" cy="38" r="1.5" fill="currentColor" opacity="0.12"/>
<circle cx="157" cy="38" r="1.5" fill="currentColor" opacity="0.12"/>
<!-- long legs (standing on stage) -->
<line x1="77" y1="108" x2="70" y2="110" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.25"/>
<line x1="93" y1="108" x2="100" y2="110" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.25"/>
<!-- big flat feet on stage -->
<ellipse cx="67" cy="112" rx="8" ry="3" fill="currentColor" opacity="0.2"/>
<ellipse cx="103" cy="112" rx="8" ry="3" fill="currentColor" opacity="0.2"/>
<!-- audience: 3 tiny shadoks below stage -->
<!-- tiny shadok 1 (left) -->
<ellipse cx="40" cy="165" rx="8" ry="10" fill="currentColor" opacity="0.18"/>
<circle cx="40" cy="152" r="6" fill="currentColor" opacity="0.22"/>
<circle cx="39" cy="151" r="1" fill="currentColor" opacity="0.45"/>
<circle cx="42" cy="151" r="1" fill="currentColor" opacity="0.45"/>
<polygon points="45,151 50,153 45,155" fill="currentColor" opacity="0.2"/>
<line x1="37" y1="175" x2="35" y2="195" stroke="currentColor" stroke-width="1.5" opacity="0.15"/>
<line x1="43" y1="175" x2="45" y2="195" stroke="currentColor" stroke-width="1.5" opacity="0.15"/>
<!-- tiny shadok 2 (center) -->
<ellipse cx="85" cy="168" rx="8" ry="10" fill="currentColor" opacity="0.18"/>
<circle cx="85" cy="155" r="6" fill="currentColor" opacity="0.22"/>
<circle cx="83" cy="154" r="1" fill="currentColor" opacity="0.45"/>
<circle cx="87" cy="154" r="1" fill="currentColor" opacity="0.45"/>
<polygon points="90,154 95,156 90,158" fill="currentColor" opacity="0.2"/>
<line x1="82" y1="178" x2="80" y2="198" stroke="currentColor" stroke-width="1.5" opacity="0.15"/>
<line x1="88" y1="178" x2="90" y2="198" stroke="currentColor" stroke-width="1.5" opacity="0.15"/>
<!-- tiny shadok 3 (right) -->
<ellipse cx="130" cy="163" rx="8" ry="10" fill="currentColor" opacity="0.18"/>
<circle cx="130" cy="150" r="6" fill="currentColor" opacity="0.22"/>
<circle cx="128" cy="149" r="1" fill="currentColor" opacity="0.45"/>
<circle cx="132" cy="149" r="1" fill="currentColor" opacity="0.45"/>
<polygon points="135,149 140,151 135,153" fill="currentColor" opacity="0.2"/>
<line x1="127" y1="173" x2="125" y2="193" stroke="currentColor" stroke-width="1.5" opacity="0.15"/>
<line x1="133" y1="173" x2="135" y2="193" stroke="currentColor" stroke-width="1.5" opacity="0.15"/>
</svg>
<!-- 6. Correcteur leaning forward, magnifying glass over manuscript, red pen -->
<svg class="shadok shadok-correcteur" viewBox="0 0 170 210" fill="none" xmlns="http://www.w3.org/2000/svg">
<!-- body (leaning forward heavily) -->
<ellipse cx="75" cy="82" rx="21" ry="28" fill="currentColor" opacity="0.28" transform="rotate(20 75 82)"/>
<!-- head (craned forward) -->
<circle cx="95" cy="48" r="15" fill="currentColor" opacity="0.3"/>
<!-- beak (pursed, critical) -->
<polygon points="108,45 118,48 108,52" fill="currentColor" opacity="0.3"/>
<!-- eyes (squinting, one bigger peering through glass) -->
<circle cx="92" cy="45" r="1.8" fill="currentColor" opacity="0.5"/>
<circle cx="101" cy="46" r="3" fill="currentColor" opacity="0.6"/>
<!-- arm holding magnifying glass -->
<line x1="90" y1="72" x2="120" y2="90" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.3"/>
<!-- magnifying glass (big) -->
<circle cx="128" cy="98" r="18" stroke="currentColor" stroke-width="2.5" fill="none" opacity="0.3"/>
<circle cx="128" cy="98" r="16" fill="currentColor" opacity="0.06"/>
<!-- handle -->
<line x1="140" y1="112" x2="155" y2="135" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.3"/>
<!-- manuscript under magnifying glass -->
<rect x="95" y="115" width="55" height="70" rx="2" fill="currentColor" opacity="0.1"/>
<!-- text lines on manuscript -->
<line x1="100" y1="125" x2="140" y2="125" stroke="currentColor" stroke-width="0.8" opacity="0.18"/>
<line x1="100" y1="132" x2="145" y2="132" stroke="currentColor" stroke-width="0.8" opacity="0.18"/>
<line x1="100" y1="139" x2="138" y2="139" stroke="currentColor" stroke-width="0.8" opacity="0.18"/>
<line x1="100" y1="146" x2="142" y2="146" stroke="currentColor" stroke-width="0.8" opacity="0.18"/>
<line x1="100" y1="153" x2="135" y2="153" stroke="currentColor" stroke-width="0.8" opacity="0.18"/>
<!-- crossed-out text (red corrections) -->
<line x1="100" y1="132" x2="130" y2="132" stroke="currentColor" stroke-width="1.5" opacity="0.35"/>
<line x1="105" y1="146" x2="125" y2="146" stroke="currentColor" stroke-width="1.5" opacity="0.35"/>
<!-- other arm holding red pen -->
<line x1="58" y1="78" x2="35" y2="100" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.25"/>
<!-- red pen -->
<line x1="35" y1="100" x2="25" y2="115" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" opacity="0.35"/>
<circle cx="24" cy="117" r="1.5" fill="currentColor" opacity="0.4"/>
<!-- long legs -->
<line x1="65" y1="108" x2="50" y2="178" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.25"/>
<line x1="82" y1="105" x2="95" y2="178" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.25"/>
<!-- big flat feet -->
<ellipse cx="45" cy="181" rx="10" ry="4" fill="currentColor" opacity="0.25"/>
<ellipse cx="100" cy="181" rx="10" ry="4" fill="currentColor" opacity="0.25"/>
</svg>
<!-- 7. Colporteur walking profile, books in wooden crate on back, walking stick, hat -->
<svg class="shadok shadok-colporteur" viewBox="0 0 160 210" fill="none" xmlns="http://www.w3.org/2000/svg">
<!-- body (profile, walking right, leaning forward under weight) -->
<ellipse cx="70" cy="72" rx="20" ry="25" fill="currentColor" opacity="0.28" transform="rotate(15 70 72)"/>
<!-- head -->
<circle cx="82" cy="38" r="14" fill="currentColor" opacity="0.3"/>
<!-- hat (brimmed) -->
<ellipse cx="82" cy="26" rx="18" ry="5" fill="currentColor" opacity="0.25"/>
<rect x="72" y="16" width="20" height="12" rx="3" fill="currentColor" opacity="0.2"/>
<!-- beak (profile right) -->
<polygon points="94,36 106,40 94,43" fill="currentColor" opacity="0.32"/>
<!-- eye (profile one visible, determined) -->
<circle cx="88" cy="35" r="2.5" fill="currentColor" opacity="0.6"/>
<!-- wooden crate on back (big, loaded with books) -->
<rect x="25" y="35" width="40" height="50" rx="3" stroke="currentColor" stroke-width="2" fill="currentColor" opacity="0.12"/>
<!-- strap over shoulder -->
<line x1="45" y1="35" x2="78" y2="55" stroke="currentColor" stroke-width="2" opacity="0.25"/>
<line x1="65" y1="35" x2="85" y2="60" stroke="currentColor" stroke-width="2" opacity="0.25"/>
<!-- books in crate (visible spines) -->
<rect x="28" y="38" width="6" height="44" rx="1" fill="currentColor" opacity="0.22"/>
<rect x="36" y="40" width="5" height="42" rx="1" fill="currentColor" opacity="0.18"/>
<rect x="43" y="37" width="7" height="45" rx="1" fill="currentColor" opacity="0.2"/>
<rect x="52" y="39" width="5" height="43" rx="1" fill="currentColor" opacity="0.16"/>
<rect x="58" y="38" width="4" height="44" rx="1" fill="currentColor" opacity="0.2"/>
<!-- arm forward with walking stick -->
<line x1="88" y1="65" x2="115" y2="80" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.3"/>
<!-- walking stick (long) -->
<line x1="115" y1="80" x2="125" y2="195" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" opacity="0.3"/>
<!-- other arm back holding strap -->
<line x1="55" y1="68" x2="45" y2="55" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.25"/>
<!-- long legs (walking stride) front leg forward, back leg behind -->
<line x1="78" y1="95" x2="100" y2="170" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.25"/>
<line x1="62" y1="95" x2="40" y2="170" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.25"/>
<!-- big flat feet (walking) -->
<ellipse cx="105" cy="173" rx="11" ry="4" fill="currentColor" opacity="0.25"/>
<ellipse cx="35" cy="173" rx="10" ry="4" fill="currentColor" opacity="0.22"/>
</svg>
<!-- 8. Illustratrice at easel, brush in one hand, palette in other, canvas showing a shadok -->
<svg class="shadok shadok-illustratrice" viewBox="0 0 180 220" fill="none" xmlns="http://www.w3.org/2000/svg">
<!-- easel legs -->
<line x1="105" y1="30" x2="85" y2="210" stroke="currentColor" stroke-width="2" opacity="0.2"/>
<line x1="145" y1="30" x2="165" y2="210" stroke="currentColor" stroke-width="2" opacity="0.2"/>
<line x1="125" y1="60" x2="125" y2="210" stroke="currentColor" stroke-width="2" opacity="0.18"/>
<!-- canvas on easel -->
<rect x="95" y="25" width="60" height="75" rx="2" fill="currentColor" opacity="0.08"/>
<rect x="95" y="25" width="60" height="75" rx="2" stroke="currentColor" stroke-width="1.5" fill="none" opacity="0.2"/>
<!-- tiny shadok drawing on canvas! -->
<ellipse cx="125" cy="65" rx="8" ry="10" fill="currentColor" opacity="0.18"/>
<circle cx="125" cy="52" r="6" fill="currentColor" opacity="0.2"/>
<polygon points="130,51 136,53 130,55" fill="currentColor" opacity="0.18"/>
<circle cx="123" cy="51" r="1" fill="currentColor" opacity="0.3"/>
<circle cx="127" cy="51" r="1" fill="currentColor" opacity="0.3"/>
<line x1="121" y1="75" x2="118" y2="88" stroke="currentColor" stroke-width="1" opacity="0.15"/>
<line x1="129" y1="75" x2="132" y2="88" stroke="currentColor" stroke-width="1" opacity="0.15"/>
<!-- body (3/4 view, standing back from easel) -->
<ellipse cx="55" cy="90" rx="22" ry="28" fill="currentColor" opacity="0.28"/>
<!-- head (looking at canvas) -->
<circle cx="62" cy="52" r="16" fill="currentColor" opacity="0.3"/>
<!-- beak (profile right, toward canvas) -->
<polygon points="75,50 88,54 76,56" fill="currentColor" opacity="0.3"/>
<!-- eyes (artistic scrutiny, different sizes) -->
<circle cx="60" cy="49" r="2" fill="currentColor" opacity="0.55"/>
<circle cx="69" cy="50" r="2.8" fill="currentColor" opacity="0.6"/>
<!-- arm holding brush toward canvas -->
<line x1="73" y1="82" x2="100" y2="60" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.3"/>
<!-- paintbrush -->
<line x1="100" y1="60" x2="108" y2="52" stroke="currentColor" stroke-width="2" stroke-linecap="round" opacity="0.35"/>
<circle cx="110" cy="50" r="2" fill="currentColor" opacity="0.3"/>
<!-- other arm holding palette -->
<line x1="38" y1="85" x2="18" y2="95" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.25"/>
<!-- palette (big, with color dots) -->
<ellipse cx="12" cy="102" rx="16" ry="12" fill="currentColor" opacity="0.15"/>
<!-- thumb hole -->
<circle cx="12" cy="108" r="3" fill="currentColor" opacity="0.05"/>
<!-- paint dabs on palette -->
<circle cx="6" cy="97" r="2.5" fill="currentColor" opacity="0.3"/>
<circle cx="14" cy="94" r="2" fill="currentColor" opacity="0.25"/>
<circle cx="21" cy="98" r="2.5" fill="currentColor" opacity="0.28"/>
<circle cx="8" cy="104" r="2" fill="currentColor" opacity="0.22"/>
<circle cx="18" cy="103" r="2" fill="currentColor" opacity="0.3"/>
<!-- long legs -->
<line x1="45" y1="116" x2="35" y2="188" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.25"/>
<line x1="65" y1="116" x2="75" y2="188" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.25"/>
<!-- big flat feet -->
<ellipse cx="30" cy="191" rx="10" ry="4" fill="currentColor" opacity="0.25"/>
<ellipse cx="80" cy="191" rx="10" ry="4" fill="currentColor" opacity="0.25"/>
</svg>
<div class="container-content">
<!-- Page de couverture du livre -->
<HomeBookSection
class="mb-10 hero-compact"
:show-chapters="false"
@open-player="showBookPlayer = true"
@open-pdf="showPdfReader = true"
/>
<header class="mb-10 text-center">
<p class="mb-2 font-mono text-sm tracking-widest text-primary uppercase">{{ content?.kicker }}</p>
<h1 class="page-title font-display font-bold tracking-tight" style="color: hsl(var(--color-text))">
{{ content?.title }}
</h1>
<p class="mt-3 mx-auto max-w-2xl text-sm" style="color: hsl(var(--color-text-muted))">
{{ content?.description }}
</p>
</header>
<div class="mx-auto max-w-3xl">
<ul class="flex flex-col gap-3">
<li
v-for="chapter in chapters"
:key="chapter.path"
>
<NuxtLink
:to="`/economique/modele-eco/${chapter.stem?.split('/').pop()}`"
class="card-surface flex items-start gap-4 group"
>
<span class="font-mono text-2xl font-bold text-primary/30 leading-none mt-1 w-10 text-right flex-shrink-0">
{{ String(chapter.order).padStart(2, '0') }}
</span>
<div class="min-w-0 flex-1">
<div class="flex items-baseline gap-2 flex-wrap">
<h2 class="font-display text-lg font-semibold text-white group-hover:text-primary transition-colors">
{{ chapter.title }}
</h2>
<span v-if="chapter.page" class="text-xs font-mono text-white/30 flex-shrink-0">p.&nbsp;{{ chapter.page }}</span>
</div>
<p v-if="chapter.description" class="mt-1 text-sm text-white/50">
{{ chapter.description }}
</p>
<div class="mt-2 flex items-center gap-3">
<span v-if="chapter.readingTime" class="text-xs text-white/30">
<span class="i-lucide-clock inline-block h-3 w-3 mr-1 align-middle" />
{{ chapter.readingTime }}
</span>
<SongBadges :chapter-slug="chapter.stem?.split('/').pop() ?? ''" />
</div>
</div>
<div class="i-lucide-chevron-right h-5 w-5 text-white/20 group-hover:text-primary/60 transition-colors flex-shrink-0 mt-2" />
</NuxtLink>
</li>
</ul>
</div>
</div>
<BookPlayer v-model="showBookPlayer" />
<BookPdfReader v-model="showPdfReader" />
</div>
</template>
<script setup lang="ts">
definePageMeta({
layout: 'default',
})
const { data: content } = await usePageContent('economique/modele-eco')
useHead({
title: content.value?.meta?.title ?? 'Table des matières',
})
const { data: chapters } = await useAsyncData('book-toc', () =>
queryCollection('book').order('order', 'ASC').all(),
)
const showBookPlayer = ref(false)
const showPdfReader = ref(false)
</script>
<style scoped>
.page-title {
font-size: clamp(1.5rem, 3.5vw, 2rem);
}
/* Compact the HomeBookSection hero */
:deep(.hero-compact .book-hero),
:deep(.hero-compact .section-hero) {
padding-top: 2rem;
padding-bottom: 2rem;
}
:deep(.hero-compact img),
:deep(.hero-compact .book-cover) {
max-height: 260px;
}
:deep(.hero-compact h1),
:deep(.hero-compact .hero-title) {
font-size: clamp(1.4rem, 3vw, 2rem);
}
:deep(.hero-compact .hero-description) {
font-size: 0.9rem;
}
/* Shadok illustrations — shared */
.shadok {
position: absolute;
pointer-events: none;
width: clamp(70px, 10vw, 140px);
z-index: 0;
}
/* 1. Typographe — top left */
.shadok-typographe {
top: 1%;
left: 1%;
opacity: 0.24;
color: hsl(var(--color-primary));
animation: shadok-float-1 9s ease-in-out infinite;
}
/* 2. Lectrice — top right, sitting */
.shadok-lectrice {
top: 2%;
right: 1%;
opacity: 0.22;
color: hsl(var(--color-accent));
animation: shadok-float-2 11s ease-in-out infinite;
}
/* 3. Calligraphe — left, 40% */
.shadok-calligraphe {
top: 40%;
left: 1%;
opacity: 0.2;
color: hsl(var(--color-primary));
animation: shadok-float-3 10s ease-in-out infinite;
}
/* 4. Relieur — right, 35% */
.shadok-relieur {
top: 35%;
right: 2%;
opacity: 0.24;
color: hsl(var(--color-accent));
animation: shadok-float-4 8s ease-in-out infinite;
}
/* 5. Conteuse — bottom left */
.shadok-conteuse {
bottom: 6%;
left: 1%;
opacity: 0.22;
color: hsl(var(--color-primary));
animation: shadok-float-5 12s ease-in-out infinite;
}
/* 6. Correcteur — bottom right, leaning */
.shadok-correcteur {
bottom: 5%;
right: 1%;
opacity: 0.2;
color: hsl(var(--color-accent));
animation: shadok-float-6 9.5s ease-in-out infinite;
}
/* 7. Colporteur — center bottom, walking */
.shadok-colporteur {
bottom: 2%;
left: 50%;
transform: translateX(-50%);
opacity: 0.18;
color: hsl(var(--color-primary));
animation: shadok-float-7 7.5s ease-in-out infinite;
}
/* 8. Illustratrice — right, 58% */
.shadok-illustratrice {
top: 58%;
right: 1%;
opacity: 0.26;
color: hsl(var(--color-accent));
animation: shadok-float-8 10.5s ease-in-out infinite;
}
/* Float animations — each unique duration and offset */
@keyframes shadok-float-1 {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-10px); }
}
@keyframes shadok-float-2 {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-8px); }
}
@keyframes shadok-float-3 {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-12px); }
}
@keyframes shadok-float-4 {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-9px); }
}
@keyframes shadok-float-5 {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-11px); }
}
@keyframes shadok-float-6 {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-7px); }
}
@keyframes shadok-float-7 {
0%, 100% { transform: translateX(-50%) translateY(0); }
50% { transform: translateX(-50%) translateY(-10px); }
}
@keyframes shadok-float-8 {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-8px); }
}
/* Hidden on mobile */
@media (max-width: 768px) {
.shadok {
display: none;
}
}
</style>

View File

@@ -0,0 +1,119 @@
<template>
<div class="section-padding">
<div class="container-content">
<div class="mx-auto max-w-2xl">
<div class="section-icon mx-auto mb-6">
<span v-if="content?.icon === 'g1'" class="g1-icon">Ğ1</span>
<div v-else :class="`i-lucide-${content?.icon ?? 'coins'}`" class="h-12 w-12" />
</div>
<p class="mb-2 font-mono text-sm tracking-widest text-primary uppercase text-center">
{{ content?.kicker }}
</p>
<h1 class="font-display text-3xl font-bold mb-4 text-center" style="color: hsl(var(--color-text))">
{{ content?.title }}
</h1>
<p class="text-lg leading-relaxed mb-8 text-center" style="color: hsl(var(--color-text-muted))">
{{ content?.description }}
</p>
<!-- Content -->
<div v-if="content?.content" class="prose-block mb-8">
<p class="leading-relaxed whitespace-pre-line" style="color: hsl(var(--color-text-muted))">
{{ content.content }}
</p>
</div>
<!-- External links -->
<div v-if="content?.links" class="flex flex-col gap-3 mb-10">
<a
v-for="link in content.links"
:key="link.href"
:href="link.href"
target="_blank"
rel="noopener"
class="link-card group"
>
<div class="link-icon">
<div :class="`i-lucide-${link.icon ?? 'external-link'} h-4 w-4`" />
</div>
<span class="text-sm font-medium" style="color: hsl(var(--color-text))">{{ link.label }}</span>
<div class="i-lucide-arrow-up-right h-3.5 w-3.5 ml-auto text-primary/40 group-hover:text-primary transition-colors" />
</a>
</div>
<div class="text-center">
<UiBaseButton variant="ghost" to="/economique">
<div class="i-lucide-arrow-left mr-2 h-4 w-4" />
Autonomie économique
</UiBaseButton>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
const { data: content } = await usePageContent('economique/monnaie-libre')
useHead({
title: content.value?.meta?.title ?? 'Monnaie libre',
})
</script>
<style scoped>
.g1-icon {
font-family: var(--font-display);
font-weight: 700;
font-size: 1.75rem;
line-height: 1;
}
.section-icon {
display: flex;
align-items: center;
justify-content: center;
width: 5rem;
height: 5rem;
border-radius: 1rem;
background: hsl(var(--color-primary) / 0.1);
border: 1px solid hsl(var(--color-primary) / 0.2);
color: hsl(var(--color-primary));
}
.prose-block {
padding: 1.5rem;
border-radius: 0.75rem;
background: hsl(var(--color-surface));
}
.link-card {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.75rem 1rem;
border-radius: 0.5rem;
border: 1px solid hsl(var(--color-primary) / 0.1);
background: hsl(var(--color-surface));
text-decoration: none;
transition: border-color 0.2s;
}
.link-card:hover {
border-color: hsl(var(--color-primary) / 0.25);
}
.link-icon {
display: flex;
align-items: center;
justify-content: center;
width: 1.75rem;
height: 1.75rem;
border-radius: 0.375rem;
background: hsl(var(--color-primary) / 0.1);
color: hsl(var(--color-primary));
flex-shrink: 0;
}
</style>

View File

@@ -0,0 +1,85 @@
<template>
<div class="section-padding">
<div class="container-content">
<div class="mx-auto max-w-2xl">
<div class="section-icon mx-auto mb-6">
<div :class="`i-lucide-${content?.icon ?? 'users'}`" class="h-12 w-12" />
</div>
<p class="mb-2 font-mono text-sm tracking-widest text-primary uppercase text-center">
{{ content?.kicker }}
</p>
<h1 class="font-display text-3xl font-bold mb-4 text-center" style="color: hsl(var(--color-text))">
{{ content?.title }}
</h1>
<p class="text-lg leading-relaxed mb-8 text-center" style="color: hsl(var(--color-text-muted))">
{{ content?.description }}
</p>
<span v-if="content?.gestation" class="gestation-badge mx-auto mb-8">
<div class="i-lucide-flask-conical h-3 w-3" />
En gestation
</span>
<!-- Content -->
<div v-if="content?.content" class="prose-block mb-10">
<p class="leading-relaxed whitespace-pre-line" style="color: hsl(var(--color-text-muted))">
{{ content.content }}
</p>
</div>
<div class="text-center">
<UiBaseButton variant="ghost" to="/economique">
<div class="i-lucide-arrow-left mr-2 h-4 w-4" />
Autonomie économique
</UiBaseButton>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
const { data: content } = await usePageContent('economique/productions-collectives')
useHead({
title: content.value?.meta?.title ?? 'Productions collectives',
})
</script>
<style scoped>
.section-icon {
display: flex;
align-items: center;
justify-content: center;
width: 5rem;
height: 5rem;
border-radius: 1rem;
background: hsl(var(--color-primary) / 0.1);
border: 1px solid hsl(var(--color-primary) / 0.2);
color: hsl(var(--color-primary));
}
.gestation-badge {
display: flex;
align-items: center;
justify-content: center;
gap: 0.25rem;
padding: 0.25rem 0.75rem;
border-radius: 9999px;
background: hsl(var(--color-accent) / 0.12);
color: hsl(var(--color-accent));
font-size: 0.75rem;
font-weight: 500;
font-family: var(--font-mono);
width: fit-content;
}
.prose-block {
padding: 1.5rem;
border-radius: 0.75rem;
background: hsl(var(--color-surface));
}
</style>

View File

@@ -1,152 +1,340 @@
<template>
<div class="relative overflow-hidden section-padding min-h-[70vh] flex items-center justify-center">
<!-- Shadok jongleur: juggling coins (top-left) -->
<svg class="shadok-juggler" viewBox="0 0 240 300" fill="none" aria-hidden="true">
<!-- 1. Shadok funambule: walking on tightrope (top-left) -->
<svg class="shadok-funambule" viewBox="0 0 170 200" fill="none" aria-hidden="true">
<!-- Tightrope -->
<line x1="5" y1="170" x2="165" y2="170" stroke="currentColor" stroke-width="2" opacity="0.3"/>
<!-- Body (small oval, leaning forward) -->
<ellipse cx="85" cy="110" rx="20" ry="28" fill="currentColor" opacity="0.25" transform="rotate(-5 85 110)"/>
<!-- Head -->
<circle cx="88" cy="72" r="16" fill="currentColor" opacity="0.3"/>
<!-- Eyes (focused, looking down-right) -->
<circle cx="93" cy="70" r="1.8" fill="currentColor" opacity="0.5"/>
<circle cx="99" cy="71" r="1.8" fill="currentColor" opacity="0.5"/>
<!-- Beak (pointy, right side) -->
<polygon points="103,74 116,72 103,78" fill="currentColor" opacity="0.3"/>
<!-- Balancing pole (big, horizontal) -->
<line x1="10" y1="92" x2="160" y2="88" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.35"/>
<!-- Arms holding pole -->
<line x1="67" y1="100" x2="45" y2="92" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<line x1="103" y1="98" x2="125" y2="90" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<!-- Left leg (forward, on rope) -->
<line x1="78" y1="136" x2="70" y2="170" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<ellipse cx="70" cy="172" rx="8" ry="3" fill="currentColor" opacity="0.3"/>
<!-- Right leg (back, lifted) -->
<line x1="92" y1="136" x2="108" y2="165" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<ellipse cx="108" cy="167" rx="8" ry="3" fill="currentColor" opacity="0.3"/>
</svg>
<!-- 2. Shadok accordeoniste: playing accordion (top-right, profile) -->
<svg class="shadok-accordeoniste" viewBox="0 0 180 210" fill="none" aria-hidden="true">
<!-- Body (profile, small) -->
<ellipse cx="70" cy="115" rx="22" ry="28" fill="currentColor" opacity="0.25"/>
<!-- Head (profile) -->
<circle cx="72" cy="75" r="16" fill="currentColor" opacity="0.3"/>
<!-- Beret -->
<ellipse cx="72" cy="60" rx="18" ry="6" fill="currentColor" opacity="0.3"/>
<circle cx="72" cy="56" r="4" fill="currentColor" opacity="0.25"/>
<!-- Eye (profile, one visible) -->
<circle cx="80" cy="73" r="1.8" fill="currentColor" opacity="0.5"/>
<!-- Beak (profile right) -->
<polygon points="86,76 98,74 86,80" fill="currentColor" opacity="0.3"/>
<!-- Accordion bellows (big, extended right) -->
<rect x="92" y="95" width="12" height="45" rx="2" fill="currentColor" opacity="0.3"/>
<line x1="98" y1="98" x2="98" y2="137" stroke="currentColor" stroke-width="0.8" opacity="0.2"/>
<rect x="107" y="93" width="8" height="49" rx="2" fill="currentColor" opacity="0.25"/>
<rect x="118" y="91" width="8" height="53" rx="2" fill="currentColor" opacity="0.2"/>
<rect x="129" y="89" width="8" height="57" rx="2" fill="currentColor" opacity="0.25"/>
<rect x="140" y="87" width="12" height="61" rx="2" fill="currentColor" opacity="0.3"/>
<!-- Keyboard dots on right panel -->
<circle cx="146" cy="100" r="1.5" fill="currentColor" opacity="0.2"/>
<circle cx="146" cy="110" r="1.5" fill="currentColor" opacity="0.2"/>
<circle cx="146" cy="120" r="1.5" fill="currentColor" opacity="0.2"/>
<circle cx="146" cy="130" r="1.5" fill="currentColor" opacity="0.2"/>
<!-- Left arm on bellows -->
<line x1="52" y1="105" x2="92" y2="105" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<!-- Right arm stretched to far end -->
<line x1="90" y1="108" x2="140" y2="100" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<!-- Foot tapping (right lifted) -->
<line x1="60" y1="141" x2="52" y2="195" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<ellipse cx="52" cy="197" rx="8" ry="3" fill="currentColor" opacity="0.3"/>
<line x1="80" y1="141" x2="90" y2="188" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<ellipse cx="90" cy="190" rx="8" ry="3" fill="currentColor" opacity="0.3" transform="rotate(-15 90 190)"/>
<!-- Music notes -->
<text x="155" y="80" fill="currentColor" opacity="0.25" font-size="14">&#9834;</text>
<text x="145" y="68" fill="currentColor" opacity="0.2" font-size="11">&#9835;</text>
</svg>
<!-- 3. Shadok jongleur: 4 balls in the air (top-center) -->
<svg class="shadok-jongleur" viewBox="0 0 160 210" fill="none" aria-hidden="true">
<!-- Body -->
<ellipse cx="120" cy="160" rx="38" ry="46" fill="currentColor" opacity="0.85"/>
<ellipse cx="80" cy="120" rx="21" ry="27" fill="currentColor" opacity="0.25"/>
<!-- Head -->
<circle cx="120" cy="98" r="24" fill="currentColor" opacity="0.8"/>
<!-- Eyes (looking up at coins) -->
<circle cx="112" cy="92" r="3.5" fill="currentColor" opacity="0.2"/>
<circle cx="130" cy="92" r="3.5" fill="currentColor" opacity="0.2"/>
<circle cx="113" cy="91" r="1.5" fill="currentColor" opacity="0.5"/>
<circle cx="131" cy="91" r="1.5" fill="currentColor" opacity="0.5"/>
<!-- Smile -->
<path d="M112 108 Q120 114 128 108" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" fill="none" opacity="0.35"/>
<!-- Arms up (juggling) -->
<line x1="85" y1="145" x2="55" y2="105" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" opacity="0.6"/>
<line x1="155" y1="145" x2="185" y2="105" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" opacity="0.6"/>
<!-- Hands -->
<circle cx="55" cy="103" r="4" fill="currentColor" opacity="0.4"/>
<circle cx="185" cy="103" r="4" fill="currentColor" opacity="0.4"/>
<!-- Juggling coins -->
<circle cx="90" cy="55" r="8" fill="currentColor" opacity="0.35"/>
<text x="86" y="59" fill="currentColor" opacity="0.5" font-size="10" font-weight="bold">$</text>
<circle cx="120" cy="40" r="8" fill="currentColor" opacity="0.3"/>
<text x="116" y="44" fill="currentColor" opacity="0.45" font-size="10" font-weight="bold">$</text>
<circle cx="150" cy="50" r="8" fill="currentColor" opacity="0.32"/>
<text x="146" y="54" fill="currentColor" opacity="0.48" font-size="10" font-weight="bold">$</text>
<!-- Legs -->
<line x1="105" y1="203" x2="95" y2="260" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" opacity="0.6"/>
<line x1="135" y1="203" x2="145" y2="260" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" opacity="0.6"/>
<circle cx="80" cy="82" r="15" fill="currentColor" opacity="0.3"/>
<!-- Eyes (looking up, wide) -->
<circle cx="74" cy="78" r="2" fill="currentColor" opacity="0.5"/>
<circle cx="86" cy="78" r="2" fill="currentColor" opacity="0.5"/>
<!-- Mouth open (concentration) -->
<ellipse cx="80" cy="92" rx="4" ry="3" fill="currentColor" opacity="0.2"/>
<!-- Beak (small, front view) -->
<polygon points="80,86 87,89 80,92" fill="currentColor" opacity="0.3"/>
<!-- Arms up wide -->
<line x1="60" y1="110" x2="30" y2="75" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<line x1="100" y1="110" x2="130" y2="75" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<!-- 4 juggling balls in arc -->
<circle cx="40" cy="35" r="8" fill="currentColor" opacity="0.3"/>
<circle cx="70" cy="18" r="8" fill="currentColor" opacity="0.25"/>
<circle cx="100" cy="15" r="8" fill="currentColor" opacity="0.3"/>
<circle cx="128" cy="30" r="8" fill="currentColor" opacity="0.25"/>
<!-- Long legs -->
<line x1="72" y1="145" x2="60" y2="200" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<ellipse cx="60" cy="202" rx="8" ry="3" fill="currentColor" opacity="0.3"/>
<line x1="88" y1="145" x2="100" y2="200" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<ellipse cx="100" cy="202" rx="8" ry="3" fill="currentColor" opacity="0.3"/>
</svg>
<!-- Shadok échelle: on a wobbly ladder (top-right) -->
<svg class="shadok-ladder" viewBox="0 0 220 320" fill="none" aria-hidden="true">
<!-- Ladder (tilting) -->
<line x1="80" y1="50" x2="70" y2="300" stroke="currentColor" stroke-width="3" opacity="0.35"/>
<line x1="150" y1="50" x2="140" y2="300" stroke="currentColor" stroke-width="3" opacity="0.35"/>
<!-- Rungs -->
<line x1="82" y1="80" x2="148" y2="80" stroke="currentColor" stroke-width="2.5" opacity="0.3"/>
<line x1="83" y1="120" x2="147" y2="120" stroke="currentColor" stroke-width="2.5" opacity="0.3"/>
<line x1="84" y1="160" x2="146" y2="160" stroke="currentColor" stroke-width="2.5" opacity="0.3"/>
<line x1="85" y1="200" x2="145" y2="200" stroke="currentColor" stroke-width="2.5" opacity="0.3"/>
<line x1="86" y1="240" x2="144" y2="240" stroke="currentColor" stroke-width="2.5" opacity="0.3"/>
<!-- Shadok on top (arms out for balance) -->
<ellipse cx="115" cy="68" rx="18" ry="14" fill="currentColor" opacity="0.85"/>
<circle cx="115" cy="46" r="14" fill="currentColor" opacity="0.8"/>
<!-- Eyes (worried) -->
<circle cx="110" cy="43" r="3" fill="currentColor" opacity="0.25"/>
<circle cx="122" cy="43" r="3" fill="currentColor" opacity="0.25"/>
<circle cx="110" cy="44" r="1.2" fill="currentColor" opacity="0.5"/>
<circle cx="122" cy="44" r="1.2" fill="currentColor" opacity="0.5"/>
<!-- Worried mouth -->
<path d="M108 52 Q115 49 122 52" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" fill="none" opacity="0.3"/>
<!-- Arms out (balancing) -->
<line x1="97" y1="62" x2="60" y2="55" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" opacity="0.6"/>
<line x1="133" y1="62" x2="170" y2="55" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" opacity="0.6"/>
</svg>
<!-- Shadok acrobate: doing a cartwheel (center) -->
<svg class="shadok-acrobat" viewBox="0 0 260 240" fill="none" aria-hidden="true">
<!-- Body (sideways, mid-cartwheel) -->
<ellipse cx="130" cy="120" rx="30" ry="38" fill="currentColor" opacity="0.85" transform="rotate(45 130 120)"/>
<!-- Head -->
<circle cx="155" cy="82" r="20" fill="currentColor" opacity="0.8"/>
<!-- Eyes (dizzy/happy) -->
<path d="M148 78 Q152 74 156 78" stroke="currentColor" stroke-width="2" stroke-linecap="round" fill="none" opacity="0.4"/>
<path d="M160 78 Q164 74 168 78" stroke="currentColor" stroke-width="2" stroke-linecap="round" fill="none" opacity="0.4"/>
<!-- Smile -->
<path d="M150 90 Q158 95 165 90" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" fill="none" opacity="0.3"/>
<!-- Arms (one touching ground, one up) -->
<line x1="110" y1="100" x2="80" y2="130" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.6"/>
<line x1="150" y1="105" x2="185" y2="70" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.6"/>
<!-- Hand on ground -->
<circle cx="78" cy="132" r="4" fill="currentColor" opacity="0.4"/>
<!-- Legs (splayed in cartwheel) -->
<line x1="125" y1="155" x2="100" y2="195" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.6"/>
<line x1="140" y1="150" x2="175" y2="175" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.6"/>
<!-- Motion lines -->
<path d="M70 110 Q60 105 55 115" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" fill="none" opacity="0.2"/>
<path d="M190 60 Q200 55 205 65" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" fill="none" opacity="0.2"/>
</svg>
<!-- Shadok dormeur: sleeping on a cloud (bottom-left) -->
<svg class="shadok-sleeper" viewBox="0 0 260 220" fill="none" aria-hidden="true">
<!-- Cloud -->
<ellipse cx="130" cy="150" rx="80" ry="25" fill="currentColor" opacity="0.2"/>
<circle cx="80" cy="140" r="25" fill="currentColor" opacity="0.18"/>
<circle cx="120" cy="130" r="30" fill="currentColor" opacity="0.2"/>
<circle cx="165" cy="135" r="22" fill="currentColor" opacity="0.18"/>
<circle cx="190" cy="142" r="18" fill="currentColor" opacity="0.15"/>
<!-- Shadok body (lying down) -->
<ellipse cx="130" cy="125" rx="35" ry="18" fill="currentColor" opacity="0.85"/>
<!-- Head (on cloud, sideways) -->
<ellipse cx="85" cy="118" rx="18" ry="16" fill="currentColor" opacity="0.8"/>
<!-- Closed eyes (sleeping) -->
<path d="M76 115 Q80 112 84 115" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" fill="none" opacity="0.4"/>
<path d="M88 115 Q92 112 96 115" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" fill="none" opacity="0.4"/>
<!-- Snooze bubbles -->
<text x="70" y="100" fill="currentColor" opacity="0.3" font-size="12" font-weight="bold">z</text>
<text x="60" y="85" fill="currentColor" opacity="0.25" font-size="16" font-weight="bold">z</text>
<text x="48" y="68" fill="currentColor" opacity="0.2" font-size="20" font-weight="bold">z</text>
<!-- Legs (curled) -->
<path d="M165 125 Q180 130 175 140" stroke="currentColor" stroke-width="3" stroke-linecap="round" fill="none" opacity="0.5"/>
<path d="M160 130 Q172 138 168 148" stroke="currentColor" stroke-width="3" stroke-linecap="round" fill="none" opacity="0.5"/>
</svg>
<!-- Shadok cuisinier: cooking in a cauldron (bottom-right) -->
<svg class="shadok-cook" viewBox="0 0 240 300" fill="none" aria-hidden="true">
<!-- 4. Shadok cracheur de feu: head tilted back, flame from mouth -->
<svg class="shadok-cracheur" viewBox="0 0 170 220" fill="none" aria-hidden="true">
<!-- Body -->
<ellipse cx="120" cy="145" rx="38" ry="45" fill="currentColor" opacity="0.85"/>
<ellipse cx="75" cy="125" rx="22" ry="30" fill="currentColor" opacity="0.25"/>
<!-- Head (tilted back) -->
<circle cx="78" cy="82" r="16" fill="currentColor" opacity="0.3" />
<!-- Eyes (looking up) -->
<circle cx="73" cy="76" r="1.8" fill="currentColor" opacity="0.5"/>
<circle cx="83" cy="76" r="1.8" fill="currentColor" opacity="0.5"/>
<!-- Beak (pointing up, open) -->
<polygon points="78,66 85,52 72,66" fill="currentColor" opacity="0.3"/>
<!-- Flame stream from beak (big, upward-right) -->
<path d="M82 54 Q100 25 95 10 Q110 30 120 8 Q115 35 135 15 Q120 42 140 30 Q118 50 130 45" stroke="currentColor" stroke-width="2" stroke-linecap="round" fill="currentColor" opacity="0.2"/>
<!-- Torch in left hand -->
<line x1="55" y1="115" x2="30" y2="90" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<rect x="24" y="78" width="8" height="16" rx="2" fill="currentColor" opacity="0.35"/>
<path d="M28 78 Q28 68 32 72 Q28 65 28 78" fill="currentColor" opacity="0.25"/>
<!-- Right arm out for balance -->
<line x1="95" y1="112" x2="125" y2="100" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<!-- Long legs -->
<line x1="66" y1="153" x2="55" y2="210" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<ellipse cx="55" cy="212" rx="8" ry="3" fill="currentColor" opacity="0.3"/>
<line x1="84" y1="153" x2="95" y2="210" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<ellipse cx="95" cy="212" rx="8" ry="3" fill="currentColor" opacity="0.3"/>
</svg>
<!-- 5. Shadok trapeziste: hanging from trapeze, body in arc -->
<svg class="shadok-trapeziste" viewBox="0 0 150 220" fill="none" aria-hidden="true">
<!-- Trapeze ropes -->
<line x1="40" y1="0" x2="50" y2="40" stroke="currentColor" stroke-width="2" stroke-linecap="round" opacity="0.3"/>
<line x1="110" y1="0" x2="100" y2="40" stroke="currentColor" stroke-width="2" stroke-linecap="round" opacity="0.3"/>
<!-- Trapeze bar -->
<line x1="48" y1="40" x2="102" y2="40" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" opacity="0.35"/>
<!-- Arms (hanging from bar) -->
<line x1="60" y1="42" x2="65" y2="70" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<line x1="90" y1="42" x2="85" y2="70" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<!-- Body (arched, graceful) -->
<ellipse cx="75" cy="95" rx="20" ry="25" fill="currentColor" opacity="0.25" transform="rotate(10 75 95)"/>
<!-- Head (below body, looking down) -->
<circle cx="78" cy="72" r="14" fill="currentColor" opacity="0.3"/>
<!-- Eyes (excited, looking down) -->
<circle cx="74" cy="74" r="1.8" fill="currentColor" opacity="0.5"/>
<circle cx="84" cy="75" r="1.8" fill="currentColor" opacity="0.5"/>
<!-- Beak (pointing down-right) -->
<polygon points="86,78 96,84 86,82" fill="currentColor" opacity="0.3"/>
<!-- Legs (pointed, graceful, extending down-right) -->
<line x1="80" y1="118" x2="95" y2="175" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<line x1="70" y1="118" x2="82" y2="180" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<!-- Pointed feet -->
<polygon points="95,175 105,180 95,182" fill="currentColor" opacity="0.3"/>
<polygon points="82,180 92,185 82,187" fill="currentColor" opacity="0.3"/>
</svg>
<!-- 6. Shadok batteur: behind drum kit, sticks raised -->
<svg class="shadok-batteur" viewBox="0 0 180 210" fill="none" aria-hidden="true">
<!-- Snare drum (center, big) -->
<ellipse cx="90" cy="165" rx="35" ry="12" fill="currentColor" opacity="0.2"/>
<rect x="55" y="155" width="70" height="20" rx="3" fill="currentColor" opacity="0.2"/>
<ellipse cx="90" cy="155" rx="35" ry="12" fill="currentColor" opacity="0.25"/>
<!-- Hi-hat (left) -->
<ellipse cx="30" cy="140" rx="18" ry="5" fill="currentColor" opacity="0.2"/>
<line x1="30" y1="140" x2="30" y2="185" stroke="currentColor" stroke-width="2" opacity="0.2"/>
<!-- Cymbal (right) -->
<ellipse cx="155" cy="120" rx="20" ry="5" fill="currentColor" opacity="0.2"/>
<line x1="155" y1="120" x2="155" y2="185" stroke="currentColor" stroke-width="2" opacity="0.2"/>
<!-- Body (behind kit) -->
<ellipse cx="90" cy="110" rx="22" ry="28" fill="currentColor" opacity="0.25"/>
<!-- Head -->
<circle cx="120" cy="85" r="24" fill="currentColor" opacity="0.8"/>
<!-- Chef hat -->
<ellipse cx="120" cy="62" rx="22" ry="18" fill="currentColor" opacity="0.35"/>
<rect x="105" y="68" width="30" height="6" rx="1" fill="currentColor" opacity="0.4"/>
<!-- Eyes (focused on cooking) -->
<circle cx="112" cy="82" r="3.5" fill="currentColor" opacity="0.2"/>
<circle cx="130" cy="82" r="3.5" fill="currentColor" opacity="0.2"/>
<circle cx="113" cy="83" r="1.5" fill="currentColor" opacity="0.5"/>
<circle cx="131" cy="83" r="1.5" fill="currentColor" opacity="0.5"/>
<!-- Tongue out (concentrating) -->
<path d="M115 96 Q120 100 125 96" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" fill="none" opacity="0.3"/>
<!-- Arm with ladle -->
<line x1="155" y1="135" x2="185" y2="175" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" opacity="0.6"/>
<!-- Ladle -->
<line x1="185" y1="175" x2="175" y2="200" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" opacity="0.5"/>
<ellipse cx="175" cy="205" rx="8" ry="5" fill="currentColor" opacity="0.35"/>
<!-- Other arm -->
<line x1="85" y1="140" x2="60" y2="175" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" opacity="0.6"/>
<!-- Legs -->
<line x1="105" y1="188" x2="95" y2="250" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" opacity="0.6"/>
<line x1="135" y1="188" x2="145" y2="250" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" opacity="0.6"/>
<!-- Cauldron -->
<path d="M55 220 Q55 260 120 260 Q185 260 185 220" fill="currentColor" opacity="0.3"/>
<ellipse cx="120" cy="220" rx="65" ry="12" fill="currentColor" opacity="0.25"/>
<ellipse cx="120" cy="220" rx="65" ry="12" stroke="currentColor" stroke-width="2" fill="none" opacity="0.35"/>
<!-- Steam -->
<path d="M95 210 Q90 195 95 185" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" fill="none" opacity="0.2"/>
<path d="M120 208 Q118 190 122 180" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" fill="none" opacity="0.2"/>
<path d="M145 210 Q148 195 143 185" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" fill="none" opacity="0.2"/>
<circle cx="90" cy="70" r="16" fill="currentColor" opacity="0.3"/>
<!-- Eyes (intense, looking at drums) -->
<circle cx="84" cy="68" r="2" fill="currentColor" opacity="0.5"/>
<circle cx="96" cy="69" r="2" fill="currentColor" opacity="0.5"/>
<!-- Beak (front, small) -->
<polygon points="90,76 97,80 90,82" fill="currentColor" opacity="0.3"/>
<!-- Arms raised with drumsticks -->
<line x1="70" y1="100" x2="40" y2="65" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<line x1="40" y1="65" x2="25" y2="50" stroke="currentColor" stroke-width="2" stroke-linecap="round" opacity="0.35"/>
<line x1="110" y1="100" x2="140" y2="60" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<line x1="140" y1="60" x2="158" y2="48" stroke="currentColor" stroke-width="2" stroke-linecap="round" opacity="0.35"/>
<!-- Motion lines on sticks -->
<path d="M22 52 Q18 48 20 44" stroke="currentColor" stroke-width="1" stroke-linecap="round" fill="none" opacity="0.2"/>
<path d="M160 50 Q164 46 162 42" stroke="currentColor" stroke-width="1" stroke-linecap="round" fill="none" opacity="0.2"/>
<!-- Legs (tucked behind kit) -->
<line x1="80" y1="136" x2="70" y2="195" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.35"/>
<ellipse cx="70" cy="197" rx="7" ry="3" fill="currentColor" opacity="0.25"/>
<line x1="100" y1="136" x2="110" y2="195" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.35"/>
<ellipse cx="110" cy="197" rx="7" ry="3" fill="currentColor" opacity="0.25"/>
</svg>
<!-- 7. Shadok marionnettiste: holding puppet strings -->
<svg class="shadok-marionnettiste" viewBox="0 0 160 220" fill="none" aria-hidden="true">
<!-- Body -->
<ellipse cx="80" cy="75" rx="20" ry="26" fill="currentColor" opacity="0.25"/>
<!-- Head -->
<circle cx="80" cy="40" r="15" fill="currentColor" opacity="0.3"/>
<!-- Eyes (looking down at puppet) -->
<circle cx="75" cy="42" r="1.8" fill="currentColor" opacity="0.5"/>
<circle cx="86" cy="43" r="1.8" fill="currentColor" opacity="0.5"/>
<!-- Beak -->
<polygon points="89,44 100,42 89,48" fill="currentColor" opacity="0.3"/>
<!-- Arms down holding control bar -->
<line x1="62" y1="68" x2="50" y2="105" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<line x1="98" y1="68" x2="110" y2="105" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<!-- Control bar (cross) -->
<line x1="40" y1="108" x2="120" y2="108" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" opacity="0.35"/>
<line x1="80" y1="98" x2="80" y2="118" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" opacity="0.35"/>
<!-- Puppet strings -->
<line x1="45" y1="108" x2="55" y2="155" stroke="currentColor" stroke-width="1" opacity="0.25"/>
<line x1="80" y1="118" x2="70" y2="158" stroke="currentColor" stroke-width="1" opacity="0.25"/>
<line x1="115" y1="108" x2="85" y2="155" stroke="currentColor" stroke-width="1" opacity="0.25"/>
<line x1="80" y1="98" x2="70" y2="148" stroke="currentColor" stroke-width="1" opacity="0.25"/>
<!-- Mini puppet shadok -->
<circle cx="70" cy="155" r="8" fill="currentColor" opacity="0.2"/>
<ellipse cx="70" cy="172" rx="10" ry="13" fill="currentColor" opacity="0.15"/>
<!-- Puppet eyes -->
<circle cx="67" cy="154" r="1" fill="currentColor" opacity="0.4"/>
<circle cx="74" cy="154" r="1" fill="currentColor" opacity="0.4"/>
<!-- Puppet beak -->
<polygon points="76,156 82,155 76,158" fill="currentColor" opacity="0.2"/>
<!-- Puppet legs -->
<line x1="64" y1="184" x2="58" y2="208" stroke="currentColor" stroke-width="2" stroke-linecap="round" opacity="0.25"/>
<line x1="76" y1="184" x2="82" y2="208" stroke="currentColor" stroke-width="2" stroke-linecap="round" opacity="0.25"/>
<!-- Master shadok legs -->
<line x1="72" y1="99" x2="62" y2="155" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.3"/>
<line x1="88" y1="99" x2="98" y2="155" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.3"/>
<ellipse cx="62" cy="157" rx="7" ry="3" fill="currentColor" opacity="0.25"/>
<ellipse cx="98" cy="157" rx="7" ry="3" fill="currentColor" opacity="0.25"/>
</svg>
<!-- 8. Shadok clown: oversized shoes, red nose, squirting flower -->
<svg class="shadok-clown" viewBox="0 0 160 210" fill="none" aria-hidden="true">
<!-- Body -->
<ellipse cx="80" cy="110" rx="22" ry="28" fill="currentColor" opacity="0.25"/>
<!-- Head -->
<circle cx="80" cy="70" r="16" fill="currentColor" opacity="0.3"/>
<!-- Party hat (tall cone) -->
<polygon points="80,38 68,62 92,62" fill="currentColor" opacity="0.25"/>
<circle cx="80" cy="38" r="3" fill="currentColor" opacity="0.35"/>
<!-- Big red nose (circle on beak) -->
<circle cx="92" cy="74" r="5" fill="currentColor" opacity="0.4"/>
<!-- Beak behind nose -->
<polygon points="88,72 100,74 88,78" fill="currentColor" opacity="0.2"/>
<!-- Eyes (different directions goofy) -->
<circle cx="74" cy="66" r="2" fill="currentColor" opacity="0.5"/>
<circle cx="85" cy="64" r="2" fill="currentColor" opacity="0.5"/>
<!-- Big goofy grin -->
<path d="M72 82 Q80 90 88 82" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" fill="none" opacity="0.3"/>
<!-- Squirting flower on chest -->
<circle cx="80" cy="95" r="6" fill="currentColor" opacity="0.25"/>
<circle cx="80" cy="95" r="2.5" fill="currentColor" opacity="0.35"/>
<!-- Water squirt from flower -->
<path d="M86 93 Q100 85 105 90" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" fill="none" opacity="0.2"/>
<circle cx="107" cy="90" r="2" fill="currentColor" opacity="0.2"/>
<circle cx="112" cy="88" r="1.5" fill="currentColor" opacity="0.15"/>
<!-- Arms -->
<line x1="60" y1="100" x2="35" y2="90" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<line x1="100" y1="100" x2="125" y2="85" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<!-- Long legs -->
<line x1="72" y1="136" x2="50" y2="185" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<line x1="88" y1="136" x2="110" y2="185" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<!-- OVERSIZED shoes -->
<ellipse cx="42" cy="190" rx="18" ry="6" fill="currentColor" opacity="0.3"/>
<ellipse cx="118" cy="190" rx="18" ry="6" fill="currentColor" opacity="0.3"/>
</svg>
<!-- 9. Shadok acrobate: mid-cartwheel, body rotated 90deg -->
<svg class="shadok-acrobate" viewBox="0 0 160 200" fill="none" aria-hidden="true">
<!-- Body (rotated 90 degrees) -->
<ellipse cx="80" cy="100" rx="20" ry="26" fill="currentColor" opacity="0.25" transform="rotate(90 80 100)"/>
<!-- Head (at bottom-right, inverted) -->
<circle cx="112" cy="100" r="14" fill="currentColor" opacity="0.3"/>
<!-- Eyes (dizzy, spiral-ish) -->
<path d="M108 97 Q110 94 113 97" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" fill="none" opacity="0.4"/>
<path d="M117 97 Q119 94 122 97" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" fill="none" opacity="0.4"/>
<!-- Beak (pointing right) -->
<polygon points="124,102 136,100 124,106" fill="currentColor" opacity="0.3"/>
<!-- Limbs in pinwheel pattern -->
<!-- Arm up-right -->
<line x1="85" y1="80" x2="110" y2="35" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<!-- Arm down-left -->
<line x1="75" y1="120" x2="50" y2="165" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<!-- Leg up-left (hand on ground) -->
<line x1="65" y1="88" x2="25" y2="55" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<circle cx="23" cy="53" r="4" fill="currentColor" opacity="0.25"/>
<!-- Leg down-right -->
<line x1="95" y1="112" x2="135" y2="150" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<!-- Feet -->
<ellipse cx="110" cy="33" rx="6" ry="3" fill="currentColor" opacity="0.25" transform="rotate(-60 110 33)"/>
<ellipse cx="135" cy="152" rx="6" ry="3" fill="currentColor" opacity="0.25" transform="rotate(40 135 152)"/>
<!-- Motion arc -->
<path d="M30 40 Q20 50 25 60" stroke="currentColor" stroke-width="1" stroke-linecap="round" fill="none" opacity="0.2"/>
<path d="M140 140 Q148 148 145 158" stroke="currentColor" stroke-width="1" stroke-linecap="round" fill="none" opacity="0.2"/>
</svg>
<!-- 10. Shadok regisseur: headset, clipboard, running -->
<svg class="shadok-regisseur" viewBox="0 0 160 210" fill="none" aria-hidden="true">
<!-- Body (leaning forward, running) -->
<ellipse cx="75" cy="105" rx="20" ry="27" fill="currentColor" opacity="0.25" transform="rotate(-12 75 105)"/>
<!-- Head -->
<circle cx="80" cy="68" r="15" fill="currentColor" opacity="0.3"/>
<!-- Headset arc -->
<path d="M66 60 Q80 48 94 60" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" fill="none" opacity="0.35"/>
<!-- Headset earpiece -->
<ellipse cx="66" cy="64" rx="4" ry="6" fill="currentColor" opacity="0.3"/>
<!-- Headset mic -->
<path d="M66 70 Q62 78 70 80" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" fill="none" opacity="0.3"/>
<!-- Eyes (stressed, one looking forward one sideways) -->
<circle cx="76" cy="65" r="2" fill="currentColor" opacity="0.5"/>
<circle cx="87" cy="64" r="2" fill="currentColor" opacity="0.5"/>
<!-- Beak -->
<polygon points="91,68 104,66 91,72" fill="currentColor" opacity="0.3"/>
<!-- Lanyard -->
<line x1="80" y1="82" x2="80" y2="100" stroke="currentColor" stroke-width="1.5" opacity="0.25"/>
<!-- Badge -->
<rect x="74" y="100" width="12" height="15" rx="2" fill="currentColor" opacity="0.25"/>
<rect x="76" y="103" width="8" height="3" rx="1" fill="currentColor" opacity="0.15"/>
<!-- Right arm pointing forward (with authority) -->
<line x1="92" y1="95" x2="135" y2="75" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<!-- Pointing finger -->
<line x1="135" y1="75" x2="145" y2="70" stroke="currentColor" stroke-width="2" stroke-linecap="round" opacity="0.35"/>
<!-- Left arm holding clipboard -->
<line x1="58" y1="98" x2="38" y2="110" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<!-- Clipboard (big) -->
<rect x="22" y="105" width="22" height="30" rx="2" fill="currentColor" opacity="0.25"/>
<rect x="28" y="102" width="10" height="5" rx="1" fill="currentColor" opacity="0.3"/>
<!-- Clipboard lines -->
<line x1="26" y1="114" x2="40" y2="114" stroke="currentColor" stroke-width="1" opacity="0.15"/>
<line x1="26" y1="120" x2="38" y2="120" stroke="currentColor" stroke-width="1" opacity="0.15"/>
<line x1="26" y1="126" x2="40" y2="126" stroke="currentColor" stroke-width="1" opacity="0.15"/>
<!-- Legs (running stride, long) -->
<line x1="68" y1="130" x2="40" y2="195" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<ellipse cx="38" cy="197" rx="9" ry="3" fill="currentColor" opacity="0.3"/>
<line x1="82" y1="130" x2="115" y2="190" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<ellipse cx="117" cy="192" rx="9" ry="3" fill="currentColor" opacity="0.3"/>
</svg>
<div class="container-content relative z-10 text-center">
<p class="mb-3 font-mono text-sm tracking-widest text-accent uppercase">{{ content?.kicker }}</p>
<h1 class="page-title font-display font-extrabold tracking-tight text-white">
<h1 class="page-title font-display font-extrabold tracking-tight" style="color: hsl(var(--color-text))">
{{ content?.title }}
</h1>
<p class="mt-4 text-lg text-white/50">
<p class="mt-4 text-lg" style="color: hsl(var(--color-text-muted))">
{{ content?.description }}
</p>
</div>
@@ -170,98 +358,201 @@ useHead({
font-size: clamp(2.5rem, 6vw, 3.5rem);
}
.shadok-juggler {
/* 1. Funambule — top-left */
.shadok-funambule {
position: absolute;
left: 4%;
top: 5%;
width: clamp(100px, 14vw, 190px);
opacity: 0.3;
width: clamp(70px, 10vw, 140px);
opacity: 0.24;
pointer-events: none;
color: hsl(var(--color-primary));
animation: shadok-bounce-juggler 4s ease-in-out infinite;
animation: shadok-sway-funambule 9s ease-in-out infinite;
}
.shadok-ladder {
/* 2. Accordeoniste — top-right */
.shadok-accordeoniste {
position: absolute;
right: 4%;
top: 3%;
width: clamp(90px, 12vw, 170px);
opacity: 0.28;
right: 3%;
top: 4%;
width: clamp(70px, 10vw, 140px);
opacity: 0.22;
pointer-events: none;
color: hsl(var(--color-accent));
animation: shadok-wobble-ladder 5s ease-in-out infinite;
animation: shadok-bounce-accordeon 8s ease-in-out infinite;
}
.shadok-acrobat {
/* 3. Jongleur — top-center */
.shadok-jongleur {
position: absolute;
left: 50%;
top: 55%;
top: 2%;
transform: translateX(-50%);
width: clamp(100px, 13vw, 180px);
width: clamp(70px, 10vw, 130px);
opacity: 0.2;
pointer-events: none;
color: hsl(var(--color-primary));
animation: shadok-spin-acrobat 6s ease-in-out infinite;
animation: shadok-float-jongleur 7s ease-in-out infinite;
}
.shadok-sleeper {
/* 4. Cracheur de feu — left 5%, 40% */
.shadok-cracheur {
position: absolute;
left: 3%;
bottom: 5%;
width: clamp(110px, 15vw, 210px);
opacity: 0.25;
left: 5%;
top: 40%;
width: clamp(70px, 10vw, 140px);
opacity: 0.22;
pointer-events: none;
color: hsl(var(--color-accent));
animation: shadok-float-sleeper 8s ease-in-out infinite;
animation: shadok-flicker-cracheur 8s ease-in-out infinite;
}
.shadok-cook {
/* 5. Trapeziste — right 3%, 30% */
.shadok-trapeziste {
position: absolute;
right: 3%;
top: 30%;
width: clamp(70px, 10vw, 130px);
opacity: 0.2;
pointer-events: none;
color: hsl(var(--color-primary));
animation: shadok-swing-trapeze 10s ease-in-out infinite;
}
/* 6. Batteur — left 4%, bottom 15% */
.shadok-batteur {
position: absolute;
left: 4%;
bottom: 15%;
width: clamp(70px, 10vw, 140px);
opacity: 0.2;
pointer-events: none;
color: hsl(var(--color-accent));
animation: shadok-pulse-batteur 7s ease-in-out infinite;
}
/* 7. Marionnettiste — right 4%, bottom 20% */
.shadok-marionnettiste {
position: absolute;
right: 4%;
bottom: 20%;
width: clamp(70px, 10vw, 130px);
opacity: 0.22;
pointer-events: none;
color: hsl(var(--color-primary));
animation: shadok-bob-marionnette 9s ease-in-out infinite;
}
/* 8. Clown — bottom-left */
.shadok-clown {
position: absolute;
left: 6%;
bottom: 3%;
width: clamp(70px, 10vw, 135px);
opacity: 0.24;
pointer-events: none;
color: hsl(var(--color-accent));
animation: shadok-wobble-clown 8s ease-in-out infinite;
}
/* 9. Acrobate — center-bottom */
.shadok-acrobate {
position: absolute;
left: 50%;
bottom: 2%;
transform: translateX(-50%);
width: clamp(70px, 10vw, 130px);
opacity: 0.18;
pointer-events: none;
color: hsl(var(--color-primary));
animation: shadok-spin-acrobate 12s ease-in-out infinite;
}
/* 10. Regisseur — bottom-right */
.shadok-regisseur {
position: absolute;
right: 3%;
bottom: 4%;
width: clamp(100px, 14vw, 200px);
opacity: 0.3;
width: clamp(70px, 10vw, 140px);
opacity: 0.22;
pointer-events: none;
color: hsl(var(--color-primary));
animation: shadok-bounce-cook 5s ease-in-out infinite;
color: hsl(var(--color-accent));
animation: shadok-rush-regisseur 7s ease-in-out infinite;
}
@keyframes shadok-bounce-juggler {
@keyframes shadok-sway-funambule {
0%, 100% { transform: translateY(0) rotate(0deg); }
25% { transform: translateY(-5px) rotate(2deg); }
75% { transform: translateY(-3px) rotate(-2deg); }
}
@keyframes shadok-bounce-accordeon {
0%, 100% { transform: translateY(0); }
30% { transform: translateY(-12px); }
60% { transform: translateY(-6px); }
40% { transform: translateY(-8px); }
70% { transform: translateY(-3px); }
}
@keyframes shadok-wobble-ladder {
@keyframes shadok-float-jongleur {
0%, 100% { transform: translateX(-50%) translateY(0); }
50% { transform: translateX(-50%) translateY(-10px); }
}
@keyframes shadok-flicker-cracheur {
0%, 100% { transform: translateY(0) scale(1); }
30% { transform: translateY(-4px) scale(1.02); }
60% { transform: translateY(-2px) scale(0.99); }
}
@keyframes shadok-swing-trapeze {
0%, 100% { transform: rotate(0deg); }
25% { transform: rotate(3deg); }
75% { transform: rotate(-3deg); }
25% { transform: rotate(4deg); }
75% { transform: rotate(-4deg); }
}
@keyframes shadok-spin-acrobat {
@keyframes shadok-pulse-batteur {
0%, 100% { transform: translateY(0); }
15% { transform: translateY(-4px); }
30% { transform: translateY(0); }
45% { transform: translateY(-6px); }
60% { transform: translateY(0); }
}
@keyframes shadok-bob-marionnette {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-7px); }
}
@keyframes shadok-wobble-clown {
0%, 100% { transform: translateY(0) rotate(0deg); }
30% { transform: translateY(-5px) rotate(-3deg); }
70% { transform: translateY(-2px) rotate(3deg); }
}
@keyframes shadok-spin-acrobate {
0% { transform: translateX(-50%) rotate(0deg); }
25% { transform: translateX(-50%) rotate(15deg); }
50% { transform: translateX(-50%) rotate(0deg); }
75% { transform: translateX(-50%) rotate(-15deg); }
50% { transform: translateX(-50%) rotate(10deg); }
100% { transform: translateX(-50%) rotate(0deg); }
}
@keyframes shadok-float-sleeper {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-6px); }
}
@keyframes shadok-bounce-cook {
0%, 100% { transform: translateY(0); }
40% { transform: translateY(-10px); }
70% { transform: translateY(-4px); }
@keyframes shadok-rush-regisseur {
0%, 100% { transform: translateX(0) translateY(0); }
25% { transform: translateX(3px) translateY(-5px); }
50% { transform: translateX(-2px) translateY(-2px); }
75% { transform: translateX(2px) translateY(-6px); }
}
@media (max-width: 768px) {
.shadok-juggler { display: none; }
.shadok-ladder { display: none; }
.shadok-acrobat { display: none; }
.shadok-sleeper { display: none; }
.shadok-cook { display: none; }
.shadok-funambule,
.shadok-accordeoniste,
.shadok-jongleur,
.shadok-cracheur,
.shadok-trapeziste,
.shadok-batteur,
.shadok-marionnettiste,
.shadok-clown,
.shadok-acrobate,
.shadok-regisseur {
display: none;
}
}
</style>

View File

@@ -1,105 +0,0 @@
<template>
<div class="section-padding">
<div class="container-content">
<div class="mx-auto max-w-2xl">
<div class="gestation-icon mx-auto mb-6">
<div class="i-lucide-flask-conical h-12 w-12 text-accent" />
</div>
<h1 class="font-display text-3xl font-bold text-white mb-4 text-center">
{{ item?.label ?? 'En gestation' }}
</h1>
<p class="text-lg text-white/60 leading-relaxed mb-8 text-center">
{{ item?.description ?? 'Cette initiative est en cours de préparation.' }}
</p>
<!-- Présentation spécifique -->
<div v-if="item?.presentation" class="presentation-card mb-10">
<div class="presentation-icon">
<div class="i-lucide-rocket h-5 w-5" />
</div>
<h2 class="font-display text-xl font-semibold text-white mb-2">
{{ item.presentation.title }}
</h2>
<p class="text-white/60 leading-relaxed">
{{ item.presentation.text }}
</p>
<p class="mt-4 text-sm text-white/30 italic">En cours de développement.</p>
</div>
<!-- Bouton SejeteralO pour tarifs-eau -->
<div v-if="slug === 'tarifs-eau'" class="text-center mb-10">
<UiBaseButton :href="sejeteral0Url" target="_blank">
<div class="i-lucide-external-link mr-2 h-4 w-4" />
Lancer SejeteralO
</UiBaseButton>
</div>
<div class="text-center">
<UiBaseButton variant="ghost" to="/">
<div class="i-lucide-arrow-left mr-2 h-4 w-4" />
Retour à l'accueil
</UiBaseButton>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
const route = useRoute()
const slug = route.params.slug as string
const { data: content } = await usePageContent('home')
const appConfig = useAppConfig()
const sejeteral0Url = (appConfig.sejeteral0 as { url: string }).url
const item = computed(() => {
const axes = (content.value as any)?.axes
if (!axes) return null
for (const axis of Object.values(axes) as any[]) {
for (const it of axis.items ?? []) {
if (it.to === `/gestation/${slug}`) return it
}
}
return null
})
useHead({
title: item.value?.label ?? `En gestation — ${slug}`,
})
</script>
<style scoped>
.gestation-icon {
display: flex;
align-items: center;
justify-content: center;
width: 5rem;
height: 5rem;
border-radius: 1rem;
background: hsl(var(--color-accent) / 0.1);
border: 1px solid hsl(var(--color-accent) / 0.2);
}
.presentation-card {
padding: 1.5rem;
border-radius: 0.75rem;
border: 1px solid hsl(var(--color-primary) / 0.15);
background: hsl(var(--color-surface));
}
.presentation-icon {
display: flex;
align-items: center;
justify-content: center;
width: 2.5rem;
height: 2.5rem;
border-radius: 0.5rem;
background: hsl(var(--color-primary) / 0.1);
color: hsl(var(--color-primary));
margin-bottom: 1rem;
}
</style>

View File

@@ -2,20 +2,33 @@
<div class="section-padding">
<div class="container-content mx-auto max-w-3xl">
<h1 class="font-display text-3xl font-bold text-gradient mb-2">Messages des visiteurs</h1>
<p class="text-white/50 mb-8">Les mots laissés par celles et ceux qui passent par ici.</p>
<p class="page-subtitle mb-8">Les mots laissés par celles et ceux qui passent par ici.</p>
<div v-if="messages?.length" class="space-y-4">
<div v-for="msg in messages" :key="msg.id" class="message-card">
<p class="text-white/80 leading-relaxed">{{ msg.text }}</p>
<div class="mt-3 flex items-center gap-2 text-xs text-white/40">
<span class="font-semibold text-white/60">{{ msg.author }}</span>
<span>&middot;</span>
<span>{{ formatDate(msg.createdAt) }}</span>
<!-- En-tête auteur -->
<div class="flex items-center gap-2 mb-2">
<span class="msg-author font-semibold text-sm">{{ msg.author }}</span>
<span class="type-pill">{{ typeLabel(msg.type) }}</span>
<span class="msg-date text-xs ml-auto">{{ formatDate(msg.createdAt) }}</span>
</div>
<!-- Texte du message -->
<p class="msg-text text-sm leading-relaxed">{{ msg.text }}</p>
<!-- Réponse -->
<div v-if="msg.reply?.text" class="reply-thread">
<div class="reply-connector" aria-hidden="true" />
<div class="reply-block">
<div class="flex items-center gap-1.5 mb-1">
<div class="i-lucide-corner-down-right h-3 w-3 reply-icon" />
<span class="reply-author text-xs font-semibold">Le Librodrome</span>
</div>
<p class="reply-text text-sm leading-relaxed italic">{{ msg.reply.text }}</p>
</div>
</div>
</div>
</div>
<p v-else class="text-center text-white/40 py-12">Aucun message pour l'instant.</p>
<p v-else class="text-center page-subtitle py-12">Aucun message pour l'instant.</p>
<div class="mt-8 text-center">
<NuxtLink to="/" class="btn-ghost text-sm">
@@ -34,6 +47,17 @@ useHead({
const { data: messages } = await useFetch('/api/messages')
const TYPE_LABELS: Record<string, string> = {
reaction: 'Réaction',
question: 'Question',
suggestion: 'Suggestion',
retour: 'Retour',
}
function typeLabel(type: string) {
return TYPE_LABELS[type] ?? type
}
function formatDate(iso: string) {
const date = new Date(iso)
return date.toLocaleDateString('fr-FR', { day: 'numeric', month: 'long', year: 'numeric' })
@@ -41,10 +65,59 @@ function formatDate(iso: string) {
</script>
<style scoped>
.page-subtitle { color: hsl(var(--color-text) / 0.5); }
.msg-author { color: hsl(var(--color-text) / 0.75); }
.msg-date { color: hsl(var(--color-text) / 0.38); }
.msg-text { color: hsl(var(--color-text) / 0.78); white-space: pre-line; }
.reply-icon { color: hsl(var(--color-primary) / 0.5); }
.reply-author { color: hsl(var(--color-primary) / 0.8); }
.reply-text { color: hsl(var(--color-text) / 0.62); }
.message-card {
background: hsl(var(--color-surface));
border: 1px solid hsl(var(--color-text) / 0.1);
border-radius: 0.75rem;
padding: 1.25rem 1.5rem;
}
.type-pill {
font-size: 0.6rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
padding: 0.1rem 0.45rem;
border-radius: 9999px;
background: hsl(var(--color-primary) / 0.1);
color: hsl(var(--color-primary) / 0.7);
}
.reply-thread {
position: relative;
display: flex;
gap: 0;
margin-top: 0.75rem;
padding-left: 1.25rem;
}
.reply-connector {
position: absolute;
left: 0.5rem;
top: -0.5rem;
bottom: 0.5rem;
width: 2px;
background: linear-gradient(
to bottom,
hsl(var(--color-primary) / 0.35),
hsl(var(--color-primary) / 0.15)
);
border-radius: 2px;
}
.reply-block {
flex: 1;
background: hsl(var(--color-primary) / 0.05);
border-left: 2px solid hsl(var(--color-primary) / 0.25);
border-radius: 0 0.5rem 0.5rem 0;
padding: 0.6rem 0.875rem;
}
</style>

View File

@@ -1,212 +0,0 @@
<template>
<div class="relative overflow-hidden section-padding">
<!-- Shadok reader: character with big glasses reading a book -->
<svg class="shadok-reader" viewBox="0 0 240 300" fill="none" aria-hidden="true">
<!-- Body -->
<ellipse cx="120" cy="170" rx="45" ry="55" fill="currentColor" opacity="0.85"/>
<!-- Head -->
<circle cx="120" cy="100" r="28" fill="currentColor" opacity="0.8"/>
<!-- Big round glasses -->
<circle cx="107" cy="94" r="11" stroke="currentColor" stroke-width="2.5" fill="none" opacity="0.5"/>
<circle cx="133" cy="94" r="11" stroke="currentColor" stroke-width="2.5" fill="none" opacity="0.5"/>
<line x1="118" y1="94" x2="122" y2="94" stroke="currentColor" stroke-width="2" opacity="0.5"/>
<!-- Eyes behind glasses -->
<circle cx="108" cy="93" r="2.5" fill="currentColor" opacity="0.5"/>
<circle cx="134" cy="93" r="2.5" fill="currentColor" opacity="0.5"/>
<!-- Arms holding book -->
<line x1="78" y1="155" x2="60" y2="180" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" opacity="0.6"/>
<line x1="162" y1="155" x2="180" y2="180" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" opacity="0.6"/>
<!-- Book (open) -->
<rect x="55" y="175" width="55" height="40" rx="2" fill="currentColor" opacity="0.35"/>
<rect x="110" y="175" width="55" height="40" rx="2" fill="currentColor" opacity="0.3"/>
<line x1="110" y1="175" x2="110" y2="215" stroke="currentColor" stroke-width="2" opacity="0.5"/>
<!-- Book lines (text) -->
<line x1="65" y1="188" x2="100" y2="188" stroke="currentColor" stroke-width="1" opacity="0.2"/>
<line x1="65" y1="195" x2="95" y2="195" stroke="currentColor" stroke-width="1" opacity="0.2"/>
<line x1="65" y1="202" x2="98" y2="202" stroke="currentColor" stroke-width="1" opacity="0.2"/>
<!-- Legs -->
<line x1="105" y1="222" x2="95" y2="270" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" opacity="0.6"/>
<line x1="135" y1="222" x2="145" y2="270" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" opacity="0.6"/>
</svg>
<!-- Shadok stack: pile of books tilting -->
<svg class="shadok-stack" viewBox="0 0 160 220" fill="none" aria-hidden="true">
<!-- Bottom book -->
<rect x="20" y="170" width="120" height="22" rx="3" fill="currentColor" opacity="0.5" transform="rotate(-2 80 181)"/>
<!-- Second book -->
<rect x="30" y="145" width="100" height="20" rx="3" fill="currentColor" opacity="0.45" transform="rotate(3 80 155)"/>
<!-- Third book -->
<rect x="25" y="120" width="110" height="18" rx="3" fill="currentColor" opacity="0.4" transform="rotate(-4 80 129)"/>
<!-- Fourth book -->
<rect x="35" y="97" width="90" height="18" rx="3" fill="currentColor" opacity="0.35" transform="rotate(5 80 106)"/>
<!-- Fifth book (tilting more) -->
<rect x="40" y="74" width="80" height="17" rx="3" fill="currentColor" opacity="0.3" transform="rotate(-7 80 82)"/>
<!-- Top book (really tilting) -->
<rect x="45" y="52" width="70" height="16" rx="3" fill="currentColor" opacity="0.25" transform="rotate(10 80 60)"/>
<!-- Tiny Shadok sitting on top -->
<ellipse cx="85" cy="42" rx="10" ry="8" fill="currentColor" opacity="0.5"/>
<circle cx="85" cy="30" r="6" fill="currentColor" opacity="0.45"/>
<circle cx="87" cy="29" r="1.5" fill="currentColor" opacity="0.3"/>
</svg>
<!-- Shadok scribe: character with quill and inkwell -->
<svg class="shadok-scribe" viewBox="0 0 240 280" fill="none" aria-hidden="true">
<ellipse cx="120" cy="155" rx="40" ry="48" fill="currentColor" opacity="0.85"/>
<circle cx="120" cy="92" r="25" fill="currentColor" opacity="0.8"/>
<ellipse cx="120" cy="72" rx="20" ry="8" fill="currentColor" opacity="0.35"/>
<circle cx="112" cy="90" r="4" fill="currentColor" opacity="0.2"/>
<circle cx="130" cy="90" r="4" fill="currentColor" opacity="0.2"/>
<circle cx="113" cy="91" r="1.8" fill="currentColor" opacity="0.5"/>
<circle cx="131" cy="91" r="1.8" fill="currentColor" opacity="0.5"/>
<path d="M118 104 Q120 108 122 104" fill="currentColor" opacity="0.3"/>
<line x1="160" y1="140" x2="190" y2="170" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" opacity="0.6"/>
<line x1="190" y1="170" x2="210" y2="145" stroke="currentColor" stroke-width="2" stroke-linecap="round" opacity="0.45"/>
<path d="M210 145 Q215 140 212 135 Q208 138 210 145" fill="currentColor" opacity="0.3"/>
<line x1="80" y1="148" x2="55" y2="180" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" opacity="0.6"/>
<path d="M100 200 Q90 230 95 250" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" fill="none" opacity="0.6"/>
<path d="M140 200 Q150 230 145 250" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" fill="none" opacity="0.6"/>
<rect x="45" y="185" width="20" height="18" rx="3" fill="currentColor" opacity="0.35"/>
<ellipse cx="55" cy="185" rx="12" ry="4" fill="currentColor" opacity="0.3"/>
<rect x="170" y="175" width="40" height="50" rx="2" fill="currentColor" opacity="0.2"/>
<line x1="178" y1="188" x2="202" y2="188" stroke="currentColor" stroke-width="1" opacity="0.15"/>
<line x1="178" y1="195" x2="200" y2="195" stroke="currentColor" stroke-width="1" opacity="0.15"/>
<line x1="178" y1="202" x2="198" y2="202" stroke="currentColor" stroke-width="1" opacity="0.15"/>
</svg>
<div class="container-content">
<!-- Page de couverture du livre -->
<HomeBookSection
class="mb-16"
@open-player="showBookPlayer = true"
@open-pdf="showPdfReader = true"
/>
<header class="mb-12 text-center">
<p class="mb-2 font-mono text-sm tracking-widest text-primary uppercase">{{ content?.kicker }}</p>
<h1 class="page-title font-display font-bold tracking-tight text-white">
{{ content?.title }}
</h1>
<p class="mt-4 mx-auto max-w-2xl text-white/60">
{{ content?.description }}
</p>
</header>
<div class="mx-auto max-w-3xl">
<ul class="flex flex-col gap-3">
<li
v-for="chapter in chapters"
:key="chapter.path"
>
<NuxtLink
:to="`/modele-eco/${chapter.stem?.split('/').pop()}`"
class="card-surface flex items-start gap-4 group"
>
<span class="font-mono text-2xl font-bold text-primary/30 leading-none mt-1 w-10 text-right flex-shrink-0">
{{ String(chapter.order).padStart(2, '0') }}
</span>
<div class="min-w-0 flex-1">
<h2 class="font-display text-lg font-semibold text-white group-hover:text-primary transition-colors">
{{ chapter.title }}
</h2>
<p v-if="chapter.description" class="mt-1 text-sm text-white/50">
{{ chapter.description }}
</p>
<div class="mt-2 flex items-center gap-3">
<span v-if="chapter.readingTime" class="text-xs text-white/30">
<span class="i-lucide-clock inline-block h-3 w-3 mr-1 align-middle" />
{{ chapter.readingTime }}
</span>
<SongBadges :chapter-slug="chapter.stem?.split('/').pop() ?? ''" />
</div>
</div>
<div class="i-lucide-chevron-right h-5 w-5 text-white/20 group-hover:text-primary/60 transition-colors flex-shrink-0 mt-2" />
</NuxtLink>
</li>
</ul>
</div>
</div>
<BookPlayer v-model="showBookPlayer" />
<BookPdfReader v-model="showPdfReader" />
</div>
</template>
<script setup lang="ts">
definePageMeta({
layout: 'default',
})
const { data: content } = await usePageContent('modele-eco')
useHead({
title: content.value?.meta?.title ?? 'Table des matières',
})
const { data: chapters } = await useAsyncData('book-toc', () =>
queryCollection('book').order('order', 'ASC').all(),
)
const showBookPlayer = ref(false)
const showPdfReader = ref(false)
</script>
<style scoped>
.page-title {
font-size: clamp(2rem, 5vw, 2.75rem);
}
.shadok-reader {
position: absolute;
right: 2%;
top: 3%;
width: clamp(110px, 15vw, 220px);
opacity: 0.3;
pointer-events: none;
color: hsl(var(--color-primary));
animation: shadok-float-reader 9s ease-in-out infinite;
}
.shadok-stack {
position: absolute;
left: 2%;
bottom: 5%;
width: clamp(100px, 13vw, 180px);
opacity: 0.25;
pointer-events: none;
color: hsl(var(--color-accent));
animation: shadok-float-stack 11s ease-in-out infinite;
}
@keyframes shadok-float-reader {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-10px); }
}
@keyframes shadok-float-stack {
0%, 100% { transform: translateY(0) rotate(0deg); }
50% { transform: translateY(-8px) rotate(-2deg); }
}
.shadok-scribe {
position: absolute;
left: 50%;
bottom: 3%;
transform: translateX(-50%);
width: clamp(100px, 13vw, 180px);
opacity: 0.25;
pointer-events: none;
color: hsl(var(--color-primary));
animation: shadok-float-scribe 10s ease-in-out infinite;
}
@keyframes shadok-float-scribe {
0%, 100% { transform: translateX(-50%) translateY(0); }
50% { transform: translateX(-50%) translateY(-8px); }
}
@media (max-width: 768px) {
.shadok-reader { display: none; }
.shadok-stack { display: none; }
.shadok-scribe { display: none; }
}
</style>

View File

@@ -1,168 +0,0 @@
<template>
<div class="relative overflow-hidden section-padding">
<!-- Shadok jardinier: character with watering can and plant -->
<svg class="shadok-jardinier" viewBox="0 0 240 300" fill="none" aria-hidden="true">
<!-- Body -->
<ellipse cx="110" cy="160" rx="40" ry="48" fill="currentColor" opacity="0.85"/>
<!-- Head -->
<circle cx="110" cy="96" r="25" fill="currentColor" opacity="0.8"/>
<!-- Straw hat -->
<ellipse cx="110" cy="78" rx="35" ry="8" fill="currentColor" opacity="0.4"/>
<path d="M85 78 Q110 60 135 78" fill="currentColor" opacity="0.35"/>
<!-- Eyes (focused, looking down at plant) -->
<circle cx="102" cy="94" r="4" fill="currentColor" opacity="0.2"/>
<circle cx="120" cy="94" r="4" fill="currentColor" opacity="0.2"/>
<circle cx="103" cy="95" r="1.8" fill="currentColor" opacity="0.5"/>
<circle cx="121" cy="95" r="1.8" fill="currentColor" opacity="0.5"/>
<!-- Smile -->
<path d="M103 106 Q110 111 118 106" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" fill="none" opacity="0.3"/>
<!-- Arm holding watering can -->
<line x1="70" y1="150" x2="40" y2="170" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" opacity="0.6"/>
<!-- Watering can -->
<rect x="20" y="165" width="30" height="20" rx="3" fill="currentColor" opacity="0.4"/>
<line x1="20" y1="168" x2="10" y2="160" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" opacity="0.4"/>
<!-- Water drops -->
<circle cx="12" cy="165" r="1.5" fill="currentColor" opacity="0.25"/>
<circle cx="8" cy="170" r="1.5" fill="currentColor" opacity="0.2"/>
<circle cx="15" cy="172" r="1.5" fill="currentColor" opacity="0.2"/>
<!-- Other arm -->
<line x1="150" y1="150" x2="170" y2="180" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" opacity="0.6"/>
<!-- Legs -->
<line x1="95" y1="205" x2="85" y2="260" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" opacity="0.6"/>
<line x1="125" y1="205" x2="135" y2="260" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" opacity="0.6"/>
<!-- Plant -->
<line x1="180" y1="220" x2="180" y2="180" stroke="currentColor" stroke-width="2" stroke-linecap="round" opacity="0.4"/>
<path d="M180 195 Q195 185 190 175" stroke="currentColor" stroke-width="2" stroke-linecap="round" fill="none" opacity="0.35"/>
<path d="M180 205 Q165 195 168 185" stroke="currentColor" stroke-width="2" stroke-linecap="round" fill="none" opacity="0.35"/>
<path d="M180 185 Q192 172 188 165" stroke="currentColor" stroke-width="2" stroke-linecap="round" fill="none" opacity="0.3"/>
<!-- Pot -->
<path d="M170 220 L175 240 L185 240 L190 220 Z" fill="currentColor" opacity="0.35"/>
</svg>
<!-- Shadok bâtisseur: character with trowel building a wall -->
<svg class="shadok-batisseur" viewBox="0 0 260 300" fill="none" aria-hidden="true">
<!-- Body -->
<ellipse cx="130" cy="150" rx="40" ry="48" fill="currentColor" opacity="0.85"/>
<!-- Head -->
<circle cx="130" cy="86" r="25" fill="currentColor" opacity="0.8"/>
<!-- Hard hat -->
<ellipse cx="130" cy="68" rx="28" ry="6" fill="currentColor" opacity="0.4"/>
<rect x="108" y="60" width="44" height="10" rx="3" fill="currentColor" opacity="0.35"/>
<!-- Eyes (determined) -->
<circle cx="122" cy="84" r="4" fill="currentColor" opacity="0.2"/>
<circle cx="140" cy="84" r="4" fill="currentColor" opacity="0.2"/>
<circle cx="123" cy="83" r="1.8" fill="currentColor" opacity="0.5"/>
<circle cx="141" cy="83" r="1.8" fill="currentColor" opacity="0.5"/>
<!-- Grin -->
<path d="M123 96 Q130 101 138 96" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" fill="none" opacity="0.3"/>
<!-- Arm with trowel -->
<line x1="170" y1="140" x2="210" y2="120" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" opacity="0.6"/>
<!-- Trowel -->
<polygon points="210,115 230,110 225,120 210,122" fill="currentColor" opacity="0.45"/>
<line x1="210" y1="118" x2="200" y2="125" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" opacity="0.4"/>
<!-- Other arm -->
<line x1="90" y1="145" x2="65" y2="170" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" opacity="0.6"/>
<!-- Legs -->
<line x1="115" y1="195" x2="105" y2="255" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" opacity="0.6"/>
<line x1="145" y1="195" x2="155" y2="255" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" opacity="0.6"/>
<!-- Wall (bricks) -->
<rect x="40" y="200" width="50" height="16" rx="1" fill="currentColor" opacity="0.3"/>
<rect x="45" y="183" width="40" height="16" rx="1" fill="currentColor" opacity="0.28"/>
<rect x="50" y="166" width="30" height="16" rx="1" fill="currentColor" opacity="0.25"/>
<!-- Brick lines -->
<line x1="65" y1="200" x2="65" y2="216" stroke="currentColor" stroke-width="1" opacity="0.15"/>
<line x1="55" y1="183" x2="55" y2="199" stroke="currentColor" stroke-width="1" opacity="0.15"/>
</svg>
<div class="container-content">
<header class="mb-12 text-center">
<p class="mb-2 font-mono text-sm tracking-widest text-primary uppercase">{{ content?.kicker }}</p>
<h1 class="page-title font-display font-bold tracking-tight text-white">
{{ content?.title }}
</h1>
<p class="mt-4 mx-auto max-w-2xl text-white/60">
{{ content?.description }}
</p>
</header>
<div class="mx-auto max-w-3xl flex flex-col gap-6">
<div
v-for="(extract, i) in content?.extracts"
:key="i"
class="card-surface"
>
<p class="mb-2 font-mono text-xs tracking-widest text-accent uppercase">
{{ extract.chapter }}
</p>
<blockquote class="border-l-2 border-primary/30 pl-4 text-white/70 italic leading-relaxed whitespace-pre-line">
{{ extract.text }}
</blockquote>
<div class="mt-4">
<NuxtLink
:to="`/modele-eco/${extract.chapterSlug}`"
class="inline-flex items-center gap-1 text-sm text-primary hover:text-primary/80 transition-colors"
>
Lire le chapitre
<div class="i-lucide-arrow-right h-3.5 w-3.5" />
</NuxtLink>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
definePageMeta({
layout: 'default',
})
const { data: content } = await usePageContent('numerique')
useHead({
title: content.value?.meta?.title ?? 'Autonomie numérique',
})
</script>
<style scoped>
.page-title {
font-size: clamp(2rem, 5vw, 2.75rem);
}
.shadok-jardinier {
position: absolute;
left: 2%;
top: 5%;
width: clamp(100px, 14vw, 200px);
opacity: 0.28;
pointer-events: none;
color: hsl(var(--color-primary));
animation: shadok-float-jardinier 10s ease-in-out infinite;
}
.shadok-batisseur {
position: absolute;
right: 2%;
bottom: 5%;
width: clamp(110px, 15vw, 210px);
opacity: 0.3;
pointer-events: none;
color: hsl(var(--color-accent));
animation: shadok-float-batisseur 11s ease-in-out infinite;
}
@keyframes shadok-float-jardinier {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-10px); }
}
@keyframes shadok-float-batisseur {
0%, 100% { transform: translateY(0) rotate(0deg); }
50% { transform: translateY(-8px) rotate(1deg); }
}
@media (max-width: 768px) {
.shadok-jardinier { display: none; }
.shadok-batisseur { display: none; }
}
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,586 @@
<template>
<div class="relative overflow-hidden section-padding">
<!-- Shadok codeuse sitting at desk, typing on laptop, glasses -->
<svg class="shadok-codeuse" viewBox="0 0 170 200" fill="none" aria-hidden="true">
<!-- Desk -->
<rect x="15" y="120" width="80" height="5" rx="2" fill="currentColor" opacity="0.3"/>
<line x1="25" y1="125" x2="25" y2="165" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" opacity="0.25"/>
<line x1="85" y1="125" x2="85" y2="165" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" opacity="0.25"/>
<!-- Laptop on desk -->
<rect x="30" y="108" width="50" height="12" rx="2" fill="currentColor" opacity="0.35"/>
<path d="M35 108 L40 80 L70 80 L75 108" fill="currentColor" opacity="0.28"/>
<rect x="42" y="84" width="26" height="18" rx="1" fill="currentColor" opacity="0.15"/>
<!-- Code on screen < /> -->
<text x="48" y="96" font-size="8" fill="currentColor" opacity="0.4" font-family="monospace">&lt;/&gt;</text>
<!-- Body seated, tilted forward -->
<ellipse cx="110" cy="105" rx="20" ry="26" fill="currentColor" opacity="0.25" transform="rotate(-10 110 105)"/>
<!-- Head looking at screen -->
<circle cx="105" cy="68" r="15" fill="currentColor" opacity="0.3"/>
<!-- Glasses -->
<circle cx="100" cy="66" r="5" stroke="currentColor" stroke-width="1.2" fill="none" opacity="0.4"/>
<circle cx="111" cy="66" r="5" stroke="currentColor" stroke-width="1.2" fill="none" opacity="0.4"/>
<line x1="105" y1="66" x2="106" y2="66" stroke="currentColor" stroke-width="1" opacity="0.35"/>
<!-- Eyes behind glasses -->
<circle cx="101" cy="66" r="1.5" fill="currentColor" opacity="0.5"/>
<circle cx="110" cy="65" r="1.5" fill="currentColor" opacity="0.5"/>
<!-- Beak pointy, looking left -->
<polygon points="88,68 82,65 88,72" fill="currentColor" opacity="0.35"/>
<!-- Arms reaching to laptop -->
<line x1="95" y1="95" x2="78" y2="110" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<line x1="125" y1="98" x2="80" y2="115" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.35"/>
<!-- Legs bent, seated -->
<line x1="100" y1="128" x2="90" y2="180" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<line x1="118" y1="128" x2="125" y2="180" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<!-- Feet -->
<ellipse cx="88" cy="183" rx="8" ry="3" fill="currentColor" opacity="0.3"/>
<ellipse cx="127" cy="183" rx="8" ry="3" fill="currentColor" opacity="0.3"/>
</svg>
<!-- Shadok electricien profile view, climbing utility pole, tool belt -->
<svg class="shadok-electricien" viewBox="0 0 150 210" fill="none" aria-hidden="true">
<!-- Utility pole -->
<rect x="68" y="5" width="8" height="200" rx="2" fill="currentColor" opacity="0.2"/>
<line x1="50" y1="30" x2="94" y2="30" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" opacity="0.25"/>
<line x1="45" y1="55" x2="99" y2="55" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" opacity="0.22"/>
<!-- Cables hanging -->
<path d="M50 30 Q35 45 30 35" stroke="currentColor" stroke-width="1.5" fill="none" opacity="0.2"/>
<path d="M94 30 Q110 45 115 35" stroke="currentColor" stroke-width="1.5" fill="none" opacity="0.2"/>
<path d="M45 55 Q30 68 25 60" stroke="currentColor" stroke-width="1.5" fill="none" opacity="0.18"/>
<!-- Body climbing, profile, tilted -->
<ellipse cx="95" cy="100" rx="20" ry="25" fill="currentColor" opacity="0.25" transform="rotate(15 95 100)"/>
<!-- Head looking up at cable -->
<circle cx="100" cy="65" r="14" fill="currentColor" opacity="0.3"/>
<!-- Eyes looking up -->
<circle cx="96" cy="61" r="1.5" fill="currentColor" opacity="0.5"/>
<circle cx="105" cy="60" r="1.5" fill="currentColor" opacity="0.45"/>
<!-- Beak profile right -->
<polygon points="114,63 122,60 114,67" fill="currentColor" opacity="0.35"/>
<!-- Hard hat -->
<ellipse cx="100" cy="53" rx="16" ry="5" fill="currentColor" opacity="0.3"/>
<path d="M88 53 Q100 42 112 53" fill="currentColor" opacity="0.25"/>
<!-- Arms one gripping pole, one holding cable -->
<line x1="82" y1="90" x2="74" y2="75" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<line x1="108" y1="88" x2="118" y2="60" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<!-- Tool belt -->
<ellipse cx="95" cy="118" rx="22" ry="4" fill="currentColor" opacity="0.25"/>
<rect x="82" y="118" width="6" height="8" rx="1" fill="currentColor" opacity="0.2"/>
<rect x="105" y="118" width="5" height="10" rx="1" fill="currentColor" opacity="0.2"/>
<!-- Legs climbing, spread on pole -->
<line x1="85" y1="122" x2="72" y2="185" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<line x1="105" y1="122" x2="76" y2="175" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<!-- Feet gripping pole -->
<ellipse cx="70" cy="188" rx="9" ry="3" fill="currentColor" opacity="0.3"/>
<ellipse cx="75" cy="178" rx="8" ry="3" fill="currentColor" opacity="0.28"/>
</svg>
<!-- Shadok soudeuse welding mask flipped up, torch with sparks, apron -->
<svg class="shadok-soudeuse" viewBox="0 0 160 210" fill="none" aria-hidden="true">
<!-- Body upright, slight lean forward -->
<ellipse cx="80" cy="110" rx="22" ry="28" fill="currentColor" opacity="0.25"/>
<!-- Apron -->
<path d="M62 95 L60 140 L100 140 L98 95" fill="currentColor" opacity="0.18"/>
<line x1="80" y1="95" x2="80" y2="140" stroke="currentColor" stroke-width="1" opacity="0.15"/>
<!-- Head -->
<circle cx="80" cy="65" r="16" fill="currentColor" opacity="0.3"/>
<!-- Welding mask flipped up on head -->
<rect x="65" y="42" width="30" height="18" rx="4" fill="currentColor" opacity="0.2"/>
<rect x="70" y="46" width="20" height="10" rx="2" fill="currentColor" opacity="0.12"/>
<!-- Eyes squinting from bright light -->
<line x1="73" y1="64" x2="77" y2="64" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" opacity="0.5"/>
<line x1="84" y1="63" x2="88" y2="63" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" opacity="0.45"/>
<!-- Beak small, front-facing -->
<polygon points="78,72 80,78 82,72" fill="currentColor" opacity="0.35"/>
<!-- Right arm holding torch -->
<line x1="100" y1="100" x2="135" y2="85" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<!-- Welding torch -->
<rect x="132" y="78" width="22" height="6" rx="2" fill="currentColor" opacity="0.35"/>
<line x1="154" y1="81" x2="160" y2="78" stroke="currentColor" stroke-width="2" stroke-linecap="round" opacity="0.4"/>
<!-- Sparks from torch -->
<circle cx="158" cy="75" r="1.5" fill="currentColor" opacity="0.45"/>
<circle cx="155" cy="70" r="1" fill="currentColor" opacity="0.35"/>
<circle cx="160" cy="72" r="1.2" fill="currentColor" opacity="0.4"/>
<line x1="157" y1="76" x2="160" y2="68" stroke="currentColor" stroke-width="0.8" opacity="0.3"/>
<line x1="155" y1="78" x2="152" y2="71" stroke="currentColor" stroke-width="0.8" opacity="0.25"/>
<!-- Left arm down -->
<line x1="60" y1="100" x2="45" y2="125" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<!-- Legs standing, spread -->
<line x1="70" y1="136" x2="58" y2="195" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<line x1="90" y1="136" x2="102" y2="195" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<!-- Big flat feet -->
<ellipse cx="55" cy="198" rx="9" ry="3" fill="currentColor" opacity="0.3"/>
<ellipse cx="105" cy="198" rx="9" ry="3" fill="currentColor" opacity="0.3"/>
</svg>
<!-- Shadok admin réseau tangled in cables, router at feet, frustrated -->
<svg class="shadok-admin-reseau" viewBox="0 0 160 200" fill="none" aria-hidden="true">
<!-- Body upright but struggling -->
<ellipse cx="80" cy="95" rx="21" ry="27" fill="currentColor" opacity="0.25"/>
<!-- Head tilted, frustrated -->
<circle cx="82" cy="52" r="15" fill="currentColor" opacity="0.3" />
<!-- Eyes looking in different directions (frustrated) -->
<circle cx="77" cy="50" r="1.8" fill="currentColor" opacity="0.5"/>
<circle cx="88" cy="48" r="1.8" fill="currentColor" opacity="0.45"/>
<!-- Angry eyebrows -->
<line x1="74" y1="46" x2="79" y2="47" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" opacity="0.4"/>
<line x1="91" y1="44" x2="86" y2="46" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" opacity="0.4"/>
<!-- Beak open, yelling -->
<polygon points="73,56 65,55 73,60" fill="currentColor" opacity="0.35"/>
<line x1="67" y1="56" x2="66" y2="58" stroke="currentColor" stroke-width="1" opacity="0.3"/>
<!-- Cables tangling around body -->
<path d="M50 40 Q30 60 55 80 Q80 95 60 115 Q40 130 70 140" stroke="currentColor" stroke-width="2" fill="none" opacity="0.3"/>
<path d="M110 45 Q130 65 105 85 Q85 100 110 120 Q125 130 95 145" stroke="currentColor" stroke-width="2" fill="none" opacity="0.28"/>
<path d="M75 38 Q95 50 75 70" stroke="currentColor" stroke-width="1.5" fill="none" opacity="0.22"/>
<!-- Arms trying to untangle -->
<line x1="60" y1="85" x2="40" y2="70" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<line x1="100" y1="85" x2="120" y2="68" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<!-- Legs -->
<line x1="70" y1="120" x2="60" y2="175" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<line x1="90" y1="120" x2="100" y2="175" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<!-- Feet -->
<ellipse cx="57" cy="178" rx="8" ry="3" fill="currentColor" opacity="0.3"/>
<ellipse cx="103" cy="178" rx="8" ry="3" fill="currentColor" opacity="0.3"/>
<!-- Router box at feet -->
<rect x="60" y="168" width="40" height="14" rx="3" fill="currentColor" opacity="0.25"/>
<circle cx="70" cy="175" r="1.5" fill="currentColor" opacity="0.35"/>
<circle cx="78" cy="175" r="1.5" fill="currentColor" opacity="0.3"/>
<circle cx="86" cy="175" r="1.5" fill="currentColor" opacity="0.35"/>
<!-- Antenna on router -->
<line x1="92" y1="168" x2="96" y2="155" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" opacity="0.25"/>
</svg>
<!-- Shadok imprimeuse 3D watching printer build object layer by layer -->
<svg class="shadok-imprimeuse3d" viewBox="0 0 170 210" fill="none" aria-hidden="true">
<!-- 3D printer frame -->
<rect x="10" y="90" width="65" height="60" rx="3" fill="currentColor" opacity="0.2"/>
<!-- Printer rails -->
<line x1="15" y1="95" x2="15" y2="145" stroke="currentColor" stroke-width="1.5" opacity="0.25"/>
<line x1="70" y1="95" x2="70" y2="145" stroke="currentColor" stroke-width="1.5" opacity="0.25"/>
<!-- Print bed -->
<rect x="18" y="135" width="49" height="4" rx="1" fill="currentColor" opacity="0.25"/>
<!-- Object being printed (layered) -->
<rect x="32" y="128" width="20" height="7" rx="1" fill="currentColor" opacity="0.3"/>
<rect x="34" y="122" width="16" height="6" rx="1" fill="currentColor" opacity="0.25"/>
<rect x="36" y="117" width="12" height="5" rx="1" fill="currentColor" opacity="0.2"/>
<!-- Extruder head -->
<rect x="34" y="110" width="16" height="6" rx="2" fill="currentColor" opacity="0.35"/>
<line x1="42" y1="116" x2="42" y2="118" stroke="currentColor" stroke-width="1.5" opacity="0.4"/>
<!-- Body leaning forward, watching intently -->
<ellipse cx="120" cy="105" rx="20" ry="25" fill="currentColor" opacity="0.25" transform="rotate(-15 120 105)"/>
<!-- Head looking down at printer -->
<circle cx="108" cy="70" r="15" fill="currentColor" opacity="0.3"/>
<!-- Eyes both staring down at printer, fascinated -->
<circle cx="103" cy="72" r="1.8" fill="currentColor" opacity="0.5"/>
<circle cx="112" cy="73" r="1.8" fill="currentColor" opacity="0.5"/>
<!-- Beak pointing down -->
<polygon points="100,78 96,82 104,82" fill="currentColor" opacity="0.35"/>
<!-- Arms one pointing at printer -->
<line x1="105" y1="95" x2="75" y2="105" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<line x1="135" y1="98" x2="150" y2="115" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.35"/>
<!-- Legs standing, leaning -->
<line x1="112" y1="128" x2="105" y2="192" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<line x1="128" y1="128" x2="138" y2="192" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<!-- Feet -->
<ellipse cx="103" cy="195" rx="8" ry="3" fill="currentColor" opacity="0.3"/>
<ellipse cx="140" cy="195" rx="8" ry="3" fill="currentColor" opacity="0.3"/>
</svg>
<!-- Shadok cryptographe 3/4 view, cloak, giant key + padlock -->
<svg class="shadok-cryptographe" viewBox="0 0 170 210" fill="none" aria-hidden="true">
<!-- Mysterious cloak -->
<path d="M55 70 Q50 110 45 160 L125 160 Q120 110 115 70" fill="currentColor" opacity="0.15"/>
<path d="M45 160 Q40 170 38 180 L132 180 Q130 170 125 160" fill="currentColor" opacity="0.12"/>
<!-- Body under cloak -->
<ellipse cx="85" cy="105" rx="22" ry="28" fill="currentColor" opacity="0.25"/>
<!-- Head 3/4 view, slightly turned -->
<circle cx="85" cy="55" r="16" fill="currentColor" opacity="0.3"/>
<!-- Hood suggestion -->
<path d="M68 50 Q85 35 102 50" fill="currentColor" opacity="0.18"/>
<!-- Eyes mysterious, one slightly larger -->
<circle cx="80" cy="53" r="2" fill="currentColor" opacity="0.5"/>
<circle cx="91" cy="52" r="1.5" fill="currentColor" opacity="0.4"/>
<!-- Beak 3/4 profile -->
<polygon points="75,58 67,55 74,62" fill="currentColor" opacity="0.35"/>
<!-- Left arm holding giant key -->
<line x1="63" y1="95" x2="30" y2="110" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<!-- Giant key -->
<circle cx="18" cy="108" r="12" stroke="currentColor" stroke-width="2.5" fill="none" opacity="0.4"/>
<line x1="30" y1="108" x2="55" y2="108" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.35"/>
<line x1="48" y1="108" x2="48" y2="118" stroke="currentColor" stroke-width="2" stroke-linecap="round" opacity="0.3"/>
<line x1="53" y1="108" x2="53" y2="115" stroke="currentColor" stroke-width="2" stroke-linecap="round" opacity="0.3"/>
<!-- Right arm holding padlock -->
<line x1="107" y1="95" x2="140" y2="85" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<!-- Padlock big -->
<rect x="135" y="82" width="28" height="24" rx="4" fill="currentColor" opacity="0.3"/>
<path d="M141 82 L141 70 Q149 58 157 70 L157 82" stroke="currentColor" stroke-width="2.5" fill="none" opacity="0.35"/>
<circle cx="149" cy="95" r="3" fill="currentColor" opacity="0.2"/>
<line x1="149" y1="98" x2="149" y2="103" stroke="currentColor" stroke-width="1.5" opacity="0.2"/>
<!-- Legs long, cloak partly covering -->
<line x1="75" y1="130" x2="65" y2="195" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<line x1="95" y1="130" x2="108" y2="195" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<!-- Feet -->
<ellipse cx="63" cy="198" rx="8" ry="3" fill="currentColor" opacity="0.3"/>
<ellipse cx="110" cy="198" rx="8" ry="3" fill="currentColor" opacity="0.3"/>
</svg>
<!-- Shadok droneuse looking up at flying drone, remote in hands -->
<svg class="shadok-droneuse" viewBox="0 0 160 210" fill="none" aria-hidden="true">
<!-- Drone above -->
<rect x="60" y="12" width="30" height="8" rx="3" fill="currentColor" opacity="0.3"/>
<!-- Drone arms -->
<line x1="55" y1="16" x2="35" y2="16" stroke="currentColor" stroke-width="2" stroke-linecap="round" opacity="0.25"/>
<line x1="95" y1="16" x2="115" y2="16" stroke="currentColor" stroke-width="2" stroke-linecap="round" opacity="0.25"/>
<!-- Propellers spinning -->
<ellipse cx="35" cy="14" rx="12" ry="3" fill="currentColor" opacity="0.2"/>
<ellipse cx="115" cy="14" rx="12" ry="3" fill="currentColor" opacity="0.2"/>
<!-- Propeller blur lines -->
<line x1="25" y1="12" x2="45" y2="12" stroke="currentColor" stroke-width="0.8" opacity="0.15"/>
<line x1="105" y1="12" x2="125" y2="12" stroke="currentColor" stroke-width="0.8" opacity="0.15"/>
<!-- Drone camera -->
<circle cx="75" cy="22" r="3" fill="currentColor" opacity="0.25"/>
<!-- Drone legs -->
<line x1="62" y1="20" x2="58" y2="28" stroke="currentColor" stroke-width="1.5" opacity="0.2"/>
<line x1="88" y1="20" x2="92" y2="28" stroke="currentColor" stroke-width="1.5" opacity="0.2"/>
<!-- Body upright, looking up -->
<ellipse cx="78" cy="115" rx="20" ry="26" fill="currentColor" opacity="0.25"/>
<!-- Head tilted back looking up -->
<circle cx="78" cy="72" r="15" fill="currentColor" opacity="0.3"/>
<!-- Eyes looking up at drone -->
<circle cx="74" cy="68" r="1.8" fill="currentColor" opacity="0.5"/>
<circle cx="83" cy="67" r="1.8" fill="currentColor" opacity="0.45"/>
<!-- Beak pointing up -->
<polygon points="76,56 80,50 82,57" fill="currentColor" opacity="0.35"/>
<!-- Arms holding remote control -->
<line x1="60" y1="108" x2="50" y2="128" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<line x1="96" y1="108" x2="106" y2="128" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<!-- Remote control in hands -->
<rect x="42" y="126" width="70" height="14" rx="4" fill="currentColor" opacity="0.3"/>
<circle cx="55" cy="133" r="3" fill="currentColor" opacity="0.2"/>
<circle cx="100" cy="133" r="3" fill="currentColor" opacity="0.2"/>
<!-- Joysticks -->
<line x1="55" y1="130" x2="55" y2="127" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" opacity="0.3"/>
<line x1="100" y1="131" x2="102" y2="128" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" opacity="0.3"/>
<!-- Legs standing -->
<line x1="68" y1="138" x2="58" y2="195" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<line x1="88" y1="138" x2="100" y2="195" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<!-- Feet -->
<ellipse cx="55" cy="198" rx="9" ry="3" fill="currentColor" opacity="0.3"/>
<ellipse cx="103" cy="198" rx="9" ry="3" fill="currentColor" opacity="0.3"/>
</svg>
<!-- Shadok mécanicienne lying under machine, legs sticking out, wrench, oil drops -->
<svg class="shadok-mecanicienne" viewBox="0 0 180 200" fill="none" aria-hidden="true">
<!-- Machine/server box above -->
<rect x="5" y="55" width="100" height="50" rx="5" fill="currentColor" opacity="0.2"/>
<rect x="12" y="62" width="35" height="12" rx="2" fill="currentColor" opacity="0.15"/>
<rect x="12" y="80" width="35" height="12" rx="2" fill="currentColor" opacity="0.15"/>
<!-- Machine details -->
<circle cx="20" cy="68" r="2" fill="currentColor" opacity="0.3"/>
<circle cx="20" cy="86" r="2" fill="currentColor" opacity="0.25"/>
<line x1="60" y1="65" x2="90" y2="65" stroke="currentColor" stroke-width="1.5" opacity="0.15"/>
<line x1="60" y1="72" x2="85" y2="72" stroke="currentColor" stroke-width="1.5" opacity="0.15"/>
<!-- Body lying flat under machine, only partial visible -->
<ellipse cx="80" cy="118" rx="25" ry="18" fill="currentColor" opacity="0.22" transform="rotate(90 80 118)"/>
<!-- Head under machine (barely visible) -->
<circle cx="55" cy="115" r="12" fill="currentColor" opacity="0.2"/>
<!-- Arm sticking out with wrench -->
<line x1="42" y1="115" x2="15" y2="105" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<!-- Wrench -->
<path d="M8 100 L15 105 L12 108 L5 103 Z" fill="currentColor" opacity="0.35"/>
<circle cx="5" cy="100" r="5" stroke="currentColor" stroke-width="1.5" fill="none" opacity="0.3"/>
<!-- Oil drops falling -->
<ellipse cx="70" cy="108" rx="2" ry="3" fill="currentColor" opacity="0.3"/>
<ellipse cx="85" cy="112" rx="1.5" ry="2.5" fill="currentColor" opacity="0.25"/>
<ellipse cx="62" cy="105" rx="1" ry="2" fill="currentColor" opacity="0.2"/>
<!-- Legs sticking out from under machine the signature view -->
<line x1="90" y1="125" x2="130" y2="175" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<line x1="95" y1="120" x2="145" y2="165" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
<!-- Big feet sticking up -->
<ellipse cx="133" cy="178" rx="10" ry="4" fill="currentColor" opacity="0.35" transform="rotate(-25 133 178)"/>
<ellipse cx="148" cy="168" rx="10" ry="4" fill="currentColor" opacity="0.35" transform="rotate(-25 148 168)"/>
<!-- Beak visible from side -->
<polygon points="42,118 35,116 42,122" fill="currentColor" opacity="0.3"/>
<!-- Eye visible -->
<circle cx="50" cy="113" r="1.5" fill="currentColor" opacity="0.4"/>
</svg>
<div class="container-content">
<header class="mb-12 text-center">
<p class="mb-2 font-mono text-sm tracking-widest text-primary uppercase">{{ content?.kicker }}</p>
<h1 class="page-title font-display font-bold tracking-tight" style="color: hsl(var(--color-text))">
{{ content?.title }}
</h1>
<p class="mt-4 mx-auto max-w-2xl leading-relaxed" style="color: hsl(var(--color-text-muted))">
{{ content?.description }}
</p>
</header>
<div class="mx-auto max-w-3xl flex flex-col gap-6">
<div
v-for="pillar in content?.pillars"
:key="pillar.id"
class="pillar-card"
>
<div class="pillar-header">
<div class="pillar-icon">
<div :class="`i-lucide-${pillar.icon}`" class="h-5 w-5" />
</div>
<h2 class="font-display text-xl font-bold" style="color: hsl(var(--color-text))">
{{ pillar.label }}
</h2>
<span v-if="pillar.gestation" class="gestation-badge">
<div class="i-lucide-flask-conical h-3 w-3" />
En gestation
</span>
</div>
<p class="leading-relaxed whitespace-pre-line mt-3" style="color: hsl(var(--color-text-muted))">{{ pillar.text }}</p>
<!-- Project card -->
<div v-if="pillar.project" class="project-card mt-4">
<div class="project-icon">
<div class="i-lucide-rocket h-4 w-4" />
</div>
<div>
<span class="font-display font-semibold text-sm" style="color: hsl(var(--color-text))">{{ pillar.project.name }}</span>
<span class="text-sm ml-2" style="color: hsl(var(--color-text-muted))">{{ pillar.project.text }}</span>
</div>
</div>
<div v-if="pillar.to" class="mt-4">
<NuxtLink
:to="pillar.to"
class="inline-flex items-center gap-1 text-sm text-primary hover:text-primary/80 transition-colors"
>
En savoir plus
<div class="i-lucide-arrow-right h-3.5 w-3.5" />
</NuxtLink>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
definePageMeta({
layout: 'default',
})
const { data: content } = await usePageContent('numerique')
useHead({
title: content.value?.meta?.title ?? 'Autonomie numérique',
})
</script>
<style scoped>
.page-title {
font-size: clamp(2rem, 5vw, 2.75rem);
}
.pillar-card {
padding: 1.5rem;
border-radius: 0.75rem;
border: 1px solid hsl(var(--color-text) / 0.08);
background: hsl(var(--color-surface));
transition: border-color 0.2s;
}
.pillar-card:hover {
border-color: hsl(var(--color-primary) / 0.2);
}
.pillar-header {
display: flex;
align-items: center;
gap: 0.75rem;
}
.pillar-icon {
display: flex;
align-items: center;
justify-content: center;
width: 2.25rem;
height: 2.25rem;
border-radius: 0.5rem;
background: hsl(var(--color-primary) / 0.15);
color: hsl(var(--color-primary));
box-shadow: 0 0 12px hsl(var(--color-primary) / 0.12);
flex-shrink: 0;
}
.gestation-badge {
display: inline-flex;
align-items: center;
gap: 0.25rem;
margin-left: auto;
padding: 0.125rem 0.5rem;
border-radius: 9999px;
background: hsl(var(--color-accent) / 0.12);
color: hsl(var(--color-accent));
font-size: 0.7rem;
font-weight: 500;
font-family: var(--font-mono);
}
.project-card {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.75rem 1rem;
border-radius: 0.5rem;
background: hsl(var(--color-bg) / 0.5);
border: 1px solid hsl(var(--color-primary) / 0.1);
}
.project-icon {
display: flex;
align-items: center;
justify-content: center;
width: 1.75rem;
height: 1.75rem;
border-radius: 0.375rem;
background: hsl(var(--color-primary) / 0.1);
color: hsl(var(--color-primary));
flex-shrink: 0;
}
.shadok-codeuse {
position: absolute;
left: 1%;
top: 4%;
width: clamp(70px, 10vw, 140px);
opacity: 0.24;
pointer-events: none;
color: hsl(var(--color-primary));
animation: shadok-float-codeuse 9s ease-in-out infinite;
}
.shadok-electricien {
position: absolute;
right: 1%;
top: 3%;
width: clamp(70px, 10vw, 130px);
opacity: 0.22;
pointer-events: none;
color: hsl(var(--color-accent));
animation: shadok-float-electricien 11s ease-in-out infinite;
}
.shadok-soudeuse {
position: absolute;
left: 3%;
top: 40%;
width: clamp(70px, 10vw, 130px);
opacity: 0.2;
pointer-events: none;
color: hsl(var(--color-primary));
animation: shadok-float-soudeuse 8s ease-in-out infinite;
}
.shadok-admin-reseau {
position: absolute;
right: 2%;
top: 35%;
width: clamp(70px, 10vw, 130px);
opacity: 0.22;
pointer-events: none;
color: hsl(var(--color-accent));
animation: shadok-float-admin-reseau 10s ease-in-out infinite;
}
.shadok-imprimeuse3d {
position: absolute;
left: 2%;
bottom: 8%;
width: clamp(70px, 10vw, 135px);
opacity: 0.2;
pointer-events: none;
color: hsl(var(--color-accent));
animation: shadok-float-imprimeuse3d 12s ease-in-out infinite;
}
.shadok-cryptographe {
position: absolute;
right: 1%;
bottom: 6%;
width: clamp(70px, 10vw, 130px);
opacity: 0.18;
pointer-events: none;
color: hsl(var(--color-primary));
animation: shadok-float-cryptographe 11s ease-in-out infinite;
}
.shadok-droneuse {
position: absolute;
left: 40%;
bottom: 2%;
width: clamp(70px, 10vw, 130px);
opacity: 0.2;
pointer-events: none;
color: hsl(var(--color-primary));
animation: shadok-float-droneuse 7s ease-in-out infinite;
}
.shadok-mecanicienne {
position: absolute;
right: 2%;
top: 60%;
width: clamp(70px, 10vw, 140px);
opacity: 0.22;
pointer-events: none;
color: hsl(var(--color-accent));
animation: shadok-float-mecanicienne 9s ease-in-out infinite;
}
@keyframes shadok-float-codeuse {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-8px); }
}
@keyframes shadok-float-electricien {
0%, 100% { transform: translateY(0) rotate(0deg); }
50% { transform: translateY(-10px) rotate(1deg); }
}
@keyframes shadok-float-soudeuse {
0%, 100% { transform: translateY(0) rotate(0deg); }
50% { transform: translateY(-7px) rotate(-0.8deg); }
}
@keyframes shadok-float-admin-reseau {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-9px); }
}
@keyframes shadok-float-imprimeuse3d {
0%, 100% { transform: translateY(0) rotate(0deg); }
50% { transform: translateY(-11px) rotate(0.5deg); }
}
@keyframes shadok-float-cryptographe {
0%, 100% { transform: translateY(0) rotate(0deg); }
50% { transform: translateY(-8px) rotate(-0.6deg); }
}
@keyframes shadok-float-droneuse {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-12px); }
}
@keyframes shadok-float-mecanicienne {
0%, 100% { transform: translateY(0) rotate(0deg); }
50% { transform: translateY(-7px) rotate(1.2deg); }
}
@media (max-width: 768px) {
.shadok-codeuse { display: none; }
.shadok-electricien { display: none; }
.shadok-soudeuse { display: none; }
.shadok-admin-reseau { display: none; }
.shadok-imprimeuse3d { display: none; }
.shadok-cryptographe { display: none; }
.shadok-droneuse { display: none; }
.shadok-mecanicienne { display: none; }
}
</style>

View File

@@ -77,7 +77,7 @@ const palettes: Record<PaletteName, PaletteColors> = {
export const usePaletteStore = defineStore('palette', () => {
const currentPalette = ref<PaletteName>(
(import.meta.client && localStorage.getItem('palette') as PaletteName) || 'automne',
(import.meta.client && localStorage.getItem('palette') as PaletteName) || 'ete',
)
const colors = computed(() => palettes[currentPalette.value])

View File

@@ -9,6 +9,7 @@ export default defineContentConfig({
title: z.string(),
description: z.string().optional(),
order: z.number(),
page: z.number().optional(),
readingTime: z.string().optional(),
}),
}),

View File

@@ -1,37 +1,49 @@
---
title: "Introduction"
description: "Un livre et des chansons pour réaliser une économie du don, portée par les monnaie-libristes."
description: "La proposition d'un modèle économique, permis par la monnaie libre — une expérimentation concrète, économique et civile."
order: 1
page: 9
readingTime: "15 min"
---
## Ce livre est une façon
## Introduction
Ce livre est un essai. Une proposition. Une intention. Une invitation. Une façon.
[ *masc.* ]
Il ne s'agit pas d'une théorie universelle, mais d'une **expérimentation concrète, économique, civile et artistique**. Pas un guide, pas un kit : plutôt un *git* — un dépôt de réflexions ouvertes, une cartographie et quelques boussoles dans une jungle à défricher.
Ce livre est un essai : la proposition d'un modèle économique, permis par la monnaie libre. Je désigne par là une seule et unique devise, dénommée June, qui applique la Théorie Relative de la Monnaie TRM.
## À qui s'adresse ce livre ?
Ce modèle n'est pas une théorie universelle. Je vous fais la proposition d'une expérimentation concrète, économique et civile. Une expérimentation qui peut démarrer sans condition, même à l'échelle d'un petit nombre. Mais c'est en passant à l'échelle d'un bassin de vie qu'elle change de portée et devient… économique.
Ce livre s'adresse aux quelque **8 500 monnaie-libristes** (au moment de l'écriture) qui utilisent la June (Ğ1), monnaie libre fondée sur la Théorie Relative de la Monnaie. L'ambition : proposer un **modèle économique** concret pour passer de l'expérimentation monétaire à une véritable économie du don.
Ce livre n'est donc pas un guide, encore moins un kit. Il ne vous dira pas quoi faire lundi. Il propose des repères concrets et conceptuels pour nourrir une réflexion collective. Le passage à l'action vous appartient.
## Pourquoi une économie du don ?
L'expérience est destinée en premier lieu aux « monnaie-libristes » — quelque 8.500 créateurs monétaires de DUĞ1 à l'heure où j'écris ces lignes. Mais je m'adresse également à toute personne intriguée par le don avec un regard anthropologique ou économique ; aux personnes affranchies qui peuvent consacrer du temps et des savoir-faire ; à celles qui cherchent à créer un monde alternatif ; aux jeunes adultes qui ont la possibilité de faire des choix de vie.
Nous avons déjà une économie. Elle couvre nos besoins vitaux, de facto. Alors pourquoi en créer une autre ?
> *Or le pouvoir de la création monétaire est avant tout celui de créer une économie. Une monnaie n'a aucun lieu d'être sans une économie à servir.*
Pour **l'autonomie**. Et c'est tout sauf un repli. Réfuter l'autonomie, c'est nous bannir en tant qu'adultes bien vivants. L'autonomie, ici, c'est la capacité d'un groupe humain à couvrir ses besoins essentiels par ses propres moyens — sans pour autant se couper du reste du monde.
> *Il n'est ici question que d'une économie du don. Rien d'autre.*
## Ce que ce livre n'est pas
---
- Ce n'est pas une baguette magique
- Ce n'est pas un programme politique
- Ça ne dit pas quoi faire lundi
- Ce n'est pas du prêt-à-porter : à nous de tailler
## Monnaie-libristes
## Ce que ce livre est
[ *fém.* ]
Un **os à ronger**. Une cartographie avec quelques boussoles. Un livre *et* des chansons, parce que la musique porte les idées autrement.
Une partie des junistes ont la volonté d'engendrer une économie monnaie-libriste ; une autre y voit un moyen de rectifier notre économie euro ; d'autres encore y voient un jeu. Les portes d'entrée sont multiples : le logiciel libre, la blockchain et la Toile de Confiance, la sémantique du « dividende universel », les monnaies locales.
Chaque chapitre est accompagné d'un morceau qui en prolonge le propos. Le livre se lit, s'écoute, se vit.
Pour témoigner de mon propre cheminement, je suis arrivé sur la june depuis l'étude de l'économie, menant à la création monétaire, en passant par la case Bernard Lietaer. Donc par la porte de la TRM, moins fréquentée.
> *On tourne une page pour voir ?*
Beaucoup d'énergie est mise sur la tentative de « convertir » des commerçantes dans une démarche très prosélyte, vivant la monnaie libre comme une monnaie locale. C'est naturel — on reproduit des schémas familiers. Mais rester là-dessus ne crée pas une économie.
> *Car l'économie avant l'échange c'est la production.*
J'utilise l'image de la première vitesse et du geste de « passer la seconde ». Rester en première, c'est énormément d'énergie pour avancer au pas.
Il manque à beaucoup d'endroits la notion de finalité. Changer de devise n'est pas suffisant. Chaque groupe peut définir sa propre finalité — il en faut une. La monnaie libre n'est pas une baguette magique.
> *Le leitmotiv de l'autonomie collective à échelle humaine est un excellent candidat, en tant que finalité.*
L'autonomie n'est pas un repli sur soi, c'est le premier leitmotiv des parents envers leurs enfants. L'année 2026, où je reprends cet essai, s'annonce comme une année de défis. J'appelle de mes vœux la démarche collective de moins subir les agendas.
L'intention ici est de proposer un modèle, comme un os à ronger — quelques repères économiques, administratifs et symboliques. Progresser sur un terrain vierge nécessite de tâtonner et, dans notre cas, de la créativité.
> *Ce n'est pas encore l'heure du « prêt-à-porter ».*

View File

@@ -1,42 +1,87 @@
---
title: "De quel don parlons-nous ?"
description: "Du don de Marcel Mauss aux asymétries du quotidien : comprendre le triple geste donner-recevoir-rendre."
description: "Le don, en tant que vecteur clé d'une économie, est le geste de transmettre quelque chose en premier lieu, sans réciprocité symétrique, mais assuré d'un contre-don."
order: 2
readingTime: "20 min"
page: 23
readingTime: "18 min"
---
## L'oxymore apparent
Dans le titre, c'est évidemment le terme « don » qui marque le plus, car il a tendance à s'opposer au terme économie — une sorte d'oxymore. Le don flirte parfois avec la spiritualité, ce qui prend le dessus sur une notion que l'on se représente froide et intellectuelle. Mais l'est-elle vraiment ?
« Économie du don » — le mariage fait peur. Un oxymore pour l'esprit. Pour éviter tout quiproquo : il ne s'agit ni de spirituel, ni de karma, ni du « centuple divin ». Ici, le don est un **geste opératoire**, une base, une fondation pour une autre forme de construction.
## Trois mots de philo, et de socio
## Marcel Mauss et le triple geste
*[ fém. ]*
Ce n'est pas l'image d'Épinal du don gratuit. **Marcel Mauss**, dans son *Essai sur le don* (1925), a montré que le don est un cycle vital en trois temps :
Je commence par le don, en évacuant le registre spirituel : je ne parle pas du don religieux, du centuple divin, ni de celui que « l'univers me rendra ». Je parle seulement du geste.
1. **Donner** — l'initiative, le geste premier
2. **Recevoir** — accepter crée un lien, une obligation
3. **Rendre** — boucler le cycle, entretenir la relation
Marcel Mauss est souvent cité dès que l'on parle du don. À l'occasion d'une boucle bretonne, j'ai pu observer que les présentes personnes avaient une interprétation un peu fantasmée des pratiques du don dans les temps anciens — y compris les lectrices de Mauss, alors qu'il présente une tension souvent redoutable : une dimension prépondérante des rapports de domination ; une décorrélation marquée avec les échanges de subsistance.
C'est un pacte, une tension, parfois même un combat. Si tu enfreins le protocole... ça ne pardonne pas.
Notre héritage judéo-chrétien associe culturellement le don à quelque chose qui ne peut ne doit pas être rendu. Mais avant les évangiles, le don était un geste triple indissociable : donner — recevoir — rendre (contre-don).
## La monnaie-dette n'est pas la monnaie
J'ai entendu aussi l'idée qu'il faudrait pratiquer le troc, ou supprimer la monnaie comme finalité ultime. Ce propos repose sur l'idée que la monnaie est en elle-même porteuse de problèmes. Pourtant, c'est seulement la monnaie-dette qui génère ces déséquilibres. L'existence d'une création monétaire décentralisée change la donne. La promesse d'une économie du don, c'est que cette monnaie (produite par un DU selon la TRM) apparaît alors davantage comme un outil qui permet de créer des équilibres.
On entend souvent : « Brûlons la monnaie ! Le troc, la gratuité, le grand projet... » La monnaie serait le vice, la source de tout le mal. **Erreur de cible.** Ce n'est pas la monnaie le problème, c'est la *dette*. La monnaie-dette, celle qui nous tient, celle qui nous guette. Mais si la monnaie est libre ? Elle permet les équilibres.
Pour finir, il y a l'idée que le don ne peut pas être mesuré. Je conclus ce chapitre en positionnant clairement le don dont je parle — non pas une définition universelle, mais une définition dans le cadre d'un modèle éco monnaie-libriste :
## Les asymétries
> Le don, en tant que vecteur clé d'une économie, est le **geste de transmettre quelque chose**
>
> en premier lieu ;
> sans réciprocité symétrique ;
> mais assuré d'un contre-don ;
> de toute forme, de toute provenance.
>
> **« Mon économie me le rendra »**
>
> → J'y trouve ma pleine mesure.
> → J'y réalise mes propres équilibres.
>
> Le don que nous manipulons est donc : **un don qui se mesure**.
>
> Une mesure qui se fait par l'acte d'affecter un DU (*fractions et multiples de DU*), au geste vécu du don ; **selon ses échelles du moment, individuelles et collectives**.
>
> Une mesure décentralisée et relativiste, pour des milliards d'estimations, toutes légitimes.
Rien n'est symétrique. Ce n'est pas magique. Une pomme aujourd'hui n'est pas la même demain. Une cagette, ce n'est pas cinq palettes. Six heures assis à parler bien à l'aise... six heures à genoux sur un toit — est-ce le même geste ?
« Faire du DU la mesure du don », c'est davantage que « faire de la Ğ1 la monnaie du don ». Le DU a une valeur d'invariant qui permet de positionner toutes les valeurs relativement à lui. C'est la raison pour laquelle j'invite à rebaptiser les choses. Parmi ces propositions, la « monnaie » devient la « mesure ».
Les asymétries sont partout. On ne peut pas les supprimer, mais on peut les **mesurer**. Et c'est précisément ce que fait une monnaie bien conçue.
## Asymétries et Communautés
## Communautés et protocoles
*[ masc. ]*
Les collectifs qui prônent le don sans outil de mesure finissent souvent par l'usure, le ressentiment, l'abandon. Légiférer, prescrire un comportement ? C'est tentant, mais bancal.
Les mouvements alternatifs ont tendance à réunir les « déjà convaincus » et génèrent naturellement une forme d'entre-soi. Les collectifs deviennent souvent des communautés « pseudo-isolées ».
Mieux vaut un **protocole** — une façon de traiter les asymétries, d'éviter de juger, de préfigurer. Sans trancher les sorts à l'insu des participants.
Pour cadrer mon raisonnement : sur un collectif récent qui porte le don dans son code génétique, j'y ai recueilli beaucoup de témoignages d'usure, de sentiments d'abus, d'abandons, de ressentiments. Pourquoi ? Parce que dans la totalité de nos échanges, il y a des asymétries.
## Eco si nuestra
Nos échanges sont asymétriques dans la totalité des cas. Donner une journée par mois pendant un an, est-ce le même geste que donner deux semaines complètes ? Entre une journée assis à étiqueter des pots et une journée plié en deux sur une toiture, y a-t-il équilibre ? Rien n'est symétrique. Pour éviter les abus ou les déséquilibres, il suffit de se mettre d'accord sur une mesure qui fasse sens pour tous.
L'expérience « Made in Ğ1 » et le jeu **321DU** montrent qu'il est possible de simuler et de vivre une micro-économie du don. Ces prototypages en communauté pseudo-isolée révèlent les mécanismes, les biais, les opportunités.
Une population ouverte peut difficilement légiférer sur ces asymétries permanentes ; elle peut en revanche utiliser une monnaie. C'est en cela un outil précieux, probablement le seul.
> *Pour se rétablir, retomber sur nos pieds, il existe un outil qui s'appelle... « la monnaie ».*
> *Résoudre le problème des asymétries est le rôle pivot, structurel, que joue la monnaie dans l'échange.*
Si je raisonne à l'échelle de mon bassin de vie, que je donne une valeur aux dons des autres et que les autres donnent une valeur à mes dons, peu importe si quelqu'un me déçoit — je m'y retrouverai dans l'économie de mon bassin de vie. Plus besoin que tout le monde se comporte selon mes critères propres.
En commençant par retrouver dans économie le sens de « *éco - oikos* », son lieu, foyer et terre élue. « Nomos », c'est originellement la façon de distribuer, puis désigne ce que l'être humain institue. Actualisé, on peut lire dans « créer une économie » le fait de définir notre façon de :
> couvrir nos besoins pour vivre
> et
> nourrir nos plaisirs de vivre.
Rien n'empêche de pratiquer un don non mesuré, par exemple pour la beauté de l'élan. Chacun définit son périmètre et ses moments. Mais dès que le don sera mesuré, il deviendra structurant pour l'économie qui émerge.
## Le cas emblématique « eco si nuestra »
*[ fém. ]*
Eco si nuestra est bien connue des junistes. On peut entendre certaines participantes déclarer vivre avec la monnaie-libre pour un tiers de tous leurs besoins. Le premier point est d'associer le DU créé à un devoir et à une durée de travail : le DU que tu crées te rend redevable d'une heure de boulot à la communauté. Elles ont par ailleurs une concertation collective sur les « prix » pour les maintenir au plus bas, et font une double corrélation avec l'heure de travail et les calories.
Une communauté ouverte sur le monde, dont le caractère pseudo-isolé s'est néanmoins révélé lors de rencontres internationales : un gros décalage de pratiques sur les « prix » a généré ce que je baptiserais pudiquement « *les larmes du pot de confiture* ».
Cela invite chaque groupe qui voudra utiliser le DU à une réflexion sur l'interopérabilité de ses règles. Quels sont nos indicateurs relatifs locaux ?
## L'expérience « made in zion »
*[ masc. ]*
Cette expérience de vie communautaire en forêt utilisait les DUs pour structurer le fonctionnement quotidien. Pour vivre : 3 DUs pour les visiteuses, 2 pour les contributrices à l'entretien et 1 seul DU pour les amélioratrices (projets). Une rétribution des tâches par tirage dans une « boîte de gratitudes » évite les débats sur la valeur de chacune. L'effet recherché est de « gamifier » — transformer en jeu.
[ Règles complètes du jeu « 321…DU », @Qoop : https://pad.p2p.legal/s/321DU.LeJeu# ]
Ce genre de règles ne peut être que circonscrit à un lieu partagé, mais cela peut donner des idées pour un grand nombre de contextes.

View File

@@ -1,34 +1,52 @@
---
title: "La mesure du don"
description: "Le DU comme unité de mesure du don : un retournement sémantique qui change tout."
description: "Considérer le DU de Ğ1 comme unité de mesure du don une façon très factuelle de prendre la mesure du don."
order: 3
page: 43
readingTime: "8 min"
---
## Une mesure, pas un prix
## La mesure du don
Le mot « mesure » a une portée symbolique décisive. Mesurer le don, ce n'est pas le dénaturer ni le marchandiser. C'est lui donner un **référentiel commun** pour que chacun puisse s'y retrouver.
*[ fém. ]*
Le **Dividende Universel (DU)** — la quantité de monnaie libre créée chaque jour pour chaque membre — devient l'unité de mesure naturelle. Un DU, c'est la part quotidienne de création monétaire d'un être humain. Ni plus, ni moins.
Ce terme « mesure » a un caractère très fonctionnel, mais également une portée symbolique importante. Il s'agit de ne plus considérer la june (Ğ1) comme une monnaie au sens courant du terme, mais de considérer le DU de Ğ1 comme unité de mesure du don.
## Le retournement sémantique
Une façon très factuelle de *prendre la mesure* du don ;-)
Voici le basculement fondamental que propose l'économie du don en monnaie libre :
La mesure du don, de même que la notion de contre-don, n'est pas une négation du don — c'est une solution très élégante pour gérer les asymétries, éviter les ressentiments qui naissent tôt ou tard, car ces asymétries sont inévitables et permanentes.
- **« Je ne vends plus, je donne »** — mon geste premier est un don, pas une transaction marchande
- **« Je n'achète plus, je reçois »** — recevoir engage, crée un lien
- **« Je ne paye plus, je mesure »** — la monnaie libre mesure la gratitude, pas le prix
La posture « *je ne veux rien en retour* », de facto, *oblige* l'autre. Elle peut même mettre dans l'embarras : celle qui reçoit ne sait pas quel est le protocole convenable. Ici, le don n'oblige pas. Le protocole est clair — il suffit de mesurer la valeur que j'attribue à ce don, mon degré de gratitude, avec le DU comme unité.
Ce retournement n'est pas qu'un jeu de mots. Il transforme la posture intérieure de chaque participant à l'échange.
Pour bien saisir la portée de ce geste devenant structurant : je donne avant tout à mon économie, à mon bassin de vie. Si j'apprécie la personne en face, j'y mets une dimension plus affective. Mais si je ne l'apprécie pas, non seulement je peux quand même donner — mais c'est surtout désirable, car c'est mon économie, tout mon bassin de vie, que je sers.
## Trans·action
## Retournement sémantique
Le mot « transaction » retrouve son sens premier : **trans** (à travers) + **action**. C'est une action qui traverse, qui relie. Pas un échange froid entre deux intérêts, mais un geste qui crée du lien.
*[ masc. ]*
En monnaie libre, chaque transaction est une **mesure de gratitude**. Combien de DU vaut ce que tu m'as donné ? C'est toi qui le dis. C'est moi qui accepte. Et la monnaie, symétrique dans sa création, garantit que personne n'est structurellement avantagé.
À compter du moment où l'on ne considère plus le DU comme une monnaie, et que l'on considère tout geste dans le sens du don, on peut naturellement changer de vocabulaire.
## Mesurer sans juger
*Je ne vends plus, je donne.*
Le protocole de mesure remplace le besoin de légiférer. Pas besoin d'écrire des règles pour chaque situation : la mesure en DU est un **langage commun** qui permet aux asymétries de s'exprimer et de se résorber naturellement.
Ma production, mon temps. Je redonne aussitôt à « l'offre » un sens plein. C'est *mon offre*.
> *Il suffit d'une mesure. Sans morsure. Qui fait sens pour toi et moi, pour tout le monde.*
*Je n'achète plus, je reçois.*
Des biens, des services. Je ne crains plus une dépossession monétaire, je valorise la « joie de recevoir ».
*Je ne paye plus, je mesure.*
Je donne du poids, de la masse, des degrés — à la gratitude que je souhaite exprimer, que je souhaite maintenant imprimer dans un registre partagé distribué. En transférant un DU, *j'investis sur un don*, un geste que je souhaite voir se développer dans mon économie.
*On ne négocie plus un prix, on ajuste une balance.*
*On construit nos échelles de valeurs individuelles.*
Et on les frotte à celles des autres. On construit par le même temps des échelles de valeurs collectives.
*On cherche des équilibres, ensemble.*
---
Bien sûr il y a des contextes où l'on n'a pas le temps de jouer à ce jeu des équilibres — par exemple lorsqu'il y a une notion de débit ou une queue. C'est simple : le don se situe en amont. Une buvette a fait ses estimations internes d'équilibre, elle trouve une valeur de référence pour le verre servi. Le don consiste à offrir toute la buvette elle-même. Rien ne m'empêche de gratifier davantage.
On relativise, autour de notre invariant, cadeau de la TRM, le DU.

View File

@@ -1,36 +1,127 @@
---
title: "Raison d'être d'une monnaie"
description: "Pourquoi la monnaie est la clé de voûte de toute économie — et pourquoi les monnaies-dette sont génétiquement viciées."
description: "Voyons à quel point les modalités de création de chaque unité de monnaie sont déterminantes, comme un code génétique, pour l'économie que cette monnaie servira."
order: 4
readingTime: "25 min"
page: 49
readingTime: "22 min"
---
## La clé de voûte
[ *fém.* ]
La monnaie est l'outil le plus puissant et le plus sous-estimé de toute économie. C'est elle qui, en permanence, résout les asymétries. Même sans qu'on y pense. Chaque monnaie **programme sa propre engeance** — son pouvoir est immense.
Je ne vais pas faire un cours sur la monnaie, ni sur son histoire, mais l'évocation d'une absence de monnaie pose tout de même la question de sa nécessité, de son rôle structurant. Elle pose également la question du besoin d'une monnaie radicalement différente. Différente de l'euro, et plus généralement de ce qu'on appelle les « fiat ». Fiat n'est pas un acronyme, c'est le mot latin qui fait référence à quelque chose qui n'existe que par le fait d'avoir été déclaré comme tel. Fiat lux : que la lumière soit. Fiat euro : que l'euro soit.
## L'équation de Fischer
Autrement dit, avec une question simple :
**E = Q × V** — l'équation de Fischer pose les bases : la masse monétaire en circulation (E) est le produit de la quantité de monnaie (Q) par sa vitesse de circulation (V). Cette relation simple révèle pourquoi le contrôle de la création monétaire est un enjeu de pouvoir fondamental.
- Pourquoi se compliquer la vie avec une autre monnaie ?
## Trois leçons sur les monnaies-dette
## Au-delà de ses 3 fonctions
### 1. La cavalerie
[ *masc.* ]
La monnaie-dette fonctionne comme une cavalerie : pour rembourser la dette d'hier, il faut créer de la dette aujourd'hui. Le système ne tient que par la croissance perpétuelle de l'endettement. Quand la musique s'arrête, tout s'effondre.
Dans une image architecturale dont les fondations seraient les institutions bancaires, la monnaie et plus précisément la création monétaire, devient la clé de voûte de l'édifice. Tout ce que l'on construit au sein de cette structure et tout ce que l'on vit dans l'édifice, notre économie, repose sur ces institutions bancaires et tient sous l'égide de la création monétaire.
### 2. La machine à faillites
La création monétaire est programmatique, comme une ligne de code. Elle détermine certains comportements de toutes les entités économiques chaque jour. Dès que j'achète ou que je vends, dès que j'estime mentalement une valeur, aussitôt je m'inscris dans cette programmatique, dans son référentiel.
Dans un système où la monnaie est créée par le crédit, les intérêts ne sont jamais créés. Il y a donc **structurellement moins de monnaie en circulation que de dettes à rembourser**. Le système fabrique mécaniquement des perdants.
Les fameuses trois fonctions formulées par Aristote :
### 3. La pyramide arbitraire
- unité de compte : c'est la mesure.
- valeur d'échange : c'est la transaction (apporte la symétrie).
- réserve de valeur : c'est l'épargne.
Qui décide de la création monétaire ? Les banques. Et pour qui ? Pour ceux qui offrent des garanties. Le système monétaire conventionnel est une **pyramide d'accès arbitraire** à la monnaie : ceux qui sont proches de la source en bénéficient, les autres subissent la dilution.
La fonction d'unité de compte est plus importante qu'il n'y paraît. C'est parce qu'elle est unité de compte qu'elle peut être une valeur d'échange, et c'est en tant que valeur d'échange qu'il devient indispensable d'en posséder. Si je pense à une cafetière économiquement, je pense un chiffre en euros. Je ne me demande pas si je trouve juste cette équivalence entre un plein d'essence et une cafetière ; … l'est-elle ?
Dans notre économie monnaie-dette, l'usage de réserve de valeur a pris l'ascendant sur les échanges. L'équation de Fischer : E = Q x V. La quantité monétaire multipliée par sa vitesse de circulation mesure l'économie. Si la monnaie est davantage stockée pour faire des réserves, l'économie ralentit. Mais cette formule ne définit pas l'économie elle-même ; elle dévoile en creux à quel point l'économie est d'abord une affaire de production.
Petit message aux monnaie-libristes : il ne suffit pas de faire circuler la monnaie pour se réjouir de son économie. L'économie commence avec une production distribuée.
Je finirai en rappelant que la création monétaire est un pouvoir majeur, ceux qui le détiennent déterminent les échelles de valeur. Donner le même accès à la création monétaire pour tout le monde sans aucune distinction, est bien sûr un cadeau du DU. Mais ce n'est pas la monnaie, toute libre soit-elle, qui crée de l'égalité dans le monde réel.
> Voyons à quel point les modalités de création de chaque unité de monnaie sont déterminantes, comme un code génétique, pour l'économie que cette monnaie servira. Ou asservira, ce qui est devenu le cas de la « nôtre ».
## Bassin économique
[ *fém.* ]
> « à chaque monnaie son économie ».
Si on se mobilise sur le développement d'une monnaie aux modalités de création propres, c'est justement parce qu'elle permet de créer une autre économie. Et si on se mobilise c'est parce que le code monétaire que nous vivons actuellement, celui de notre euro, pose problème. Gros problème. Or rien ne peut y remédier, c'est un code… génétique.
Les monnaies locales ou complémentaires reposant sur une indexation euro, ne permettent pas vraiment de créer une économie différente. Les études économiques sur ces monnaies sont souvent biaisées car endogènes. Ces monnaies créent une sorte de « club », comparable aux « cartes réseaux ». Tous ces collectifs sont précieux car ils s'intéressent à la question monétaire, mais si l'on cherche un impact structurant, il est nécessaire de rompre à la racine avec le code monétaire des fiat.
## Problèmes génétiques des fiat
[ *masc.* ]
Voici trois problèmes majeurs non solvables, de nature strictement monétaire, programmés par le code de création de notre cher euro.
La quasi-totalité (>90%) de la monnaie qui circule est produite par ladite dette. Vous souscrivez un crédit de 100 k€, la banque crée 100.000 unités dont vous pouvez disposer. Les 100.000 unités n'existaient pas, elles sont créées ; puis détruites quand vous les rendez. Nous devons rendre cette quantité de monnaie, mais nous-mêmes ne la créons pas (les banques ont le droit, pas nous) ; nous devons donc capter des unités qui circulent, en produisant une valeur non encore produite.
## Leçon #1 — La monnaie-dette est une cavalerie
Dans ce référentiel monétaire, la croissance n'est pas une option.
[ *fém.* ]
Produire pour rendre la même quantité de monnaie ?
Non. Il faut ajouter lesdits intérêts aux 100 k. 3% sur 10 ans, ça fait près de 30 k, 30 %. Or ces 30.000 unités supplémentaires requises n'ont pas été créées au moment du crédit. Vous devez capter des unités à un autre, qui doit aussi le faire, qui lui aussi doit capter 30 % qui n'ont jamais été créés. Pensez-vous que tout le monde puisse gagner sa partie avec une telle règle du jeu ? Une véritable chaise musicale, où il manquerait non pas une mais le quart des chaises ?
## Leçon #2 — C'est une machine à faillites
Dans ce référentiel monétaire, la compétition, pour ne pas dire une féroce prédation, n'est pas une option.
[ *masc.* ]
Imaginez une population totale de 50 « personnes ». La monnaie créée M est de 5 millions d'unités circulantes. Mais elle doit reverser collectivement 6,5 M€. Où trouve-t-elle le 1,5 million d'unités qui n'existent pas ?
38 « personnes » sur 50 peuvent rembourser leurs 130 k, soit 3,8 millions détruits et 1,14 million captés par la banque. Il reste 60 k€ dans l'économie des 50 personnes, mais 95% dans les caisses de la banque ! 12 personnes en faillite monétaire collective doivent encore verser 1,56 million avec 60 k disponibles. Vous voyez le tableau ?
Comment un petit 3% d'intérêt devient un drainage massif proche de 95% ? Le phénomène est noyé dans la longue durée et dans un volume qui dépasse l'entendement de tout individu. Le seul moyen de créer les 1,5 M€ manquants, c'est de souscrire un crédit de 1,5 M€. La dette passe alors immédiatement à 3 M€. Cela ne résout jamais le problème, ça l'aggrave.
> C'est ce qu'on appelle une cavalerie : lorsque vous souscrivez un second crédit pour rembourser le premier, puis un troisième pour rembourser le second, ... un jeu qui finit rarement bien.
Il y a deux formes de pénurie structurellement monétaire : d'une part cette pénurie permanente roulante du fait de toujours devoir rendre plus de monnaie qu'il n'en a été créée ; d'autre part une pénurie itérative, erratique, lorsque les banques ferment le robinet. C'est la raison pour laquelle la peur de manquer invite tout le monde à mettre de côté, raison pour laquelle la fonction de valeur de réserve prend le pas sur sa fonction de valeur d'échange.
## Leçon #3 — Ce système monétaire est une pyramide arbitraire qui impose ses valeurs
Dans ce référentiel monétaire, subir ladite Loi du marché n'est pas une option.
[ *fém.* ]
Concrètement, vous allez chez le banquier présenter votre projet selon les critères des banques. Ce qu'ils signifient et les critères qui les définissent appartiennent aux banques, soit une poignée de personnes qui en déclarent les règles de fonctionnement.
En résumé, un très petit nombre de personnes décide ce qui vaut, ce qui est valeur, et ce qui ne l'est pas. Les déchets n'ont pas de valeur, de même les activités dites sociales ou solidaires. En dessous des déchets il y a par exemple le « geste infirmier à domicile ». Tout en haut il y a le traitement massif de la numérisation de tous nos faits et gestes.
Si les personnes ayant besoin d'un soin à domicile déterminaient de façon proportionnelle, selon leurs échelles de valeurs, la répartition des milliards de devises créées chaque jour, ... ces devises seraient-elles réparties de la même façon ?
Tous les projets cherchent la conformité, les requérants jouent la scène qui leur est demandée. Fatalement les projets de petite envergure, ceux dont les modèles éco sortent des sentiers battus, passent rarement les mailles du filet.
> La monnaie-libre et le code génétique qu'elle embarque sont radicalement différents de la monnaie telle que nous croyons la connaître ; elle propose un référentiel relativiste qu'il nous appartient de mettre à notre service. Nous avons maintenant une liberté de choisir, à nous de la saisir.
>
> Mais pouvoir ne signifie pas savoir, encore nous faut-il collectivement apprendre à le faire.
## Une économie mal codée
L'analogie informatique est éclairante : notre économie est un programme dont le **code source est vicié**. Les monnaies-dette contiennent des bugs génétiques — cavalerie, faillites programmées, pyramide — qui ne sont pas des accidents mais des propriétés structurelles.
[ *masc.* ]
La monnaie libre propose de **recoder** la base : une monnaie dont la création est symétrique entre tous les membres, dans l'espace et dans le temps.
> On entend de nos jours de belles expressions qui parlent de « dette roulante », ou de « dette qui paie la dette ». Elles sont un voile pudique sur cette cavalerie insoutenable. Le résultat de cette cavalerie, de cette croissance requise effrénée, c'est une extraction de toutes les ressources, planétaires et humaines, qui prend des allures de pillage. Or si vous suivez le raisonnement, ce pillage est inéluctable.
>
> Le besoin monétaire est exponentiel, donc ladite croissance doit l'être, ainsi la consommation d'énergie et l'extraction de toutes les ressources.
> *Chaque monnaie programme sa propre engeance. Ne t'y méprends pas, son pouvoir est immense.*
Donc tous ceux qui affirment que la croissance est obligatoire ont raison. Tant que la source monétaire qui abreuve notre économie sera une monnaie-dette.
C'est pour cela qu'en dépit de toutes les COPs, GIEC, les Conventions citoyennes, les intentions et les mobilisations, les chiffres parlent. Il n'y a pas le moindre début d'inflexion dans l'extraction, dans la consommation d'énergie et de matières, dans aucun domaine structurant de fait.
Heureusement localement, il y a d'innombrables expériences et initiatives magnifiques. Dans une vue satellitaire elles sont marginales, mais dans le paysage elles sont vitales.
Pour conclure, si l'on veut mener une réflexion sur la décroissance, sur la sobriété, la contraction, la robustesse non performante, il faut changer de création monétaire. La bonne nouvelle, c'est que la TRM ou la monnaie-libre, propose une, on ne peut plus compatible avec ces réflexions.
> Il s'agit d'embarquer dans une aventure,
> peut-être encore marginale
> mais stratégiquement valable,
> peut-être laborieuse
> mais éventuellement ludique,
> peut-être utopique
> mais, *sans doute*, significative.
Quelle part de nos besoins, quelle part de notre économie pourrait couvrir une production sans plus vraiment d'extraction, sans plus vraiment de « laissés pour compte » ? Jusqu'où peut-on pousser les curseurs ?

View File

@@ -1,51 +1,177 @@
---
title: "La TRM"
description: "Invitation à découvrir la Théorie Relative de la Monnaie de Stéphane Laborde : symétrie, DU et relativité."
description: "Corréler directement le flux monétaire au flux de la vie humaine — les principes et raisonnements de la Théorie Relative de la Monnaie."
order: 5
readingTime: "30 min"
page: 83
readingTime: "22 min"
---
## Invitation à la TRM
## La TRM Théorie Relative de la monnaie
Ce chapitre est une **invitation à lire la Théorie Relative de la Monnaie** (TRM) de Stéphane Laborde. Pas un résumé — la TRM se mérite, se travaille, se digère. Mais quelques clés pour en saisir la portée.
*[ fém. ]*
## Le flux monétaire
Si l'on veut comprendre la monnaie libre, il est utile de comprendre les principes et raisonnements de la TRM.
En monnaie libre, le flux monétaire est **corrélé à l'existence physique** de chaque être humain. Tant que tu vis, tu co-crées de la monnaie. Le Dividende Universel (DU) est cette part de création, identique pour chaque membre, chaque jour.
[ Page de Stéphane Laborde : https://trm.creationmonetaire.info/ ]
## La symétrie spatio-temporelle
Je ne parle pas des démonstrations mathématiques, heureusement pour le grand nombre. Mais les postulats, leur positionnement, les questionnements d'origine et la nature des réponses qu'ils apportent.
Le principe fondamental de la TRM tient en deux symétries :
Cet essai est directement lié à ma lecture de cette théorie, elle y est présente partout. Je vous en restitue donc une partie pour que vous puissiez suivre ce livre sans avoir lu la TRM.
- **Symétrie spatiale** : aucun individu vivant n'est privilégié par rapport à un autre dans la création monétaire
- **Symétrie temporelle** : les individus présents ne sont pas privilégiés par rapport aux individus futurs (ni l'inverse)
---
Ces deux symétries éliminent les biais génétiques des monnaies-dette : plus de pyramide, plus de privilège d'ancienneté.
## Flux monétaire et vie humaine
## La formule du DU
*[ masc. ]*
Le DU se calcule relativement à la masse monétaire existante et au nombre de membres. La formule garantit que, quels que soient le moment d'entrée et la durée de participation, chaque membre converge vers la **même part relative** de la masse monétaire.
L'un des fondements de la TRM :
## Relativité : penser en M/N
> Corréler directement le flux monétaire au flux de la vie humaine.
En monnaie libre, les montants absolus n'ont pas de sens. Ce qui compte, c'est la **part relative** : combien de DU ? Quel pourcentage de la masse monétaire moyenne par membre (M/N) ?
L'image est belle et ce n'est pas qu'une image.
Cette relativité est contre-intuitive au début, mais libératrice : elle élimine le biais de genèse (les premiers arrivés ne sont pas structurellement plus riches) et le biais d'ancienneté.
La création monétaire repose ici sur l'existence physique d'un individu. La monnaie que l'individu crée apparaît avec sa naissance dans son bassin éco. De la même façon, cette monnaie créée meurt avec lui, ou lorsqu'il décide de quitter la monnaie et son économie.
## GrateWizard
Une propriété mathématique conséquente de la TRM est la « convergence à la moyenne ». En ajoutant une quantité d'unités chaque jour (le DU quotidien), la même pour tout le monde, tous les créateurs monétaires tendent vers le même nombre de DUs. Si vous êtes en-dessous de la moyenne, le nombre de vos DUs augmente ; au-dessus, il diminue. De façon asymptotique, magique mathématique.
L'outil **GrateWizard** permet de simuler et visualiser les flux monétaires en monnaie libre. Il donne à voir la convergence, les échanges, les équilibres — et aide à développer l'intuition de la relativité.
La réflexion immédiate qui vient à l'esprit concerne « L'inflation ». Il n'y a pas une mais deux familles d'inflation bien distinctes : une inflation économique (pénurie d'un produit ou de ce qui permet de le produire) et une inflation monétaire (surabondance de monnaie dans l'économie).
## Volume des offres et diversité
Du point de vue relativiste dans la monnaie libre, le nombre de DUs finit par atteindre un plafond asymptotique dès que N se stabilise. Dans le référentiel relativiste, il n'y a pas d'inflation monétaire possible.
Une monnaie ne vaut que par ce qu'on peut obtenir avec. Le **volume des offres** et leur **diversité** sont les indicateurs clés de la santé d'une économie en monnaie libre. Plus il y a de producteurs, de services, de biens disponibles, plus la monnaie fait sens.
> Or c'est justement ce que fait précisément la monnaie libre. Il y a une création monétaire continue, corrélée aux flux de toutes les vies en cours, répartie également sur chaque personne. Dans le référentiel relativiste, il n'y a pas d'inflation monétaire possible.
## Échelles de valeurs et convergence
Et si vous faites l'impasse sur les démonstrations, retenez simplement qu'en comptant en DUs, vous voyez que l'inflation ne peut plus être une question monétaire ; vous voyez que la quantité monétaire créée par un individu naît et meurt avec lui, telle une petite vague qui passe.
Chacun a ses propres échelles de valeurs. La monnaie libre ne les uniformise pas : elle fournit un **référentiel commun** (le DU) qui permet à chacun d'exprimer ses valeurs et de les échanger. Avec le temps, les prix relatifs convergent vers une moyenne, signe que l'économie trouve ses équilibres.
C'est vraiment beau.
## Le commun monétaire
*Pointe une petite larme ? Loin d'être petite, l'arme.*
La monnaie libre est un **commun** : elle n'appartient à personne et à tout le monde. Comme l'air ou l'eau, elle est un bien partagé dont chacun est co-producteur. Ce statut de commun est garanti par la symétrie de sa création.
---
> *Le DU est la part de chacun. Ni plus, ni moins.*
## Symétrie dans l'espace-temps
*[ fém. ]*
Personne en aucun lieu ni aucune époque ne peut créer de la monnaie au détriment de l'accès monétaire d'une autre personne, de tout lieu et de toute époque. Une belle interprétation d'un leitmotiv de John Locke, celui de la non nuisance.
Dans la recherche originelle de Stéphane Laborde, en bon ingénieur, il y avait en premier lieu la recherche d'une mesure invariante. Il pose l'économie en tant que champ en expansion, or la mesure de ce champ ne cesse de fluctuer. Est-il possible de trouver un étalon, une unité de mesure qui ait la même valeur pour toutes, une mesure universelle ?
Cela ne peut pas être le cas de l'or. Dans le champ de l'économie, les référentiels ce sont les gens, nous toutes. Il s'agit donc de trouver la même unité de mesure pour moi ou pour une Papoue de Nouvelle-Guinée, pour une Inuit du 41e siècle. Cet étalon universel ne peut être aucune des valeurs produites ou extraites par l'économie elle-même. Il ne peut donc s'agir que d'un nombre.
> La seule valeur tangible, universelle, et seule valeur qu'on ne puisse pas totalement enlever d'une « zone économique » sans que cette économie s'éteigne immédiatement, c'est … la vie humaine.
Pour que le flux monétaire suive celui de notre vie, son taux de croissance doit être corrélé à la croissance de l'individu. D'où une formule logarithmique sur l'espérance de vie (ev=80), qui donne un taux de croissance proche de 10% annuel.
Bingo, c'est la naissance du Dividende Universel, le DU.
> - Créer soi-même l'unité de mesure de l'économie et toutes nos valeurs, pour manipuler et partager un invariant.
> - Créer soi-même sa part de monnaie, qui suit le flux de sa propre vie, égale à toute autre sur un principe de non-nuisance.
>
> C'est la même chose, la même formule, la formule du DU !
Plutôt génial non ?
[ Formule du DUğ1 : DU*t+1* = DU*t* + c² × (M/N)*t* / 182.625 ]
[ 5, 48 % / *an* ≤ c ≤ 9, 22 % / *an*, selon que l'on prenne ev ou ev/2 pour symétrie ]
*Cela tombe bien : « ex perfecto nihil fit ».*
*[ de la perfection rien ne naît formule alchimiste ]*
---
## Relativité
*[ masc. ]*
Que peut m'inspirer une unité de mesure universelle relativiste pour estimer les valeurs ? Je peux mesurer toutes les valeurs de façon relative — relativement à la masse monétaire créée dans mon bassin éco, « M » ; et au nombre de personnes « N » qui créent cette monnaie. Estimer les valeurs par rapport à la moyenne « M/N » est un premier repère très intéressant.
Une observation terrain dévoile rapidement un biais de genèse : le gros décalage monétaire entre les personnes qui créent leur DU depuis plus de 8 ans et ceux qui viennent de commencer. C'est paradoxal, car la monnaie-libre fait la promesse d'une « égalité ». Mais c'est une égalité d'accès à la création monétaire, pas une égalité de richesse.
De plus, la richesse n'est pas monétaire ; on s'en rend compte dès que l'on quitte le référentiel de la monnaie-dette. Dans celui de la TRM, ce n'est plus la modalité de création monétaire elle-même qui crée les écarts. C'est dans les économies que nous créerons, par exemple une économie du don, que les écarts peuvent être réduits, sans commune mesure.
---
## Ancienneté
*[ fém. ]*
Pour corriger ce biais de genèse dans les échanges eux-mêmes, on peut appliquer un *coefficient relatif à l'ancienneté*. Une simple formule qui baisse le « prix » pour les nouvelles créatrices et l'augmente pour les plus anciennes, avec un effet qui s'amenuise dans le temps.
J'ai commencé à produire un petit utilitaire pour manipuler facilement ce coefficient. Il suffit de renseigner une valeur de référence pour soi-même et un pourcentage de réduction pour une débutante. L'utilitaire donne alors la valeur relative pour toutes les anciennetés.
[ *GrateWizard* : https://pricewizard.vercel.app ]
Ce coefficient ne fait aucun jugement de valeur sur le comportement économique de l'une ou l'autre partie. C'est un correctif mathématique d'un biais de genèse mathématique, qui s'opère uniquement dans les échanges avec le consentement des parties.
---
## Volume des offres
*[ masc. ]*
Une autre observation terrain : les offres sont réduites en variété et en volume. Pour un problème de volumes limités, on peut estimer les valeurs relativement aux quantités produites. Pratiquer une valeur de référence qui augmente avec le volume reçu — inversion de flux. Prendre trop de volume peut constituer une nuisance à l'autre menacé de pénurie ; on ne l'empêche pas, mais on le régule de façon organique. Cette inversion de flux est très symbolique.
Beaucoup déplorent les déséquilibres entre les personnes qui fournissent beaucoup et d'autres qui « se contentent de consommer ». Nous mettons immédiatement les pieds dans les jugements de valeur moraux. Mais si l'on est réaliste, on sait que c'est inévitable, donc voyons comment réduire ce recours.
---
## Diversité des offres
*[ fém. ]*
Sur les personnes *« qui ne font rien pour recevoir des DUs »*, j'évite de juger et je cherche des vecteurs d'attraction qui déclencheraient le mouvement.
Il peut s'agir initialement d'un produit ou d'un service dont tout le monde fait l'usage. Si mon économie fournit de la bière parce qu'elle aura trouvé le moyen de la produire, alors tout le monde pourra se dire « je peux attraper de la bière artisanale » avec des DUs. Disposer de telles valeurs dans un bassin de vie changerait la donne. Mais à nouveau cela commence par une production.
En attendant, on peut imaginer un coefficient relatif au solde. L'idée : soustraire le nombre de DUs créés de mon solde. S'il reste positif, c'est que j'ai reçu plus de DUs qu'il n'en valorise mes offres reçues ; j'ajuste donc le degré de gratitude en l'augmentant pour les soldes négatifs et en le diminuant pour les soldes positifs.
Coefficients relatifs à la moyenne monétaire, à l'ancienneté, progressifs au volume, relatifs au solde, à la pénibilité, … à tout ce qui vous semble juste. C'est ainsi qu'une économie peut remodeler nos échelles de valeur collectives.
Les vices de notre économie montrent à quel point il est illusoire de vouloir rectifier notre économie euro. Ces leçons donnent donc de bonnes raisons de vouloir en créer une autre. La question n'est pas tant pourquoi se mobiliser, elle est davantage :
> « qui va le faire ? »
---
## Échelles de valeurs
*[ masc. ]*
La question n'est pas tant de considérer un principe puis de dire qu'on y tient beaucoup. Il s'agit davantage, dans un contexte vécu, de positionner ce qui passe devant.
Dès que l'on compte en euros, on navigue dans des chiffres et des ordres de grandeur qui ne nous appartiennent pas. Si l'on trouve qu'un produit est trop cher, c'est évidemment dans une lecture des prix du marché, et non de la valeur que le produit devrait avoir par rapport aux autres, au travail qu'il représente, ni même souvent au besoin que j'en ai.
L'exercice de construire nos propres échelles de valeurs peut paraître difficile, mais il peut devenir amusant. Naturellement nous sommes tous paumés dès que l'on ne compte plus en euros, parce que l'on perd notre référentiel collectif. Mais c'est justement cela qui nous permet de les reconstruire, cette fois-ci de façon totalement décentralisée, sur chaque bassin de vie.
C'est en cela que le DU devient une sorte de condition car c'est notre invariant. Il aura toujours la même valeur relative pour tout le monde. Il nous permet d'avoir un référentiel commun tout en naviguant dans des milliards d'échelles de valeurs différentes.
> Chercher à constituer une seule communauté est une illusion, c'est même selon moi une grosse erreur stratégique. Au contraire, il me semble plus fertile de multiplier les groupes locaux par affinité et proximité, de créer donc « recruter » des équipes ; puis entretenir des relations avec ses groupes voisins et d'autres plus lointains, cultiver des protocoles.
Dès que j'affecte un DU, je signifie une valeur aussi simple soit-elle, cela devient un geste d'investissement, je construis une économie.
---
## Convergence à la moyenne
*[ fém. ]*
Imaginez 2 personnes, l'une avec 1.000, l'autre avec 9.000. L'écart est énorme. En ajoutant 1.000 aux deux, ça donne 2.000 et 10.000. L'autre n'a plus que 5 fois plus. Vous recommencez ; l'écart tombe à facteur 3 (4k contre 12k) ; 4 coups plus tard, l'autre n'a plus que 2 fois plus (8k contre 16k). Ainsi de suite, elles finissent par se rejoindre à la moyenne.
Pas besoin de déshabiller Jacques pour habiller Paul, pas besoin de prélever les uns pour établir des équilibres avec les autres. Plutôt bouleversant par rapport à nos schémas quantitatifs de redistribution monétaire, non ?
Lorsque le nombre de créateurs monétaires sera stable, tout le monde tendra vers une moyenne qui oscille entre 3.742 et 3.925 DUs. Au jour où j'écris ces lignes, la moyenne est à moins de 1.300 DUs.
En contrepartie, j'ai la garantie de ne jamais totalement manquer dans la durée. Dans les passes difficiles, je sais pouvoir compter sur une ressource mutualisée par mon économie, car mon DU me permet de valoriser quelques dons reçus. Un petit espace vital sécurisé, pour peu que cette économie soit suffisamment développée.
---
## Commun monétaire
*[ masc. ]*
> Considérer notre création monétaire comme un commun donne une bonne résonance au terme Dividende Universel, car il suggère le fait d'être tous co-propriétaires à part égale relative du capital monétaire de notre bassin de vie.
Faire des petites projections, mettre un peu de chiffres sur son bassin de vie, est très éclairant. Dans le mien par exemple, un premier jalon repère à 1.024 créateurs monétaires pour une population de 61.444 personnes donnerait un capital commun monétaire de près de 4 millions de DUs. Que pourrait-il couvrir, quelle économie peut-il alimenter ?
Aujourd'hui nous sommes approximativement 150, avec une moyenne au doigt mouillé de 800 DUs dans la vallée, soit 120.000 DUs. Par quoi on commence ?

View File

@@ -1,42 +0,0 @@
---
title: "Créer une économie ?"
description: "Passer de la théorie à la pratique : produire, greffer une économie du don sur le tissu local, inverser les flux."
order: 6
readingTime: "25 min"
---
## Définition
Couvrir les besoins, pour vivre, nourrir les plaisirs de vivre. Avant tout : comment **produire**, comment **distribuer**, sans nuire. On en a déjà une économie. Alors pourquoi en créer une autre ? Pas « créer » au sens de partir de zéro — plutôt **greffer** une économie du don sur l'économie existante.
## Produire avant tout
Le message aux pionniers est clair : faire tourner la monnaie en rond, ce n'est pas créer. Se faire des virements autour d'une table, c'est juste du vent. L'équation de Fischer est formelle : si tu multiplies zéro production par mille transactions, ça fait toujours zéro. L'économie, c'est d'abord **produire et transformer**.
## Passer la seconde
L'analogie du régime moteur : la monnaie libre en est au « point mort ». Pour avancer, il faut **passer la seconde** — produire de vrais biens et services. Cinq vitesses de développement se dessinent, de la simple circulation monétaire à l'économie productive complète.
## L'économie de greffe
Le principe fondamental : **deux économies cohabitent**, l'économie classique (euros) et l'économie du don (monnaie libre). Il ne faut surtout pas les mélanger ni les confondre. On ne « quitte » pas l'euro — on développe une autonomie parallèle, progressive. La greffe prend quand l'économie locale du don couvre suffisamment de besoins réels.
## Connaître son bassin de vie
Avant de se lancer, il faut cartographier son territoire. Deux indicateurs clés :
- Les **mobiz** : les « monnaie-libristes biz » — ceux qui produisent et échangent activement
- Les **sherpiz** : les « sherpas biz » — ceux qui guident, forment, accompagnent
Le ratio mobiz/sherpiz sur un territoire donne une idée réaliste du potentiel de développement.
## La gestion à l'anglaise
Un concept emprunté à la mécanique : la **roue libre**. En monnaie libre, la « roue libre » est l'indicateur de rentabilité. Quand une activité tourne sans effort, en roue libre, c'est qu'elle est rentable en DU. Ce n'est pas la marge financière qui compte, c'est la **soutenabilité** du geste productif.
## L'économie de flux inversés
Le retournement fondamental : **je donne avant de recevoir**. En économie classique, le flux est conditionnel — je te donne si tu me payes. En économie du don, je donne d'abord, et la mesure (en DU) vient après, comme une gratitude.
Ce n'est pas de la naïveté : c'est un changement de posture qui transforme la relation économique. Le don est en amont. L'équipe offre le choix, le lieu, le son. Les références sont posées. Tu prends ou pas, tu gratifies selon ton estimation.
> *L'économie, c'est de l'énergie, de la chaleur c'est sûr. Je grave ma gratitude dans la chaîne.*

140
content/book/06-produire.md Normal file
View File

@@ -0,0 +1,140 @@
---
title: "Produire"
description: "Couvrir les besoins, pour vivre, nourrir les plaisirs de vivre. Avant tout comment produire, comment distribuer, sans nuire."
order: 6
page: 121
readingTime: "28 min"
---
## Produire
[ *fém.* ]
Qu'avons-nous déjà vu ? Que l'économie commençait avec une production distribuée. C'est le geste technique jalon pour ainsi dire, mais ce n'est pas sa finalité.
Je complète ici ma proposition, qui n'est pas une définition descriptive mais une formule simple pour exprimer la raison d'être d'une économie :
> *Couvrir les besoins, pour vivre,*
> *nourrir les plaisirs de vivre.*
>
> *Avant tout comment produire,*
> *comment distribuer, sans nuire.*
Maintenant que nous avons détaillé le retournement de ce modèle éco monnaie-libriste, de cette « économie du don », voyons comment amorcer.
Nous allons commencer avec une économie de proximité. Le critère est simple : ce qui nous semble accessible. La proximité est naturellement géographique, mais il peut y en avoir d'autres formes.
## Produire
[ *masc.* ]
Pour couvrir les besoins et les plaisirs de vivre, devoir préalablement produire peut paraître comme une lapalissade, mais à l'usage je me rends compte que c'est un détail négligé par les « monnaie-libristes », les « selistes » et autres expérimentateurs alter-économistes.
Produire, avant de … *donner* versus *partager* versus *répartir* versus *distribuer* versus *échanger*.
C'est aussi dans la façon de produire que l'on pourra changer ou créer un monde. La façon d'échanger peut elle-même être intégrée davantage dans la façon de produire, comme une extension organique plus naturelle.
Quelle économie peut-on créer, en poussant les curseurs de notre affranchissement aux différentes emprises structurelles ? Notamment celle de la mondialisation extractiviste, de la pétrochimie, de la centralisation numérique ?
À nouveau il s'agit de pousser les curseurs, pas de remplacer du jour au lendemain. Plutôt que « comment vivre sans pétrole ? », la question devient « combien de jours tient-on sans pétrole ? Peut-on ajouter un zéro à ce nombre ? »
## « Passer la seconde »
[ *fém.* ]
L'image évoque une vitesse, mais elle désigne plus précisément un « régime moteur ». Tant que l'on « reste en première », nous mobilisons une énergie trop importante pour un mouvement limité.
**1. En première : productions individuelles.**
- Ğmarchés, productions maison.
- Prestations individuelles et services personnels.
- Phénomène du vide-grenier.
- Position de mécène pour lesdits « producteurs ».
**2. Passer la seconde : productions collectives.**
- Productions récurrentes en volume ; et distributions.
- Filières semi-artisanales, boucles bouclantes.
- Réseaux décentralisés (épicerie, restaurant, réparation,..).
- Mutualisation locale des équipements et outils.
- Centrales villageoises (de quartier urbains et ruraux ?).
**3. Passer la troisième : déploiements logistiques.**
- Relation éco inter-bassins et ruraux-urbains.
- Transport et distribution décentralisée « à l'échelle ».
- Centrales villageoises autoconso (« grid locale »).
**4. 5. Quatrième Cinquième : industrialisation versatile et gestion de communs ? *le logement* ?**
Évoquer une 4ème voire une 5ème vitesse me permet de glisser ce terme d'industrialisation versatile, et de poser le logement comme un graal pour notre économie monnaie-libriste.
Le propos est d'inviter à une réflexion plus stratégique et à ce changement de régime. Mars 2026, l'écosystème sera capable de recevoir plus de volume et d'usage, c'est l'heure d'embrayer.
## Économie de greffe
[ *masc.* ]
Le propos n'est pas un remplacement de notre économie, une totale substitution. Ce serait ignorer sévèrement … l'économie. Le propos plus réaliste est de créer une balance, pousser les curseurs. Progressivement.
Une première réflexion porte sur la capacité d'une économie de produire ses moyens de production. Une économie qui émerge n'a pas cette capacité et repose donc sur des outils issus de l'économie euro.
Nous pouvons appeler ce phénomène « transfert d'énergie fiat ». Il est très utile lorsque ce transfert a lieu pour produire. Lorsque c'est pour une consommation directe, c'est davantage une illusion.
Une proposition de cohabitation, pour tous nos comportements économiques, est de ne pas mélanger les deux économies. Ce serait comme mélanger deux syntaxes de programmation pour coder une même fonction. En revanche, on peut y voir l'image du mélange de l'huile et de l'eau : deux espaces qui ne demandent qu'à se séparer, mais tout en restant l'un dans l'autre.
Savoir pratiquer l'une ou l'autre, savoir passer de l'une à l'autre sans trop de difficulté, est très très puissant.
S'il s'agit d'une propriété privée, il est nécessaire de trouver la motivation intrinsèque du propriétaire, mais une amitié peut suffire. S'il s'agit d'une propriété publique, il faut être très rigoureux sur le vocabulaire et bien s'attacher au caractère ludique, à « gamifier » : une « chorégraphie du don » sonne mieux qu'une économie du don. Le secret est d'être sincère, mais c'est ce que font les cœurs généreux.
Comment financer en euros les besoins euros de nos productions ? Mauvaise nouvelle, ce sera beaucoup plus difficile à faire qu'à dire. Ma priorité est de chercher des idées dans l'économie euro pour financer les besoins euros de l'économie du don. Le plus simple est donc de produire ou rendre des services avec un modèle éco classique d'une part, et mener une expérimentation de modèle éco monnaie-libriste d'autre part.
Une forme de don que l'on peut cibler s'apparente davantage au mécénat, et s'adresse aux personnes affranchies, des âmes entreprenantes qui estiment avoir suffisamment sécurisé leurs revenus. Elles peuvent investir dans des moyens de production locaux, pour une double économie.
## Connaître son bassin de vie
[ *fém.* ]
Je pars de mon vécu, un bassin de vie de 60.000 personnes. Je retiens un seuil raisonnable, qui manifesterait le franchissement d'un palier sensible, de 15 %, soit 2.250 personnes impliquées dans les productions collectives, les « *mobiz* ».
Pour amorcer, je cherche un nombre économiquement structurant d'esprits entreprenants. Dans tous les mouvements collectifs, s'il n'y a pas une poignée de personnes qui animent au départ, le collectif finit par se déliter. Ce sont les « sherpiz ». Je retiens un seuil de 3,5 %, soit près de 80 personnes pour notre vallée.
L'échelle la plus gérable à l'amorce se rapproche davantage d'un bassin de 1.000 personnes, soit 75 mobiz et 3 sherpiz.
Cela pose la question des ratios actuels de notre économie euro. Quelle est la part produite localement de ce qui est consommé ? Quels sont les moyens de productions locaux ? Quels sont les ordres de grandeur ? D'où partons-nous ?
Il germe l'idée que nous pourrions produire nos propres données, construire nos propres indicateurs, comme une sorte d'observatoire qui s'autoalimenterait de nos protocoles et outils. En attendant, connaître son bassin de vie et partir du terrain me semble nécessaire, pour ne pas « théoriser dans l'éther ».
## Gestion « à l'anglaise »
[ *masc.* ]
Je suis tenté d'ériger en « règle d'or » le fait de ne prendre aucun risque pour son outil de travail. D'abord sortir la tête de l'eau, pouvoir respirer, est nécessaire pour envisager autre chose.
La « gestion à l'anglaise » est une gestion manipulable des « entrées - sorties », très proche d'une gestion de trésorerie. Dans ma métaphore du passage de vitesse, le point mort peut désigner avant tout le fait d'être en « roue libre ».
Ce calcul combine le seuil de rentabilité saisonnier et les planchers de trésorerie. D'abord un jour de l'année, celui où l'activité commence son bénéfice net — c'est-à-dire le moment où tous les coûts, les provisions et les rémunérations de l'année entière, sont couvertes. En fonction de l'activité et de sa saisonnalité, elle peut devenir un jour dans le mois ou une heure précise d'un jour de la semaine.
Le propos de ce calcul de la roue libre, c'est que lorsque l'heure a sonné, alors je suis totalement libre… de donner.
L'économie euro me commande de m'agrandir, de me développer. Elle ne suggère jamais que je puisse alléger mon labeur. Mais rien n'oblige. À l'heure de la roue libre, j'ai le choix.
Comment justifier alors mes consommations ? Je peux les déclarer en terme de don, avec transparence : lister les matières données en annexe, et en contre partie, la mesure de ces dons en DUs reçus. Le propos n'est pas de tromper ni de dissimuler, mais d'être dans la sincérité d'un geste. Nous mettons les pieds dans une terra incognita, donc le législateur va devoir défricher et cheminer.
Nous pouvons lui présenter la dimension artistique de ce geste collectif : nous sommes réellement en train de transformer une formule mathématique en une chorégraphie sociale de dons. J'ai d'ailleurs failli proposer en titre de ce livre *« une chorégraphie du don »*.
Voici un scénario illustratif. Un food truck relativement prospère a calculé une roue libre hebdomadaire à 18h le jeudi. Il décide de donner son vendredi. Il met à dispo ses ingrédients du jour et son four, crée un lieu, apprend à faire des criques avec les sortants des « bahut » et de la « ZAC ». Aucun euro dans tout ça. Dans ce petit récit, en dehors d'une distribution de pizza, il a créé un lieu, une connexion pour la relation entre les étudiants et les employés de la zac. Le vendredi, le pizzaiolo regarde et jouit du *spectacle des criques* dans son food-truck.
## Économie de flux inversés
[ *fém.* ]
Je donne avant de recevoir. En premier lieu. Ce n'est pas vraiment une règle morale, c'est une règle dynamique.
Cela commence donc par un don de son temps, de son talent, de son humeur, de soi. Notamment les premières générations pionnières qui doivent bâtir cette économie, pour que les générations suivantes puissent embarquer dans un manège qui tourne.
Raisonner en terme de flux permet aussi de les identifier, puis d'y « veiller », les maintenir comme des canaux d'irrigation. Toute personne qui rentre dans l'économie monnaie-libriste voit quels sont les flux à « servir » et peut choisir ses contributions.
Il y aura probablement quelques projets qui nécessiteront une avance de DUs, pour des gratitudes anticipées. Prenons l'exemple du paysan meunier, ressource précieuse pour tout groupe local. Une équipe se met à disposition pour une chorégraphie dans les travaux des champs, mais le meunier ne peut encore valoriser ce don car il débute dans la monnaie-libre. Une caisse d'investissement locale peut lui fournir les DUs nécessaires à cette valorisation initiale. Il pourra ensuite recevoir ses propres DUs par le don de farine.
C'est à partir du moment où ce type de transaction croise d'autres transactions que l'économie tisse sa toile. Car si l'on projette la filière pain toute seule, on peut imaginer s'en passer — mais ce serait à nouveau la conception d'un circuit fermé.

View File

@@ -1,37 +0,0 @@
---
title: "Échanger"
description: "Organiser les échanges : filières et boucles, distribution décentralisée, connexion avec l'existant et marchés Ğ1."
order: 7
readingTime: "18 min"
---
## Le PIB comme baromètre
L'échange est le **symptôme de la production**. Le PIB, malgré ses limites, est un baromètre : il mesure le volume des échanges. En monnaie libre, l'équivalent serait le volume de transactions réelles — pas les virements entre amis, mais les échanges qui témoignent d'une production effective.
## Filières et boucles
L'économie du don s'organise en **filières** — de la matière première au produit fini — et en **boucles** — des circuits qui relient producteurs, transformateurs et consommateurs.
Trois exemples concrets :
- **Le pain** : du blé au boulanger, une filière courte qui peut fonctionner entièrement en monnaie libre
- **Le bois** : de la forêt au menuisier, une filière qui demande de la coordination
- **Le photovoltaïque** : une filière plus complexe, qui montre les limites et les possibilités
L'enjeu : les boucles doivent **boucler** — chaque maillon doit trouver ce dont il a besoin dans le circuit — **et se croiser** — les filières doivent s'interconnecter pour créer de la résilience.
## Distribuer
La distribution doit être **décentralisée**. Pas de plateforme unique, pas de point de passage obligé. Chacun est un nœud du réseau, libre de ses connexions. Les outils numériques (Ğ1, applications mobiles) facilitent les échanges mais ne doivent pas devenir des points de contrôle.
## Connecter avec l'existant
L'économie du don ne vit pas en vase clos. Elle doit se **connecter avec l'existant** : les SEL (Systèmes d'Échange Local), l'ESS (Économie Sociale et Solidaire), les associations solidaires, les réseaux de producteurs. Ces ponts élargissent le bassin d'offres et renforcent la légitimité.
## Ğ(marchés)
Les **Ğ(marchés)** — marchés en monnaie libre — sont le lieu de rencontre physique entre producteurs et consommateurs. Différents formats existent, de la simple table de troc au marché organisé avec producteurs professionnels.
L'expérience montre qu'un marché en monnaie libre réussit quand il propose des **produits de qualité** portés par de **vrais producteurs**, pas uniquement des objets de seconde main. La professionnalisation de l'offre est un signe de maturité de l'économie locale.
> *Des cercles qui se croisent, sans les regards qui toisent.*

View File

@@ -0,0 +1,83 @@
---
title: "Échanger"
description: "L'échange est davantage un baromètre, qu'une variable de l'économie. Filières, boucles, distribution et Ğ(marchés)."
order: 7
page: 147
readingTime: "20 min"
---
## Échanger
[*masc.* ]
Je retournais tout à l'heure le PIB en tant que variable économique plutôt que baromètre ; lorsque je dis que l'économie est avant tout une affaire de production, je cherche à exprimer que l'échange est davantage un baromètre, qu'une variable de l'économie.
La façon dont nous échangeons est davantage un symptôme de notre façon de produire et distribuer, qu'un vecteur lui-même de notre fonctionnement collectif.
Le film du billet qui circule et libèrerait l'économie en acquittant les dettes de chaque personne est relativement trompeur. En posant la probabilité à 10% qu'un échange soit symétrique entre 2 personnes d'une même commune, pour que cela fasse une boucle de 5 personnes, la probabilité est schématiquement de 1 sur 10 puissance 5. À 1% de symétrie possible, il faudrait 10 milliards de situations vécues par ces 5 personnes. Alors que la démonstration du film donne l'impression de révéler une loi dynamique de l'économie.
Il n'en reste pas moins que la façon d'échanger est un sujet digne d'une grande attention. C'est à cet endroit que l'on pourra construire nos échelles de valeurs collectives, trouver des équilibres, discuter des moyens inédits de produire et distribuer sans nuire — puisque l'endroit de l'échange est celui de la rencontre.
## Filières et boucles
[ *fém.* ]
Je reprends l'exemple de la filière verticale du pain. Une fois pleinement goûté l'enthousiasme pour les 2 ou 3 personnes qui fournissent du pain régulièrement, voici quelques questions utiles :
- Combien de temps vont-elles tenir ? Peuvent-elles fournir tout le groupe local ?
- À notre échelle, quels volumes et rythmes pourrait-on tenir dans la durée ? Avec combien de personnes ?
- Quelle relation peut-on créer entre les extrémités de la filière ?
- Quelles sont les limites ? Quels curseurs pouvons-nous pousser progressivement ?
J'ai déjà évoqué la valeur stratégique clé des filières qui produisent des biens déjà utilisés comme des « monnaies sociales », la bouteille de vin ou de bière. Or tous les produits de fermentation sont à la portée de tous. N'est-ce pas l'occasion de nous réapproprier le sens de la responsabilité à cet endroit, de ré-inventer nos propres protocoles de prudence et là encore, inverser les flux ?
La question des boucles d'échange circulaires est très intéressante. On peut relativiser son rôle dans l'amorce d'une économie, mais ces boucles sont le symptôme d'une économie qui tisse réellement son réseau. On se rend compte rapidement de deux éléments clé :
- Les boucles doivent boucler.
- Les boucles doivent se croiser.
Une boucle qui ne boucle pas est une impasse qui ne peut générer à terme que du ressentiment et de l'abandon. Le problème des asymétries, quand on le transpose à une boucle complète, montre que cela ne peut se faire réalistement que par un croisement des boucles.
C'est difficile de vouloir *programmer* cela, mais on peut le rendre explicite et partager une visualisation collective des boucles. Chacun pourrait identifier les trous et y pourvoir.
## Distribuer
[ *masc.* ]
Si l'on part de la production, on arrive sur cette notion de distribution avant celle d'échanges. Et puisque dans une économie du don j'inverse les flux, la demande précède l'offre : lorsqu'une équipe décide de lancer une production, c'est qu'elle répond à un besoin identifié dans son groupe local.
Chaque production collective pourra expérimenter dans ce registre : quelle part affecter à toutes les personnes qui ont donné de leur temps, quelle part réserver aux Ğmarchés, quelle part pour les échanges avec un autre bassin de vie, un « Ğ1tada » en Espagne ou une « université d'été » à Toulouse ?
C'est également à cet endroit de la distribution que peuvent avoir lieu les réflexions sur la notion d'épicerie décentralisée, comme une extension naturelle de la production et comme un nœud pour le tissage de l'économie.
## Connecter avec l'existant
[ *fém.* ]
Les initiatives pour développer une économie de proximité, circulaire ou solidaire, n'ont pas attendu la monnaie libre. Il y a également toutes les initiatives de collectifs solidaires (SEL, monnaies locales, assos caritatives), qui reposent essentiellement sur un immense bénévolat. Elles s'inscrivent malgré elles dans l'économie euro moribonde, à l'endroit des « laissés pour compte ».
Toutes ont l'occasion de se saisir de la monnaie libre pour changer la donne, et consolider cette énergie coopérative afin de produire plus d'effets. Pour la première fois elles peuvent imaginer des modalités de fonctionnement qui intègrent les mobilisations et les gratitudes, afin de décentraliser les efforts et la logistique sur les personnes qui en bénéficient directement.
Cela inclut les filières de récupération, de recyclage et autres dérivés. Ces lieux et organisations pourraient servir de plateforme logistique pour les besoins de collecte en volume des productions collectives événementielles, s'inscrire dans une mutualisation des outils et des équipements.
Puisque je parle de lieux, je finirai sur le problème majeur de l'espace, pour produire ou stocker. Il y a la voie des mécènes aux grandes propriétés. Il y a la voie du lieu public, avec la Mairie ou la collectivité propriétaire. Il y a la voie de l'établissement public, notamment les écoles, où la négociation est plus intime — un proviseur, un ou deux profs engagés. Il y a la voie du lieu recevant du public, privé mais qui peut « privatiser » des moments sur invitation.
Lorsque nous atteindrons des seuils critiques, il sera envisageable qu'un groupe local de junistes suffisamment monétisé puisse acquérir un lieu et y développe une économie monnaie-libriste. Ce sera un grand pas.
Lorsqu'une économie du don pourra loger une petite proportion de sa population, alors elle aura atteint un niveau et un rythme de croisière qui ne pourra plus reculer.
## Ğ(marchés)
[ *masc.* ]
Tous les groupes locaux pratiquent les Ğmarchés. Je constate simplement qu'il en manque un, à ma connaissance : nulle part je n'ai vu un marché qui offre la liberté de choisir sa monnaie, à 100 %. C'est intrigant car c'est pourtant le format qui s'inspire le plus de la TRM.
La quête chronique de tous nos marchés est de trouver des producteurs, des pros, afin de fournir les produits que l'on trouve sur les marchés euros. Seulement voilà, ils ne trouvent pas de retour symétrique à leur engagement.
Mon idée : si je veux adresser les exposants d'une grosse foire de producteurs locaux (dont j'ai un fichier de 300 adresses), je peux potentiellement en séduire une trentaine pour participer à une expérience laboratoire. Je les fais rentrer dans un jeu avec des règles précises et on en fait le bilan à la clôture. Pour les convaincre, je dois leur garantir la couverture de tous leurs coûts marginaux : « vous ne gagnerez peut-être pas beaucoup d'euros, mais vous êtes sûrs de ne pas en perdre ». Comment ? En mutualisant les recettes euros et en pratiquant un cercle de répartition adapté en clôture de marché.
L'idée est un guichet à l'entrée pour opérer les transactions — un peu à la manière des octrois des XII° et XIII° siècles, avant Philippe le Bel. Des « lutins » ou des « pages » formés pour accompagner les hôtes sur les stands, rendre possible le jeu des coefficients relatifs, et ainsi intégrer la jeunesse dans cette aventure qui lui est d'ailleurs surtout destinée.
Ce sera donc à ma connaissance une première expérience de Ğ(marché) [ prononcer G libre marché ], bien garni de productrices, où le chaland pourra choisir sa monnaie, choisir son économie ; soit un prix en euro, soit une gratitude en DUs.
Une comm publique s'impose, très subtile, invitant à jeu privé, sur invitation « codée » — qui prend la forme d'un Ğ(marché), qui n'est ni un marché, ni un 'non marché' … du moyen âge prospère. À cette époque cohabitaient 2 économies, l'une régie par la devise du territoire, l'autre par une mesure universelle des dons. Le pèlerin peut choisir, passer de l'une à l'autre. Tout le monde déguisé. Buvette double et improvisation Aztèque du XII° siècle à l'apéro. Ambiance.

View File

@@ -1,36 +1,109 @@
---
title: "Relation institutionnelle"
description: "Naviguer dans le cadre légal et fiscal : impôts, TVA, cotisations sociales et financement de l'écosystème."
description: "La relation institutionnelle est délicate à plusieurs titres : l'institution est par définition la garante de l'ordre établi."
order: 8
readingTime: "20 min"
page: 163
readingTime: "18 min"
---
## Une relation délicate
[ *fém.* ]
La relation avec les institutions est **délicate**. L'institution est jalouse de son pouvoir, de son monopole monétaire. Il faut distinguer les **personnes** (souvent bienveillantes, curieuses) des **rôles institutionnels** (contraints par les cadres légaux).
La relation institutionnelle est délicate à plusieurs titres :
## Impôts et taxes
- L'institution est par définition la garante de l'ordre établi, le prolongement incarné de l'autorité centrale.
- Elle est jalouse de son pouvoir et trouve les initiatives libertaires suspectes (décentralisation, autonomie, affranchissement, espace juridique à défricher, etc.), donc rapidement coupables.
- Il y a dans notre aventure un esprit DIY, système D, « on peut se débrouiller, pas besoin de tutelle » qui émancipe de la dépendance à l'Institution, qui vit cela comme une agression contre sa légitimité.
Premier point, fondamental : **pas d'échappatoire fiscale**. La monnaie libre ne dispense pas des impôts. Toute activité économique, quel que soit le moyen de paiement, reste soumise aux obligations fiscales. On n'a pas besoin de la monnaie libre pour ça — et prétendre le contraire serait nuire au projet.
En contrepartie, pas question de cloisonner, d'exclure, ou de s'opposer. Beaucoup trop d'énergie perdue dans de telles postures dogmatiques.
Il est important de distinguer les institutions des personnes qui évoluent en leur sein. On peut toujours s'adresser aux personnes physiques et non aux rôles qu'elles endossent. L'idée serait que élus et fonctionnaires cheminent avant de se demander comment la collectivité pourrait servir cette économie.
## Impôts, taxes et cotisations
[ *masc.* ]
Premier point : le propos n'est pas d'échapper aux taxes, aux lois, aux cotisations. En aucun cas.
Le propos est d'échapper aux lois dynamiques de la création monétaire. Or ces lois ne sont justement pas La loi — elles sont au-dessus des lois du législateur.
La loi PACTE et le règlement MICA ne s'intéressent de facto qu'à la transaction qui échange un actif numérique avec un euro. Dans une économie du don, nous n'avons pas du tout la pratique de cette transaction. Pourquoi ? D'abord parce que le DU est une mesure — quel sens donner au fait de vendre une mesure ? Vendre des DUs en euros, c'est automatiquement retourner dans l'économie euro.
Son positionnement général est le suivant :
- Transparence et sincérité : bonne foi.
- Prudence individuelle et collective.
- Esprit de chercheur, de labo, de jeux de rôles.
- Décentralisation des décisions et des responsabilités.
- Exploration de terrains « vierges » et des marges.
Éviter les plans sur la comète semble une attitude fertile ; on verra bien quand on aura un volume significatif.
## Environnement légal
Le cadre évolue. Le **règlement PACTE**, **MiCA** (Markets in Crypto-Assets) et la directive **DAC8** dessinent un environnement légal pour les crypto-actifs en Europe. La June (Ğ1) doit se positionner dans ce paysage — ni en fuite, ni en confrontation, mais en **conformité intelligente**.
[ *fém.* ]
Loi PACTE - Règlement MICA.
Aujourd'hui seuls les mouvements d'euros sont taxés. Dans notre cas de Juniste, seulement si vous vendez des junes pour toucher des euros : c'est sur la plus-value réalisée que vous serez taxée (flat tax 30%).
Ce qui n'a pas été vraiment étudié, c'est la possibilité juridique de réfuter la définition — non pas parce que la june n'y répondrait pas, mais parce que cette définition s'applique à un usage de l'unité qui n'est pas du tout le nôtre. Comme avoir un usage du couteau qui n'est pas du tout celui d'une arme.
Par exemple : ledit actif, s'il n'a pas fait l'objet d'une acquisition, n'est-il pas davantage un passif, compris comme notre capital propre ? D'ailleurs le terme dividende universel ne désigne-t-il pas la part de la masse monétaire créée comme un commun, dont nous serions tous actionnaires à part égale relative ?
Dans notre cas, nous manipulons une mesure universelle, le DU, pour donner une mesure aux gestes d'une performance artistique collective, une chorégraphie du don. Nous ne sommes pas du tout concernés par la définition de l'actif numérique, tant que nous ne l'utilisons pas en tant que tel. C'est une posture totalement sincère. Nous n'avons rien à cacher et consignons nos mesures de façon transparente.
Lorsque le législateur aura trouvé les solutions pour calculer des taxes dans sa monnaie sur l'économie du don, nous trouverons les solutions pour aborder cette réserve dans nos productions collectives.
## TVA
La question de la TVA est **ambiguë**. Pour les transactions entre particuliers sous les seuils de franchise, la TVA ne s'applique pas. Pour les professionnels, les règles classiques s'appliquent, même si le paiement est en monnaie libre. Le flou actuel invite à la prudence et à la transparence.
[ *masc.* ]
La question de la TVA est plus ambiguë car sa définition s'affranchit de la monnaie utilisée. Il doit être fait une corrélation entre les produits donnés et le chiffre d'affaire prévisionnel qui aurait dû être encaissé en euros.
Fort heureusement, il y a une tolérance dans la pratique du don — les producteurs peuvent le faire, y compris les épiceries. Dans ce cas, les seuils et les frontières sont flous. Voire introuvables dans les textes. À nouveau, tout est une question de dosage, d'intentions et de bonne foi.
Simple règle de prudence : si vous craignez d'être pris au dépourvu, mettez de côté une provision pour couvrir ce montant.
## Bénévolat et cotisations sociales
Le bénévolat a un cadre légal précis. Recevoir une contrepartie en monnaie libre pour une activité bénévole peut requalifier cette activité en travail dissimulé. Il faut être vigilant sur la **frontière entre don et rémunération**, et sur les implications en termes de cotisations sociales.
[ *fém.* ]
## Financement de l'écosystème
Ce sujet est peut-être le plus délicat, car il touche à ce que nous avons de plus précieux — notre temps. C'est aussi ce dont l'économie euro et sa législatrice sont le plus jalouses. Elle pose donc cette question en terme de travail dissimulé.
L'écosystème technique (développement de Duniter, applications, infrastructure) a besoin de financement. Des dispositifs existent : **Ademe**, subventions innovation, mécénat. **Axiom Team**, qui développe le protocole Duniter, utilise un système de bounties pour financer le développement — un modèle hybride entre euros et monnaie libre.
Si j'ai besoin d'une asso pour mener mes expériences, mes événements, mes productions, il me suffit de déclarer le bénévolat. Je peux déclarer dans ma déclaration de revenu la mesure de mes DUs si la législatrice tient finalement à en faire une monnaie. Mais pour l'instant sa seule monnaie c'est l'euro, et la seule phrase juridique que l'on puisse trouver dans les textes de loi pour définir la monnaie, c'est :
> « la monnaie de la France est l'euro ».
>
> Point. Rien d'autre ? Non.
C'est très drôle, de considérer les plus de 200.000 pages de textes de lois qui légifèrent sur absolument tout, et qui ne laissent qu'une phrase sibylline pour définir ce qui régit à sa racine notre économie.
Pour les esprits les plus joueurs, le caractère éphémère des organisations, des productions, leur côté ponctuel et décentralisé, peut devenir à la fois un leitmotiv ludique et un gage de robustesse. La législatrice ne va pas tout de suite trouver comment gérer ces phénomènes. Laissons-lui le temps d'aviser. Il ne s'agit pas de tricher — c'est simplement à chacun de poser son équilibre.
## Le financement de notre écosystème
[ *masc.* ]
Il faut réaliser que nous flirtons avec le domaine des cryptos, et que dans ce domaine les budgets de développement logiciel sont colossaux — l'échelle de grandeur s'exprime en millions d'euros.
Il est donc a minima remarquable que nous puissions faire vivre la june depuis plus de 8 ans sans aucune capitalisation, uniquement sur le bénévolat, les dons de la population juniste et 35 k€ d'une subvention de l'Ademe.
Les subventions constituent une source de financement potentielle, mais embarquent avec elles tous les questionnements de la relation institutionnelle. L'Ademe est unique dans le sens où elle a exceptionnellement une réelle autonomie sur ses actions. Chercher des subventions est un très gros boulot, un métier — ce travail requiert un tiers temps formé, ce qui est ambigu car un tel rôle appelle un recrutement dont le caractère récursif pose question.
On peut constater que les financements par le don dépassent les montants de subvention, mais ils ne sont alimentés que par 10 % des junistes. Il y a un réel potentiel d'auto-financement. Quoi qu'il en soit, ce financement est totalement dérisoire en regard de la prouesse technique de déployer une blockchain décentralisée — notre june est un ovni.
## Symboles et sémantique
La relation institutionnelle passe aussi par les mots. **Inverser le flux sémantique** : ne pas dire « je paye en Ğ1 » mais « j'estime ma gratitude en DU ». Ce glissement change la perception : on n'est plus dans un système monétaire alternatif suspect, mais dans une pratique de mesure de la gratitude entre citoyens.
[ *fém.* ]
> *Ce n'est plus « Que la dette soit ». C'est « Que l'équilibre soit ».*
Il suffit d'inverser le flux. L'ancienne vendeuse devenant offreuse, sait bien que la personne qui reçoit est encore plus démunie pour déclarer un chiffre. L'idée est de s'entre-aider. C'est la personne qui offre qui est la mieux placée pour donner un repère — non pas en prix, mais en offre.
> « Je positionne mon point d'équilibre à 2,75 DUs, et toi ? Ok pour jouer avec les coeff. ? »
Un bien ou service n'est plus trop cher ou bon marché, c'est un nombre de DUs qui devient peu gratifiant ou trop. On ne fait que gagner du degré de gratitude, dans les deux sens.
Je produis pour donner, mon économie me donne en retour. Je n'ai pas besoin de ressentir une symétrie dans mes échanges, je retrouve mes équilibres autour du DU. Cette symétrie est mutualisée à l'échelle de toute mon économie du don.
Le DU n'a pas de valeur, il mesure les valeurs, c'est l'unité universelle que notre existence produit.
Pour conclure ce chapitre sur un clin d'œil : le jour où nous évaluerons la valeur d'un euro en DU, par exemple pour suivre les fluctuations réelles de l'euro, alors la monnaie libre aura remporté sa plus grande victoire symbolique.

View File

@@ -1,36 +1,75 @@
---
title: "Autres greffes"
description: "Greffer l'économie du don dans divers secteurs : emploi, ESS, agriculture, artisanat, éducation."
description: "Dans l'esprit du décloisonnement, de la factorisation ou de la recherche de synergie, toute réflexion de greffe peut s'avérer fertile et créative."
order: 9
readingTime: "15 min"
page: 181
readingTime: "12 min"
---
## Décloisonnement et synergie
[ *masc.* ]
L'économie du don ne peut pas rester cantonnée à un cercle d'initiés. Elle doit **se greffer** sur les structures existantes, créer des ponts, des synergies. Chaque secteur est une opportunité de greffe — avec ses spécificités et ses précautions.
Dans l'esprit du décloisonnement, de la factorisation ou de la recherche de synergie, toute réflexion de greffe peut s'avérer fertile et créative.
## Pôle Emploi et missions locales
Il faut toujours en contrepartie faire très attention de répondre à un besoin, de ne pas tomber dans le travers fatal de proposer des solutions à des absences de problème. Il me semble beaucoup plus doux et opportun de suggérer et laisser venir, que de chercher à convaincre et faire acte de prosélytisme.
Les demandeurs d'emploi et les jeunes accompagnés par les missions locales sont des publics naturels pour l'économie du don. La monnaie libre offre une **dignité économique** à ceux que le système conventionnel marginalise. Mais attention : il ne s'agit pas de remplacer les allocations par de la Ğ1, mais de **compléter** les parcours d'insertion avec une activité productive en monnaie libre.
## Pôle Emploi et mission locale
## ESS — Économie Sociale et Solidaire
[ *fém.* ]
L'ESS partage des valeurs avec la monnaie libre : solidarité, utilité sociale, gouvernance démocratique. Mais vigilance sur l'**institutionnalisation** : l'ESS est parfois plus une étiquette qu'une pratique, et les financements publics peuvent créer des dépendances qui contredisent l'autonomie recherchée.
Typiquement ces lieux sont des foyers de disponibilités, avec une disposition d'esprit propice à un engouement pour une aventure monnaie-libriste. Parce que nous cherchons à créer une économie qui échappe à la violence programmée de l'économie euro.
## Associations populaires et caritatives
Mais attention : lorsque ces personnes fréquentent encore les institutions liées à l'absence de revenu euro, c'est que leur besoin immédiat est de « rentrer des euros ». Il faut être très vigilante sur les faux espoirs que l'on peut faire germer. En aucun cas la monnaie libre ne résoudra les problèmes de notre économie euro — elle n'a le pouvoir que d'en animer une autre.
Les secours populaires, diaconats et associations caritatives sont en première ligne du besoin. La monnaie libre peut leur offrir un outil complémentaire pour **valoriser les contributions bénévoles** et **fluidifier les échanges** au sein de leurs réseaux.
## ESS
## Agriculture et maraîchage
[ *masc.* ]
Les producteurs agricoles et maraîchers sont des **cibles prioritaires** pour l'économie du don. Ils produisent des biens essentiels, travaillent localement, et sont souvent mal rémunérés par le système conventionnel. Une filière alimentaire en monnaie libre est le socle de toute économie du don qui se respecte.
Toute la constellation que l'on appelle « Économie Sociale et Solidaire » est une source potentiellement riche du fait de probables convergences sur des intentions et des mobilisations.
## Artisanat, commerce, entreprise
La vigilance à entretenir est la même que précédemment. Attention de ne pas réinventer le fil à couper le beurre. Une autre vigilance porterait sur l'institutionnalisation de ces organisations, leur fragilité et leurs dépendances financières — elles sont rarement libres de facto.
La **roue libre** s'applique ici : quand un artisan ou un commerçant peut fonctionner en monnaie libre sans effort excessif, c'est que la greffe prend. Les boucles organiques — artisan → client → fournisseur → artisan — doivent se construire naturellement, pas par contrainte.
Une petite dédicace aux fablab, qui peuvent devenir des lieux très structurants : une capacité à concevoir des outils locaux, un développement logiciel, et un tropisme génétique pour l'éducation populaire. Dans la perspective d'une autonomie numérique à l'échelle d'un bassin de vie, les fablabs peuvent devenir très précieux.
## Lycées et écoles
Les fablabs sont écartelés entre leur vocation d'éducation populaire et la pression des financeurs qui requiert un « modèle économique ». L'idée de lancer une FabUnit, jambe dédiée au modèle économique, pour libérer la deuxième jambe pour une éducation et une recherche populaire sans retenue.
La tranche **15-25 ans** est une destination stratégique. Les jeunes adoptent plus facilement les nouveaux outils, questionnent plus naturellement les systèmes établis. Dans 10 ans, la monnaie libre fêtera sa majorité — et la génération qui aura grandi avec sera celle qui la portera.
> « du déchet à l'ustensile et au mobilier ».
> *Chaque greffe est une invitation. Chaque secteur est un terrain d'expérimentation.*
## Assos populaires et caritatives
[ *fém.* ]
Je pose ce titre pour ne pas oublier ces associations plus historiques de solidarité — le secours populaire, les restos du cœur, les diaconats protestants, les scouts... Dans notre culture cloisonnée, on oublie qu'elles constituent un foyer considérable de générosité, d'élan altruiste et d'énergie consacrée.
Le DU peut potentiellement étendre ou fluidifier une boucle locale, une entraide, sans confusion réglementaire ou administrative.
## Productions agricoles, maraîchages
[ *masc.* ]
Les producteurs agricoles et les maraîchers sont les cibles prioritaires de la plupart des groupes locaux. Vous êtes naturellement très précieux, la notion d'autonomie est très concrète pour vous — et paradoxalement cela peut rendre plus difficile l'adoption de la monnaie libre. Pourquoi s'encombrer d'une monnaie alors que vous vous en êtes bien passé jusqu'ici ?
La seule justification serait de vouloir généraliser cette autonomie à l'échelle du bassin de vie, l'ouvrir à de nouvelles populations. Expérimenter la monnaie libre vous relie directement à son réseau, non seulement technique mais avant tout humain.
## Artisanat Commerce Entreprise
[ *fém.* ]
Un mécénat militant est bienvenu et généreux. Il peut constituer le premier pas pour développer la relation avec un groupe local. Mais il ne durera probablement pas. C'est l'occasion de chercher des motivations qui ne soient pas celle de l'intérêt financier.
Côté juniste, pour les âmes prosélytes qui veulent convaincre les professionnels, une posture structurante serait de systématiquement proposer ses propres services ou produits. Une boulangère qui réfléchit à offrir un petit pourcentage de sa production voit quatre puis six junistes parmi ses clients lui proposer régulièrement leurs services : couturière, réparateur de vélos, graphiste, préparateur de pommades... Elle trouve petit à petit du sens à offrir et disposer de DUs pour gratifier les services de ses clients.
Le circuit se dessine, sans plan préalable. Mais ici dans cette petite comptine, il commence par les offres.
## Lycées - Écoles
[ *masc.* ]
Haa la jeunesse.
Elle est la destination définitivement la plus précieuse et stratégique. La génération des 15-25 ans constitue de facto le renouvellement des pensées et des initiatives. Nous ne multiplierons jamais assez les greffons avec les 15-25 ans.
Il faut bien réaliser que c'est pour cette génération que nous œuvrons. Nous embarquons une aventure dont le pas de temps est de 10 ans. Il faut s'attendre à beaucoup d'échecs, de tâtonnements. Une économie qui émerge ne peut être qu'incomplète, bancale, fragile. En contrepartie, 10 ans ça passe vite. Nous en sommes aujourd'hui à 8, l'âge de l'enfance qui découvre le monde ? Dans 10 ans, la monnaie libre fêtera sa majorité.
Dès que nous parviendrons à intéresser les jeunes et qu'ils intègrent, voire portent le mouvement, cela change automatiquement la donne. Ce sera alors très excitant d'accompagner ce phénomène. Chaque participation des 15-25 ans donnera immédiatement beaucoup plus de sens et d'énergie à cette aventure.
Quel intérêt peuvent-ils y trouver ? Découvrir qu'une création monétaire a plus d'effet sur le quotidien qu'un raisonnement politique, jouer au Ğeconomicus ? Comprendre les cryptos et découvrir un ovni ? Mettre les pieds dans un espace vierge où tout est à inventer, un monde à créer ? Construire la filière bière ? Lancer une activité ? Organiser un bel événement avec un final concert ?

View File

@@ -1,37 +1,46 @@
---
title: "Et maintenant ?... action ?"
description: "L'appel à l'action : événementiel, coopérations ponctuelles et questions pratiques pour passer à l'acte."
title: "Et maintenant ? action ?"
description: "Une chorégraphie du don ne se télécharge pas en fichier .ods. Elle s'improvise à plusieurs, elle se répète, elle se rate parfois."
order: 10
readingTime: "8 min"
page: 193
readingTime: "5 min"
---
## Événementiel
[ *fém.* ]
L'événementiel est un **outil de régénération**. Marchés, rencontres, ateliers, festivals — chaque événement en monnaie libre est une occasion de montrer que ça marche, de recruter de nouveaux producteurs, de créer du lien. L'événement n'est pas un but en soi : c'est un **catalyseur**.
Je crois que l'événement est un bon outil pour régénérer de l'énergie, je mise sur cette vertu de l'événementiel, une date, un but et une fin, une cadence, un rassemblement, un marqueur.
## Coopérations ponctuelles plutôt que fédération
Je suis souvent chagriné par les entre-soi manifestes, non seulement des monnaie-libristes, mais aussi de nombreuses organisations altermondialistes ou solidaires. Comment rendre tout ça plus perméable, poreux, interconnecté ?
Le mouvement de la monnaie libre résiste à la structuration verticale — et c'est une force. Plutôt qu'une fédération avec statuts, président et congrès annuel, l'approche privilégie les **coopérations ponctuelles** : des collaborations sur des projets concrets, avec des acteurs qui se choisissent librement.
Paradoxalement l'idée de la fédération ne semble pas si fructueuse. Fédérer requiert un prosélytisme et convoque la notion de représentation, de la voix unique ou officielle. Le propos me semble davantage tenir dans des coopérations ponctuelles, la factorisation des efforts, la visibilité collective, la complémentarité.
Cette horizontalité est cohérente avec les principes de la monnaie libre : pas de structure pyramidale, pas de point de contrôle central.
Plus modeste, plus réaliste.
## Raison d'être
> Sa raison d'être ? Passer la seconde.
> De quoi ? D'une autonomie collective.
> Où ça ? À l'échelle du bassin de vie.
> Pour qui ? Toute personne qui veut une émancipation populaire, agir, ne pas subir.
La raison d'être de tout cet effort tient en deux mots : **passer la seconde**. Sortir du stade expérimental pour construire une véritable **autonomie collective** — un bassin de vie qui couvre une part significative de ses besoins en monnaie libre.
Un événement structurant sur deux jours, reposant sur deux jambes : vie numérique et économie ; logiciel libre et monnaie libre.
## Questions pratiques
Le chapitre économie pourrait contenir un Ğ(marché), un Ğeconomicus, et produire des feuilles de route pour les productions à suivre. Le chapitre logiciel libre pourrait contenir une install party linux, la présentation d'un bouquet de services et le déploiement effectif des premiers nœuds du réseau (stockage ipfs, réseau monétique, ia localisée, messagerie chiffrée, nextcloud, forum, wiki, cms, visio, …).
Trois questions à se poser avant de se lancer :
Bienvenue au Librodrome, un rendez-vous chorégraphique et ludique, une performance artistique d'émancipation civile économique.
### Quelles ressources ?
Quels producteurs, quels savoir-faire, quelles infrastructures sont disponibles sur le territoire ? La cartographie du bassin de vie est le point de départ.
Je me suis dispensé de toute fiche prête à l'emploi. Ce n'est pas démissionnaire, c'est fait pour déclencher les initiatives sans les préfigurer. Voici quelques questions déterminantes pour une réflexion orientée vers l'action.
### Quel chemin ?
Pas de plan quinquennal. Le chemin se dessine en marchant, par essai et erreur, par greffe successive. L'important est de commencer petit et d'apprendre vite.
## Quelles sont nos ressources ?
### Quel rythme ?
Le rythme cardiaque du mouvement — ni trop rapide (burnout), ni trop lent (essoufflement). Les protocoles de décision doivent rester légers pour maintenir l'élan.
Les premières sont humaines. C'est nous. Quelle disponibilité je peux dégager ? Quelles sont mes aptitudes, les rôles que je peux jouer ? Un premier topo des ressources matérielles et logistiques permet de voir d'où on part, et de repérer ce qui manquerait.
## À vous de danser
## Quel chemin ?
> *Ce livre a seulement tenté de décrire quelques pas de danse possibles. À vous d'inventer les vôtres.*
Au-delà des productions individuelles, quel besoin ou plaisir voulons-nous couvrir ? Par quoi on commence ? À quelle échelle — à rayon de vélo, à l'échelle du groupe local, de la vallée ?
Étaler les envies les plus mobilisantes puis faire le tour des contraintes et des facilités permet d'y voir plus clair. Quand une idée commence à agréger un bon nombre de personnes, projeter l'ambiance de la production choisie, sans se brider, met de bonne humeur. Lorsqu'une équipe prend forme autour d'une production, dépoussiérer très tôt le traitement des conflits et des départs paraît sage.
## Quel est notre rythme cardiaque ?
Quels protocoles pour prendre rapidement les décisions ? Quels espaces pour raisonner ensemble, quels outils ? Ces aspects peuvent être traités sans précipitation. Attention aux « commissions » hâtives dans les petits nombres, car elles satellisent et nuisent à la fluidité. Plutôt que de préfigurer comment doivent être prises les décisions, un « observatoire des décisions » qui consigne comment elles sont prises dans les faits, constituerait une base de réflexion initiale très riche. Pour donner sa chance à de nouvelles façons de faire, il faut leur donner la chance de se manifester.
> « Une chorégraphie du don ne se télécharge pas en fichier .ods. Elle s'improvise à plusieurs, elle se répète, elle se rate parfois, elle se réécrit selon les personnes et les lieux. Ce livre a seulement tenté de décrire quelques pas de danse possibles, à vous d'inventer les vôtres. »

View File

@@ -1,48 +1,177 @@
---
title: "Chapitres annexes"
description: "Approfondissements : cryptomonnaies, blockchain, logiciel libre, Duniter et questions techniques."
title: "Chapitres annexes, sujets connexes"
description: "Le monde numérique est telle une galaxie, avec différentes constellations. La june évolue au croisement de deux grandes constellations qui se superposent en partie, les cryptos et le logiciel libre."
order: 11
readingTime: "25 min"
page: 199
readingTime: "28 min"
---
## Les cryptos
[ *masc.* ]
L'enjeu numérique est tout aussi important que celui de l'économie. Avec l'avènement des ia et le potentiel déferlement des robots domestiques que 2026 semble annoncer, la question de la vie numérique va s'imposer comme cruciale. En outre, notre aventure monnaie-libriste est issue de cette dimension nécessairement logicielle. La june évolue au croisement de deux grandes constellations qui se superposent en partie, les cryptos et le logiciel libre.
---
## *Les cryptos*
[ *fém.* ]
Je commence avec un chapitre sur les cryptos, puisque techniquement, la june en est une. Nous allons voir en contrepartie à quel point elle est un ovni et n'est comparable en quasiment rien avec les autres.
### La June est une crypto
Oui, la June (Ğ1) est une **cryptomonnaie** au sens technique : elle repose sur un réseau décentralisé, des algorithmes cryptographiques et une blockchain. Mais elle est fondamentalement différente des cryptos spéculatives par sa **co-création symétrique** (le DU) et l'absence de minage compétitif.
[ *masc.* ]
### DeX vs CeX
Si l'on considère la définition d'une crypto selon qu'elle repose sur l'usage de clés cryptographiques, un protocole de consensus et un registre distribué ; et qu'elle produit et manipule une unité numérique échangeable ; alors oui, la june répond à toutes ces définitions. La june est une crypto complète, qui dispose et déploie son propre réseau monétique, les nœuds que nous appelons « forges ».
Deux modèles d'échange coexistent dans l'univers crypto :
- **CeX** (Centralized Exchange) : plateformes centralisées type Binance, Coinbase — rapides mais points de contrôle
- **DeX** (Decentralized Exchange) : échanges décentralisés, pair-à-pair — plus lents mais souverains
### Introduction sur un DeX vs. CeX
La June fonctionne nativement en **DeX** : chaque nœud du réseau est un point d'échange, sans intermédiaire centralisé.
[ *fém.* ]
### La question du bankrun
Une petite partie de junistes n'attend que l'introduction de la june sur un marché (DeX). Une autre partie redoute ce moment, car l'absence totale de relation structurée entre la june et toute autre devise suffit en elle-même à la distinguer.
Un scénario souvent discuté : que se passerait-il si les monnaie-libristes convertissaient massivement leurs Ğ1 en euros ? L'analyse montre qu'avec une stratégie progressive (type **martingale**), une communauté de quelques milliers de membres pourrait théoriquement exercer une pression significative sur le marché des changes — mais ce n'est ni le but ni l'intérêt. L'objectif est l'**autonomie**, pas la conversion.
En ce qui me concerne, ma réserve porte sur les lois cinétiques. D'un côté, je proposais la mise en œuvre d'une économie : longue durée, rythme lent, progressif, itératif. De l'autre côté, les marchés cryptos sont un jeu de prédation monétaire, aux volumes considérables et aux vitesses vertigineuses.
### Réseau monétaire
> *« oui bien sûr bravo c'est super ce que vous faites, tu me tiendras au jus, et bonne chance hein. Moi j'ai une nouvelle idée pour faire 100€ par semaine avec un bot sur le ĞeX, je vais essayer ça ce we. »*
La June possède son **propre réseau monétaire**, contrairement aux tokens qui vivent sur des blockchains tierces. C'est un réseau complet : protocole, consensus, certification d'identité, création monétaire — tout est intégré. Cette souveraineté technique est un atout majeur, mais aussi une responsabilité.
Si deux flux dynamiques rentrent en contact direct, le jeu addictif de la spéculation monétaire l'emporte et finit par siphonner tout effort de création économique.
## Le logiciel libre
Ma position serait de créer une seconde monnaie TRM, dédiée à cette introduction sur un DeX — une sorte de « sas » symbolique et technique. Si cette devise sur les marchés monte en flèche sur un quiproquo ou s'écroule à zéro, peu importe : la june peut continuer son petit bonhomme de chemin, tranquillement, sans se soucier de l'autre. Du point de vue des amateurs des cryptos, régler les variables de ce fork — *la F1, fork one* — de façon plus appropriée aux marchés serait passionnant : espérance de vie courte, taux de croissance plus élevé, actualisation du DU quotidienne, …
### Linux, le modèle
### Question du « bankrun »
Linux est la démonstration vivante que le **logiciel libre** peut produire des systèmes de qualité supérieure. Le noyau Linux fait tourner la majorité des serveurs mondiaux, Android, les supercalculateurs. La preuve que la collaboration ouverte surpasse la concurrence fermée.
[ *masc.* ]
### Duniter
Si l'on craint l'emprise bancaire sur nos comptes, les cryptos sont-elles réellement une réponse ? J'ai commencé mon exploration avec un « investissement » de 16k€ selon une stratégie baptisée « bankrun de l'ignorant ». Je répartis 50-50 à tous les endroits où il y a polarité : 50 % stable coins, 50 % cryptos ; puis les stables moitié dollar, moitié euro. Dans les cryptos, j'ai du bitcoin — mais « wrapé » sur le réseau solana, le réseau qui me semble le plus éloigné dans sa conception du bitcoin.
**Duniter** est le logiciel libre qui fait tourner la June. Écrit initialement en TypeScript puis migré, il implémente la TRM dans un protocole blockchain. Sous licence **AGPL** (la plus libre des licences libres), il garantit que le code reste ouvert et modifiable par tous.
Attention, il ne s'agit en rien d'une recommandation. C'est un dispositif de recherche, une expérience de laboratoire, dont je livrerai les résultats.
### Axiom Team
Je suis également passé par la case Nastasia Hadjadji et son enquête « no crypto ». Elle dresse un tableau sans concession et dénonce les fondements structurels et les dérives comportementales. La crypto est devenue une légende, un récit écrit pour des esprits libertaires. La promesse de décentralisation n'est plus tenue : 80 % du minage actuel des bitcoins sont dans les mains de 5 entreprises. Toute la structure financière peut être qualifiée de « ponzinomics » — un circuit fermé qui consomme de l'énergie, des technos de pointe et des dollars, sans rien produire en sortie. Pour finir, les arguments sur la folie que représente cette industrie produisant des nombres, vis-à-vis de l'extraction de matière et de consommation d'énergie, sont valables. Il faut reconnaître que nous sommes en plein scénario « Shadok ».
**Axiom Team** est l'équipe de développeurs qui maintient et fait évoluer Duniter. Financée par des bounties et des dons (en euros et en Ğ1), elle incarne le modèle hybride du développement en monnaie libre. La migration vers une nouvelle architecture blockchain en **Rust** (framework Substrate) est en cours — un chantier majeur pour la pérennité du réseau.
### Réseau monétique
### CC-BY-NC
[ *fém.* ]
Le livre lui-même est publié sous licence **Creative Commons BY-NC** : libre de diffusion et d'adaptation, à condition de citer l'auteur et de ne pas en faire un usage commercial. Cohérent avec l'esprit du don.
D'autres cryptos, comme solana ou polkadot, se distinguent grandement du bitcoin car ils ont davantage été conçus pour la décentralisation.
> *Codeurs de rêve, vous décentralisez, vous open sourcez — un monde moins obscur.*
[ entracte publicitaire : notons au passage que la june est de loin la championne dans ce registre, car elle va jusqu'à donner un handicap aux machines les plus performantes ; et elle prend le raspberry pi comme référence pour servir un nœud. ]
Notre blockchain ne repose sur aucun calcul de rétribution dans la devise. C'est le seul écosystème de la famille des cryptos, dont les « forgerons » ne sont rétribués que par une caisse de dons.
Je suis maintenant surtout intéressé par les possibilités d'exploiter un réseau monétique dont on puisse maîtriser les nœuds à petite échelle. Cette expérience m'a permis de réaliser à quel point il est précieux pour la june d'avoir fait le choix de déployer son propre réseau, indépendant de tout autre modèle de création de devises — exempte d'interférence extérieure.
Quant au bankrun, la volatilité des cours est telle qu'un « placement épargne » dont on ne veut pas se préoccuper tous les jours dépend totalement d'acquisitions aux moments des planchers les plus bas. Il faut avoir de la patience et une extrême prudence sur ses attentes.
Comme je ne pouvais décemment pas terminer un livre baptisé « économie du don » sur le chapitre des cryptos, je finirai sur un chapitre définitivement plus noble et fertile, j'ai bien sûr nommé … le libre.
---
## *Le logiciel libre*
[ *masc.* ]
Notre aventure de la monnaie libre s'inscrit dans la lignée directe du logiciel libre. Duniter est naturellement sous licence AGPL. Il est important de mesurer ce qui se joue à l'endroit de linux et du logiciel libre : c'est finalement le domaine où l'émancipation a le plus trouvé les modalités et les outils pour exister et se développer, au point que le monde non libre en dépende partiellement. Un affranchissement radical des « gafam », dont il est très fructueux de s'inspirer pour d'autres domaines.
L'époque où il fallait être geek pour utiliser linux est révolue. Les interfaces n'ont plus rien à envier aux deux autres systèmes, les stores et les applis sont au même niveau. Je trouve LibreOffice de meilleure qualité que la suite du leader mondial. C'est seulement la migration de toutes ses données et de ses usages qui reste un peu laborieuse.
Pour tout développeur, ce petit écosystème est une aubaine et un laboratoire. La communauté est très ouverte, transparente sur le git et le forum duniter. Cette blockchain hybride qui héberge une création monétaire et une toile de confiance, prête pour une migration symbolique le 8 mars 2026 (dans le framework rust Substrate), est unique en son genre et vaut le détour.
Les financements euros de cette constellation reposent essentiellement sur les dons des usagers et quelques subventions. L'association Axiom Team s'est mise au service de ce financement pour les devs, en opérant une rupture avec le cheminement de type fédération : elle met en œuvre des tableaux de bord pour permettre un fléchage des donateurs et suivre les affectations de chaque don.
J'aimerais également rendre hommage à toutes les personnes totalement réfractaires au numérique. Je n'ai pas une bonne nouvelle pour elles, le phénomène va s'amplifier. Je ne peux qu'appeler de mes vœux que les groupes locaux prennent ce problème à bras le corps et rendent possible une vie numérique sans aucun contact avec l'écran — au moins pouvoir vivre la monnaie libre ;-).
De même que pour le numérique, on peut comprendre toutes les résistances à un tel retournement des pratiques. Les craintes d'une dépendance au groupe, d'un contrôle social, d'un attachement idéologique à la propriété individuelle, sont toutes légitimes. Mais à nouveau, le propos est de pouvoir choisir son économie, pouvoir passer de l'une à l'autre, que chacun puisse mettre son curseur selon l'humeur. Le propos est de rendre possible.
Pensez à transmettre le livre si vous l'avez dans les mains. Les livres sont faits pour circuler, comme la monnaie ;-)
Merci à toutes et tous pour la lecture, notamment les huit âmes charitables qui ont bien voulu contribuer à la relecture.
Je prévois de continuer la réflexion, de façon plus collective, sur le domaine librodrome.org. Vous êtes les bienvenus pour y contribuer.
---
## Acronymes
**CC-BY-NC** : Licence Creative Commons Attribution, Pas d'Utilisation Commerciale.
**AGPL** : Affero General Public License, licence libre (utilisée par Duniter).
**TRM** : Théorie Relative de la Monnaie, par Stéphane Laborde.
**DU** : Dividende Universel (unité de création monétaire d'une monnaie libre, selon la TRM ; unité relative pour estimer les valeurs).
**Ğ1** : Unité de compte quantitative de la première monnaie libre, la « June ».
**DUĞ1** : Dividende Universel de Ğ1 (forme précise, avec symbole Ğ1).
**SEL** : Systèmes d'Échanges Locaux.
**JEU** : Jardin d'Échange Universel.
**ESS** : Économie sociale et solidaire.
**DeX** : Decentralized Exchange (plateforme d'échange décentralisée).
**CeX** : Centralized Exchange (plateforme d'échange centralisée).
**GPU** : Graphics Processing Unit (processeur graphique, utilisé pour les calculs de crypto et ia, progressivement remplacé).
**TTC** : Toutes Taxes Comprises.
**DAC8** : Directive européenne sur la coopération administrative (échange d'informations sur les crypto-actifs).
**EHPAD** : Établissement d'Hébergement pour Personnes Âgées Dépendantes.
**ZAC** : Zone d'Aménagement Concerté.
---
## Néologismes, anglicismes ou expressions spécialisées
**Monnaie libre** : désigne la monnaie basée sur la TRM (Ğ1 / June), dont le Dividende Universel, le DU, est la seule et unique modalité de création des unités monétaires.
**Monnaie-libristes / monnaie-libriste** : personnes qui utilisent la monnaie libre (pour l'instant il n'y en a qu'une ;-).
**Économie du don** : ici, modèle économique structuré autour du don (avec mesure en DU). C'est l'objet du livre.
**Économie monnaie-libriste** : économie dont l'infrastructure monétaire est la monnaie libre (Ğ1/June).
**Monnaie-dette** : terme qui désigne les monnaies créées par crédit bancaire. Elles sont obligatoires pour payer l'impôt dans la quasi-totalité des pays du monde.
**Monnaie fiat** : monnaie étatique ou bancaire, sans contrepartie matérielle, imposée par la loi dans le sens où un paiement par la monnaie fiat ne peut pas être refusé par un vendeur. Les fiat sont toutes des monnaies-dettes.
**Économie fiat** : économie structurée autour d'une monnaie fiat.
**Librodrome** : nom de code d'un événement en gestation, accompagné d'une plateforme coopérative de productions collectives.
**« Passer la seconde » (image)** : métaphore récurrente pour désigner le passage d'une production individuelle à une production collective.
**Bassin de vie / bassin économique** : unité territoriale et sociale de référence pour penser l'économie locale.
**Économie de greffe** : métaphore pour une économie qui se greffe sur l'existant sans le remplacer.
**Économie de flux inversés** : lecture vectorielle de l'économie, en termes de fluides en mouvement ; suggestion clé de chercher à inverser le sens, la flèche, des vecteurs.
**Économie du bénévolat / économie domestique** (comme catégories) : usages spécialisés, mais déjà largement employés dans les sciences sociales.
**Framework** : cadre conceptuel et pratique de travail, un environnement de travail, interfaces, outils, langages et protocoles.
**Open source / en open source** : modèle de transparence et d'ouverture appliqué ici à l'économie / aux règles du jeu.
**Pseudo-isolées / communautés pseudo-isolées** : terme emprunté à la socio/économie, revalorisé ici en tant qu'échelle peut-être appropriée ou désirable.
**Boîte à gratitudes** : dispositif ludique de tirage au sort pour rétribuer les tâches (expérience « made in zion »).
**Gamifier** : francisation de *to gamify*, transformer une pratique en jeu.
**Économie du don / mesure du don** : distinction fine entre ladite « monnaie du don » et « DU comme mesure du don ».
**Mesure** (pour « monnaie ») : proposition de rebaptiser la monnaie en « mesure » dans ce modèle.
**Eco si nuestra** : communauté espagnole qui « passe la seconde » en ajoutant des règles légiférées de fonctionnement.
**Made in zion** : nom d'une expérience communautaire en forêt.
**Mocica** : nom d'un projet de société sans monnaie / de gratuité généralisée.
**Ğmarchés** : nom donné à des marchés utilisant la Ğ1. Petite variété dans les formes et les usages de ces marchés, selon la culture et l'humeur des groupes locaux qui les animent.

View File

@@ -0,0 +1,52 @@
## Umami analytics — déployer avec le compose principal
## Usage : docker compose -f docker-compose.yml -f docker-compose.umami.yml up -d
##
## Variables à définir dans .env :
## UMAMI_DB_PASSWORD — mot de passe PostgreSQL Umami (générer avec openssl rand -hex 32)
## UMAMI_APP_SECRET — secret applicatif (générer avec openssl rand -hex 32)
## UMAMI_DOMAIN — ex: stats.librodrome.org
name: librodrome
services:
umami:
image: ghcr.io/umami-software/umami:postgresql-latest
environment:
DATABASE_URL: postgresql://umami:${UMAMI_DB_PASSWORD}@umami-db:5432/umami
DATABASE_TYPE: postgresql
APP_SECRET: ${UMAMI_APP_SECRET}
depends_on:
umami-db:
condition: service_healthy
restart: always
labels:
- "traefik.enable=true"
- "traefik.http.routers.umami.rule=Host(`${UMAMI_DOMAIN:-stats.librodrome.org}`)"
- "traefik.http.routers.umami.entrypoints=websecure"
- "traefik.http.routers.umami.tls.certresolver=letsencrypt"
- "traefik.http.services.umami.loadbalancer.server.port=3000"
networks:
- default
- traefik
umami-db:
image: postgres:15-alpine
environment:
POSTGRES_DB: umami
POSTGRES_USER: umami
POSTGRES_PASSWORD: ${UMAMI_DB_PASSWORD}
volumes:
- umami-db-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U umami"]
interval: 5s
timeout: 5s
retries: 10
restart: always
volumes:
umami-db-data:
networks:
traefik:
external: true

View File

@@ -25,8 +25,16 @@ export default defineNuxtConfig({
'i-lucide-droplets', 'i-lucide-calendar-heart',
// Action icons
'i-lucide-play', 'i-lucide-book-open', 'i-lucide-sparkles',
'i-lucide-heart-handshake', 'i-lucide-arrow-up-right',
'i-lucide-rocket', 'i-lucide-flask-conical', 'i-lucide-arrow-right',
// Decision page
'i-lucide-vote', 'i-lucide-scroll-text', 'i-lucide-git-branch',
// sweethomeCloud + territoire
'i-lucide-home', 'i-lucide-hard-drive', 'i-lucide-video',
'i-lucide-message-circle', 'i-lucide-bot', 'i-lucide-globe-2',
'i-lucide-map-pin', 'i-lucide-euro', 'i-lucide-package',
'i-lucide-settings', 'i-lucide-layout-grid', 'i-lucide-server',
'i-lucide-chevron-down', 'i-lucide-info',
],
},
@@ -54,8 +62,12 @@ export default defineNuxtConfig({
runtimeConfig: {
adminPassword: '',
adminSecret: '',
umamiApiKey: '',
public: {
siteUrl: 'https://librodrome.org',
tenantId: 'librodrome',
umamiUrl: '',
umamiWebsiteId: '',
},
},

View File

@@ -0,0 +1,39 @@
import { readdir, stat } from 'node:fs/promises'
import { join } from 'node:path'
interface PageEntry {
path: string
label: string
section?: string
}
async function listYamlFiles(dir: string, prefix = ''): Promise<PageEntry[]> {
const entries: PageEntry[] = []
const items = await readdir(dir)
for (const item of items) {
const fullPath = join(dir, item)
const s = await stat(fullPath)
if (s.isDirectory()) {
const subEntries = await listYamlFiles(fullPath, prefix ? `${prefix}/${item}` : item)
entries.push(...subEntries)
}
else if (item.endsWith('.yml')) {
const name = item.replace('.yml', '')
const path = prefix ? `${prefix}/${name}` : name
entries.push({
path,
label: name,
section: prefix || undefined,
})
}
}
return entries
}
export default defineEventHandler(async () => {
const pagesDir = join(process.cwd(), 'site', 'pages')
return await listYamlFiles(pagesDir)
})

View File

@@ -0,0 +1,21 @@
import { mkdir } from 'node:fs/promises'
import { join, dirname } from 'node:path'
export default defineEventHandler(async (event) => {
const path = getRouterParam(event, 'path')
if (!path || !/^[a-z0-9-/]+$/.test(path)) {
throw createError({ statusCode: 400, statusMessage: 'Invalid page path' })
}
const body = await readBody(event)
const relativePath = `pages/${path}.yml`
// Ensure subdirectory exists
const fullPath = join(process.cwd(), 'site', relativePath)
await mkdir(dirname(fullPath), { recursive: true })
await writeYaml(relativePath, body)
gitSyncContent(`Mise à jour page ${path}`, [`site/${relativePath}`])
return { ok: true }
})

View File

@@ -1,12 +0,0 @@
export default defineEventHandler(async (event) => {
const name = getRouterParam(event, 'name')
if (!name || !/^[a-z0-9-]+$/.test(name)) {
throw createError({ statusCode: 400, statusMessage: 'Invalid page name' })
}
const body = await readBody(event)
await writeYaml(`pages/${name}.yml`, body)
gitSyncContent(`Mise à jour page ${name}`, [`site/pages/${name}.yml`])
return { ok: true }
})

View File

@@ -1,3 +1,5 @@
const EMPTY = { messages: [] as any[] }
export default defineEventHandler(async (event) => {
const id = Number(getRouterParam(event, 'id'))
@@ -5,7 +7,7 @@ export default defineEventHandler(async (event) => {
throw createError({ statusCode: 400, statusMessage: 'ID invalide' })
}
const data = await readYaml<{ messages: any[] }>('messages.yml')
const data = await readDataYaml<{ messages: any[] }>('messages.yml', EMPTY)
const index = data.messages.findIndex(m => m.id === id)
if (index === -1) {
@@ -13,8 +15,7 @@ export default defineEventHandler(async (event) => {
}
data.messages.splice(index, 1)
await writeYaml('messages.yml', data)
gitSyncContent(`Suppression message #${id}`, ['site/messages.yml'])
await writeDataYaml('messages.yml', data)
return { ok: true }
})

View File

@@ -1,3 +1,5 @@
const EMPTY = { messages: [] as any[] }
export default defineEventHandler(async (event) => {
const id = Number(getRouterParam(event, 'id'))
@@ -5,8 +7,15 @@ export default defineEventHandler(async (event) => {
throw createError({ statusCode: 400, statusMessage: 'ID invalide' })
}
const body = await readBody<{ text?: string; published?: boolean; author?: string }>(event)
const data = await readYaml<{ messages: any[] }>('messages.yml')
const body = await readBody<{
text?: string
published?: boolean
author?: string
type?: string
reply?: string | null
}>(event)
const data = await readDataYaml<{ messages: any[] }>('messages.yml', EMPTY)
const message = data.messages.find(m => m.id === id)
if (!message) {
@@ -16,9 +25,14 @@ export default defineEventHandler(async (event) => {
if (body.text !== undefined) message.text = body.text
if (body.published !== undefined) message.published = body.published
if (body.author !== undefined) message.author = body.author
if (body.type !== undefined) message.type = body.type
if ('reply' in body) {
message.reply = body.reply
? { text: body.reply, publishedAt: new Date().toISOString() }
: null
}
await writeYaml('messages.yml', data)
gitSyncContent(`Mise à jour message #${id}`, ['site/messages.yml'])
await writeDataYaml('messages.yml', data)
return { ok: true }
})

View File

@@ -1,4 +1,6 @@
const EMPTY = { messages: [] as any[] }
export default defineEventHandler(async () => {
const data = await readYaml<{ messages: any[] }>('messages.yml')
const data = await readDataYaml<{ messages: any[] }>('messages.yml', EMPTY)
return data.messages.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime())
})

View File

@@ -1,14 +1,14 @@
export default defineEventHandler(async (event) => {
const name = getRouterParam(event, 'name')
const path = getRouterParam(event, 'path')
if (!name || !/^[a-z0-9-]+$/.test(name)) {
throw createError({ statusCode: 400, statusMessage: 'Invalid page name' })
if (!path || !/^[a-z0-9-/]+$/.test(path)) {
throw createError({ statusCode: 400, statusMessage: 'Invalid page path' })
}
try {
return await readYaml(`pages/${name}.yml`)
return await readYaml(`pages/${path}.yml`)
}
catch {
throw createError({ statusCode: 404, statusMessage: `Page "${name}" not found` })
throw createError({ statusCode: 404, statusMessage: `Page "${path}" not found` })
}
})

View File

@@ -1,7 +1,8 @@
const EMPTY = { messages: [] as any[] }
export default defineEventHandler(async () => {
const data = await readYaml<{ messages: any[] }>('messages.yml')
const published = data.messages
const data = await readDataYaml<{ messages: any[] }>('messages.yml', EMPTY)
return data.messages
.filter(m => m.published)
.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime())
return published
})

View File

@@ -1,24 +1,28 @@
export default defineEventHandler(async (event) => {
const body = await readBody<{ author: string; email?: string; text: string }>(event)
const EMPTY = { messages: [] as any[] }
if (!body.author?.trim() || !body.text?.trim()) {
throw createError({ statusCode: 400, statusMessage: 'Nom et message requis' })
export default defineEventHandler(async (event) => {
const body = await readBody<{ author: string; email?: string; text: string; type?: string }>(event)
if (!body.text?.trim()) {
throw createError({ statusCode: 400, statusMessage: 'Message requis' })
}
const data = await readYaml<{ messages: any[] }>('messages.yml')
const data = await readDataYaml<{ messages: any[] }>('messages.yml', EMPTY)
const maxId = data.messages.reduce((max, m) => Math.max(max, m.id || 0), 0)
data.messages.push({
id: maxId + 1,
author: body.author.trim(),
author: body.author?.trim() || 'Anonyme',
email: body.email?.trim() || '',
text: body.text.trim(),
type: body.type || 'reaction',
published: false,
createdAt: new Date().toISOString(),
reply: null,
})
await writeYaml('messages.yml', data)
await writeDataYaml('messages.yml', data)
return { ok: true }
})

View File

@@ -0,0 +1,43 @@
/**
* GET /api/stats
* Public stats endpoint — proxies Umami for cross-instance federation / observatoires.
* Each librodrome instance exposes its own metrics here.
* Observatoires call this endpoint on each instance and aggregate.
*
* Env vars required (private, server-side):
* NUXT_UMAMI_API_KEY — Umami API key (read-only)
* NUXT_UMAMI_WEBSITE_ID — Umami website ID (internal, server-side)
* NUXT_PUBLIC_UMAMI_URL — Umami base URL
* NUXT_PUBLIC_TENANT_ID — e.g. "librodrome" or "librodrome-bordeaux"
*/
export default defineEventHandler(async () => {
const config = useRuntimeConfig()
const { umamiApiKey } = config
const { umamiUrl, umamiWebsiteId, tenantId } = config.public
if (!umamiApiKey || !umamiUrl || !umamiWebsiteId) {
return { tenant: tenantId, configured: false }
}
const endAt = Date.now()
const startAt = endAt - 30 * 24 * 60 * 60 * 1000 // 30 days
const [stats, pageviews] = await Promise.all([
$fetch<Record<string, unknown>>(`${umamiUrl}/api/websites/${umamiWebsiteId}/stats`, {
headers: { 'x-umami-api-key': umamiApiKey },
query: { startAt, endAt },
}).catch(() => null),
$fetch<Record<string, unknown>>(`${umamiUrl}/api/websites/${umamiWebsiteId}/pageviews`, {
headers: { 'x-umami-api-key': umamiApiKey },
query: { startAt, endAt, unit: 'day', timezone: 'Europe/Paris' },
}).catch(() => null),
])
return {
tenant: tenantId,
configured: true,
period: '30d',
stats,
pageviews,
}
})

View File

@@ -1,9 +1,10 @@
export default defineEventHandler((event) => {
const path = getRequestURL(event).pathname
// /lire → /economique/modele-eco
if (path.startsWith('/lire')) {
const rest = path.slice(5) // remove '/lire'
return sendRedirect(event, `/modele-eco${rest || '/'}`, 301)
return sendRedirect(event, `/economique/modele-eco${rest || '/'}`, 301)
}
if (path.startsWith('/ecouter')) {
@@ -14,4 +15,31 @@ export default defineEventHandler((event) => {
if (path === '/autonomie' || path === '/autonomie/') {
return sendRedirect(event, '/numerique', 301)
}
if (path === '/decision' || path === '/decision/') {
return sendRedirect(event, '/citoyenne/decision', 301)
}
// /modele-eco → /economique/modele-eco
if (path.startsWith('/modele-eco')) {
const rest = path.slice(11) // remove '/modele-eco'
return sendRedirect(event, `/economique/modele-eco${rest || '/'}`, 301)
}
// Redirect old /gestation/* routes to proper sections
if (path.startsWith('/gestation/')) {
const slug = path.slice(11).replace(/\/$/, '')
const numeriquePages = ['logiciel-libre', 'authentification-wot', 'cloud-libre']
if (numeriquePages.includes(slug)) {
return sendRedirect(event, `/numerique/${slug}`, 301)
}
if (slug === 'productions-collectives') {
return sendRedirect(event, '/economique/productions-collectives', 301)
}
if (slug === 'tarifs-eau') {
return sendRedirect(event, '/citoyenne/tarifs-eau', 301)
}
// Fallback
return sendRedirect(event, '/', 301)
}
})

View File

@@ -1,8 +1,10 @@
import { readFile, writeFile } from 'node:fs/promises'
import { readFile, writeFile, mkdir } from 'node:fs/promises'
import { existsSync } from 'node:fs'
import { join } from 'node:path'
import yaml from 'yaml'
const dataDir = join(process.cwd(), 'site')
const runtimeDataDir = join(process.cwd(), 'data')
const cache = new Map<string, { data: unknown; mtime: number }>()
@@ -35,3 +37,19 @@ export function invalidateCache(relativePath?: string): void {
cache.clear()
}
}
// Runtime data — stored in data/ (Docker volume, never overwritten by deploys)
export async function readDataYaml<T = unknown>(relativePath: string, defaultValue: T): Promise<T> {
const filePath = join(runtimeDataDir, relativePath)
if (!existsSync(filePath)) return defaultValue
const raw = await readFile(filePath, 'utf-8')
return yaml.parse(raw) as T
}
export async function writeDataYaml(relativePath: string, data: unknown): Promise<void> {
await mkdir(runtimeDataDir, { recursive: true })
const filePath = join(runtimeDataDir, relativePath)
const raw = yaml.stringify(data, { lineWidth: 120 })
await writeFile(filePath, raw, 'utf-8')
}

View File

@@ -543,10 +543,10 @@ chapterSongs:
- chapterSlug: 05-trm
songId: ainsi-soit-il
primary: true
- chapterSlug: 06-economie
- chapterSlug: 06-produire
songId: la-croissance-une-option
primary: true
- chapterSlug: 07-echange
- chapterSlug: 07-echanger
songId: monnaie-libre-une-essence
primary: true
- chapterSlug: 08-institution
@@ -572,9 +572,9 @@ chapterPages:
page: 49
- chapterSlug: 05-trm
page: 83
- chapterSlug: 06-economie
- chapterSlug: 06-produire
page: 121
- chapterSlug: 07-echange
- chapterSlug: 07-echanger
page: 147
- chapterSlug: 08-institution
page: 163

22
site/pages/citoyenne.yml Normal file
View File

@@ -0,0 +1,22 @@
meta:
title: Autonomie citoyenne
kicker: Autonomie citoyenne
title: La décision
description: >
Se donner les moyens de la décision collective. Reprendre la main
sur les processus qui structurent notre vie commune — à l'échelle
des bassins de vie.
items:
- id: decision
label: Décision collective
icon: gavel
description: Se donner les moyens de la décision collective.
to: /citoyenne/decision
- id: tarifs-eau
label: Tarifs de l'eau
icon: droplets
description: Application pour obtenir justice sociale et incitation dynamique à la réduction. Permet de confier la décision à la population des communes.
to: /citoyenne/tarifs-eau
gestation: true

View File

@@ -0,0 +1,21 @@
meta:
title: Décision collective — Autonomie citoyenne
kicker: Autonomie citoyenne
title: Plateforme Décision
icon: gavel
description: Se donner les moyens de la décision collective.
features:
- icon: vote
title: Décisions on-chain
text: Des décisions transparentes et vérifiables, inscrites sur la blockchain.
- icon: scroll-text
title: Les Mandats
text: Formaliser et suivre les mandats confiés aux personnes désignées.
- icon: scroll-text
title: Documents de référence
text: Les textes fondateurs et documents qui encadrent la prise de décision.
- icon: git-branch
title: Les Protocoles
text: Les règles et processus qui structurent la décision collective.

View File

@@ -0,0 +1,20 @@
meta:
title: Tarifs de l'eau — Autonomie citoyenne
kicker: Autonomie citoyenne
title: Tarifs de l'eau
icon: droplets
gestation: true
description: >
Application pour obtenir justice sociale et incitation dynamique
à la réduction. Permet de confier la décision à la population des communes.
project:
name: SejeteralO
text: Simulateur de tarification de l'eau — justice sociale et écologie.
content: >
La tarification de l'eau est un levier essentiel de justice sociale
et d'incitation à la réduction de la consommation. SejeteralO permet
aux communes de simuler et comparer différentes grilles tarifaires.

29
site/pages/economique.yml Normal file
View File

@@ -0,0 +1,29 @@
meta:
title: Autonomie économique
kicker: Autonomie économique
title: La création monétaire
description: >
Pour fonctionner autrement, il faut créer une économie qui le permette.
Au fondement de toute économie se trouve la création monétaire — son code,
ses règles, ses bénéficiaires. Tant que ce code est écrit par d'autres,
pour d'autres, aucune autonomie réelle n'est possible.
La monnaie libre propose un autre code : symétrique, transparent, partagé.
items:
- id: monnaie-libre
label: Monnaie libre
icon: g1
description: "La Ğ1 (June) : chaque membre crée la même part de monnaie, chaque jour. Sans dette ni intérêt — le dividende universel."
to: /economique/monnaie-libre
- id: modele-eco
label: Économie du don
icon: scale
description: Un livre et des chansons pour une proposition de modèle économique fondé sur le don.
to: /economique/modele-eco
- id: productions-collectives
label: Productions collectives
icon: users
description: Une plateforme pour faciliter la création d'équipes et la réalisation de productions à l'échelle des bassins de vie.
to: /economique/productions-collectives
gestation: true

View File

@@ -0,0 +1,23 @@
meta:
title: Commandez le livre papier
kicker: Le livre
title: Commandez le livre papier
icon: shopping-bag
description: >
« Une économie du don — enfin concevable » est disponible en version papier.
Vous pouvez le commander en ligne ou le demander en librairie.
bookelis:
label: Commander sur Bookelis
url: https://www.bookelis.com/economie/56818-Une-economie-du-don-enfin-concevable.html
librairie:
title: En librairie
text: >
Le livre est disponible à la commande dans toute librairie jusqu'au printemps 2027.
Cette disponibilité dépend d'un abonnement de référencement Hachette,
qui permet aux libraires de le trouver dans leur catalogue et de le commander
auprès de leur distributeur. Au-delà de cette date, la commande en ligne
sur Bookelis reste disponible.

View File

@@ -0,0 +1,59 @@
meta:
title: Monnaie libre — Autonomie économique
kicker: Autonomie économique
title: Monnaie libre
icon: g1
description: >
Toute économie repose sur un code monétaire.
Le nôtre programme la cavalerie et la machine à faillites.
La monnaie libre recode : chaque membre crée la même part relative de monnaie,
chaque jour. Symétrique dans l'espace, symétrique dans le temps.
content: >
La Théorie Relative de la Monnaie (TRM), formulée par Stéphane Laborde, pose
la question autrement : à quelle condition une monnaie ne privilégie-t-elle
personne dans sa création ? Ni aucun individu parmi ses contemporains,
ni aucune génération par rapport aux suivantes ?
La réponse est la double symétrie.
Symétrie spatiale : chaque membre crée la même part de monnaie que ses voisins.
Symétrie temporelle : les membres d'aujourd'hui ne privent pas ceux de demain.
Le Dividende Universel (DU) est la traduction pratique de ces deux symétries —
une part égale de création monétaire, pour chaque membre certifié, chaque jour.
Ni plus, ni moins.
Ce n'est pas une monnaie complémentaire indexée sur l'euro.
Ce n'est pas une monnaie locale. Ce n'est pas une spéculation.
La Ğ1 (June) est la première implémentation de la TRM — lancée le 8 mars 2017,
plus de 6 000 membres, des milliers d'offres et de demandes,
une conférence biannuelle. Une économie vivante.
L'entrée se fait par la toile de confiance (WoT) : chaque nouveau membre
est certifié par des membres existants, dans un graphe de confiance humain.
Pas de banque, pas d'État, pas de biométrie.
C'est aussi ce qui lie monnaie libre et identité décentralisée —
la même clé ouvre les deux portes.
Le DU comme unité de mesure change la posture.
On ne vend plus — on donne. On n'achète plus — on reçoit.
On ne paye plus — on mesure.
La monnaie libre mesure la gratitude. Pas le prix.
links:
- label: monnaie-libre.fr
href: https://monnaie-libre.fr
icon: external-link
- label: Théorie Relative de la Monnaie
href: https://trm.creationmonetaire.info
icon: book-open
- label: duniter.org — le logiciel
href: https://duniter.org
icon: code-2
- label: Forum Duniter
href: https://forum.duniter.org
icon: message-square
- label: Forum Monnaie Libre
href: https://forum.monnaie-libre.fr
icon: users

View File

@@ -0,0 +1,16 @@
meta:
title: Productions collectives — Autonomie citoyenne
kicker: Autonomie citoyenne
title: Productions collectives
icon: users
gestation: true
description: >
Une plateforme pour faciliter la création d'équipes et la réalisation
de productions à l'échelle des bassins de vie.
content: >
Les productions collectives permettent de mutualiser les compétences
et les ressources pour réaliser des projets concrets au service
du territoire.

View File

@@ -1,24 +1,31 @@
hero:
heading:
- "Construire une autonomie collective."
- "à l'échelle des bassins de vie"
- "Pousser les curseurs"
- "Autonomie numérique, économique, citoyenne."
- "— s'en donner les moyens —"
- Construire une autonomie collective.
- à l'échelle des bassins de vie
- Pousser les curseurs
- Autonomie numérique, économique, citoyenne.
- — s'en donner les moyens —
citations:
- "Il s'agit d'émancipation."
- "Les trois dimensions qui nous émancipent sont le numérique, l'économie et le politique."
- "Elles peuvent nous asservir tout autant."
- "Ce sont les 3 axes de l'espace dans lequel nous naviguons."
approach: "Dans chaque dimension, nous adressons ce qui est le plus en amont"
- Il s'agit d'émancipation.
- Les trois dimensions qui nous émancipent sont le numérique, l'économie et le politique.
- Elles peuvent nous asservir tout autant.
- Ce sont les 3 axes de l'espace dans lequel nous naviguons.
approach: Dans chaque dimension, nous adressons ce qui est le plus en amont
axes:
- label: citoyenne
value: la décision
- label: numérique
value: le code source
- label: économie
value: la création monétaire
- label: citoyenne
value: la décision
audience: "Dans cet espace, nous nous adressons :"
addressees:
- label: aux collectifs et associations
value: de toutes sortes
- label: aux entreprises
value: qui veulent innover, expérimenter
- label: aux collectivités
value: qui veulent émanciper leur population
book:
kicker: Modèle économique
title: Une économie du don — enfin concevable
@@ -29,47 +36,46 @@ book:
cta:
player: Présentation musicale
pdf: Lecture du livre
axes:
numerique:
title: Autonomie numérique
icon: monitor
items:
- label: Logiciel libre
description: Maîtriser le code source, c'est maîtriser l'outil. Le logiciel libre est la base de l'autonomie numérique.
to: /gestation/logiciel-libre
description: "Passer à Linux, adopter des équivalents libres, un assistant shell local sans cloud — guide de migration et atelier au prochain Librodrome."
to: /numerique/logiciel-libre
gestation: true
icon: code-2
presentation:
title: wishBounty
text: Application pour le financement fléché des développements.
- label: Authentification — WoT
description: Une toile de confiance décentralisée, sans autorité centrale. Chaque identité est certifiée par ses pairs.
to: /gestation/authentification-wot
description: "Une clé pour les accès, une signature pour les actes. Le seul système sans autorité centrale ni biométrie — DID W3C, Duniter, EUDI Wallet 2026."
to: /numerique/authentification-wot
gestation: true
icon: share-2
presentation:
title: trustWallet
text: Gestionnaire de confiances.
- label: Cloud libre
description: Héberger ses propres services pour ne dépendre de personne. Serveurs, noms de domaine, infrastructure.
to: /gestation/cloud-libre
description: "Sortir des datacenters — auto-hébergement, web3, IPFS, local-first. Conformité RGPD et droit à l'oubli."
to: /numerique/cloud-libre
gestation: true
icon: cloud
presentation:
title: Bouquet de services
text: "Un bouquet de services complet : Drive, Visio, Forum, Wiki, CMS. IA frugale localisée."
title: sweethomeCloud
text: "Bouquet de services libres auto-hébergés — clé en main."
economie:
title: Autonomie économique
icon: coins
items:
- label: Monnaie libre
description: "La Ğ1 (June) : une monnaie co-créée par ses membres, sans dette ni intérêt. Le dividende universel comme base."
href: https://monnaie-libre.fr
description: "La Ğ1 (June) : chaque membre crée la même part de monnaie, chaque jour. Sans dette ni intérêt — le dividende universel."
to: /economique/monnaie-libre
icon: g1
- label: Économie du don
description: Un livre et des chansons pour une proposition de modèle économique fondé sur le don.
to: /modele-eco
to: /economique/modele-eco
icon: scale
actions:
- id: open-player
@@ -77,15 +83,22 @@ axes:
icon: play
highlight: true
- id: open-pdf
label: Lecture du livre
label: Lire le livre
icon: book-open
- id: launch-gratewizard
label: grateWizard
icon: sparkles
- id: link
label: Présentation des chapitres
icon: list
to: /economique/modele-eco
secondary: true
- id: link
label: Commandez le livre
icon: shopping-bag
to: /economique/commande
secondary: true
- label: Productions collectives
description: Une plateforme pour faciliter la création d'équipes et la réalisation de productions à l'échelle des bassins de vie.
to: /gestation/productions-collectives
description: Une plateforme pour faciliter la création d'équipes et la réalisation de productions à l'échelle des
bassins de vie.
to: /economique/productions-collectives
gestation: true
icon: users
politique:
@@ -94,14 +107,19 @@ axes:
items:
- label: Décision collective
description: Se donner les moyens de la décision collective.
to: /decision
to: /citoyenne/decision
icon: gavel
- label: Tarifs de l'eau
description: Application pour obtenir justice sociale et incitation dynamique à la réduction. Permet de confier la décision à la population des communes.
to: /gestation/tarifs-eau
description: Application pour obtenir justice sociale et incitation dynamique à la réduction. Permet de confier la
décision à la population des communes.
to: /citoyenne/tarifs-eau
gestation: true
icon: droplets
gratewizard:
title: grateWizard
subtitle: Un utilitaire pratique pour estimer les valeurs de façon relative
description: Explorez le modèle économique, posez vos questions, suivez le fil du raisonnement.
icon: sparkles
evenement:
title: Le librodrome,
subtitle: c'est également un événement.

View File

@@ -1,46 +1,48 @@
kicker: Autonomie numérique
title: Le code source
description: Des passages du livre qui éclairent la démarche d'autonomie numérique — maîtriser le code source, c'est maîtriser l'outil.
description: "Maîtriser le code source, c'est maîtriser l'outil. L'autonomie numérique est le socle : sans elle, pas de monnaie libre, pas de décision souveraine."
meta:
title: Autonomie numérique
extracts:
- chapter: Introduction
chapterSlug: 01-introduction
pillars:
- id: logiciel-libre
label: Logiciel libre
icon: code-2
text: >
Ben. Pour l'autonomie.
...C'est tout — sauf un repli.
Balkanisation ? que nenni !
Réfuter l'autonomie... c'est fallacieux,
c'est nous bannir en tant qu'adultes, bien vivants,
restez là sans mot dire,
"restez des enfants !"...
mmh, suspect et sans avenir.
- chapter: Introduction
chapterSlug: 01-introduction
Le logiciel libre n'est pas qu'une question technique.
C'est la condition d'existence d'outils qui nous appartiennent —
que l'on peut auditer, modifier, partager.
Sans logiciel libre, toute promesse de souveraineté numérique est creuse.
project:
name: wishBounty
text: Application pour le financement fléché des développements libres.
gestation: true
to: /numerique/logiciel-libre
- id: authentification-wot
label: "Authentification — WoT"
icon: share-2
text: >
Ne plus subir les agendas. Créer les nôtr'.
On manque de repères ?... Entre autres, ...
Faut les trouver,...
En produisant, les inventer.
- chapter: "Raison d'être d'une monnaie"
chapterSlug: 04-monnaie
Une toile de confiance décentralisée, sans autorité centrale.
Chaque identité est certifiée par ses pairs — pas par un serveur,
pas par une entreprise. C'est le fondement de la monnaie libre Ğ1
et de toute gouvernance entre égaux.
project:
name: trustWallet
text: Gestionnaire de confiances.
gestation: true
to: /numerique/authentification-wot
- id: cloud-libre
label: Cloud libre
icon: cloud
text: >
Même accès pour tous.
Même pouvoir de création.
Ce n'est plus "Que la dette soit".
C'est "Que l'équilibre soit".
- chapter: "Créer une économie ?"
chapterSlug: 06-economie
text: >
Le D.U, c'est une mesure.
Pour ne plus obliger. Pour ne plus devoir.
Je donne à mon économie, j'alimente le réservoir.
Je compte sur les autres, sur mon économie,
Pour y trouver ma pleine mesure.
- chapter: "Et maintenant ?… action ?"
chapterSlug: 10-maintenant
text: >
Mais la monnaie n'est pas la richesse.
C'est juste le mètre... pas le tissu.
C'est le baromètre... pas le climat.
Ne confondons pas la carte et le territoire.
Héberger ses propres services pour ne dépendre de personne.
Serveurs, noms de domaine, infrastructure.
Un bouquet de services complet — Drive, Visio, Forum, Wiki, CMS —
et demain, une IA frugale localisée.
project:
name: sweethomeCloud
text: "Bouquet de services libres auto-hébergés — clé en main pour chaque territoire."
gestation: true
to: /numerique/cloud-libre

View File

@@ -0,0 +1,208 @@
meta:
title: Authentification WoT — Identité numérique décentralisée
kicker: Autonomie numérique
title: "Authentification — WoT"
icon: share-2
description: >
Vous avez les outils, vous maîtrisez votre infrastructure.
Reste la pièce manquante : une identité numérique qui n'appartient qu'à vous.
Pas de biométrie, pas d'autorité centrale — une clé, et une toile de confiance.
C'est le seul système qui les évite tous les deux.
project:
name: trustWallet
text: Gestionnaire de confiances — accès, permissions, DID/UCAN/VC sur clés certifiées Duniter.
gestation: true
sections:
- type: arguments
title: Pourquoi l'authentification actuelle est un problème
items:
- icon: lock
title: Mots de passe
text: >
Stockés côté serveur, volés par millions. Have I Been Pwned recense
des milliards de comptes compromis. Ta sécurité dépend intégralement
de la sécurité d'un tiers que tu ne contrôles pas.
- icon: chrome
title: "Se connecter avec Google"
text: >
Déléguer l'authentification à Google ou Meta, c'est en faire l'infrastructure
d'identité mondiale. Un compte suspendu = toutes tes connexions coupées.
Ces entreprises savent quand, où et à quoi tu te connectes.
- icon: fingerprint
title: Biométrie — irrévocable
text: >
Un mot de passe compromis se change. Une empreinte digitale jamais.
Si tes données biométriques sont volées — et elles l'ont été
(OPM breach USA 2015, 5,6 millions d'empreintes) — la compromission est définitive.
- icon: building-2
title: Autorités de Certification (X.509)
text: >
HTTPS repose sur ~130 CA mondiales. DigiNotar (2011), Symantec (2015) :
des CA compromises ont permis des attaques sur des millions d'utilisateurs.
La confiance sur le web est centralisée chez Google, Apple et Microsoft.
- type: fiche
title: Comment fonctionne une Web of Trust
steps:
- n: 1
title: Générer une paire de clés sur ta machine
text: >
Clé privée (secrète, ne quitte jamais ton appareil) et clé publique
(diffusée librement). Ce qui est signé avec la clé privée peut être
vérifié par n'importe qui avec la clé publique.
tip: "Algorithme recommandé : Ed25519 — clé courte, rapide, sans paramètres douteux"
- n: 2
title: Publier ta clé publique
text: >
Dans un DID Document, sur un keyserver, dans ton profil Duniter.
N'importe qui peut vérifier tes signatures. Personne ne peut usurper
ton identité sans ta clé privée.
- n: 3
title: Être certifié par des membres certifiés
text: >
Dans Duniter, 5 membres certifiés (sigQty=5) certifient que tu es une personne réelle,
dans un rayon de 3 sauts du nœud sentinelle (stepMax=3).
La confiance émerge du graphe — pas d'un serveur central.
- n: 4
title: La confiance se propage dans le graphe
text: >
Plus un nœud est certifié par des membres eux-mêmes bien certifiés,
plus son identité est robuste. Théorie des graphes appliquée à l'identité —
sans juge, sans notaire, sans serveur.
- n: 5
title: Une clé pour les accès, une signature pour les actes
text: >
La même clé permet de s'authentifier à des services
et de signer des actes (documents, transactions, votes).
Tout ce que tu fais est vérifiable. Rien n'est transmissible à ton insu.
- type: insight
title: Le seul système sans autorité centrale ni biométrie
icon: shield-check
variant: info
text: >
Toute authentification mainstream dépend d'au moins une entité centrale :
mot de passe (le serveur), OAuth (Google ou Meta), biométrie (le fabricant ou l'État),
HTTPS (les Autorités de Certification). La Web of Trust distribue la confiance
dans le réseau lui-même — structurellement différent.
Les standards W3C (DID 2022, VC 2025) et l'EUDI Wallet européen (fin 2026)
montrent que cette architecture sort des cercles militants
pour entrer dans la politique numérique des États.
points:
- "DID v1.0 : Recommandation W3C finale — juillet 2022"
- "Verifiable Credentials v2.0 : Recommandation W3C finale — mai 2025"
- "eIDAS 2.0 (UE) : wallet d'identité numérique pour chaque citoyen européen avant fin 2026"
- "Duniter/Ğ1 : seule blockchain avec WoT intégrée comme mécanisme de gouvernance du protocole"
- type: equivalents
title: De l'ancien modèle aux standards décentralisés
categories:
- label: Authentification
items:
- from: Mot de passe
to: "Clé Ed25519 + WebAuthn"
url: https://webauthn.io
note: Signature cryptographique — aucun secret côté serveur
- from: "Se connecter avec Google"
to: SIOPv2 + wallet DID
url: https://openid.net/specs/openid-connect-self-issued-v2-1_0.html
note: "S'authentifier avec son propre wallet, sans compte tiers"
- from: Certificat X.509
to: "did:web / did:key"
url: https://www.w3.org/TR/did-1.0/
note: Identifiant décentralisé résolu sans CA centrale
- label: Standards W3C
items:
- from: Registre central d'identités
to: "DID v1.0 (W3C)"
url: https://www.w3.org/TR/did-1.0/
note: "Recommandation W3C finale — juillet 2022"
- from: Attestation papier ou numérique centralisée
to: "Verifiable Credentials v2.0"
url: https://www.w3.org/TR/vc-data-model-2.0/
note: "Recommandation W3C finale — mai 2025. Divulgation sélective."
- from: "OAuth / OIDC classique"
to: "OpenID4VP + OpenID4VCI"
url: https://openid.net/specs/openid-4-verifiable-presentations-1_0.html
note: Pont entre wallets DID et services web existants
- label: Implémentations
items:
- from: Monnaie et identité centralisées
to: "Duniter / Ğ1"
url: https://duniter.fr
note: "WoT intégrée au protocole — 5 certifications, rayon 3"
- from: Wallet propriétaire
to: trustWallet
note: "Gestionnaire de confiances — accès, permissions, DID/UCAN/VC sur clés certifiées Duniter (en gestation)"
- from: SSI entreprise
to: "SpruceID / ssi (Rust)"
url: https://spruceid.com
note: Librairie open source pour DID et Verifiable Credentials
- type: arguments
title: Ce que la WoT rend possible
items:
- icon: log-in
title: Connexion décentralisée
text: >
Via SIOPv2 + OpenID4VP, un wallet d'identité répond aux requêtes
d'authentification de n'importe quel service compatible.
Pas de compte Google. Une clé. Final depuis juillet 2025.
- icon: file-signature
title: Signature légale
text: >
La signature qualifiée (eIDAS QES) a la même valeur juridique
qu'une signature manuscrite dans l'UE. L'EUDI Wallet
inclura la signature qualifiée pour chaque citoyen européen.
- icon: bar-chart-2
title: Vote vérifiable
text: >
Cryptographie asymétrique et Zero-Knowledge Proofs permettent
un décompte public vérifiable sans révéler le vote individuel.
La vérification est mathématique, pas institutionnelle.
- icon: coins
title: Création monétaire — Ğ1
text: >
Le cas d'usage le plus radical : l'identité dans la WoT Duniter détermine
l'accès au Dividende Universel. La WoT est un mécanisme de justice économique.
Pas de WoT → pas de création monétaire libre.
- icon: building
title: Services et gouvernance
text: >
Accès aux services publics (eIDAS 2.0), gouvernance de DAOs,
contrats intelligents vérifiables, réseaux de confiance décentralisés.
Une identité → une clé pour tous les accès.
- type: links
title: Ressources
items:
- label: "W3C DID v1.0"
url: https://www.w3.org/TR/did-1.0/
desc: "Standard des identifiants décentralisés — Recommandation W3C finale (2022)"
- label: "W3C Verifiable Credentials v2.0"
url: https://www.w3.org/TR/vc-data-model-2.0/
desc: "Standard des attestations vérifiables — Recommandation W3C finale (mai 2025)"
- label: Duniter
url: https://duniter.fr
desc: "Blockchain WoT pour la monnaie libre Ğ1 — gouvernance par toile de confiance"
- label: trustWallet (en gestation)
desc: "Gestionnaire de confiances — accès, permissions, signatures DID/UCAN/VC sur clés certifiées de la WoT Duniter"
- label: SpruceID
url: https://spruceid.com
desc: Librairie open source Rust pour DID et Verifiable Credentials
- label: "EUDI Wallet (eIDAS 2.0)"
url: https://ec.europa.eu/digital-building-blocks/sites/display/EUDIGITALIDENTITYWALLET/
desc: "Wallet d'identité numérique européen — déploiement prévu fin 2026"
- label: "OpenID4VP"
url: https://openid.net/specs/openid-4-verifiable-presentations-1_0.html
desc: "Standard final (juillet 2025) — authentification avec wallet DID"
- label: KERI
url: https://keri.one
desc: "Key Event Receipt Infrastructure — identité décentralisée, IETF draft"
- label: SSI Meetup
url: https://www.ssimeetup.org
desc: Webinaires et ressources sur la Self-Sovereign Identity

View File

@@ -0,0 +1,340 @@
meta:
title: Cloud libre — Sortir des datacenters
kicker: Autonomie numérique
title: Cloud libre
icon: cloud
description: >
Tes données vivent dans des datacenters dont tu ne connais pas l'adresse,
sous des juridictions que tu ne contrôles pas. Sortir de là est possible —
en trois paliers : auto-hébergement, services fédérés, architectures décentralisées.
Ces dernières émergent à peine.
project:
name: sweethomeCloud
text: Bouquet de services libres auto-hébergés — clé en main pour chaque bassin de vie.
gestation: true
sections:
- type: arguments
title: Pourquoi sortir des datacenters ?
items:
- icon: shield
title: RGPD — conformité structurelle
text: >
Google, Amazon, Microsoft stockent tes données sous juridiction américaine (CLOUD Act).
L'auto-hébergement ou un hébergeur associatif européen offre
une conformité RGPD structurelle — pas seulement déclarative.
- icon: eye-off
title: Vie privée des données
text: >
Dans un cloud commercial, tes documents, photos et communications
sont analysés, profilés, monétisés. Ce n'est pas une théorie —
c'est le modèle économique déclaré.
- icon: zap
title: Écologie numérique
text: >
Les datacenters consomment 1 à 2 % de l'électricité mondiale.
Un petit serveur domestique consomme 5 à 10 W.
Héberger chez soi ou mutualiser dans un hébergeur associatif
réduit l'empreinte réelle.
- icon: key
title: Propriété réelle de tes données
text: >
Un service cloud peut fermer, changer ses CGU, couper ton compte.
Tes données te reviennent quand tu les héberges toi-même —
portabilité réelle, pas juste un bouton "Exporter".
- icon: git-branch
title: Résilience décentralisée
text: >
Un réseau de services distribués n'a pas de point de défaillance unique.
Un nœud fermé, les autres continuent.
C'est l'architecture d'Internet telle qu'elle avait été conçue.
- type: tiers
title: Trois niveaux d'architecture libre
tiers:
- level: Niveau 1
title: Auto-hébergement
icon: server
badge: Maîtrise totale
text: >
Ton propre serveur chez toi ou chez un hébergeur associatif.
Contrôle absolu, RGPD pleinement compatible.
YunoHost installe plus de 200 applications libres en un clic.
tools:
- "Nextcloud — fichiers, agenda, contacts, visio"
- "YunoHost — auto-hébergement simplifié"
- "Syncthing — sync pair-à-pair sans serveur"
- "FreedomBox — serveur domestique clé-en-main"
- level: Niveau 2
title: Services fédérés
icon: share-2
badge: Interconnecté
text: >
Des instances indépendantes interconnectées via des protocoles ouverts
(ActivityPub, Matrix). Tu rejoins un serveur de confiance
ou héberges le tien. Les données restent chez l'opérateur choisi.
tools:
- "Mastodon — réseau social fédéré"
- "PeerTube — vidéo hébergée et fédérée"
- "Matrix + Element — messagerie décentralisée"
- "Pixelfed — galerie photo fédérée"
- "Funkwhale — musique fédérée"
- level: Niveau 3
title: Architectures décentralisées
icon: globe-2
badge: Émergent
text: >
Pas de serveur central, pas d'opérateur unique. Les fichiers sont
fragmentés, chiffrés côté client, distribués sur des nœuds indépendants mondiaux.
Ces architectures émergent à peine — prometteuses, pas encore grand public.
tools:
- "IPFS — protocole pair-à-pair (ipfs.tech)"
- "Filecoin — marché de stockage sur IPFS"
- "Storj — stockage distribué chiffré côté client"
- "Sia — stockage décentralisé (Sia Foundation)"
- "Arweave — stockage permanent (modèle endowment)"
- type: equivalents
title: Vos services cloud — en version libre
categories:
- label: "Fichiers & synchronisation"
items:
- from: "Google Drive / Dropbox"
to: Nextcloud
url: https://nextcloud.com/fr
note: "Fichiers, agenda, contacts, photos, visio — auto-hébergeable"
- from: Dropbox
to: Syncthing
url: https://syncthing.net
note: "Sync pair-à-pair chiffré, sans serveur central"
- from: "Dropbox / Box"
to: Storj
url: https://storj.io
note: Stockage distribué chiffré côté client
- label: "Email & calendrier"
items:
- from: "Gmail / Outlook"
to: Proton Mail
url: https://proton.me/fr/mail
note: "Chiffrement bout-en-bout, hébergé en Suisse"
- from: Google Calendar
to: Nextcloud Calendar
url: https://nextcloud.com/fr
note: "CalDAV standard, auto-hébergeable"
- label: "Vidéo & média"
items:
- from: YouTube
to: PeerTube
url: https://joinpeertube.org
note: "Hébergement vidéo fédéré — choisir son instance"
- from: Google Photos
to: Nextcloud Photos
url: https://nextcloud.com/fr
note: Galerie auto-hébergée
- label: Réseaux sociaux
items:
- from: "Twitter / X"
to: Mastodon
url: https://joinmastodon.org
note: "Réseau social fédéré — 30 M+ comptes"
- from: Instagram
to: Pixelfed
url: https://pixelfed.org
note: Galerie photo fédérée
- from: "Slack / Teams"
to: "Matrix + Element"
url: https://element.io
note: Messagerie décentralisée et fédérée
- label: Auto-hébergement facile
items:
- from: Pas de serveur
to: YunoHost
url: https://yunohost.org
note: "Installe 200+ apps libres en un clic (Debian)"
- from: Infrastructure cloud
to: FreedomBox
url: https://freedombox.org
note: Serveur domestique clé-en-main (projet Debian)
- from: Node Web3
to: Umbrel
url: https://umbrel.com
note: "App store décentralisé, nœud Bitcoin/Lightning"
- type: insight
title: RGPD et droit à l'oubli — une tension structurelle
icon: scale
variant: warning
text: >
Le droit à l'oubli (RGPD art. 17) exige l'effacement des données personnelles sur demande.
Les architectures décentralisées y résistent structurellement :
une blockchain est un registre immuable répliqué sur tous les nœuds,
IPFS conserve les données tant qu'un seul nœud les héberge.
C'est une tension réelle, non résolue, que le législateur européen commence à instruire
(lignes directrices CEPD 02/2025).
points:
- "Auto-hébergement : conformité RGPD totale — tu effaces ce que tu veux"
- "Services fédérés : dépend de l'opérateur de l'instance — choisir un hébergeur CHATONS"
- "IPFS / blockchain : chiffrer + détruire la clé (la donnée devient inintelligible). Accepté par la CNIL sous conditions strictes."
- "Off-chain data : stocker uniquement un hash sur la chaîne, les données personnelles sur un serveur effaçable — approche la plus propre"
- "Référence officielle : guide CNIL blockchain et RGPD + lignes directrices CEPD 02/2025"
- type: projet
title: sweethomeCloud
kicker: Prochain projet
icon: home
badge: En gestation
text: >
Un bouquet de services libres, auto-hébergés et prêts à l'emploi —
pour les collectivités, associations et collectifs qui veulent
maîtriser leur infrastructure numérique sans expertise technique.
sweethomeCloud s'appuie sur les briques documentées dans cette page :
Nextcloud, Matrix, Jitsi, YunoHost, Ollama — packagées, maintenues,
documentées ensemble. Une instance par territoire.
features:
- icon: hard-drive
label: "Drive, agenda, contacts — Nextcloud"
- icon: video
label: "Visioconférence — Jitsi / Galène"
- icon: message-circle
label: "Messagerie chiffrée souveraine"
- icon: bot
label: "IA locale — Ollama, aucune donnée ne sort"
- icon: globe-2
label: "CMS, forum, wiki, sites web"
- icon: shield
label: "Décentralisé local-first — votre territoire, vos données"
- type: territoire
title: Autonomie des bassins de vie
icon: map-pin
text: >
Un bassin de vie de 50 000 personnes peut déployer et maintenir son propre réseau
numérique souverain. Le matériel est accessible, les logiciels libres sont matures.
sweethomeCloud sera ce bouquet — packagé, documenté, maintenu ensemble.
Deux modèles économiques, au choix.
economies:
- label: "Euro non vénale"
desc: "Portage associatif ou coopératif — contribution libre, cotisation symbolique, financement public"
icon: euro
- label: "Monnaie libre Ğ1 — à 100%"
desc: "Services financés exclusivement en Ğ1, créée par ses membres. Aligné avec l'économie du don."
icon: coins
bouquet:
title: "Le bouquet sweethomeCloud"
categories:
- label: Collaboration quotidienne
icon: users
items:
- "Drive, agenda, contacts, photos — Nextcloud"
- "Visioconférence — Jitsi / Galène"
- "Messagerie chiffrée souveraine"
- "Email hébergé — Postfix + Dovecot"
- label: "Web & publication"
icon: globe-2
items:
- "CMS — WordUp"
- "Forum"
- "Wiki"
- "Sites web, pages publiques"
- label: "Infrastructure technique"
icon: settings
items:
- "Git — Forgejo"
- "Automatisation — n8n"
- "Mailer transactionnel — Postal"
- "DNS souverain"
- label: "Souveraineté protocole"
icon: shield
items:
- "Nœud Duniter — Ğ1"
- "Stockage IPFS"
- "Miroirs logiciels libres"
- label: "Intelligence artificielle locale"
icon: bot
items:
- "LLM généraliste en file — Ollama + Llama 8B"
- "LLM lourd sur demande — Llama 70B"
- "IA code — Codestral / DeepSeek"
- "Aucune donnée ne sort du territoire"
- label: "Applications du territoire"
icon: layout-grid
items:
- "librodrome — économie & autonomie"
- "libreDecision — décision collective"
- "Applications métier à venir"
estimate:
toggle: "Estimation matérielle pour 50 000 personnes"
note: >
Basé sur 5% d'utilisation simultanée au pic (2 500 connectés),
20 Go de stockage par habitant (taux d'occupation réel 30%),
matériel reconditionné. Hypothèses conservatives et vérifiées.
hardware:
- role: "Serveurs principaux"
detail: "4× reconditionné — 2× Xeon 16 cœurs, 128 Go RAM, SSD NVMe"
usage: "Nextcloud, mail, forum, wiki, git, n8n, CMS, apps, Matrix"
cost: "10 000 14 000 €"
note: "Dell PowerEdge R740 / HPE DL380 Gen10 reconditionné"
- role: "Stockage NAS"
detail: "24× HDD 10 To RAID6 → 160 To utilisables"
usage: "Drive (100 To), email (40 To), IPFS cache, backups"
cost: "5 000 7 000 €"
note: "~20 Go/habitant, occupation réelle ~30% — largement suffisant au démarrage"
- role: "Visioconférence"
detail: "3× serveur 8 cœurs / 32 Go — load balancé (Jitsi Octo)"
usage: "250 participants simultanés — 75-100 max par nœud Jitsi"
cost: "6 000 9 000 €"
note: "1 seul serveur Jitsi ne peut pas dépasser 100 participants — chiffre vérifié"
- role: "IA légère — Llama 8B"
detail: "4× PC gamer avec RTX 4090 24 Go VRAM"
usage: "Ollama — Q&A, résumé, assistance — 1 requête simultanée/GPU"
cost: "8 000 10 000 €"
note: "Llama 3.1 8B Q4 : 6-8 Go VRAM, 100-150 tokens/s par RTX 4090"
- role: "IA lourde — Llama 70B"
detail: "1× serveur avec 2× RTX 4090 (48 Go VRAM cumulés)"
usage: "Llama 3.1 70B Q4 (42 Go VRAM min), IA code, analyse longue"
cost: "5 000 8 000 €"
note: "Le modèle est splitté sur 2 GPU — seule option abordable sans datacenter"
- role: "Réseau & DNS"
detail: "Fibre pro 1 Gbps symétrique + switch 10 Gbps + 2× mini-PC DNS"
usage: "Tous services, DNS souverain redondant (BIND9)"
cost: "2 000 € + 300 €/mois fibre"
note: "Le DNS est la pièce la plus critique — la redondance est obligatoire"
totals:
invest: "36 000 48 000 €"
annual: "8 000 12 000 €/an"
per_person: "~2 €/an par habitant"
note: "Moins cher qu'un café — pour une infrastructure numérique complète et souveraine."
- type: links
title: Ressources
items:
- label: YunoHost
url: https://yunohost.org
desc: Auto-hébergement simplifié — installe 200+ apps libres en un clic
- label: CHATONS
url: https://www.chatons.org
desc: "96+ hébergeurs alternatifs éthiques et décentralisés (initié par Framasoft)"
- label: Nextcloud
url: https://nextcloud.com/fr
desc: Suite collaborative auto-hébergeable de référence
- label: Dégooglisons Internet
url: https://degooglisons-internet.org
desc: Alternatives libres aux services Google — par Framasoft
- label: IPFS
url: https://ipfs.tech
desc: Protocole de stockage pair-à-pair décentralisé
- label: Storj
url: https://storj.io
desc: Stockage distribué chiffré côté client
- label: Sia
url: https://sia.tech
desc: "Stockage décentralisé — Sia Foundation (non-profit, très actif)"
- label: CNIL — Blockchain et RGPD
url: https://www.cnil.fr/fr/blockchain-et-rgpd-quelles-solutions-pour-un-usage-responsable-en-presence-de-donnees-personnelles
desc: Guide officiel sur les enjeux RGPD des architectures décentralisées
- label: Switching.software
url: https://switching.software
desc: Annuaire d'alternatives éthiques aux logiciels propriétaires

View File

@@ -0,0 +1,243 @@
meta:
title: Logiciel libre — Migrer sur Linux
kicker: Autonomie numérique
title: Logiciel libre
icon: code-2
description: >
Passer à Linux, adopter LibreOffice, Signal, Nextcloud.
Un guide concret d'équivalents libres, une fiche de migration
étape par étape — et un atelier au prochain Librodrome.
project:
name: wishBounty
text: Application pour le financement fléché des développements libres.
gestation: true
sections:
- type: arguments
title: Pourquoi migrer ?
items:
- icon: shield
title: Vie privée
text: >
Windows et macOS collectent vos données en permanence, sans possibilité
de désactiver entièrement la télémétrie. Linux ne le fait pas par défaut —
tout le code est auditable.
- icon: coins
title: "Coût réel : 0 €"
text: >
Windows 11 coûte 145 €, Office 365 70 €/an.
Linux + LibreOffice sont gratuits. Sur 5 ans pour une association,
l'écart devient un argument décisif.
- icon: recycle
title: Durabilité matérielle
text: >
Linux redonne 5 à 10 ans de vie à des machines abandonnées par Windows 11
(qui exige TPM 2.0). Moins de déchets électroniques — argument écologique concret.
- icon: users
title: Bien commun
text: >
Le logiciel libre est un commun numérique. Tu n'es pas client, tu participes.
Cohérent avec l'économie du don — l'argent économisé sur les licences
peut financer des communs.
- icon: lock-open
title: Indépendance réelle
text: >
Pas de mise à jour forcée qui casse le système. Pas d'abonnement qui expire
et verrouille tes fichiers. Ton OS t'appartient vraiment.
- icon: bot
title: Assistance permanente gratuite
text: >
Un LLM open source installé localement (Ollama + phi3:mini, 4 Go de RAM)
peut rédiger les commandes shell dans ton terminal sur simple demande en français.
Sans connexion à aucun serveur. Sans abonnement. Sans envoyer tes données.
- type: fiche
title: "Fiche de migration — 6 étapes"
steps:
- n: 1
title: Sauvegarder ses données
text: >
Copie documents, photos, mails sur un disque externe ou clé USB.
Ne commence rien sans ça.
- n: 2
title: Tester sans risque — Live USB
text: >
Démarre Linux Mint depuis une clé USB. Tu explores, tu testes LibreOffice,
tu vérifies le WiFi — sans toucher à ton Windows existant.
tip: "Créer la clé : balenaEtcher (gratuit) + image Linux Mint sur linuxmint.com"
- n: 3
title: Choisir sa distribution
text: >
Linux Mint Cinnamon pour migrer depuis Windows (interface familière).
Ubuntu pour un maximum de ressources en ligne.
Les deux sont excellents pour démarrer.
- n: 4
title: Installer — double-boot ou remplacement
text: >
Double-boot si tu veux garder Windows le temps de la transition.
Remplacement complet quand tu es prêt·e — c'est le vrai départ.
- n: 5
title: Installer ses équivalents libres
text: >
LibreOffice est déjà là. Ajoute Thunderbird, VLC, GIMP, Signal Desktop
via le gestionnaire de paquets en un clic — pas de téléchargement manuel.
- n: 6
title: Rejoindre une communauté
text: >
Forum ubuntu-fr.org, groupe local, ou atelier Librodrome.
Les questions trouvent toujours réponse.
- type: equivalents
title: Vos logiciels propriétaires — en version libre
categories:
- label: Bureautique
items:
- from: Microsoft Office
to: LibreOffice
url: https://fr.libreoffice.org
note: Suite complète gratuite multiplateforme
- from: Microsoft Office
to: OnlyOffice
url: https://www.onlyoffice.com/fr
note: "Meilleure compat .docx/.xlsx"
- label: "Photo & graphisme"
items:
- from: Photoshop
to: GIMP
url: https://www.gimp.org
note: Retouche et manipulation d'images
- from: "Photoshop / Affinity"
to: Krita
url: https://krita.org/fr
note: Peinture numérique et illustration
- from: Lightroom
to: Darktable
url: https://www.darktable.org
note: Développement photo RAW
- from: Illustrator
to: Inkscape
url: https://inkscape.org/fr
note: Dessin vectoriel SVG
- from: Figma
to: Penpot
url: https://penpot.app
note: "Design UI/UX, auto-hébergeable"
- label: "Email & messagerie"
items:
- from: Outlook
to: Thunderbird
url: https://www.thunderbird.net/fr
note: Client mail desktop complet
- from: WhatsApp
to: Session
url: https://getsession.org
note: "Pas de numéro de téléphone, réseau Oxen décentralisé"
- from: "Slack / Teams"
to: "Element (Matrix)"
url: https://element.io
note: Protocole décentralisé et fédéré
- from: "Zoom / Teams"
to: Jitsi Meet
url: https://meet.jit.si
note: Visio sans compte requis
- label: "Navigateur & stockage"
items:
- from: Chrome
to: Firefox
url: https://www.mozilla.org/fr/firefox
note: Standard libre de référence
- from: Chrome
to: Librewolf
url: https://librewolf.net
note: Firefox durci pour la vie privée
- from: "Dropbox / Drive"
to: Nextcloud
url: https://nextcloud.com/fr
note: "Fichiers, agenda, contacts — auto-hébergeable"
- from: Dropbox
to: Syncthing
url: https://syncthing.net
note: "Sync pair-à-pair, sans serveur central"
- label: Développement
items:
- from: VSCode
to: VSCodium
url: https://vscodium.com
note: VSCode sans télémétrie Microsoft
- from: Spotify
to: Navidrome
url: https://www.navidrome.org
note: Serveur musique auto-hébergé
- type: llm
title: Un assistant shell local — sans cloud
text: >
Un petit LLM open source sur ta machine répond à tes questions sur les commandes Linux
en langage naturel, sans envoyer aucune donnée à un serveur distant.
Il suggère. Toi tu décides.
tool: Ollama
tool_url: https://ollama.com
model: "phi3:mini"
ram: "4 Go de RAM suffisent"
commands:
- "curl -fsSL https://ollama.com/install.sh | sh"
- "ollama pull phi3:mini"
- "ollama run phi3:mini \"Comment trouver les fichiers modifiés depuis 7 jours ?\""
rules:
- Lis chaque commande avant de l'exécuter — le LLM suggère, tu valides
- "Utilise --dry-run ou -n quand disponible (rsync, make…)"
- Pour les commandes root/sudo, vérification manuelle obligatoire
- "Préfère phi3:mini ou qwen2.5-coder — pas les modèles uncensored"
- type: atelier
title: Atelier migration — prochain Librodrome
text: >
Une demi-journée pour passer à Linux en douceur, en groupe.
Chaque participant repart avec une clé USB Linux Mint bootable
et un plan de migration personnalisé.
format: "Demi-journée — 6 à 12 participants"
programme:
- icon: usb
label: Live USB — tester Linux sans risque sur sa propre machine
- icon: list
label: "Équivalents logiciels — chacun fait son inventaire personnel"
- icon: hard-drive
label: Installation guidée sur machine volontaire
- icon: package
label: "Gestionnaire de paquets — installer ce dont on a besoin"
- icon: users
label: Questions ouvertes et suivi post-atelier
- type: links
title: Ressources
items:
- label: Framalibre
url: https://framalibre.org
desc: "Annuaire du logiciel libre — plus de 1000 logiciels référencés"
- label: Framasoft
url: https://framasoft.org/fr/
desc: Écosystème et alternatives éthiques au numérique centralisé
- label: Soyez résolus
url: https://soyezresolu.org/
desc: Guide pratique pour libérer les usages numériques (associations)
- label: Guide d'installation Linux Mint
url: https://linuxmint-installation-guide.readthedocs.io/en/latest/
desc: Guide officiel Linux Mint
- label: Documentation Ubuntu francophone
url: https://doc.ubuntu-fr.org/debutant
desc: Guide débutant Ubuntu en français
- label: Forum ubuntu-fr
url: https://forum.ubuntu-fr.org
desc: Communauté d'entraide Linux en français
- label: April
url: https://www.april.org
desc: Association pour la promotion du logiciel libre depuis 1996
- label: Ollama
url: https://ollama.com
desc: Installer et gérer des LLMs locaux open source
- label: Librezo
url: https://librezo.fr
desc: Collectif d'artisans numériques travaillant exclusivement en logiciel libre

View File

@@ -8,9 +8,9 @@ navigation:
- label: numérique
to: /numerique
- label: économique
to: /modele-eco
to: /economique
- label: citoyenne
to: /decision
to: /citoyenne
extra:
- label: Événement
to: /evenement