Compare commits
10 Commits
3a5c40a886
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b9e6b4a96c | ||
|
|
c52fa6007d | ||
|
|
d4ff840e13 | ||
|
|
7691cc4139 | ||
|
|
088333e4d4 | ||
|
|
07449de187 | ||
|
|
9d92c4a5b3 | ||
|
|
8f548afb17 | ||
|
|
9caf11c8ab | ||
|
|
c564e7be5f |
58
CLAUDE.md
58
CLAUDE.md
@@ -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
101
CONTRIBUTING.md
Normal 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
124
README.md
@@ -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`.
|
||||
|
||||
12
app/app.vue
12
app/app.vue
@@ -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'
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
33
app/components/book/Actions.vue
Normal file
33
app/components/book/Actions.vue
Normal 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>
|
||||
@@ -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;
|
||||
|
||||
@@ -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"
|
||||
>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
class="msg-input"
|
||||
/>
|
||||
<input
|
||||
v-model="form.email"
|
||||
type="email"
|
||||
placeholder="Email (optionnel)"
|
||||
class="msg-input"
|
||||
/>
|
||||
</div>
|
||||
<input
|
||||
v-model="form.author"
|
||||
type="text"
|
||||
placeholder="Votre nom"
|
||||
class="msg-input"
|
||||
/>
|
||||
<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>·</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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 }
|
||||
|
||||
20
app/composables/useTracking.ts
Normal file
20
app/composables/useTracking.ts
Normal 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 }
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
97
app/pages/admin/pages/index.vue
Normal file
97
app/pages/admin/pages/index.vue
Normal 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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
159
app/pages/citoyenne/[slug].vue
Normal file
159
app/pages/citoyenne/[slug].vue
Normal 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>
|
||||
631
app/pages/citoyenne/index.vue
Normal file
631
app/pages/citoyenne/index.vue
Normal 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>
|
||||
@@ -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>
|
||||
453
app/pages/economique/commande.vue
Normal file
453
app/pages/economique/commande.vue
Normal 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>
|
||||
647
app/pages/economique/index.vue
Normal file
647
app/pages/economique/index.vue
Normal 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>
|
||||
@@ -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>
|
||||
583
app/pages/economique/modele-eco/index.vue
Normal file
583
app/pages/economique/modele-eco/index.vue
Normal 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. {{ 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>
|
||||
119
app/pages/economique/monnaie-libre.vue
Normal file
119
app/pages/economique/monnaie-libre.vue
Normal 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>
|
||||
85
app/pages/economique/productions-collectives.vue
Normal file
85
app/pages/economique/productions-collectives.vue
Normal 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>
|
||||
@@ -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">♪</text>
|
||||
<text x="145" y="68" fill="currentColor" opacity="0.2" font-size="11">♫</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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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>·</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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
1902
app/pages/numerique/[slug].vue
Normal file
1902
app/pages/numerique/[slug].vue
Normal file
File diff suppressed because it is too large
Load Diff
586
app/pages/numerique/index.vue
Normal file
586
app/pages/numerique/index.vue
Normal 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"></></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>
|
||||
@@ -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])
|
||||
|
||||
@@ -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(),
|
||||
}),
|
||||
}),
|
||||
|
||||
@@ -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 ».*
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 ?
|
||||
|
||||
@@ -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 ?
|
||||
|
||||
@@ -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
140
content/book/06-produire.md
Normal 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é.
|
||||
@@ -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.*
|
||||
83
content/book/07-echanger.md
Normal file
83
content/book/07-echanger.md
Normal 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.
|
||||
@@ -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.
|
||||
|
||||
@@ -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 ?
|
||||
|
||||
@@ -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 dé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. »
|
||||
|
||||
@@ -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.
|
||||
|
||||
52
docker/docker-compose.umami.yml
Normal file
52
docker/docker-compose.umami.yml
Normal 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
|
||||
@@ -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: '',
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
39
server/api/admin/content/pages.get.ts
Normal file
39
server/api/admin/content/pages.get.ts
Normal 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)
|
||||
})
|
||||
21
server/api/admin/content/pages/[...path].put.ts
Normal file
21
server/api/admin/content/pages/[...path].put.ts
Normal 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 }
|
||||
})
|
||||
@@ -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 }
|
||||
})
|
||||
@@ -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 }
|
||||
})
|
||||
|
||||
@@ -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 }
|
||||
})
|
||||
|
||||
@@ -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())
|
||||
})
|
||||
|
||||
@@ -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` })
|
||||
}
|
||||
})
|
||||
@@ -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
|
||||
})
|
||||
|
||||
@@ -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 }
|
||||
})
|
||||
|
||||
43
server/api/stats/index.get.ts
Normal file
43
server/api/stats/index.get.ts
Normal 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,
|
||||
}
|
||||
})
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -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')
|
||||
}
|
||||
|
||||
@@ -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
22
site/pages/citoyenne.yml
Normal 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
|
||||
21
site/pages/citoyenne/decision.yml
Normal file
21
site/pages/citoyenne/decision.yml
Normal 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.
|
||||
20
site/pages/citoyenne/tarifs-eau.yml
Normal file
20
site/pages/citoyenne/tarifs-eau.yml
Normal 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
29
site/pages/economique.yml
Normal 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
|
||||
23
site/pages/economique/commande.yml
Normal file
23
site/pages/economique/commande.yml
Normal 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.
|
||||
59
site/pages/economique/monnaie-libre.yml
Normal file
59
site/pages/economique/monnaie-libre.yml
Normal 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
|
||||
16
site/pages/economique/productions-collectives.yml
Normal file
16
site/pages/economique/productions-collectives.yml
Normal 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.
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
208
site/pages/numerique/authentification-wot.yml
Normal file
208
site/pages/numerique/authentification-wot.yml
Normal 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
|
||||
340
site/pages/numerique/cloud-libre.yml
Normal file
340
site/pages/numerique/cloud-libre.yml
Normal 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
|
||||
243
site/pages/numerique/logiciel-libre.yml
Normal file
243
site/pages/numerique/logiciel-libre.yml
Normal 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
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user