Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f1dbf21723 | ||
|
|
28a6daf382 | ||
|
|
bb8fad9ff3 | ||
|
|
b6143c5cf7 | ||
|
|
16d66072b9 | ||
|
|
8de9a81b65 | ||
|
|
44e9576b38 | ||
|
|
fb8c71fc59 | ||
|
|
8ccdda39a9 | ||
|
|
d2bc0d9e46 | ||
|
|
cdd3fa57de | ||
|
|
44dbcfd5cf | ||
|
|
26ce315aa6 | ||
|
|
64e714c1dd | ||
|
|
03f763ab57 | ||
|
|
230f79a8c0 | ||
|
|
89d5f83c7f | ||
|
|
4057f50bd3 | ||
|
|
c32d88bec1 |
@@ -50,7 +50,7 @@ site/
|
||||
site.yml # Config globale (nav, footer, GrateWizard)
|
||||
bookplayer.config.yml # Config player/chapitres
|
||||
data/
|
||||
messages.yml # Runtime — volume Docker ../data:/src/data — JAMAIS dans git
|
||||
messages.yml # Runtime — prod : volume nommé librodrome-data sur /app/data — JAMAIS dans git
|
||||
server/
|
||||
api/content/ # GET pages YAML (chemins imbriqués)
|
||||
api/admin/content/ # PUT pages YAML + liste
|
||||
@@ -64,17 +64,19 @@ docker/
|
||||
|
||||
## Données runtime (CRITIQUE)
|
||||
|
||||
- `data/messages.yml` : volume Docker monté `../data:/src/data` (relatif à `docker/`)
|
||||
- Persisté entre les rebuilds — **jamais écrasé par les commits ni par le déploiement**
|
||||
- `data/messages.yml` : en prod, volume nommé `librodrome-data` (`external: true`) monté
|
||||
sur `/app/data` (l'image tourne dans `/app`, le serveur lit `process.cwd()/data`).
|
||||
Créé et semé à la main sur sonic par l'acte `actes/2026-10-26-persistance-messages.md` ;
|
||||
un déploiement sans ce volume échoue au lieu de repartir de la graine.
|
||||
- Persisté entre les rebuilds depuis le 26/10/2026 — **jamais écrasé par les commits ni
|
||||
par le déploiement** (avant, il ne l'a jamais été : voir le dossier d'acte)
|
||||
- Structure message : `{ id, author, email, text, type, published, createdAt, reply: { text, publishedAt } | null }`
|
||||
- Types : `reaction` (ancien, affiché "Réaction", plus proposé dans les formulaires) | `question` | `suggestion` | `retour`
|
||||
- En dev local : `<racine>/data/messages.yml`
|
||||
- Graine : `site/messages.yml` (versionné) copié par `server/plugins/seed-messages.ts`
|
||||
si `data/messages.yml` est absent
|
||||
- ⚠ Constat du 26/10/2026 : l'image tourne dans `/app` (WORKDIR depuis `07bf07a`) et
|
||||
résout `data/` depuis `process.cwd()`, alors que le compose monte `/src/data` et
|
||||
`/src/public`. La persistance en prod n'est donc pas assurée par la config ; effet
|
||||
sur le serveur non vérifié (voir HANDOFF).
|
||||
- ⚠ Hors volume, rien ne persiste : éditions /admin de `site/` et `content/`, médias
|
||||
envoyés par /admin (`/app/public`, jamais servis). Décisions d'Yvv en attente (HANDOFF).
|
||||
- **Avant toute migration de chemin ou écriture sur data/ : demander confirmation**
|
||||
|
||||
## Intégration GrateWizard
|
||||
@@ -147,10 +149,19 @@ prod) ; `data/messages.yml` ; lien de commande du livre (Bookelis,
|
||||
|
||||
## Conventions
|
||||
|
||||
- **Consignes d'Yvv**, datées et mot pour mot : `docs/consignes-yvv.md`. Les relire avant de
|
||||
toucher au design ; y inscrire toute nouvelle consigne le jour où elle est donnée.
|
||||
- CSS via UnoCSS + variables CSS palettes ; composants Vue SFC `<script setup lang="ts">`
|
||||
- Shadoks SVG inline thématiques sur chaque page (hidden mobile, opacity 0.18–0.28)
|
||||
- Oiseaux du kit du studio DA (depuis le 03/11/2026, à la place des shadoks) : `UiOiseau`,
|
||||
`app/utils/oiseaux.ts` (copiés tels quels du kit), posés par `UiOiseauPose` près du contenu
|
||||
de leur métier, jamais en rangée. Règles du kit : currentColor, au moins 72 px, opacité
|
||||
0,18–0,28 près du texte et 0,10–0,14 derrière une carte, un oiseau jamais deux fois sur une
|
||||
page, un seul sous 768 px, pas de miroir pour calligraphe et typographe. Consignes :
|
||||
`docs/consignes-yvv.md` § Les oiseaux.
|
||||
- Hexagramme 益 (#42 Yi, Augmentation) dans `layouts/default.vue`
|
||||
- Signature § (logo calligraphique SVG gradient) dans `TheHeader.vue` — ne pas modifier sans demander
|
||||
- Logotype validé du studio DA (`UiLogotype`) dans `TheHeader.vue`, et `UiSectionSign` du
|
||||
kit — ne pas modifier sans demander. Source du kit :
|
||||
`/home/yvv/Documents/PROD/IMAGES/_atelier/2026-10-27-librodrome-logo/livrables/kit-site/`.
|
||||
|
||||
## SEO & Recherche IA — Checklist permanente
|
||||
|
||||
|
||||
@@ -1,5 +1,28 @@
|
||||
# HANDOFF — librodrome
|
||||
|
||||
## ÉTAT — 2026-10-26 vers 19h50
|
||||
|
||||
**En ligne** : `4057f50` (code de `a6ba0a6`), les messages des visiteurs sont persistés dans le volume
|
||||
nommé `librodrome-data` sur `/app/data`. Acte exécuté le 26/10, 19h38-19h44, par la
|
||||
session dev-65 (orch), et vérifié : sauvegarde, veille, bascule, redémarrage à froid,
|
||||
1 message conservé, 0 graine rejouée. Dossier : `actes/2026-10-26-persistance-messages.md`.
|
||||
|
||||
**Restent à Yvv** (dossier, § Hors champ) :
|
||||
1. Éditions /admin (`site/`, `content/`), toujours perdues à chaque déploiement :
|
||||
/admin en lecture seule, git sync rétabli, ou volumes (déconseillé).
|
||||
2. Médias /admin : écrits dans `/app/public`, jamais servis.
|
||||
3. Sauvegarde de sonic `~/librodrome-sauvegardes/2026-10-26/` (vraies données de
|
||||
visiteurs) : la garder, ou la supprimer après un déploiement suivant réussi (« supprime »).
|
||||
4. Canon démenti : `/DEV/AGENTS.md` (Démarrage §3) et `harness/conventions/git.md`
|
||||
disent encore que librodrome écrit du YAML en prod via /admin et git sync.
|
||||
|
||||
**Preuve en vraie grandeur, faite** : déploiement suivant (`4057f50`, doc seule, pipeline
|
||||
n° 59, sur le « Déploie » d'Yvv) : conteneur recréé, `messages.yml` inchangé
|
||||
(`c8afc7de…a6b8`), 0 graine rejouée.
|
||||
|
||||
**Local, non poussé** : le commit qui consigne cette preuve (doc seule) ; il part avec
|
||||
le prochain déploiement.
|
||||
|
||||
## REPRISE — bascule quota 2026-09-29 14h05
|
||||
|
||||
**Fait, en production** (librodrome.org, déployé et vérifié ; armé par Yvv) :
|
||||
@@ -9,9 +32,8 @@
|
||||
(`UiStatusBadge`) ; LaPlanK, sweetHome, bouquet aligné sur les stacks
|
||||
HCO/LaPlanK ; correctifs du lecteur (pastille, MP3 direct).
|
||||
- `90f77da` : image OG et favicon avec le §, logo JSON-LD corrigé.
|
||||
- Local, non poussé : `1c7cc45` (compte rendu d'acte), `852a488` (ce HANDOFF)
|
||||
et la fusion fiche + boucle dans `CLAUDE.md` du 26/10/2026 ; doc seule, part
|
||||
avec le prochain déploiement. Dossiers : `actes/2026-09-29-*.md`.
|
||||
- `1c7cc45`, `852a488` et `23b1767` (doc) sont partis avec `a6ba0a6` le 26/10.
|
||||
Dossiers : `actes/2026-09-29-*.md`.
|
||||
|
||||
**Prochaine action** : aucune en cours. Attendre les retours d'Yvv sur la prod.
|
||||
|
||||
@@ -28,6 +50,8 @@
|
||||
neuf : messages visiteurs re-semés depuis `site/messages.yml`, éditions /admin
|
||||
perdues. Rétablir les montages (`/app/...`) et décider du sort du git sync
|
||||
touche aux données de prod : à trancher par Yvv avant le prochain déploiement.
|
||||
→ **26/10/2026, vérifié sur sonic : confirmé**, puis corrigé et mis en ligne le même
|
||||
jour (voir ÉTAT ci-dessus).
|
||||
|
||||
**Ce qui tourne** : rien (serveur de dev sur 3000 arrêté).
|
||||
|
||||
|
||||
@@ -0,0 +1,281 @@
|
||||
# Acte — persistance des messages de librodrome.org (volume nommé sur /app/data)
|
||||
Classe : déploie (un volume créé et semé sur sonic, puis le push de `main`, qui met en ligne sans étape de test)
|
||||
Projet : librodrome Date : 2026-10-26
|
||||
**État : EXÉCUTÉ** le 26/10/2026, 19h38-19h44, par la session dev-65 (orch, sous
|
||||
mandat de rôle, relecture indépendante inscrite) ; préparé vers 18h par la session de
|
||||
diagnostic. Section d'exécution en fin de dossier.
|
||||
|
||||
## Constat (diagnostic du 26/10/2026, en lecture seule)
|
||||
|
||||
| Fait | Preuve (26/10/2026, sauf date indiquée) |
|
||||
|---|---|
|
||||
| La prod tourne sur sonic | `getent ahostsv4 librodrome.org` → 161.97.174.60 (sonic, `harness/conventions/infra.md`) |
|
||||
| Un seul conteneur : `lpk-librodrome-main-app-1`, image `lpk-librodrome-main-app` (`sha256:8fe50a5b7c65…`), créé le 2026-09-29T06:59:03Z | `docker ps -a`, `docker inspect` |
|
||||
| Répertoire de travail `/app` ; le serveur lit et écrit `process.cwd()/data`, donc `/app/data` | `docker inspect` (`WorkingDir=/app`) ; `server/utils/content.ts`, `server/plugins/seed-messages.ts` |
|
||||
| Montages : deux binds seulement, `…/LPK/librodrome/data → /src/data` et `…/LPK/librodrome/docker/public → /src/public`, aucun volume nommé | `docker inspect` (`.Mounts`, `.HostConfig.Binds`) |
|
||||
| Les sources de ces binds sont des dossiers vides de root sous `/woodpecker/src/git.open.us.org/LPK/librodrome/`, créés par le démon le 2026-08-12 à 00h58m36Z, la seconde où s'est terminé le pipeline #55. L'ancien chemin `EHV/librodrome/` est vide lui aussi (créé le 23/03 à 03h10m44Z, fin du #36 à 03h10m45Z). | `ls -la --time-style=full-iso`, `find` sur sonic ; API CI. Mécanisme : la CI lance compose depuis son espace de travail, et un chemin relatif y désigne un chemin de l'hôte qui n'existe pas (déjà noté en 8a38c86, 28/02). |
|
||||
| `/app/data/messages.yml` vit dans la couche du conteneur, et non dans un montage : 1099 octets, daté du 2026-09-29T06:59:05Z, sha256 `c8afc7de…a6b8`, identique à la graine `site/messages.yml`, avec 1 message (celui d'Yvv) | `docker exec … sha256sum`, `stat`, `grep -c` ; `docker diff` → `A /app/data/messages.yml` |
|
||||
| Graine rejouée au premier démarrage du conteneur actuel | `docker logs` : `2026-09-29T06:59:05Z [seed-messages] data/messages.yml initialisé depuis site/messages.yml`, une seule fois. Les redémarrages des 12/10 et 18/10 (×4) ne l'ont pas rejouée : un redémarrage garde la couche, une recréation (un déploiement) la détruit. |
|
||||
| Aucune édition /admin dans le conteneur actuel | `docker diff` : rien sous `/app/site` ni `/app/content` |
|
||||
| `ADMIN_GIT_SYNC=false`, pas de `/app/.git`, et aucun commit `[admin]` dans l'historique | `docker inspect` (variable non secrète) ; `ls -d /app/.git` → absent ; `git log --all --grep='^\[admin\]'` → 0 |
|
||||
| Les secrets `NUXT_ADMIN_PASSWORD` et `NUXT_ADMIN_SECRET` sont définis ; les variables Umami sont vides | `docker inspect`, longueur seulement (aucune valeur relevée) |
|
||||
| 42 déploiements réussis sur `main` du 25/02 au 29/09 (#4 à #57) ; les derniers : #46 le 24/04, #50 le 24/07 (manuel, 10 s : recréation non vérifiée), #55 le 12/08, #56 et #57 le 29/09 | `https://ci.open.us.org/api/repos/1/pipelines` (lecture anonyme, Woodpecker 3.19.0) |
|
||||
|
||||
**Diagnostic** : la persistance de la prod est cassée, et elle l'a toujours été.
|
||||
Avant le 19/03 (`b9e6b4a`), les messages s'écrivaient dans `site/messages.yml`, dans
|
||||
le conteneur ; depuis, dans `/app/data`, dans le conteneur aussi. Chaque déploiement
|
||||
a donc remis les messages à zéro, puis à la graine à partir du 28/03 (`f633940`).
|
||||
Les éditions /admin de `site/` et `content/` ont subi le même sort, et ne sont jamais
|
||||
revenues dans git.
|
||||
|
||||
**Pertes** : depuis le 29/09, rien n'a été perdu (aucun message reçu, aucune édition).
|
||||
Avant cette date, ce qui a été perdu n'est pas vérifiable : les conteneurs détruits
|
||||
emportent leur couche, et les journaux n'en gardent pas trace (le journal de
|
||||
l'application commence le 29/09, celui de fabio le 07/10).
|
||||
|
||||
## Intention
|
||||
Monter les messages des visiteurs sur un volume nommé `librodrome-data` en
|
||||
`/app/data`, pour qu'ils survivent aux déploiements. Il faut le faire maintenant :
|
||||
au prochain push, tout message reçu d'ici là serait perdu. Le code est prêt sur la
|
||||
branche locale `fix/persistance-messages` (a6ba0a6), et il a été répété en local.
|
||||
|
||||
## Avant — prémisses vivantes
|
||||
|
||||
Sur le poste, dans `/home/yvv/Documents/PROD/DEV/librodrome` (attendu en commentaire) :
|
||||
|
||||
```bash
|
||||
git status --short # P1 : vide
|
||||
git fetch -q origin && git rev-parse origin/main # P2 : 90f77da49b1c77faf9807ba48740b0abd22aff46
|
||||
# sinon ARRÊT : quelqu'un a poussé (pull --rebase, rebaser la branche, refaire le dossier)
|
||||
git rev-parse fix/persistance-messages # P3 : a6ba0a6662d54e6ed42ef26ce9e1411e873a2484
|
||||
git merge-base --is-ancestor origin/main a6ba0a6662d54e6ed42ef26ce9e1411e873a2484; echo $? # P4 : 0
|
||||
git diff --name-only origin/main a6ba0a6662d54e6ed42ef26ce9e1411e873a2484 \
|
||||
| grep -E '(^|/)data/|\.env|\.db$|\.enc$|\.key$|\.pem$|id_rsa|id_ed25519|IDENTIFIANTS'; echo $? # P5 : 1 (rien)
|
||||
curl -4 -s 'https://ci.open.us.org/api/repos/1/pipelines?perPage=3' \
|
||||
| python3 -I -c 'import sys,json; [print(p["number"], p["status"]) for p in json.load(sys.stdin)]' # P6 : « 57 success » en tête, rien en running ni pending
|
||||
curl -4 -s -o /dev/null -w '%{http_code}\n' https://librodrome.org/api/health # P7 : 200 (server/api/health.ts ne fait que répondre)
|
||||
curl -4 -s https://librodrome.org/api/messages \
|
||||
| python3 -I -c 'import sys,json; print(len(json.load(sys.stdin)))' # P8 : 1 au 26/10 (lecture seule : server/api/messages/index.get.ts)
|
||||
```
|
||||
Pour P8, un autre nombre veut dire que des messages ont été publiés depuis : le
|
||||
noter, il doit se retrouver après.
|
||||
|
||||
Sur sonic, les prémisses sont vérifiées par le script de l'étape 1, qui s'arrête en
|
||||
les nommant : conteneur en marche, `WorkingDir=/app`, `/app/data` non monté, volume
|
||||
`librodrome-data` absent, dossier de sauvegarde absent, `docker diff` vide sous
|
||||
`/app/site` et `/app/content`. Une édition /admin trouvée arrête l'acte : elle doit
|
||||
d'abord passer dans git, sinon ce déploiement l'effacerait.
|
||||
|
||||
Contenu sortant (`git log origin/main..a6ba0a6 --stat`, relevé le 26/10) :
|
||||
```
|
||||
a6ba0a6 2026-10-26 docker : messages persistés dans un volume nommé monté sur /app/data
|
||||
docker/docker-compose.dev.yml | 3 ++-
|
||||
docker/docker-compose.yml | 16 ++++++++++++++--
|
||||
23b1767 2026-10-26 claude.md : fiche et boucle de vérification fusionnées
|
||||
CLAUDE.md | 76 +++… HANDOFF.md | 11 +++…
|
||||
852a488 2026-09-29 handoff : bloc REPRISE (bascule quota 2026-09-29)
|
||||
HANDOFF.md | 39 +++…
|
||||
1c7cc45 2026-09-29 actes : og + favicon — exécuté et vérifié en production
|
||||
actes/2026-09-29-og-logo-mise-en-ligne.md | 8 +++++---
|
||||
```
|
||||
Le diff du compose retire deux lignes, les montages morts `../data:/src/data` et
|
||||
`./public:/src/public`, et en ajoute une, `data:/app/data`, plus le bloc `volumes:`
|
||||
(`librodrome-data`, `external: true`). Ce dossier n'est pas dans le push : il est
|
||||
sur `main` en local, et partira avec le déploiement suivant.
|
||||
|
||||
## Commandes exactes
|
||||
|
||||
Depuis le poste, dans `/home/yvv/Documents/PROD/DEV/librodrome`. Les scripts sont
|
||||
dans `actes/annexes/`, et chacun se retire de `/tmp` à la fin.
|
||||
|
||||
```bash
|
||||
# Étape 1 — sauvegarde, puis volume créé et semé (sonic ; le conteneur en service n'est pas touché)
|
||||
scp -q -o BatchMode=yes actes/annexes/2026-10-26-persistance-messages-avant.sh yvv@161.97.174.60:/tmp/lb-avant.sh \
|
||||
&& scp -q -o BatchMode=yes actes/annexes/2026-10-26-persistance-messages-veille.sh yvv@161.97.174.60:/tmp/lb-veille.sh \
|
||||
&& scp -q -o BatchMode=yes actes/annexes/2026-10-26-persistance-messages-apres.sh yvv@161.97.174.60:/tmp/lb-apres.sh \
|
||||
&& ssh -o BatchMode=yes yvv@161.97.174.60 'bash /tmp/lb-avant.sh' </dev/null
|
||||
# attendu : « sauvegarde : N message(s), sha256 … », « volume librodrome-data semé : sha256 … » (même empreinte), « ÉTAPE 1 OK »
|
||||
|
||||
# Étape 2 — veille sur l'ancien conteneur, jusqu'à sa disparition (30 min au plus)
|
||||
ssh -o BatchMode=yes yvv@161.97.174.60 'setsid nohup bash /tmp/lb-veille.sh >/tmp/lb-veille.log 2>&1 &' </dev/null
|
||||
|
||||
# Étape 3 — mise en ligne (push du SHA vérifié), puis attente du pipeline
|
||||
for i in 1 2 3 4 5; do if git push -q origin a6ba0a6662d54e6ed42ef26ce9e1411e873a2484:main; then break; fi; sleep 10; done; git status -sb | head -1
|
||||
for i in $(seq 1 40); do s=$(curl -4 -s -m 20 'https://ci.open.us.org/api/repos/1/pipelines?perPage=5' | python3 -I -c 'import sys,json; print(next((p["status"] for p in json.load(sys.stdin) if p["commit"].startswith("a6ba0a6662d5")), "absent"))'); echo "$(date +%T) $s"; case "$s" in success|failure|error|killed|declined) break;; esac; sleep 15; done
|
||||
# attendu : « success » en 3 min environ (#56 : 3 min 18 s, #57 : 2 min 36 s)
|
||||
|
||||
# Étape 4 — vérification sur sonic : bascule, veille, redémarrage à froid
|
||||
ssh -o BatchMode=yes yvv@161.97.174.60 'cat /tmp/lb-veille.log; bash /tmp/lb-apres.sh && rm -f /tmp/lb-veille.log' </dev/null
|
||||
# attendu : « ancien conteneur remplacé », « veille : dernière relève = sauvegarde », deux fois « fichier du volume = sauvegarde », « ÉTAPE 4 OK »
|
||||
|
||||
# Étape 5 — vu de l'extérieur
|
||||
curl -4 -s -o /dev/null -w '%{http_code}\n' https://librodrome.org/api/health
|
||||
curl -4 -s -o /dev/null -w '%{http_code}\n' https://librodrome.org/
|
||||
curl -4 -s https://librodrome.org/api/messages | python3 -I -c 'import sys,json; print(len(json.load(sys.stdin)))'
|
||||
# attendu : 200, 200, le nombre relevé en P8
|
||||
|
||||
# Étape 6 — branche locale sur la tête déployée, puis compte rendu
|
||||
git switch main && git rebase a6ba0a6662d54e6ed42ef26ce9e1411e873a2484
|
||||
```
|
||||
|
||||
Si le pipeline échoue à l'étape 3 (par exemple sur `external volume "librodrome-data"
|
||||
not found`), l'ancien conteneur continue de servir, sans changement : la répétition
|
||||
l'a montré. La veille s'arrête seule au bout de 30 min. On ne relance rien : on
|
||||
rapporte.
|
||||
|
||||
## Effet attendu
|
||||
- librodrome.org est reconstruit par Woodpecker. `/app/data` devient le volume
|
||||
`librodrome-data`, et les messages survivent désormais aux déploiements, aux
|
||||
redémarrages et à `docker compose down -v` (volume `external`).
|
||||
- Les montages morts `/src/data` et `/src/public` disparaissent. Les dossiers vides
|
||||
de root sous `/woodpecker/src/git.open.us.org/{EHV,LPK}/librodrome/` restent sur
|
||||
l'hôte, sans effet ; les retirer demande root (hors acte).
|
||||
- Ce qui ne change pas : les éditions /admin de `site/` et `content/` restent perdues
|
||||
à chaque déploiement, les médias envoyés par /admin ne sont toujours pas servis, et
|
||||
`ADMIN_GIT_SYNC` reste à `false` (décisions d'Yvv, voir plus bas).
|
||||
- Fenêtre : entre la sauvegarde et la bascule (3 min environ), un message posté va
|
||||
dans l'ancien conteneur. La veille le relève toutes les 5 s, et l'étape 4 s'arrête
|
||||
s'il y en a un. Le risque résiduel tient en quelques secondes : la dernière relève,
|
||||
puis l'arrêt du conteneur. Le débit observé est de 0 message en 27 jours.
|
||||
- Irréversibilité : réversible (voir Retour). Ni l'acte ni son retour ne suppriment le
|
||||
volume ni la sauvegarde.
|
||||
|
||||
## Vérifications après
|
||||
- [x] P7 et P8 rejoués : `200`, et le même nombre de messages publiés
|
||||
- [x] `lb-apres.sh` : `/app/data` monté sur le volume `librodrome-data`, aucun autre montage, 0 ligne `seed-messages`, fichier du volume = sauvegarde, idem après `docker restart`
|
||||
- [x] La veille a vu partir l'ancien conteneur, et sa dernière relève est identique à la sauvegarde
|
||||
- [x] Pipeline du commit `a6ba0a6662d5` : `success`
|
||||
- [x] Sauvegarde présente sur sonic : `~/librodrome-sauvegardes/2026-10-26/` (700, fichiers 600 ; `messages.yml`, `SHA256SUMS`, `ancien-conteneur.id`, relève de la veille)
|
||||
- [x] Au déploiement suivant, quel qu'il soit : `docker exec lpk-librodrome-main-app-1 sha256sum /app/data/messages.yml` inchangé, et aucune ligne `seed-messages`. C'est la preuve en vraie grandeur.
|
||||
|
||||
## Retour
|
||||
|
||||
Retour du code, par un nouvel acte « déploie » :
|
||||
```bash
|
||||
git switch main && git revert --no-edit a6ba0a6662d54e6ed42ef26ce9e1411e873a2484 \
|
||||
&& for i in 1 2 3 4 5; do if git push -q origin "$(git rev-parse HEAD)":main; then break; fi; sleep 10; done
|
||||
```
|
||||
Effet du retour, répété en local le 26/10 : le conteneur est recréé sans le volume
|
||||
et re-semé depuis la graine. Le volume `librodrome-data` reste intact, avec tous les
|
||||
messages reçus depuis la bascule ; redéployer le correctif les remonte.
|
||||
|
||||
Arrêt après l'étape 1, avant le push : rien à défaire en production, puisque le
|
||||
conteneur n'a pas changé. Le volume et la sauvegarde restent ; un second passage de
|
||||
l'étape 1 s'arrêtera sur « le volume existe déjà », ce qui est voulu. Les retirer est
|
||||
un acte « supprime » à part.
|
||||
|
||||
La sauvegarde de sonic contient de vraies données de visiteurs (auteurs, adresses).
|
||||
Elle reste sur sonic, et ne quitte pas le serveur, jusqu'à ce qu'Yvv décide ; sa
|
||||
suppression est un acte « supprime ».
|
||||
|
||||
## Répétition (26/10/2026, sur le poste)
|
||||
Faite avec le vrai docker (29.4.1, compose v5.1.3) et les noms exacts de la prod :
|
||||
projet `lpk-librodrome-main`, conteneur `lpk-librodrome-main-app-1`, image locale
|
||||
`lpk-librodrome-main-app` (`node:22-bookworm-slim` plus le `.output`, `site/` et
|
||||
`content/` du dépôt, en couche d'image comme en prod). L'ancien conteneur venait de
|
||||
l'ancien compose (`main`), la bascule du compose d'a6ba0a6, et `HOME` pointait sur un
|
||||
faux dossier.
|
||||
- Nominal : étape 1 OK (sauvegarde, puis volume semé à la même empreinte), veille
|
||||
arrêtée à la bascule, étape 4 OK avant et après le redémarrage à froid, 0 graine
|
||||
rejouée.
|
||||
- Nouveau compose sans volume : `external volume "librodrome-data" not found`,
|
||||
sortie 1, ancien conteneur intact (même identifiant).
|
||||
- Étape 1 rejouée après la bascule : ARRÊT « /app/data est déjà monté ».
|
||||
- Édition /admin présente dans la couche : ARRÊT, aucun volume créé.
|
||||
- Message posté dans l'ancien conteneur après la sauvegarde : relevé par la veille
|
||||
(2 messages), étape 4 en ARRÊT avec la consigne.
|
||||
- Message posté après la bascule, puis `--force-recreate` : conservé, sans graine ;
|
||||
le volume survit à `down -v` ; le retour à l'ancien compose re-sème sans toucher au
|
||||
volume.
|
||||
- Second déroulé, après correction de la veille (elle relevait par nom, et le nom
|
||||
désigne le nouveau conteneur dès la bascule ; elle relève maintenant par
|
||||
identifiant, et laisse `veille.fin`), à 16h58-16h59Z :
|
||||
- étape 4 lancée avant le déploiement : ARRÊT « conteneur non recréé » ;
|
||||
- message posté après la bascule : la dernière relève reste égale à la
|
||||
sauvegarde, l'étape 4 passe avec la NOTE « fichier du volume changé (2
|
||||
messages) », avant et après le redémarrage ;
|
||||
- message tardif dans l'ancien conteneur : ARRÊT, comme au premier déroulé.
|
||||
- Non déroulés : la sortie de la veille au bout de 30 min sans bascule, et
|
||||
l'arrêt « la veille n'a pas constaté la bascule » (fenêtre de moins de 5 s
|
||||
entre la recréation et la fin de la veille).
|
||||
- Répétition retirée du poste : conteneur, volume et image locale supprimés, ainsi
|
||||
que `/tmp/lbacte` (rien que du factice et une copie du build).
|
||||
|
||||
Différences avec la prod : node 22 au lieu de 23.11 (sans effet sur les montages ni
|
||||
sur la graine), compose du poste au lieu de celui de l'image `docker:dind` de la CI,
|
||||
et pas de proxy fabio devant le conteneur.
|
||||
|
||||
## Hors champ — décisions d'Yvv
|
||||
1. **Éditions /admin** (`site/`, `content/`) : perdues à chaque déploiement, jamais
|
||||
remontées dans git. Trois voies : (a) /admin en lecture seule en prod, et le
|
||||
contenu ne s'édite plus que par git ; (b) rétablir le git sync : un clone avec
|
||||
droit d'écriture sur `LPK/librodrome` (acte « accorde »), `.git` monté,
|
||||
`ADMIN_GIT_SYNC=true` en CI, et chaque édition devient un push, donc un
|
||||
déploiement ; (c) des volumes sur `site/` et `content/`, ce qui fait deux
|
||||
vérités face à git (déconseillé).
|
||||
2. **Médias /admin** : `upload.post.ts` écrit dans `/app/public`, qui n'existe pas
|
||||
dans l'image, et Nitro ne sert que le manifeste figé à la construction
|
||||
(`.output/server/chunks/nitro/nitro.mjs`, `readAsset` et `assets[id]`). Un envoi
|
||||
n'est donc jamais servi. `/admin/media` lit `/app/public` : déduit du code, il
|
||||
doit répondre 500 (non sondé).
|
||||
3. **Canon démenti** : `/DEV/AGENTS.md` (Démarrage §3) et
|
||||
`harness/conventions/git.md` (« Début de session », point 1) disent que librodrome
|
||||
écrit du YAML en prod via /admin et git sync. C'est faux depuis l'origine
|
||||
(preuves ci-dessus). Même chose pour `CLAUDE.md` du dépôt, § Données runtime :
|
||||
« Persisté entre les rebuilds — jamais écrasé ».
|
||||
4. **Sauvegarde sur sonic** : la garder, ou la supprimer après un déploiement
|
||||
suivant réussi (acte « supprime »).
|
||||
|
||||
## Exécution
|
||||
|
||||
Acte exécuté le 26/10/2026, 19h38-19h44, par la session dev-65 (orch, mandat de rôle
|
||||
du 26-27/10). Relecture indépendante de a6ba0a6 et des scripts : un sous-agent neuf,
|
||||
verdict « ok » à 18h48, inscrite dans `state/relectures.json`. Sorties relevées par
|
||||
orch, puis consignées par la session librodrome-e9.
|
||||
|
||||
- **Prémisses** (19:38:45) : P1 vide, P2 `90f77da49b1c…`, P3 `a6ba0a6662d5…`, P4 `0`,
|
||||
P5 `1`, P6 `57 success` / `56 success` / `55 success`, P7 `200`, P8 `1`.
|
||||
Contrôle ajouté (relecture, m1) : `docker diff` du conteneur, 0 entrée sous
|
||||
`/app/(public|site|content)`.
|
||||
- **Étape 1** (19:39:43-19:39:51) : « sauvegarde : 1 message(s), sha256
|
||||
c8afc7de728fb9d57f6565a36b40f16fe7beb438768f8f9127183bea9569a6b8, 1099 2026-09-29
|
||||
06:59:05.542963509 +0000 » ; « volume librodrome-data semé : sha256 c8afc7de…a6b8 » ;
|
||||
« ÉTAPE 1 OK ».
|
||||
- **Étape 2** : veille lancée vers 19h40.
|
||||
- **Étape 3** : boucle de push lancée seule. Écart au déroulé : sans le
|
||||
`; git status -sb | head -1` final, que le garde refuse sous mandat. `origin/main` =
|
||||
`a6ba0a6662d54e6ed42ef26ce9e1411e873a2484`. Pipeline n° 58 : running à 19:42:41,
|
||||
success à 19:43:51.
|
||||
- **Étape 4** (jusqu'à 19:44:14) : « ancien conteneur remplacé, constaté à
|
||||
2026-10-26T18:43:46Z » ; « veille : dernière relève = sauvegarde
|
||||
(2026-10-26T18:43:41Z) » ; bascule, puis redémarrage à froid, deux fois « fichier du
|
||||
volume = sauvegarde (1 message(s)) » ; « ÉTAPE 4 OK ».
|
||||
Contrôle indépendant (relecture, m2) : journal du conteneur lu (2 lignes), 0 ligne
|
||||
`seed-messages` ; seul montage, le volume `librodrome-data` sur `/app/data` ; conteneur
|
||||
créé le 2026-10-26T18:43:43Z.
|
||||
- **Sauvegarde** sur sonic : `~/librodrome-sauvegardes/2026-10-26/` (700, fichiers 600 :
|
||||
`SHA256SUMS`, `ancien-conteneur.id`, `derniere-releve.date`,
|
||||
`messages-derniere-releve.yml`, `messages.stat`, `messages.yml`, `veille.fin`).
|
||||
`/tmp/lb-*` retirés, aucune veille en cours.
|
||||
- **Étape 5** (19:44:41) : health `200`, accueil `200`, messages `1`.
|
||||
- **Étape 6** : `main` locale rebasée sur `a6ba0a6` sans conflit ; ce dossier (`52f0a00`)
|
||||
devient `c32d88b`.
|
||||
|
||||
Mineurs de la relecture, non bloquants, à reprendre si l'on réutilise ces scripts :
|
||||
m1 `avant.sh` ne regarde pas `/app/public`, où /admin écrit ses médias (paré à la
|
||||
main, ci-dessus) ; m2 `|| true` sur `docker logs` dans `apres.sh` (paré par le contrôle
|
||||
indépendant) ; m3 pas de témoin de fin d'étape 1 ; m4 `CLAUDE.md` décrivait encore
|
||||
`../data:/src/data` (corrigé dans ce commit) ; m5 les statuts `blocked` et `skipped` ne
|
||||
sont pas prévus par la boucle de l'étape 3 ; m6 le dossier de sauvegarde est à retirer
|
||||
à la main si l'étape 1 échoue.
|
||||
|
||||
**Preuve en vraie grandeur** : faite au déploiement suivant, par la session
|
||||
librodrome-e9, sur le « Déploie » d'Yvv tapé dans cette session (26/10, vers 19h50 :
|
||||
« il bosse, je le court-circuite. Déploie […] »). Push de `4057f50` (doc seule) ; le
|
||||
pipeline n° 59 passe de running (19:51:39) à success (19:53:49). Sur sonic, en lecture
|
||||
seule : le conteneur `4f97cff481d8…`, créé à 18:43:43Z, est remplacé par `154b4906183c…`,
|
||||
créé à 18:53:43Z ; seul montage, `librodrome-data` sur `/app/data` ; `messages.yml` reste
|
||||
à `c8afc7de…a6b8` avant comme après ; 0 ligne `seed-messages` (journal : 1 ligne). Vu de
|
||||
l'extérieur (19:54:44) : health `200`, accueil `200`, messages `1`.
|
||||
|
||||
Reste ouvert : les décisions du § Hors champ.
|
||||
@@ -0,0 +1,109 @@
|
||||
# Acte — Console de l'accueil : la graduation d'Yvv au survol
|
||||
Classe : déploie
|
||||
Projet : librodrome Date : 2026-11-03
|
||||
**État : EXÉCUTÉ (03/11, 06h11-06h16), par orch (session dev-63) sous mandat « déploie » (inscrit du 03/11 au 08/11), après la relecture neuve « 64e714c : ok »**
|
||||
|
||||
## Intention
|
||||
Mettre en ligne la console de l'accueil selon la consigne d'Yvv du 03/11 : au survol,
|
||||
numérique monte à 3/4, économique à 1/2, citoyenne à 2/3. Chaque curseur reste à son cran,
|
||||
et un nouveau survol rejoue sa montée. Le panneau suit le même geste. Avant le premier geste,
|
||||
l'animation lente du 29/09 continue, et l'axe en avant monte à son cran, jamais au-dessus.
|
||||
Les consignes sont écrites mot pour mot dans `docs/consignes-yvv.md`. La première relecture
|
||||
(« 230f79a : à reprendre ») a été traitée par `03f763a` et `64e714c`.
|
||||
|
||||
## Avant — prémisses vivantes
|
||||
|
||||
Sur le poste, dans `/home/yvv/Documents/PROD/DEV/librodrome`. L'attendu, relevé le 03/11 à
|
||||
05h57, est en commentaire :
|
||||
|
||||
```bash
|
||||
git fetch -q origin && git rev-parse origin/main # P1 : 4057f50bd37fa8a3ba3ea9dc6c9c38d51d4f6434
|
||||
# sinon ARRÊT : quelqu'un a poussé
|
||||
git merge-base --is-ancestor origin/main 64e714c1dde9897e46d36caf6cab737799081c96; echo $? # P2 : 0
|
||||
git diff --name-only origin/main 64e714c1dde9897e46d36caf6cab737799081c96 \
|
||||
| grep -E '(^|/)data/|\.env|\.db$|\.enc$|\.key$|\.pem$|id_rsa|id_ed25519|IDENTIFIANTS'; echo $? # P3 : 1 (rien)
|
||||
curl -4 -s 'https://ci.open.us.org/api/repos/1/pipelines?perPage=3' \
|
||||
| python3 -I -c 'import sys,json; [print(p["number"], p["status"]) for p in json.load(sys.stdin)]' # P4 : « 59 success » en tête, rien en cours
|
||||
curl -4 -s -o /dev/null -w '%{http_code}\n' https://librodrome.org/api/health # P5 : 200
|
||||
curl -4 -s https://librodrome.org/api/messages \
|
||||
| python3 -I -c 'import sys,json; print(len(json.load(sys.stdin)))' # P6 : 1
|
||||
ssh -o BatchMode=yes yvv@161.97.174.60 'docker diff lpk-librodrome-main-app-1 | grep -E " /app/(site|content)(/|$)"; echo $?; docker exec lpk-librodrome-main-app-1 sha256sum /app/data/messages.yml' </dev/null
|
||||
# P7 : « 1 » (aucune édition /admin, que ce déploiement effacerait)
|
||||
# puis c8afc7de728fb9d57f6565a36b40f16fe7beb438768f8f9127183bea9569a6b8 /app/data/messages.yml
|
||||
```
|
||||
|
||||
Une édition /admin trouvée en P7 arrête l'acte : elle doit d'abord passer dans git.
|
||||
|
||||
Relecture : `~/harness/etat/relectures.json` doit porter « 64e714c : ok », d'un relecteur neuf.
|
||||
|
||||
Contenu sortant (`git log origin/main..64e714c --stat`, relevé le 03/11 à 05h57) :
|
||||
```
|
||||
64e714c console : l'animation monte l'axe en avant à son cran
|
||||
03f763a console : graduation d'Yvv, la montée se rejoue à chaque survol
|
||||
230f79a console : au survol, le curseur monte sans faire descendre les autres
|
||||
89d5f83 actes : persistance des messages — preuve au déploiement suivant
|
||||
actes/2026-10-26-persistance-messages.md | 14 ++-
|
||||
app/components/home/HomeConsole.vue | 166 ++++++++++++++++++++++++-----
|
||||
docs/consignes-yvv.md | 50 ++++++++++
|
||||
```
|
||||
Ni `data/`, ni `site/`, ni configuration, ni secret. Ce dossier n'est pas dans le push : il
|
||||
partira avec le déploiement suivant.
|
||||
|
||||
## Commandes exactes
|
||||
|
||||
```bash
|
||||
# 1. La mise en ligne : la poussée du SHA relu
|
||||
cd /home/yvv/Documents/PROD/DEV/librodrome && for i in 1 2 3 4 5; do if git push -q origin 64e714c1dde9897e46d36caf6cab737799081c96:main; then break; fi; sleep 10; done
|
||||
|
||||
# 2. Attente du pipeline (environ 3 min)
|
||||
for i in $(seq 1 40); do s=$(curl -4 -s -m 20 'https://ci.open.us.org/api/repos/1/pipelines?perPage=5' | python3 -I -c 'import sys,json; print(next((p["status"] for p in json.load(sys.stdin) if p["commit"].startswith("64e714c1dde9")), "absent"))'); echo "$(date +%T) $s"; case "$s" in success|failure|error|killed|declined) break;; esac; sleep 15; done
|
||||
# attendu : success
|
||||
|
||||
# 3. Branche main locale sur la tête déployée (avance rapide seule ; main n'est extraite nulle part)
|
||||
git -C /home/yvv/Documents/PROD/DEV/librodrome fetch . 64e714c1dde9897e46d36caf6cab737799081c96:main
|
||||
```
|
||||
|
||||
Si le pipeline échoue, l'ancien conteneur continue de servir, sans changement : on ne
|
||||
relance rien, on rapporte.
|
||||
|
||||
## Effet attendu
|
||||
- librodrome.org est reconstruit. Seule la console de l'accueil change de comportement.
|
||||
Les messages restent sur le volume `librodrome-data`, sans être touchés.
|
||||
- Irréversibilité : réversible (voir Retour).
|
||||
|
||||
## Vérifications après
|
||||
- [x] Pipeline du commit `64e714c1dde9` : `success`
|
||||
- [x] P5 et P6 rejoués : `200`, puis le même nombre de messages publiés
|
||||
- [x] Sur sonic : `messages.yml` a la même empreinte qu'en P7, et le journal du conteneur n'a aucune ligne `seed-messages`
|
||||
- [x] Vu de l'extérieur, sur https://librodrome.org, dans un navigateur sans tête : au survol d'économique, `--level` passe de 0 à 0,50 ; les deux autres ne bougent pas ; le panneau « Autonomie économique » s'affiche
|
||||
|
||||
## Retour
|
||||
Par un nouvel acte « déploie » :
|
||||
```bash
|
||||
git switch main && git revert --no-edit 64e714c1dde9897e46d36caf6cab737799081c96 03f763a 230f79a \
|
||||
&& for i in 1 2 3 4 5; do if git push -q origin "$(git rev-parse HEAD)":main; then break; fi; sleep 10; done
|
||||
```
|
||||
Le retour ne touche ni le volume ni les messages.
|
||||
|
||||
## Exécution
|
||||
|
||||
Acte exécuté le 03/11/2026, 06h11-06h16, par orch (session dev-63, e66db8ca), sous mandat
|
||||
« déploie » ; consigne d'Yvv du 03/11 à 06h00 : « livre et déploie tout ce que tu peux ».
|
||||
|
||||
- Relecture : « 230f79a : à reprendre » (05h34), puis « 64e714c : ok » (06h05), d'un sous-agent
|
||||
neuf, inscrite dans `~/harness/etat/relectures.json`. Légers, pour un lot suivant : un curseur
|
||||
non touché peut se figer à mi-course si le premier geste tombe pendant la rotation ; un
|
||||
nouveau survol à moins de 500 ms ne rejoue pas la montée ; un clic sans survol ni focus
|
||||
affiche le panneau sans monter le curseur ; la flèche part de l'axe sélectionné.
|
||||
- Prémisses rejouées à 06h11:39 : P1 `4057f50…`, P2 0, P3 1, P4 « 59 success », P5 200, P6 1,
|
||||
P7 1 et `c8afc7de…a6b8`.
|
||||
- Poussée de `64e714c1dde9897e46d36caf6cab737799081c96:main` à 06h11:5x, passée du premier coup.
|
||||
- Pipeline n° 60 : `running` de 06h12:08, `success` à 06h14:27.
|
||||
- Après : `/api/health` 200, `/` 200, 1 message publié ; conteneur `Up 20 seconds (healthy)` ;
|
||||
`messages.yml` `c8afc7de…a6b8`, inchangé ; 0 ligne `seed-messages`.
|
||||
- Vu de l'extérieur (Chromium sans tête, https://librodrome.org, 1280 px), `--level` :
|
||||
repos 0,75 / 0,336 / 0,17, panneau numérique ; survol d'économique 0,75 / **0,50** / 0,17,
|
||||
panneau économique ; survol de citoyenne 0,75 / 0,50 / **0,667** ; survol de numérique
|
||||
0,75 / 0,50 / 0,667 ; 4 s après la sortie, inchangé ; 0 erreur dans la console.
|
||||
- `main` local avancé sur `64e714c` (`git fetch . 64e714c…:main`).
|
||||
- Ce dossier n'est pas en ligne : il partira avec le déploiement suivant.
|
||||
@@ -0,0 +1,63 @@
|
||||
#!/usr/bin/env bash
|
||||
# Acte 2026-10-26-persistance-messages — étape 4, sur sonic, sous yvv, après le déploiement.
|
||||
# Vérifie la bascule, puis un redémarrage à froid, puis revérifie.
|
||||
# Lancement : ssh yvv@161.97.174.60 'bash /tmp/lb-apres.sh' </dev/null
|
||||
set -euo pipefail
|
||||
|
||||
C=lpk-librodrome-main-app-1
|
||||
V=librodrome-data
|
||||
D="$HOME/librodrome-sauvegardes/2026-10-26"
|
||||
|
||||
stop() { echo "ARRÊT : $*" >&2; exit 1; }
|
||||
|
||||
check() {
|
||||
[ "$(docker inspect -f '{{.State.Running}}' "$C")" = true ] || stop "$C ne tourne pas"
|
||||
src=$(docker inspect -f '{{range .Mounts}}{{if eq .Destination "/app/data"}}{{.Type}} {{.Name}}{{end}}{{end}}' "$C")
|
||||
[ "$src" = "volume $V" ] || stop "/app/data n'est pas le volume $V (trouvé : '$src')"
|
||||
others=$(docker inspect -f '{{range .Mounts}}{{.Destination}} {{end}}' "$C")
|
||||
[ "$others" = "/app/data " ] || stop "montages inattendus : $others"
|
||||
s=$(docker logs "$C" 2>&1 | grep -c 'seed-messages' || true)
|
||||
[ "$s" = 0 ] || stop "la graine a été rejouée ($s ligne(s) seed-messages)"
|
||||
hc=$(docker exec "$C" sha256sum /app/data/messages.yml | cut -d' ' -f1)
|
||||
nc=$(docker exec "$C" grep -c '^ - id:' /app/data/messages.yml || true)
|
||||
if [ "$hc" = "$h" ]; then
|
||||
echo " fichier du volume = sauvegarde ($nc message(s))"
|
||||
else
|
||||
echo " NOTE : le fichier du volume a changé depuis la sauvegarde ($nc message(s)) : message reçu après la bascule, à regarder"
|
||||
fi
|
||||
}
|
||||
|
||||
wait_up() {
|
||||
for _ in $(seq 1 30); do
|
||||
if docker exec "$C" node -e "fetch('http://127.0.0.1:3000/api/health').then(r=>process.exit(r.ok?0:1),()=>process.exit(1))" 2>/dev/null; then
|
||||
return 0
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
stop "$C ne répond pas sur /api/health après 60 s"
|
||||
}
|
||||
|
||||
[ -s "$D/ancien-conteneur.id" ] || stop "étape 1 non jouée"
|
||||
old=$(cat "$D/ancien-conteneur.id")
|
||||
h=$(cut -d' ' -f1 "$D/SHA256SUMS")
|
||||
new=$(docker inspect -f '{{.Id}}' "$C")
|
||||
[ "$new" != "$old" ] || stop "conteneur non recréé : le déploiement n'a pas eu lieu"
|
||||
|
||||
# La veille doit avoir vu l'ancien conteneur partir, sans message arrivé après la sauvegarde.
|
||||
[ -s "$D/veille.fin" ] || stop "la veille n'a pas constaté la bascule (encore en cours ?) : attendre 10 s et relancer cette étape"
|
||||
[ -s "$D/messages-derniere-releve.yml" ] || stop "aucune relève de la veille"
|
||||
hl=$(sha256sum "$D/messages-derniere-releve.yml" | cut -d' ' -f1)
|
||||
[ "$hl" = "$h" ] || stop "un message est arrivé dans l'ancien conteneur après la sauvegarde ($(cat "$D/derniere-releve.date")) : le reporter dans le volume avant toute autre chose (hors acte, décision d'Yvv)"
|
||||
echo "veille : dernière relève = sauvegarde ($(head -1 "$D/derniere-releve.date"))"
|
||||
|
||||
echo "bascule :"
|
||||
wait_up
|
||||
check
|
||||
|
||||
echo "redémarrage à froid :"
|
||||
docker restart "$C" >/dev/null
|
||||
wait_up
|
||||
check
|
||||
|
||||
rm -f -- "$0"
|
||||
echo "ÉTAPE 4 OK"
|
||||
@@ -0,0 +1,49 @@
|
||||
#!/usr/bin/env bash
|
||||
# Acte 2026-10-26-persistance-messages — étape 1, sur sonic, sous yvv.
|
||||
# Prémisses, sauvegarde des messages, volume librodrome-data créé et semé.
|
||||
# Ne touche pas au conteneur en service (lectures seulement).
|
||||
# Lancement : ssh yvv@161.97.174.60 'bash /tmp/lb-avant.sh' </dev/null
|
||||
set -euo pipefail
|
||||
|
||||
C=lpk-librodrome-main-app-1
|
||||
IMG=lpk-librodrome-main-app
|
||||
V=librodrome-data
|
||||
D="$HOME/librodrome-sauvegardes/2026-10-26"
|
||||
|
||||
stop() { echo "ARRÊT : $*" >&2; exit 1; }
|
||||
|
||||
# --- Prémisses
|
||||
[ "$(docker inspect -f '{{.State.Running}}' "$C")" = true ] || stop "$C ne tourne pas"
|
||||
[ "$(docker inspect -f '{{.Config.WorkingDir}}' "$C")" = /app ] || stop "WorkingDir de $C différent de /app"
|
||||
m=$(docker inspect -f '{{range .Mounts}}{{if eq .Destination "/app/data"}}{{.Type}}{{end}}{{end}}' "$C")
|
||||
[ -z "$m" ] || stop "/app/data est déjà monté ($m) : l'acte a déjà été joué ?"
|
||||
if docker volume inspect "$V" >/dev/null 2>&1; then stop "le volume $V existe déjà"; fi
|
||||
[ ! -e "$D" ] || stop "$D existe déjà"
|
||||
dd=$(docker diff "$C")
|
||||
modifs=$(printf '%s\n' "$dd" | grep -E '^[ACD] /app/(site|content)(/|$)' || true)
|
||||
if [ -n "$modifs" ]; then
|
||||
echo "$modifs" >&2
|
||||
stop "éditions /admin présentes dans $C et absentes de git : les reporter dans git avant de déployer"
|
||||
fi
|
||||
|
||||
# --- Sauvegarde (reste sur sonic, dossier 700, fichiers 600)
|
||||
umask 077
|
||||
mkdir -p "$D"
|
||||
docker inspect -f '{{.Id}}' "$C" > "$D/ancien-conteneur.id"
|
||||
docker exec "$C" cat /app/data/messages.yml > "$D/messages.yml"
|
||||
docker exec "$C" stat -c '%s %y' /app/data/messages.yml > "$D/messages.stat"
|
||||
[ -s "$D/messages.yml" ] || stop "sauvegarde vide"
|
||||
n=$(grep -c '^ - id:' "$D/messages.yml" || true)
|
||||
h=$(sha256sum "$D/messages.yml" | cut -d' ' -f1)
|
||||
echo "$h messages.yml" > "$D/SHA256SUMS"
|
||||
echo "sauvegarde : $n message(s), sha256 $h, $(cat "$D/messages.stat")"
|
||||
|
||||
# --- Volume créé et semé avec la sauvegarde
|
||||
docker volume create "$V" >/dev/null
|
||||
docker run --rm -i --entrypoint sh -v "$V":/data "$IMG" -c 'cat > /data/messages.yml' < "$D/messages.yml"
|
||||
hv=$(docker run --rm --entrypoint sha256sum -v "$V":/data:ro "$IMG" /data/messages.yml | cut -d' ' -f1)
|
||||
[ "$hv" = "$h" ] || stop "empreinte du volume ($hv) différente de la sauvegarde ($h)"
|
||||
echo "volume $V semé : sha256 $hv"
|
||||
|
||||
rm -f -- "$0"
|
||||
echo "ÉTAPE 1 OK"
|
||||
@@ -0,0 +1,32 @@
|
||||
#!/usr/bin/env bash
|
||||
# Acte 2026-10-26-persistance-messages — étape 2, sur sonic, sous yvv, en arrière-plan.
|
||||
# Relève toutes les 5 s le fichier de messages de l'ancien conteneur, jusqu'à ce
|
||||
# que le déploiement le remplace (ou 30 min au plus). Lectures seulement, sauf
|
||||
# l'écriture de la relève dans le dossier de sauvegarde.
|
||||
# Lancement : ssh yvv@161.97.174.60 'setsid nohup bash /tmp/lb-veille.sh >/tmp/lb-veille.log 2>&1 &' </dev/null
|
||||
set -uo pipefail
|
||||
|
||||
C=lpk-librodrome-main-app-1
|
||||
D="$HOME/librodrome-sauvegardes/2026-10-26"
|
||||
|
||||
[ -s "$D/ancien-conteneur.id" ] || { echo "ARRÊT : étape 1 non jouée"; exit 1; }
|
||||
old=$(cat "$D/ancien-conteneur.id")
|
||||
umask 077
|
||||
i=0
|
||||
while [ "$(docker inspect -f '{{.Id}}' "$C" 2>/dev/null)" = "$old" ] && [ "$i" -lt 360 ]; do
|
||||
# par l'identifiant : après la bascule, le nom désigne déjà le nouveau conteneur
|
||||
if docker exec "$old" cat /app/data/messages.yml > "$D/.releve.tmp" 2>/dev/null && [ -s "$D/.releve.tmp" ]; then
|
||||
mv "$D/.releve.tmp" "$D/messages-derniere-releve.yml"
|
||||
date -u +%FT%TZ > "$D/derniere-releve.date"
|
||||
fi
|
||||
i=$((i + 1))
|
||||
sleep 5
|
||||
done
|
||||
rm -f "$D/.releve.tmp"
|
||||
if [ "$i" -ge 360 ]; then
|
||||
echo "FIN SANS BASCULE : 30 min écoulées, l'ancien conteneur tourne toujours"
|
||||
else
|
||||
date -u +%FT%TZ > "$D/veille.fin"
|
||||
echo "ancien conteneur remplacé, constaté à $(cat "$D/veille.fin")"
|
||||
fi
|
||||
rm -f -- "$0"
|
||||
@@ -29,7 +29,7 @@ useSeoMeta({
|
||||
ogSiteName: 'Le Librodrome',
|
||||
ogType: 'website',
|
||||
ogLocale: 'fr_FR',
|
||||
ogImage: `${siteUrl}/og-default.png?v=2`,
|
||||
ogImage: `${siteUrl}/og-default.png?v=3`,
|
||||
ogImageWidth: 1200,
|
||||
ogImageHeight: 630,
|
||||
twitterCard: 'summary_large_image',
|
||||
@@ -51,7 +51,7 @@ useHead({
|
||||
url: siteUrl,
|
||||
logo: {
|
||||
'@type': 'ImageObject',
|
||||
url: `${siteUrl}/og-default.png?v=2`,
|
||||
url: `${siteUrl}/og-default.png?v=3`,
|
||||
},
|
||||
description: 'Plateforme d\'autonomie numérique, économique et citoyenne à l\'échelle des bassins de vie.',
|
||||
},
|
||||
|
||||
@@ -29,6 +29,12 @@
|
||||
--ease-in-out-expo: cubic-bezier(0.87, 0, 0.13, 1);
|
||||
}
|
||||
|
||||
/* A page whose birds may stand behind a card (UiOiseauPose, pose « derriere »):
|
||||
the stacking context they go back into, above the page background */
|
||||
.oiseaux-scene {
|
||||
isolation: isolate;
|
||||
}
|
||||
|
||||
/* Fader level of the home console — registered so it can transition smoothly */
|
||||
@property --level {
|
||||
syntax: '<number>';
|
||||
|
||||
@@ -8,15 +8,19 @@
|
||||
· {{ readingTime }}
|
||||
</span>
|
||||
</div>
|
||||
<h1 class="chapter-title font-display font-bold leading-tight tracking-tight text-white">
|
||||
<h1 class="chapter-title relative font-display font-bold leading-tight tracking-tight text-white">
|
||||
{{ title }}
|
||||
<!-- The calligrapher in the left margin, feet on the title's rule (rule e) -->
|
||||
<UiOiseauPose nom="calligraphe" pose="marge-g" ecart="40px" :w="112" />
|
||||
</h1>
|
||||
<p v-if="description" class="mt-3 text-lg text-white/60">
|
||||
{{ description }}
|
||||
</p>
|
||||
|
||||
<!-- Songs + PDF actions row -->
|
||||
<div class="mt-4 flex flex-wrap items-center gap-2">
|
||||
<!-- Songs + PDF actions row. The box maker in the right margin, feet at the bottom of the
|
||||
buttons (every chapter has its PDF page) -->
|
||||
<div class="relative mt-4 flex flex-wrap items-center gap-2">
|
||||
<UiOiseauPose nom="cartonnier" pose="marge-d" ecart="40px" :w="76" retourne />
|
||||
<!-- Associated songs badges -->
|
||||
<button
|
||||
v-for="song in songs"
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
<div
|
||||
ref="root"
|
||||
class="console"
|
||||
:class="{ 'console--compact': compact, 'console--still': reduced }"
|
||||
@pointerenter="hovered = true"
|
||||
@pointerleave="hovered = false"
|
||||
@focusin="hovered = true"
|
||||
@focusout="hovered = false"
|
||||
:class="{ 'console--compact': compact, 'console--still': reduced, 'console--hand': engaged }"
|
||||
@pointerenter="pointerIn = true"
|
||||
@pointerleave="pointerIn = false"
|
||||
@focusin="focusIn = true"
|
||||
@focusout="onFocusOut"
|
||||
>
|
||||
<div class="console-board">
|
||||
<!-- Scale, read bottom to top: from asservissement to émancipation -->
|
||||
@@ -30,12 +30,17 @@
|
||||
type="button"
|
||||
role="tab"
|
||||
class="fader"
|
||||
:class="{ 'fader--active': axis.key === active }"
|
||||
:class="{ 'fader--active': axis.key === active, 'fader--held': held.has(axis.key), 'fader--rising': rising.has(axis.key), 'fader--stopped': stopped.has(axis.key) }"
|
||||
:style="{ '--hue': `var(${axis.hue})`, '--level': levels[axis.key] ?? 0, '--i': i }"
|
||||
:data-axis="axis.key"
|
||||
:aria-selected="axis.key === active"
|
||||
:aria-controls="`panel-${uid}`"
|
||||
:tabindex="axis.key === active ? 0 : -1"
|
||||
@click="choose(axis.key)"
|
||||
@pointerdown="lastPointer = $event.pointerType"
|
||||
@pointerenter="onEnter($event, axis.key)"
|
||||
@pointerleave="hoverKey = null"
|
||||
@focus="onFocus($event, axis.key)"
|
||||
@click="onClick($event, axis.key)"
|
||||
>
|
||||
<span class="fader-track" aria-hidden="true">
|
||||
<span class="fader-fill" />
|
||||
@@ -127,65 +132,200 @@ const tabs = ref<HTMLButtonElement[]>([])
|
||||
const current = computed(() => props.axes.find(a => a.key === active.value) ?? props.axes[0])
|
||||
|
||||
// ── Motion ──────────────────────────────────────────────────────────────
|
||||
// The cursors move on their own, slowly: each one drifts, and every few
|
||||
// beats another axis is pushed up towards émancipation. Visitors watch it;
|
||||
// they can still pick an axis (click, arrows), which stops the rotation.
|
||||
// Yvv's instructions, word for word: docs/consignes-yvv.md (29/09 and 03/11).
|
||||
//
|
||||
// Before any gesture (29/09): the cursors move on their own, slowly; the low
|
||||
// ones drift, and every few beats another axis is pushed up to its own
|
||||
// graduation (03/11), the level the hand gives it.
|
||||
// SSR renders every cursor at the bottom, so the first move is the rise.
|
||||
//
|
||||
// The visitor's hand takes over for good (03/11): from the first hover,
|
||||
// keyboard focus or tap on a cursor, the rotation stops. The touched cursor
|
||||
// replays its rise, from the bottom to its own graduation, and its panel
|
||||
// shows. The others are never lowered by the hand (orch decision, 03/11
|
||||
// 13h10): a cursor already touched stays at its graduation; a cursor never
|
||||
// touched that the rotation was taking down stops where it is at the first
|
||||
// gesture; one it was taking up finishes, gently, its rise to the level the
|
||||
// rotation gave it, then stays there.
|
||||
const BEAT_MS = 2600
|
||||
const BEATS_PER_AXIS = 3
|
||||
/** Graduation of each axis, from asservissement (0) to émancipation (1) */
|
||||
const GRADUATION: Record<AxisKey, number> = { numerique: 3 / 4, economie: 1 / 2, politique: 2 / 3 }
|
||||
/** The focus and the click of one tap, within this delay, give one rise */
|
||||
const SAME_GESTURE_MS = 500
|
||||
|
||||
const levels = reactive<Partial<Record<AxisKey, number>>>({})
|
||||
/** Cursors the hand has touched: they no longer glide with the rotation */
|
||||
const held = reactive(new Set<AxisKey>())
|
||||
/** Cursors replaying their rise (fast transition) */
|
||||
const rising = reactive(new Set<AxisKey>())
|
||||
/** Untouched cursors the first gesture stopped on their way down */
|
||||
const stopped = reactive(new Set<AxisKey>())
|
||||
const reduced = ref(false)
|
||||
const hovered = ref(false)
|
||||
const visible = ref(true)
|
||||
const chosen = ref(false)
|
||||
const engaged = ref(false)
|
||||
const pointerIn = ref(false)
|
||||
const focusIn = ref(false)
|
||||
const hoverKey = ref<AxisKey | null>(null)
|
||||
let lastPointer = ''
|
||||
/** The tab the arrow keys are moving the focus to: its focus is the same gesture */
|
||||
let keyFocus: AxisKey | null = null
|
||||
/** When a keyboard focus last raised each cursor (to pair it with the click of a tap) */
|
||||
const focusRiseAt: Partial<Record<AxisKey, number>> = {}
|
||||
const riseRun: Partial<Record<AxisKey, number>> = {}
|
||||
let beat = 0
|
||||
let timer: ReturnType<typeof setInterval> | undefined
|
||||
|
||||
function place() {
|
||||
props.axes.forEach((axis, i) => {
|
||||
const isActive = axis.key === active.value
|
||||
if (reduced.value) {
|
||||
levels[axis.key] = isActive ? 0.94 : 0.3
|
||||
// The axis put forward goes up to its own graduation, as under the hand
|
||||
if (axis.key === active.value) {
|
||||
levels[axis.key] = GRADUATION[axis.key] ?? 0.75
|
||||
return
|
||||
}
|
||||
const wave = Math.sin(beat * 0.9 + i * 2.1)
|
||||
levels[axis.key] = isActive ? 0.87 + 0.06 * wave : 0.3 + 0.14 * wave
|
||||
levels[axis.key] = reduced.value ? 0.3 : 0.3 + 0.14 * Math.sin(beat * 0.9 + i * 2.1)
|
||||
})
|
||||
}
|
||||
|
||||
function faderEl(key: AxisKey) {
|
||||
return root.value?.querySelector<HTMLElement>(`[data-axis="${key}"]`) ?? null
|
||||
}
|
||||
|
||||
/** First gesture: the rotation stops for good. An untouched cursor on its way down stops
|
||||
* where it is drawn; one on its way up finishes its glide to the target the rotation gave it.
|
||||
* `touched` is the cursor of this gesture: it replays its rise anyway */
|
||||
function engage(touched?: AxisKey) {
|
||||
if (engaged.value) return
|
||||
engaged.value = true
|
||||
clearInterval(timer)
|
||||
for (const axis of props.axes) {
|
||||
const key = axis.key
|
||||
if (key === touched || held.has(key)) continue
|
||||
const el = faderEl(key)
|
||||
const drawn = el ? Number.parseFloat(getComputedStyle(el).getPropertyValue('--level')) : Number.NaN
|
||||
const target = levels[key] ?? 0
|
||||
if (Number.isFinite(drawn) && drawn > target) {
|
||||
stopped.add(key)
|
||||
levels[key] = drawn
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** The touched cursor replays its rise: back to the bottom, then up to its graduation */
|
||||
async function rise(key: AxisKey) {
|
||||
held.add(key)
|
||||
stopped.delete(key)
|
||||
const target = GRADUATION[key] ?? 0.75
|
||||
if (reduced.value) {
|
||||
levels[key] = target
|
||||
return
|
||||
}
|
||||
const run = (riseRun[key] ?? 0) + 1
|
||||
riseRun[key] = run
|
||||
rising.delete(key)
|
||||
levels[key] = 0
|
||||
await nextTick()
|
||||
// Commit the bottom (no transition) before the rise starts from it
|
||||
const el = faderEl(key)
|
||||
if (el) getComputedStyle(el).getPropertyValue('--level')
|
||||
if (riseRun[key] !== run) return
|
||||
rising.add(key)
|
||||
levels[key] = target
|
||||
}
|
||||
|
||||
/** A cursor under the visitor's hand: it rises and its panel shows. Every rollover replays
|
||||
* the rise, however soon after the previous one (Yvv, 03/11) */
|
||||
function touch(key: AxisKey) {
|
||||
engage(key)
|
||||
active.value = key
|
||||
rise(key)
|
||||
}
|
||||
|
||||
/** The panel of a cursor already raised by this same gesture: no second rise */
|
||||
function select(key: AxisKey) {
|
||||
engage(key)
|
||||
active.value = key
|
||||
}
|
||||
|
||||
function onEnter(e: PointerEvent, key: AxisKey) {
|
||||
// A finger scrolling over a cursor is not a rollover: taps go through the click
|
||||
if (e.pointerType === 'touch') return
|
||||
hoverKey.value = key
|
||||
touch(key)
|
||||
}
|
||||
|
||||
function isKeyboardFocus(el: HTMLElement) {
|
||||
try {
|
||||
return el.matches(':focus-visible')
|
||||
}
|
||||
catch {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
function onFocus(e: FocusEvent, key: AxisKey) {
|
||||
// The arrow key that moved the focus here already raised the cursor
|
||||
if (keyFocus === key) return
|
||||
// Keyboard focus is a rollover; a focus from the mouse or a tap is handled elsewhere
|
||||
if (hoverKey.value === key || !isKeyboardFocus(e.currentTarget as HTMLElement)) return
|
||||
focusRiseAt[key] = Date.now()
|
||||
touch(key)
|
||||
}
|
||||
|
||||
function onClick(e: MouseEvent, key: AxisKey) {
|
||||
const pointer = (e as PointerEvent).pointerType || lastPointer
|
||||
lastPointer = ''
|
||||
// A tap is a rollover: one rise per tap, even when the browser also gave it a keyboard focus
|
||||
const tapFocused = Date.now() - (focusRiseAt[key] ?? 0) < SAME_GESTURE_MS
|
||||
if (pointer === 'touch' && !tapFocused) return touch(key)
|
||||
// A click with no hover nor focus before it (a screen reader, a script) raises the cursor
|
||||
// with its panel; after a hover or a keyboard focus, the cursor is already up
|
||||
if (hoverKey.value !== key && !held.has(key)) return touch(key)
|
||||
select(key)
|
||||
}
|
||||
|
||||
function onFocusOut(e: FocusEvent) {
|
||||
focusIn.value = !!root.value?.contains(e.relatedTarget as Node | null)
|
||||
}
|
||||
|
||||
function advance() {
|
||||
const idx = props.axes.findIndex(a => a.key === active.value)
|
||||
active.value = props.axes[(idx + 1) % props.axes.length]!.key
|
||||
}
|
||||
|
||||
function onBeat() {
|
||||
if (!visible.value) return
|
||||
if (!visible.value || engaged.value) return
|
||||
beat++
|
||||
if (!chosen.value && !hovered.value && beat % BEATS_PER_AXIS === 0) advance()
|
||||
if (!pointerIn.value && !focusIn.value && beat % BEATS_PER_AXIS === 0) advance()
|
||||
place()
|
||||
}
|
||||
|
||||
function choose(key: AxisKey) {
|
||||
chosen.value = true
|
||||
active.value = key
|
||||
}
|
||||
|
||||
watch(active, place)
|
||||
watch(active, () => {
|
||||
if (!engaged.value) place()
|
||||
})
|
||||
|
||||
function onKeydown(e: KeyboardEvent) {
|
||||
lastPointer = ''
|
||||
const keys = ['ArrowRight', 'ArrowLeft', 'ArrowUp', 'ArrowDown', 'Home', 'End']
|
||||
if (!keys.includes(e.key)) return
|
||||
e.preventDefault()
|
||||
const idx = props.axes.findIndex(a => a.key === active.value)
|
||||
// From the tab that has the focus: a hover may have selected another axis meanwhile
|
||||
const from = (e.target as HTMLElement | null)?.closest<HTMLElement>('[data-axis]')?.dataset.axis
|
||||
const idx = props.axes.findIndex(a => a.key === (from ?? active.value))
|
||||
const last = props.axes.length - 1
|
||||
let next = idx
|
||||
if (e.key === 'ArrowRight' || e.key === 'ArrowUp') next = idx === last ? 0 : idx + 1
|
||||
if (e.key === 'ArrowLeft' || e.key === 'ArrowDown') next = idx === 0 ? last : idx - 1
|
||||
if (e.key === 'Home') next = 0
|
||||
if (e.key === 'End') next = last
|
||||
choose(props.axes[next]!.key)
|
||||
nextTick(() => tabs.value[next]?.focus())
|
||||
const key = props.axes[next]!.key
|
||||
touch(key)
|
||||
nextTick(() => {
|
||||
// focus() fires the focus event at once: it is part of this key press, not a new rise
|
||||
keyFocus = key
|
||||
tabs.value[next]?.focus()
|
||||
keyFocus = null
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
@@ -279,6 +419,18 @@ onMounted(() => {
|
||||
transition-delay: calc(var(--i) * 160ms);
|
||||
}
|
||||
|
||||
/* A cursor the hand has touched no longer glides: it drops to the bottom at once, then
|
||||
replays its rise. At the first gesture, an untouched cursor on its way down stops where it
|
||||
is; one on its way up finishes its glide, then the rotation is over */
|
||||
.fader--held,
|
||||
.fader--stopped {
|
||||
transition: none;
|
||||
}
|
||||
|
||||
.fader--held.fader--rising {
|
||||
transition: --level 1.1s cubic-bezier(0.22, 1, 0.36, 1);
|
||||
}
|
||||
|
||||
.fader:focus-visible {
|
||||
outline: 2px solid hsl(var(--hue));
|
||||
outline-offset: 2px;
|
||||
@@ -544,6 +696,7 @@ onMounted(() => {
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.fader,
|
||||
.fader--held.fader--rising,
|
||||
.fader-fill,
|
||||
.fader-name,
|
||||
.fader-lever,
|
||||
|
||||
@@ -1,13 +1,21 @@
|
||||
<template>
|
||||
<div class="format">
|
||||
<header v-if="format && showHeader" class="format-head">
|
||||
<h2 class="format-title">{{ format.title }}</h2>
|
||||
<!-- The accordion player at the end of the title, on its baseline: on the event card it stood
|
||||
at the height of the reader, and its ground line touched the card's link (rules c, h).
|
||||
S and from 1200 px: the band above leaves 125 px, and a perched bird keeps a void of its
|
||||
own height above it (rule f); 8 px under the baseline, within the 12 px of rule e -->
|
||||
<h2 class="format-title">{{ format.title }}<UiOiseauPose nom="accordeoniste" pose="perche" bout left="16px" bottom="-8px" :w="72" retourne ton="accent" :seuil="1200" /></h2>
|
||||
<p class="format-text">{{ format.text }}</p>
|
||||
</header>
|
||||
|
||||
<div class="format-grid">
|
||||
<!-- Éditorial : le livre et les chansons -->
|
||||
<article v-if="book" class="format-card format-card--book">
|
||||
<!-- No bird behind the book card (DA, 03/11 after 19h): lowered under the header's first
|
||||
line, the reader only showed her bubble; the page margin and the gutter leave her no
|
||||
side to come out by, and the free top of the card holds the accordion player. She
|
||||
stands on the order page -->
|
||||
<div
|
||||
class="format-cover"
|
||||
role="img"
|
||||
@@ -123,6 +131,7 @@ defineEmits<{
|
||||
}
|
||||
|
||||
.format-grid {
|
||||
isolation: isolate;
|
||||
display: grid;
|
||||
gap: 1.25rem;
|
||||
}
|
||||
@@ -132,6 +141,7 @@ defineEmits<{
|
||||
}
|
||||
|
||||
.format-card {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
<div class="va">
|
||||
<!-- Hero : the headline, and the three cursors to push -->
|
||||
<section class="va-hero">
|
||||
<HomeShadoks />
|
||||
<div class="container-content va-hero-grid">
|
||||
<div class="va-intro">
|
||||
<h1 class="va-h1">
|
||||
@@ -20,17 +19,27 @@
|
||||
</div>
|
||||
|
||||
<div class="va-console">
|
||||
<p class="va-console-title">{{ heading[2] }}</p>
|
||||
<HomeConsole
|
||||
v-model="active"
|
||||
:axes="axes"
|
||||
:prefix="consoleLabels?.prefix"
|
||||
:top-label="consoleLabels?.top"
|
||||
:bottom-label="consoleLabels?.bottom"
|
||||
:stacks="bouquet?.inService"
|
||||
:stacks-to="lpk?.to"
|
||||
:stacks-label="lpk ? `En service chez ${lpk.name} :` : 'En service :'"
|
||||
/>
|
||||
<p class="va-console-title">
|
||||
{{ heading[2] }}
|
||||
</p>
|
||||
<!-- The DJ stands behind the console board, like behind its decks: only its head, with
|
||||
its headphones, shows over the board. Perched on the board, under the sticky header,
|
||||
it had no void of its own height above it (rule f, DA 03/11 after 17h35). The scene
|
||||
keeps it under the board without touching the console -->
|
||||
<div class="va-console-scene">
|
||||
<UiOiseauPose nom="dj" pose="derriere" right="24px" top="-95px" :w="150" retourne ton="accent" />
|
||||
<UiOiseauPose nom="dj" pose="derriere" right="24px" top="-95px" :w="150" retourne ton="accent" mobile-seul :seuil="768" />
|
||||
<HomeConsole
|
||||
v-model="active"
|
||||
:axes="axes"
|
||||
:prefix="consoleLabels?.prefix"
|
||||
:top-label="consoleLabels?.top"
|
||||
:bottom-label="consoleLabels?.bottom"
|
||||
:stacks="bouquet?.inService"
|
||||
:stacks-to="lpk?.to"
|
||||
:stacks-label="lpk ? `En service chez ${lpk.name} :` : 'En service :'"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -61,6 +70,7 @@
|
||||
<div class="container-content">
|
||||
<h2 class="va-h2">{{ toolboxTitle }}</h2>
|
||||
<HomeToolbox
|
||||
:oiseaux="toolboxOiseaux"
|
||||
:axes="axes"
|
||||
:lpk="lpk"
|
||||
:bouquet="bouquet"
|
||||
@@ -76,6 +86,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { AxisKey } from '~/composables/useHomeData'
|
||||
import type { OiseauPlace } from '~/utils/oiseauPose'
|
||||
|
||||
defineEmits<{
|
||||
'open-player': []
|
||||
@@ -97,6 +108,14 @@ const axesLine = computed(() =>
|
||||
.filter(Boolean)
|
||||
.map(text => ({ text, hue: AXIS_WORDS[text] })))
|
||||
const active = ref<AxisKey>('numerique')
|
||||
|
||||
// The governance crew stands in the empty space under the citoyenne column of the toolbox
|
||||
const toolboxOiseaux: Partial<Record<AxisKey, OiseauPlace[]>> = {
|
||||
politique: [
|
||||
{ nom: 'capitaine', pose: 'libre', left: 'calc(34% - 75px)', bottom: '-190px', w: 150, op: 0.24 },
|
||||
{ nom: 'matelot', pose: 'libre', left: 'calc(80% - 40px)', bottom: '-300px', w: 80, op: 0.26, retourne: true },
|
||||
],
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -183,7 +202,13 @@ const active = ref<AxisKey>('numerique')
|
||||
.va-approach::after { content: ' →'; }
|
||||
}
|
||||
|
||||
.va-console-scene {
|
||||
position: relative;
|
||||
isolation: isolate;
|
||||
}
|
||||
|
||||
.va-console-title {
|
||||
position: relative;
|
||||
margin: 0 0 0.75rem;
|
||||
font-family: var(--font-display);
|
||||
font-weight: 700;
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
<!-- Formulaire -->
|
||||
<UiScrollReveal>
|
||||
<div class="message-form-card">
|
||||
<!-- The postwoman stands on the message form -->
|
||||
<UiOiseauPose nom="factrice" pose="perche" right="30px" :w="76" retourne :seuil="1200" />
|
||||
<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">
|
||||
@@ -167,6 +169,7 @@ function formatDate(iso: string) {
|
||||
|
||||
/* ── Carte formulaire ── */
|
||||
.message-form-card {
|
||||
position: relative;
|
||||
background: hsl(var(--color-surface));
|
||||
border-radius: 16px;
|
||||
padding: 1.5rem;
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
<template>
|
||||
<div class="shadoks" aria-hidden="true">
|
||||
<!-- Shadok bird decoration -->
|
||||
<svg class="shadok-bird" viewBox="0 0 180 260" fill="none" aria-hidden="true">
|
||||
<ellipse cx="90" cy="100" rx="45" ry="40" fill="currentColor" opacity="0.85"/>
|
||||
<circle cx="130" cy="60" r="22" fill="currentColor" opacity="0.8"/>
|
||||
<path d="M110 85 Q125 70 128 63" stroke="currentColor" stroke-width="8" stroke-linecap="round" opacity="0.7" fill="none"/>
|
||||
<circle cx="136" cy="55" r="5" fill="currentColor" opacity="0.3"/>
|
||||
<circle cx="137" cy="54" r="2" fill="currentColor" opacity="0.6"/>
|
||||
<polygon points="150,58 175,50 152,65" fill="currentColor" opacity="0.6"/>
|
||||
<line x1="75" y1="138" x2="60" y2="230" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" opacity="0.6"/>
|
||||
<line x1="105" y1="138" x2="115" y2="230" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" opacity="0.6"/>
|
||||
<circle cx="66" cy="190" r="4" fill="currentColor" opacity="0.4"/>
|
||||
<circle cx="111" cy="190" r="4" fill="currentColor" opacity="0.4"/>
|
||||
<path d="M60 230 L45 233 M60 230 L55 236 M60 230 L65 235" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" opacity="0.5"/>
|
||||
<path d="M115 230 L100 233 M115 230 L110 236 M115 230 L120 235" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" opacity="0.5"/>
|
||||
<path d="M48 95 Q20 80 15 65" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.5" fill="none"/>
|
||||
<path d="M48 100 Q22 92 10 85" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4" fill="none"/>
|
||||
<path d="M48 105 Q25 102 12 100" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.3" fill="none"/>
|
||||
</svg>
|
||||
|
||||
<!-- Shadok boulanger -->
|
||||
<svg class="shadok-boulanger" viewBox="0 0 240 300" 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="68" rx="18" ry="22" fill="currentColor" opacity="0.35"/>
|
||||
<rect x="105" y="78" width="30" height="5" rx="1" fill="currentColor" opacity="0.4"/>
|
||||
<path d="M110 88 Q114 84 118 88" stroke="currentColor" stroke-width="2" stroke-linecap="round" fill="none" opacity="0.4"/>
|
||||
<path d="M124 88 Q128 84 132 88" stroke="currentColor" stroke-width="2" stroke-linecap="round" fill="none" opacity="0.4"/>
|
||||
<path d="M112 102 Q120 108 128 102" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" fill="none" opacity="0.35"/>
|
||||
<line x1="160" y1="145" x2="190" y2="135" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" opacity="0.6"/>
|
||||
<rect x="185" y="125" width="40" height="10" rx="5" fill="currentColor" opacity="0.4" transform="rotate(-15 205 130)"/>
|
||||
<line x1="80" y1="148" x2="55" y2="175" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" opacity="0.6"/>
|
||||
<line x1="105" y1="200" x2="95" y2="258" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" opacity="0.6"/>
|
||||
<line x1="135" y1="200" x2="145" y2="258" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" opacity="0.6"/>
|
||||
<rect x="30" y="180" width="50" height="40" rx="4" fill="currentColor" opacity="0.25"/>
|
||||
<rect x="35" y="185" width="40" height="20" rx="2" fill="currentColor" opacity="0.15"/>
|
||||
<ellipse cx="55" cy="195" rx="12" ry="6" fill="currentColor" opacity="0.12"/>
|
||||
</svg>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.shadoks {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.shadok-bird {
|
||||
position: absolute;
|
||||
right: 3%;
|
||||
top: 6%;
|
||||
width: clamp(90px, 10vw, 150px);
|
||||
opacity: 0.22;
|
||||
color: hsl(var(--axis-num));
|
||||
animation: shadok-float 8s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.shadok-boulanger {
|
||||
position: absolute;
|
||||
left: 1.5%;
|
||||
bottom: 4%;
|
||||
width: clamp(80px, 9vw, 140px);
|
||||
opacity: 0.2;
|
||||
color: hsl(var(--axis-eco));
|
||||
animation: shadok-float 9s ease-in-out infinite reverse;
|
||||
}
|
||||
|
||||
@keyframes shadok-float {
|
||||
0%, 100% { transform: translateY(0); }
|
||||
50% { transform: translateY(-10px); }
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.shadoks { display: none; }
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.shadok-bird, .shadok-boulanger { animation: none; }
|
||||
}
|
||||
</style>
|
||||
@@ -9,6 +9,7 @@
|
||||
:style="{ '--hue': `var(${axis.hue})` }"
|
||||
:aria-labelledby="`tb-${uid}-${axis.key}`"
|
||||
>
|
||||
<UiOiseauPose v-for="o in oiseaux?.[axis.key] ?? []" :key="`${o.nom}-${o.pose}`" v-bind="o" class="toolbox-oiseau" />
|
||||
<header class="toolbox-head">
|
||||
<p class="toolbox-lever">{{ axis.lever }}</p>
|
||||
<h3 :id="`tb-${uid}-${axis.key}`" class="toolbox-title">
|
||||
@@ -61,7 +62,8 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { HomeAxis } from '~/composables/useHomeData'
|
||||
import type { AxisKey, HomeAxis } from '~/composables/useHomeData'
|
||||
import type { OiseauPlace } from '~/utils/oiseauPose'
|
||||
|
||||
withDefaults(defineProps<{
|
||||
axes: HomeAxis[]
|
||||
@@ -70,6 +72,8 @@ withDefaults(defineProps<{
|
||||
gratewizard?: { title: string, subtitle: string }
|
||||
showPromise?: boolean
|
||||
anchors?: boolean
|
||||
/** Birds standing by an axis column (home page) */
|
||||
oiseaux?: Partial<Record<AxisKey, OiseauPlace[]>>
|
||||
}>(), {
|
||||
showPromise: true,
|
||||
anchors: false,
|
||||
@@ -106,7 +110,13 @@ function onAction(id: string) {
|
||||
}
|
||||
}
|
||||
|
||||
/* The birds under a column only make sense with the three columns side by side */
|
||||
@container (max-width: 59.99rem) {
|
||||
.toolbox-oiseau { display: none; }
|
||||
}
|
||||
|
||||
.toolbox-axis {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
|
||||
@@ -1,44 +1,7 @@
|
||||
<template>
|
||||
<footer class="footer-wrap border-t border-[hsl(var(--color-text)/0.1)] bg-[hsl(var(--color-surface))]">
|
||||
<!-- Shadok pattern -->
|
||||
<svg class="footer-shadok-pattern" viewBox="0 0 400 80" fill="none" aria-hidden="true">
|
||||
<g transform="translate(20,10)">
|
||||
<ellipse cx="15" cy="25" rx="12" ry="14" fill="currentColor" opacity="0.2"/>
|
||||
<circle cx="15" cy="10" r="7" fill="currentColor" opacity="0.16"/>
|
||||
<line x1="10" y1="38" x2="8" y2="55" stroke="currentColor" stroke-width="1.5" opacity="0.15"/>
|
||||
<line x1="20" y1="38" x2="22" y2="55" stroke="currentColor" stroke-width="1.5" opacity="0.15"/>
|
||||
</g>
|
||||
<g transform="translate(80,15)">
|
||||
<ellipse cx="15" cy="22" rx="10" ry="12" fill="currentColor" opacity="0.16"/>
|
||||
<circle cx="15" cy="8" r="6" fill="currentColor" opacity="0.13"/>
|
||||
<line x1="10" y1="33" x2="8" y2="48" stroke="currentColor" stroke-width="1.5" opacity="0.12"/>
|
||||
<line x1="20" y1="33" x2="22" y2="48" stroke="currentColor" stroke-width="1.5" opacity="0.12"/>
|
||||
</g>
|
||||
<g transform="translate(140,8)">
|
||||
<ellipse cx="15" cy="25" rx="11" ry="13" fill="currentColor" opacity="0.18"/>
|
||||
<circle cx="15" cy="10" r="6.5" fill="currentColor" opacity="0.15"/>
|
||||
<line x1="10" y1="37" x2="7" y2="54" stroke="currentColor" stroke-width="1.5" opacity="0.14"/>
|
||||
<line x1="20" y1="37" x2="23" y2="54" stroke="currentColor" stroke-width="1.5" opacity="0.14"/>
|
||||
</g>
|
||||
<g transform="translate(210,18)">
|
||||
<ellipse cx="15" cy="20" rx="10" ry="11" fill="currentColor" opacity="0.14"/>
|
||||
<circle cx="15" cy="7" r="5.5" fill="currentColor" opacity="0.11"/>
|
||||
<line x1="10" y1="30" x2="9" y2="44" stroke="currentColor" stroke-width="1.5" opacity="0.1"/>
|
||||
<line x1="20" y1="30" x2="21" y2="44" stroke="currentColor" stroke-width="1.5" opacity="0.1"/>
|
||||
</g>
|
||||
<g transform="translate(270,12)">
|
||||
<ellipse cx="15" cy="24" rx="12" ry="14" fill="currentColor" opacity="0.18"/>
|
||||
<circle cx="15" cy="9" r="7" fill="currentColor" opacity="0.15"/>
|
||||
<line x1="10" y1="37" x2="7" y2="55" stroke="currentColor" stroke-width="1.5" opacity="0.14"/>
|
||||
<line x1="20" y1="37" x2="23" y2="55" stroke="currentColor" stroke-width="1.5" opacity="0.14"/>
|
||||
</g>
|
||||
<g transform="translate(340,16)">
|
||||
<ellipse cx="15" cy="22" rx="10" ry="12" fill="currentColor" opacity="0.16"/>
|
||||
<circle cx="15" cy="8" r="6" fill="currentColor" opacity="0.13"/>
|
||||
<line x1="10" y1="33" x2="8" y2="48" stroke="currentColor" stroke-width="1.5" opacity="0.12"/>
|
||||
<line x1="20" y1="33" x2="22" y2="48" stroke="currentColor" stroke-width="1.5" opacity="0.12"/>
|
||||
</g>
|
||||
</svg>
|
||||
<!-- The frieze of the birds (studio DA), standing on the bottom edge -->
|
||||
<UiOiseau nom="frise" class="footer-shadok-pattern" />
|
||||
<div class="container-content px-4 py-8 relative z-1">
|
||||
<div class="flex flex-col items-center gap-4 md:flex-row md:justify-between">
|
||||
<!-- Credits -->
|
||||
@@ -47,9 +10,9 @@
|
||||
</p>
|
||||
|
||||
<!-- Links -->
|
||||
<nav class="flex items-center gap-4" aria-label="Liens du pied de page">
|
||||
<nav v-if="links.length" class="flex items-center gap-4" aria-label="Liens du pied de page">
|
||||
<NuxtLink
|
||||
v-for="link in site?.footer.links"
|
||||
v-for="link in links"
|
||||
:key="link.to"
|
||||
:to="link.to"
|
||||
class="text-sm text-[hsl(var(--color-text)/0.4)] transition-colors hover:text-[hsl(var(--color-text)/0.7)]"
|
||||
@@ -64,6 +27,14 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
const { data: site } = await useSiteContent()
|
||||
const router = useRouter()
|
||||
|
||||
/** The footer links of site.yml, without those to a page the site does not have yet: the link
|
||||
* to /mentions-legales (03/11: no such page, its text is still to be written) shows again by
|
||||
* itself once the page exists */
|
||||
interface FooterLink { label: string, to: string }
|
||||
const links = computed<FooterLink[]>(() => (((site.value as any)?.footer?.links ?? []) as FooterLink[])
|
||||
.filter(l => /^(https?:|mailto:)/.test(l.to) || router.resolve(l.to).matched.length > 0))
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -72,13 +43,14 @@ const { data: site } = await useSiteContent()
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* The whole frieze fits in the footer (mask: contain), never cropped; pale under the links */
|
||||
.footer-shadok-pattern {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
top: 0.75rem;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
opacity: 1;
|
||||
height: calc(100% - 0.75rem);
|
||||
opacity: 0.12;
|
||||
pointer-events: none;
|
||||
color: hsl(var(--color-primary));
|
||||
}
|
||||
|
||||
@@ -2,13 +2,12 @@
|
||||
<header class="sticky top-0 z-40 border-b border-[hsl(var(--color-text)/0.08)] bg-[hsl(var(--color-bg)/0.85)] backdrop-blur-xl">
|
||||
<div class="container-content flex h-[var(--header-height)] items-center justify-between px-4">
|
||||
<!-- Logo -->
|
||||
<NuxtLink to="/" class="logo-link flex items-center gap-2.5">
|
||||
<UiSectionSign class="logo-icon" />
|
||||
<span class="logo-text">{{ site?.identity.name }}</span>
|
||||
<NuxtLink to="/" class="logo-link flex items-center" :aria-label="site?.identity.name || 'Le librodrome'">
|
||||
<UiLogotype class="logo-lockup" />
|
||||
</NuxtLink>
|
||||
|
||||
<!-- Desktop navigation -->
|
||||
<nav class="hidden md:flex items-center gap-1" aria-label="Navigation principale">
|
||||
<nav class="nav-main hidden md:flex items-center gap-1" aria-label="Navigation principale">
|
||||
<!-- Autonomie : prefix + axis buttons -->
|
||||
<span class="nav-prefix">Autonomie :</span>
|
||||
<NuxtLink
|
||||
@@ -72,31 +71,20 @@ const AXIS_HUES = ['--axis-num', '--axis-eco', '--axis-cit']
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.logo-icon {
|
||||
/* The validated lockup (studio DA, 27/10): the Geste already leans 25°, no rotation on hover */
|
||||
.logo-lockup {
|
||||
height: 2.3rem;
|
||||
width: auto;
|
||||
height: 2.1rem;
|
||||
flex-shrink: 0;
|
||||
transform: rotate(-8deg);
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.logo-link:hover .logo-icon {
|
||||
transform: rotate(0deg) scale(1.08);
|
||||
.logo-link:hover .logo-lockup {
|
||||
transform: scale(1.04);
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.logo-icon { transition: none; }
|
||||
}
|
||||
|
||||
.logo-text {
|
||||
font-family: var(--font-logo);
|
||||
font-weight: 400;
|
||||
font-size: 1.25rem;
|
||||
letter-spacing: 0.04em;
|
||||
background-image: linear-gradient(to right, hsl(var(--color-primary)), hsl(var(--color-accent)));
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
.logo-lockup { transition: none; }
|
||||
}
|
||||
|
||||
.nav-prefix {
|
||||
@@ -129,4 +117,22 @@ const AXIS_HUES = ['--axis-num', '--axis-eco', '--axis-cit']
|
||||
.btn-ghost--muted {
|
||||
color: hsl(var(--color-text) / 0.45);
|
||||
}
|
||||
|
||||
/* From 768 to 1023 px the kit's logotype (166 px) and the whole navigation share the bar:
|
||||
tighter buttons instead of an overflow; the logotype of the studio DA kit stays as it is */
|
||||
@media (max-width: 1023px) {
|
||||
.nav-main {
|
||||
gap: 0.125rem;
|
||||
}
|
||||
|
||||
.nav-axis,
|
||||
.btn-ghost--muted {
|
||||
padding-left: 0.5rem;
|
||||
padding-right: 0.5rem;
|
||||
}
|
||||
|
||||
.nav-sep {
|
||||
margin: 0 0.25rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
<script setup lang="ts">
|
||||
// One of the 61 birds (studio DA, validated 2026-10-28), drawn from /images/oiseaux/<nom>.svg as a
|
||||
// mask filled with currentColor : it takes the colour the page gives it, like the shadoks did.
|
||||
// The classes of the figure it replaces keep its place, size, opacity and animation.
|
||||
import { OISEAUX } from '~/utils/oiseaux'
|
||||
|
||||
const props = defineProps<{ nom: string }>()
|
||||
const o = computed(() => OISEAUX[props.nom])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span
|
||||
v-if="o"
|
||||
class="oiseau"
|
||||
aria-hidden="true"
|
||||
:style="{ aspectRatio: o.ratio, '--oiseau': `url(/images/oiseaux/${props.nom}.svg)` }"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.oiseau {
|
||||
display: block;
|
||||
background-color: currentColor;
|
||||
-webkit-mask: var(--oiseau) center / contain no-repeat;
|
||||
mask: var(--oiseau) center / contain no-repeat;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,170 @@
|
||||
<script setup lang="ts">
|
||||
// A bird of the librodrome placed on its block (composition of 03/11, docs/consignes-yvv.md):
|
||||
// it stands on a real edge of the page, next to the content its trade is about.
|
||||
// The host block is the positioning context: position relative, overflow visible.
|
||||
//
|
||||
// Poses
|
||||
// perche on the block's top edge (bottom: 100%), x with left / right
|
||||
// marge-g beside the block, on its left; feet at the block's bottom, raised by `bottom`
|
||||
// marge-d the same, on its right
|
||||
// derriere large and pale, behind the block (z-index -1): only what sticks out shows.
|
||||
// Needs a stacking context above the block (isolation: isolate) and a block
|
||||
// that creates none itself (no z-index, transform, opacity, filter).
|
||||
// libre placed only by left / right / top / bottom (an empty area next to its block)
|
||||
//
|
||||
// `bout`: a perched bird at the end (or the start) of a line of a title. The component is then a
|
||||
// zero-size anchor in the text, at its baseline, written right after the text (the gap is `left`,
|
||||
// 12 px by default) or right before it (the gap is `right`): the bird follows the end of the text
|
||||
// at every width, wherever the line breaks (rule e, docs/da-oiseaux.md).
|
||||
//
|
||||
// Birds in the margin, behind a block or free need room around the column: they show from
|
||||
// 1200 px wide, the perched ones from 768 px (`seuil` overrides, for instance 1024 for a perched
|
||||
// bird whose block has no void above it until the grid splits in two); under 768 px, only a
|
||||
// bird marked `mobile`.
|
||||
// A perched bird needs, above its edge, a void at least as tall as itself (DA rule of 03/11);
|
||||
// where blocks stand close, the bird goes in the margin or behind the block.
|
||||
//
|
||||
// Drawing rules of the studio DA (kit README, §2.5): one colour (currentColor), proportions
|
||||
// intact and never cropped, 72 px wide at least, opacity 0.18-0.28 next to text and 0.10-0.14
|
||||
// large behind a card, mirror allowed except for the calligraphe and the typographe.
|
||||
// Birds standing on an edge do not move; only the one behind a card drifts a little (Yvv, 03/11).
|
||||
import { OISEAUX } from '~/utils/oiseaux'
|
||||
|
||||
type Pose = 'perche' | 'marge-g' | 'marge-d' | 'derriere' | 'libre'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
nom: string
|
||||
pose?: Pose
|
||||
/** Width in px: S 72-90, M 108-156, L 200-250 */
|
||||
w?: number
|
||||
left?: string
|
||||
right?: string
|
||||
top?: string
|
||||
bottom?: string
|
||||
/** Gap between the block and a bird in the margin */
|
||||
ecart?: string
|
||||
retourne?: boolean
|
||||
/** Shown under 768 px (one bird per page at most) */
|
||||
mobile?: boolean
|
||||
/** Shown under 768 px only: the phone place of a bird placed elsewhere on wider screens */
|
||||
mobileSeul?: boolean
|
||||
/** Narrowest window (px) where the bird shows: 1200 by default for the margin, behind
|
||||
* and free poses, 768 for a perched bird */
|
||||
seuil?: 768 | 1024 | 1200
|
||||
ton?: 'primary' | 'accent'
|
||||
op?: number
|
||||
/** At the end (or the start) of a line of text: written in the text, without a space */
|
||||
bout?: boolean
|
||||
}>(), { pose: 'perche', w: 80, ecart: '2.5rem', ton: 'primary' })
|
||||
|
||||
/** These two write and compose from left to right: never mirrored */
|
||||
const NO_MIRROR = new Set(['calligraphe', 'typographe'])
|
||||
|
||||
const sol = computed(() => OISEAUX[props.nom]?.sol ?? 5)
|
||||
|
||||
const style = computed(() => {
|
||||
const s: Record<string, string> = { 'width': `${Math.max(72, props.w)}px`, '--sol': `${sol.value}%` }
|
||||
if (props.pose === 'perche') s.bottom = props.bout ? '0px' : '100%'
|
||||
if (props.bout && props.left === undefined && props.right === undefined) s.left = '12px'
|
||||
if (props.pose === 'marge-g') s.right = `calc(100% + ${props.ecart})`
|
||||
if (props.pose === 'marge-d') s.left = `calc(100% + ${props.ecart})`
|
||||
if (props.pose === 'marge-g' || props.pose === 'marge-d') s.bottom = '0px'
|
||||
for (const k of ['left', 'right', 'top', 'bottom'] as const) {
|
||||
if (props[k] !== undefined) s[k] = props[k]!
|
||||
}
|
||||
if (props.op !== undefined) s.opacity = String(props.op)
|
||||
return s
|
||||
})
|
||||
|
||||
const classes = computed(() => [
|
||||
'oiseau-pose',
|
||||
`oiseau-pose--${props.pose}`,
|
||||
{
|
||||
'oiseau-pose--retourne': props.retourne && !NO_MIRROR.has(props.nom),
|
||||
'oiseau-pose--mobile': props.mobile || props.mobileSeul,
|
||||
'oiseau-pose--mobile-seul': props.mobileSeul,
|
||||
'oiseau-pose--large': (props.seuil ?? (props.pose === 'perche' ? 768 : 1200)) >= 1200,
|
||||
'oiseau-pose--moyen': props.seuil === 1024,
|
||||
'oiseau-pose--accent': props.ton === 'accent',
|
||||
},
|
||||
])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span v-if="bout" class="oiseau-bout"><UiOiseau :nom="nom" :class="classes" :style="style" :data-oiseau="nom" :data-pose="pose" /></span>
|
||||
<UiOiseau v-else :nom="nom" :class="classes" :style="style" :data-oiseau="nom" :data-pose="pose" />
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* The anchor of a bird at the end of a line: no size, on the text's baseline */
|
||||
.oiseau-bout {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 0;
|
||||
height: 0;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
.oiseau-pose {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
pointer-events: none;
|
||||
color: hsl(var(--color-primary));
|
||||
opacity: 0.24;
|
||||
/* The drawing's ground line lands on the edge */
|
||||
transform: translateY(var(--sol));
|
||||
}
|
||||
|
||||
.oiseau-pose--accent {
|
||||
color: hsl(var(--color-accent));
|
||||
}
|
||||
|
||||
.oiseau-pose--perche {
|
||||
opacity: 0.28;
|
||||
}
|
||||
|
||||
.oiseau-pose--derriere {
|
||||
z-index: -1;
|
||||
opacity: 0.12;
|
||||
animation: oiseau-derive 12s ease-in-out infinite alternate;
|
||||
}
|
||||
|
||||
.oiseau-pose--retourne {
|
||||
scale: -1 1;
|
||||
}
|
||||
|
||||
@keyframes oiseau-derive {
|
||||
from { translate: 0 0; }
|
||||
to { translate: 0 -6px; }
|
||||
}
|
||||
|
||||
@media (max-width: 1199px) {
|
||||
.oiseau-pose--large {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1023px) {
|
||||
.oiseau-pose--moyen {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.oiseau-pose--mobile-seul {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.oiseau-pose:not(.oiseau-pose--mobile) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.oiseau-pose--derriere {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -21,7 +21,7 @@ export function useSeoPage(opts: {
|
||||
const title = opts.title || 'Le Librodrome'
|
||||
const description = opts.description
|
||||
|| 'Autonomie numérique, économique et citoyenne. Un livre et des chansons sur l\'économie du don.'
|
||||
const rawImage = opts.image || '/og-default.png?v=2'
|
||||
const rawImage = opts.image || '/og-default.png?v=3'
|
||||
const image = rawImage.startsWith('http') ? rawImage : `${siteUrl}${rawImage}`
|
||||
const canonical = `${siteUrl}${route.path}`
|
||||
const type = opts.type || 'website'
|
||||
|
||||
@@ -36,11 +36,21 @@ useScrollTracking()
|
||||
grid-template-rows: auto 1fr auto;
|
||||
}
|
||||
|
||||
/* The seal's block, between the page and the footer: a bird of a page that stands on the footer's
|
||||
top rule takes its offset from here (evenement, the puppeteer) */
|
||||
.app-main {
|
||||
--seal-w: 44px;
|
||||
--seal-mt: 2rem;
|
||||
--seal-mb: 1rem;
|
||||
--seal-block: calc(var(--seal-mt) + var(--seal-w) * 105 / 112 + var(--seal-mb));
|
||||
}
|
||||
|
||||
/* === Seal — 益 Yì (Increase) === */
|
||||
.app-seal {
|
||||
display: block;
|
||||
width: 44px;
|
||||
margin: 2rem 1.5rem 1rem auto;
|
||||
width: var(--seal-w);
|
||||
/* its height follows the viewBox: width × 105 / 112 (in --seal-block) */
|
||||
margin: var(--seal-mt) 1.5rem var(--seal-mb) auto;
|
||||
color: hsl(var(--color-accent));
|
||||
opacity: 0.28;
|
||||
filter: drop-shadow(1px 1px 0.5px rgba(0,0,0,0.25))
|
||||
|
||||
@@ -1,36 +1,14 @@
|
||||
<template>
|
||||
<div class="relative overflow-hidden section-padding">
|
||||
<!-- Shadok philosopher: character sitting cross-legged, floating -->
|
||||
<svg class="shadok-philosopher" viewBox="0 0 220 280" fill="none" aria-hidden="true">
|
||||
<!-- Body (round, serene) -->
|
||||
<ellipse cx="110" cy="150" rx="48" ry="55" fill="currentColor" opacity="0.85"/>
|
||||
<!-- Head -->
|
||||
<ellipse cx="110" cy="82" rx="26" ry="25" fill="currentColor" opacity="0.8"/>
|
||||
<!-- Closed eyes (meditating) -->
|
||||
<path d="M96 80 Q100 84 105 80" stroke="currentColor" stroke-width="2" stroke-linecap="round" fill="none" opacity="0.4"/>
|
||||
<path d="M115 80 Q119 84 124 80" stroke="currentColor" stroke-width="2" stroke-linecap="round" fill="none" opacity="0.4"/>
|
||||
<!-- Serene smile -->
|
||||
<path d="M102 93 Q110 98 118 93" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" fill="none" opacity="0.3"/>
|
||||
<!-- Arms resting on knees -->
|
||||
<path d="M64 155 Q55 180 70 200" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" fill="none" opacity="0.6"/>
|
||||
<path d="M156 155 Q165 180 150 200" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" fill="none" opacity="0.6"/>
|
||||
<!-- Hands on knees -->
|
||||
<circle cx="70" cy="200" r="5" fill="currentColor" opacity="0.4"/>
|
||||
<circle cx="150" cy="200" r="5" fill="currentColor" opacity="0.4"/>
|
||||
<!-- Crossed legs -->
|
||||
<path d="M80 205 Q90 230 120 235" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" fill="none" opacity="0.6"/>
|
||||
<path d="M140 205 Q130 230 100 235" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" fill="none" opacity="0.6"/>
|
||||
<!-- Floating aura lines -->
|
||||
<path d="M50 245 Q110 260 170 245" stroke="currentColor" stroke-width="1.5" stroke-dasharray="4 4" fill="none" opacity="0.25"/>
|
||||
<path d="M60 255 Q110 268 160 255" stroke="currentColor" stroke-width="1" stroke-dasharray="3 5" fill="none" opacity="0.18"/>
|
||||
<!-- Small sparkles around -->
|
||||
<circle cx="55" cy="110" r="2.5" fill="currentColor" opacity="0.2"/>
|
||||
<circle cx="170" cy="95" r="2" fill="currentColor" opacity="0.18"/>
|
||||
<circle cx="160" cy="130" r="3" fill="currentColor" opacity="0.15"/>
|
||||
</svg>
|
||||
|
||||
<div class="container-content mx-auto max-w-3xl">
|
||||
<div class="container-content relative mx-auto max-w-3xl">
|
||||
<!-- In the margins, at the height of « La vision » and of the economic proposal -->
|
||||
<UiOiseauPose nom="philosophe" pose="libre" left="calc(100% + 50px)" top="470px" :w="120" retourne />
|
||||
<UiOiseauPose nom="penseur" pose="libre" right="calc(100% + 60px)" top="720px" :w="100" />
|
||||
<ContentRenderer v-if="page" :value="page" class="prose" />
|
||||
<!-- Credit of the mascot (Yvv, word for word, 27/10) -->
|
||||
<p class="oiseaux-credit">
|
||||
N'ayant pas les moyens d'acquérir des droits, le personnage mascotte est une création qui fait un clin d'œil aux shadoks, avec respect et une grande affection, petit hommage en passant.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -51,23 +29,11 @@ const { data: page } = await useAsyncData('about', () =>
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.shadok-philosopher {
|
||||
position: absolute;
|
||||
right: 3%;
|
||||
bottom: 6%;
|
||||
width: clamp(120px, 15vw, 220px);
|
||||
opacity: 0.3;
|
||||
pointer-events: none;
|
||||
color: hsl(var(--color-primary));
|
||||
animation: shadok-float-philosopher 11s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes shadok-float-philosopher {
|
||||
0%, 100% { transform: translateY(0); }
|
||||
50% { transform: translateY(-12px); }
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.shadok-philosopher { display: none; }
|
||||
.oiseaux-credit {
|
||||
margin: 3rem 0 0;
|
||||
font-size: 0.8rem;
|
||||
line-height: 1.5;
|
||||
color: hsl(var(--color-text) / 0.5);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -60,8 +60,9 @@
|
||||
<div
|
||||
v-if="content?.features"
|
||||
id="section-features"
|
||||
class="grid gap-4 sm:grid-cols-2 mb-12 max-w-2xl mx-auto section-anchor"
|
||||
class="relative grid gap-4 sm:grid-cols-2 mb-12 max-w-2xl mx-auto section-anchor"
|
||||
>
|
||||
<UiOiseauPose v-for="o in oiseaux.fonctions ?? []" :key="`${o.nom}-${o.pose}`" v-bind="o" />
|
||||
<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`" />
|
||||
@@ -77,6 +78,7 @@
|
||||
id="section-project"
|
||||
class="project-card mb-8 max-w-2xl mx-auto section-anchor"
|
||||
>
|
||||
<UiOiseauPose v-for="o in oiseaux.projet ?? []" :key="`${o.nom}-${o.pose}`" v-bind="o" />
|
||||
<div class="project-icon">
|
||||
<div class="i-lucide-rocket h-5 w-5" />
|
||||
</div>
|
||||
@@ -93,8 +95,9 @@
|
||||
<div
|
||||
v-if="content?.content"
|
||||
id="section-content"
|
||||
class="prose-block mb-10 max-w-2xl mx-auto section-anchor"
|
||||
class="prose-block relative mb-10 max-w-2xl mx-auto section-anchor"
|
||||
>
|
||||
<UiOiseauPose v-for="o in oiseaux.texte ?? []" :key="`${o.nom}-${o.pose}`" v-bind="o" />
|
||||
<p class="leading-relaxed whitespace-pre-line" style="color: hsl(var(--color-text-muted))">
|
||||
{{ content.content }}
|
||||
</p>
|
||||
@@ -119,9 +122,32 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { OiseauPlace } from '~/utils/oiseauPose'
|
||||
|
||||
const route = useRoute()
|
||||
const slug = route.params.slug as string
|
||||
|
||||
// Birds of each page: by the features grid, the project card and the text block
|
||||
interface OiseauxPage { fonctions?: OiseauPlace[], projet?: OiseauPlace[], texte?: OiseauPlace[] }
|
||||
const OISEAUX_PAGE: Record<string, OiseauxPage> = {
|
||||
'decision': {
|
||||
fonctions: [
|
||||
{ nom: 'mime', pose: 'marge-g', ecart: '48px', w: 104 },
|
||||
// on the line under the first row of cards (16 px gap between the rows)
|
||||
{ nom: 'avocate', pose: 'marge-d', ecart: '44px', bottom: 'calc(50% + 8px)', w: 96, retourne: true },
|
||||
// above the grid's left corner the page is empty (the links row starts at its middle)
|
||||
{ nom: 'tisserand', pose: 'perche', left: '24px', w: 84 },
|
||||
],
|
||||
},
|
||||
'tarifs-eau': {
|
||||
// the actor in the right margin, feet on the card's top edge: perched on the card, it stood
|
||||
// 11 px under the introduction (rule f, DA 03/11 after 17h35); the summary starts lower
|
||||
projet: [{ nom: 'comedien', pose: 'marge-d', ecart: '24px', bottom: '100%', w: 100, retourne: true }],
|
||||
texte: [{ nom: 'potier', pose: 'marge-g', ecart: '52px', w: 112 }],
|
||||
},
|
||||
}
|
||||
const oiseaux = computed<OiseauxPage>(() => OISEAUX_PAGE[slug] ?? {})
|
||||
|
||||
const [{ data: content }, { data: homeData }] = await Promise.all([
|
||||
usePageContent(`citoyenne/${slug}`),
|
||||
usePageContent('home'),
|
||||
@@ -189,6 +215,7 @@ useSeoPage({
|
||||
}
|
||||
|
||||
.project-card {
|
||||
position: relative;
|
||||
padding: 1.5rem;
|
||||
border-radius: 0.75rem;
|
||||
border: 1px solid hsl(var(--color-primary) / 0.15);
|
||||
|
||||
@@ -1,339 +1,16 @@
|
||||
<template>
|
||||
<div class="axis-scope axis-scope--cit 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="axis-scope axis-scope--cit oiseaux-scene relative overflow-hidden section-padding">
|
||||
<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 }}
|
||||
<p class="relative mb-2 font-mono text-sm tracking-widest text-primary uppercase">
|
||||
<!-- Phone: the title fills the width; the bird stands at the end of this line, on its baseline.
|
||||
A flat bird (37-44 px tall at 72 px wide): 93 px separate this baseline from the sticky
|
||||
header, and a perched bird keeps a void of its own height above it (rule f) -->
|
||||
{{ content?.kicker }}<UiOiseauPose nom="potier" pose="perche" bout :w="72" mobile-seul />
|
||||
</p>
|
||||
<h1 class="page-title relative font-display font-bold tracking-tight" style="color: hsl(var(--color-text))">
|
||||
<!-- At the end of the title, on its baseline (rule e, docs/da-oiseaux.md) -->
|
||||
{{ content?.title }}<UiOiseauPose nom="vigie" pose="perche" bout left="16px" :w="96" retourne :seuil="1200" />
|
||||
</h1>
|
||||
<p class="mt-4 mx-auto max-w-2xl leading-relaxed" style="color: hsl(var(--color-text-muted))">
|
||||
{{ content?.description }}
|
||||
@@ -341,7 +18,9 @@
|
||||
</header>
|
||||
|
||||
<div class="mx-auto max-w-3xl flex flex-col gap-6">
|
||||
<!-- Decision collective -->
|
||||
<!-- Decision collective — the card clips its corners, the birds stand on a wrapper -->
|
||||
<div class="relative">
|
||||
<UiOiseauPose nom="juge" pose="marge-d" ecart="40px" :w="84" retourne />
|
||||
<div class="item-card">
|
||||
<NuxtLink to="/citoyenne/decision" class="item-overlay" aria-label="Décision collective" tabindex="-1" />
|
||||
<div class="item-body group">
|
||||
@@ -365,8 +44,15 @@
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tarifs de l'eau -->
|
||||
<div class="relative">
|
||||
<!-- 24 px between the two cards: no room above them for a perched bird (DA rule of 03/11) -->
|
||||
<!-- Out by the side of the card, not by its corners: the card is a link (rule h); its feet
|
||||
46 px above the card's bottom, where the captain stands on the other side (rule c) -->
|
||||
<UiOiseauPose nom="cartographe" pose="derriere" left="calc(100% - 100px)" bottom="46px" :w="210" />
|
||||
<UiOiseauPose nom="capitaine" pose="marge-g" ecart="46px" :w="136" />
|
||||
<div class="item-card">
|
||||
<NuxtLink to="/citoyenne/tarifs-eau" class="item-overlay" aria-label="Tarifs de l'eau" tabindex="-1" />
|
||||
<div class="item-body group">
|
||||
@@ -391,6 +77,7 @@
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -517,123 +204,4 @@ useSeoPage({
|
||||
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,249 +1,17 @@
|
||||
<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="oiseaux-scene relative overflow-hidden section-padding">
|
||||
<div class="container-content">
|
||||
<div class="mx-auto max-w-2xl">
|
||||
<div class="section-icon mx-auto mb-6">
|
||||
<div class="section-icon relative 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 class="relative mb-2 font-mono text-sm tracking-widest text-primary uppercase text-center">
|
||||
<!-- Phone: the title fills the width; the bird stands at the end of this line, on its baseline -->
|
||||
{{ content?.kicker }}<UiOiseauPose nom="lectrice" pose="perche" bout :w="84" retourne mobile-seul />
|
||||
</p>
|
||||
|
||||
<h1 class="font-display text-3xl font-bold mb-4 text-center" style="color: hsl(var(--color-text))">
|
||||
<h1 class="relative font-display text-3xl font-bold mb-4 text-center" style="color: hsl(var(--color-text))">
|
||||
{{ content?.title }}
|
||||
</h1>
|
||||
|
||||
@@ -253,6 +21,10 @@
|
||||
|
||||
<!-- Bookelis CTA -->
|
||||
<div class="cta-block mb-8">
|
||||
<UiOiseauPose nom="cycliste-livreur" pose="marge-g" ecart="54px" :w="150" />
|
||||
<!-- The reader in the right margin, feet on the card's top edge: on the bag, under the
|
||||
sticky header, she had 22 px of void above her (rule f, DA 03/11 after 17h35) -->
|
||||
<UiOiseauPose nom="lectrice" pose="marge-d" ecart="24px" bottom="100%" :w="84" retourne />
|
||||
<div class="cta-icon">
|
||||
<div class="i-lucide-globe h-5 w-5" />
|
||||
</div>
|
||||
@@ -277,6 +49,7 @@
|
||||
|
||||
<!-- Librairie -->
|
||||
<div class="cta-block mb-10">
|
||||
<UiOiseauPose nom="libraire" pose="derriere" left="calc(100% - 96px)" bottom="-36px" :w="214" retourne />
|
||||
<div class="cta-icon cta-icon--accent">
|
||||
<div class="i-lucide-store h-5 w-5" />
|
||||
</div>
|
||||
@@ -325,6 +98,7 @@ useSeoPage({
|
||||
}
|
||||
|
||||
.cta-block {
|
||||
position: relative;
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
padding: 1.5rem;
|
||||
@@ -369,87 +143,4 @@ useSeoPage({
|
||||
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>
|
||||
|
||||
@@ -1,382 +1,14 @@
|
||||
<template>
|
||||
<div class="axis-scope axis-scope--eco 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="axis-scope axis-scope--eco oiseaux-scene relative overflow-hidden section-padding">
|
||||
<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>
|
||||
<p class="relative mb-2 font-mono text-sm tracking-widest text-primary uppercase">
|
||||
<!-- Phone: the title fills the width; the bird stands at the end of this line, on its baseline.
|
||||
A flat bird (37-44 px tall at 72 px wide): 93 px separate this baseline from the sticky
|
||||
header, and a perched bird keeps a void of its own height above it (rule f) -->
|
||||
{{ content?.kicker }}<UiOiseauPose nom="maraichere" pose="perche" bout :w="72" retourne mobile-seul />
|
||||
</p>
|
||||
<h1 class="page-title font-display font-bold tracking-tight" style="color: hsl(var(--color-text))">
|
||||
{{ content?.title }}
|
||||
</h1>
|
||||
@@ -386,7 +18,9 @@
|
||||
</header>
|
||||
|
||||
<div class="mx-auto max-w-3xl flex flex-col gap-8">
|
||||
<!-- Monnaie libre -->
|
||||
<!-- Monnaie libre — the card clips its Ğ1 watermark, so the bird stands on a wrapper -->
|
||||
<div class="relative">
|
||||
<UiOiseauPose nom="pompeur-de-g1" pose="marge-d" ecart="34px" :w="126" retourne />
|
||||
<NuxtLink to="/economique/monnaie-libre" class="item-card g1-card group">
|
||||
<span class="g1-watermark" aria-hidden="true">Ğ1</span>
|
||||
<div class="item-header">
|
||||
@@ -406,9 +40,16 @@
|
||||
<div class="i-lucide-arrow-right h-3.5 w-3.5" />
|
||||
</div>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
|
||||
<!-- Modèle économique — bloc livre -->
|
||||
<div class="book-block">
|
||||
<!-- The balance (the measure of the gift) peeks out from behind the book, on the left and
|
||||
halfway up: the market gardener stands low on the right, never at its height, and the
|
||||
four birds of the page do not alternate at an even step -->
|
||||
<UiOiseauPose nom="la-balance" pose="derriere" left="-150px" bottom="calc(100% - 240px)" :w="240" />
|
||||
<!-- Not by the Productions card: with the beekeeper, a pair at the same height -->
|
||||
<UiOiseauPose nom="maraichere" pose="marge-d" ecart="46px" :w="108" retourne />
|
||||
<HomeBookSection
|
||||
compact
|
||||
@open-player="showBookPlayer = true"
|
||||
@@ -418,6 +59,7 @@
|
||||
|
||||
<!-- Productions collectives -->
|
||||
<NuxtLink to="/economique/productions-collectives" class="item-card group">
|
||||
<UiOiseauPose nom="apicultrice" pose="marge-g" ecart="52px" :w="124" />
|
||||
<div class="item-header">
|
||||
<div class="item-icon">
|
||||
<div class="i-lucide-users h-5 w-5" />
|
||||
@@ -469,6 +111,7 @@ const showPdfReader = ref(false)
|
||||
}
|
||||
|
||||
.item-card {
|
||||
position: relative;
|
||||
display: block;
|
||||
padding: 1.5rem;
|
||||
border-radius: 16px;
|
||||
@@ -526,6 +169,7 @@ const showPdfReader = ref(false)
|
||||
}
|
||||
|
||||
.book-block {
|
||||
position: relative;
|
||||
padding: 1.25rem;
|
||||
border-radius: 16px;
|
||||
border: none;
|
||||
@@ -567,123 +211,4 @@ const showPdfReader = ref(false)
|
||||
.g1-card:hover .g1-watermark {
|
||||
color: hsl(var(--color-primary) / 0.11);
|
||||
}
|
||||
|
||||
/* 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>
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
<template>
|
||||
<div v-if="chapter">
|
||||
<div v-if="chapter" class="relative">
|
||||
<!-- The book's trades in the margins, each on a line of the chapter (rule e): the two who
|
||||
write keep their way, on the left, the calligrapher on the title's rule (in the chapter
|
||||
header), the typographer on the navigation's rule; the box maker in the right margin, by
|
||||
the PDF and songs buttons (in the header); the proofreader on the right, by the last
|
||||
paragraph -->
|
||||
<UiOiseauPose nom="correcteur" pose="libre" left="calc(100% + 40px)" bottom="140px" :w="108" retourne />
|
||||
<BookChapterHeader
|
||||
:title="chapter.title"
|
||||
:description="chapter.description"
|
||||
@@ -10,8 +16,11 @@
|
||||
|
||||
<BookChapterContent :content="chapter" />
|
||||
|
||||
<!-- Prev / Next navigation -->
|
||||
<nav class="mt-16 flex items-center justify-between border-t border-white/8 pt-8">
|
||||
<!-- Prev / Next navigation. The rule above it is drawn (border-t alone sets no style here):
|
||||
the typographer stands on it, in the left margin. Perched in the column, the box maker had
|
||||
20 to 27 px of void under the last paragraph (rule f) -->
|
||||
<nav class="relative mt-16 flex items-center justify-between border-t border-t-solid border-white/8 pt-8">
|
||||
<UiOiseauPose nom="typographe" pose="marge-g" ecart="64px" bottom="100%" :w="100" />
|
||||
<NuxtLink
|
||||
v-if="prevChapter"
|
||||
:to="`/economique/modele-eco/${prevChapter.stem?.split('/').pop()}`"
|
||||
|
||||
@@ -1,360 +1,5 @@
|
||||
<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="oiseaux-scene relative overflow-hidden section-padding">
|
||||
<div class="container-content">
|
||||
<!-- Page de couverture du livre -->
|
||||
<HomeBookSection
|
||||
@@ -365,9 +10,13 @@
|
||||
/>
|
||||
|
||||
<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 }}
|
||||
<p class="relative mb-2 font-mono text-sm tracking-widest text-primary uppercase">
|
||||
<!-- Phone: the title fills the width; the bird stands at the end of this line, on its baseline -->
|
||||
{{ content?.kicker }}<UiOiseauPose nom="relieur" pose="perche" bout :w="72" mobile-seul />
|
||||
</p>
|
||||
<h1 class="page-title relative font-display font-bold tracking-tight" style="color: hsl(var(--color-text))">
|
||||
<!-- Before the title, on its baseline (rule e, docs/da-oiseaux.md) -->
|
||||
<UiOiseauPose nom="relieur" pose="perche" bout right="16px" :w="108" :seuil="1200" />{{ content?.title }}
|
||||
</h1>
|
||||
<p class="mt-3 mx-auto max-w-2xl text-sm" style="color: hsl(var(--color-text-muted))">
|
||||
{{ content?.description }}
|
||||
@@ -379,7 +28,9 @@
|
||||
<li
|
||||
v-for="chapter in chapters"
|
||||
:key="chapter.path"
|
||||
class="relative"
|
||||
>
|
||||
<UiOiseauPose v-for="o in OISEAUX_CHAPITRE[chapter.stem?.split('/').pop() ?? ''] ?? []" :key="`${o.nom}-${o.pose}`" v-bind="o" />
|
||||
<NuxtLink
|
||||
:to="`/economique/modele-eco/${chapter.stem?.split('/').pop()}`"
|
||||
class="card-surface flex items-start gap-4 group"
|
||||
@@ -418,6 +69,18 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { OiseauPlace } from '~/utils/oiseauPose'
|
||||
|
||||
// Birds along the chapters, each by the one its trade tells (irregular intervals). The cards
|
||||
// stand 12 px apart: no room above them for a perched bird (DA rule of 03/11)
|
||||
const OISEAUX_CHAPITRE: Record<string, OiseauPlace[]> = {
|
||||
'01-introduction': [{ nom: 'conteuse', pose: 'marge-g', ecart: '50px', w: 122 }],
|
||||
'03-mesure': [{ nom: 'la-balance', pose: 'marge-d', ecart: '40px', w: 112, retourne: true }],
|
||||
'07-echanger': [{ nom: 'colporteur', pose: 'marge-g', ecart: '44px', w: 116 }],
|
||||
'08-institution': [{ nom: 'empileur', pose: 'marge-d', ecart: '90px', w: 104, retourne: true }],
|
||||
'11-annexes': [{ nom: 'illustratrice', pose: 'derriere', left: 'calc(100% - 110px)', bottom: '-40px', w: 220 }],
|
||||
}
|
||||
|
||||
definePageMeta({
|
||||
layout: 'default',
|
||||
})
|
||||
@@ -462,125 +125,4 @@ const showPdfReader = ref(false)
|
||||
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>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="axis-scope axis-scope--eco section-padding">
|
||||
<div class="oiseaux-scene axis-scope axis-scope--eco section-padding">
|
||||
<div class="container-content">
|
||||
|
||||
<!-- Header -->
|
||||
@@ -62,8 +62,11 @@
|
||||
<div
|
||||
v-if="content?.content"
|
||||
id="section-content"
|
||||
class="prose-block mb-8 max-w-2xl mx-auto section-anchor"
|
||||
class="prose-block relative mb-8 max-w-2xl mx-auto section-anchor"
|
||||
>
|
||||
<UiOiseauPose nom="boulangere" pose="marge-g" ecart="56px" :w="96" />
|
||||
<!-- Behind the top of the text block, which has a background (the links stand apart) -->
|
||||
<UiOiseauPose nom="vigneronne" pose="derriere" left="-140px" bottom="calc(100% - 220px)" :w="210" />
|
||||
<p class="leading-relaxed whitespace-pre-line" style="color: hsl(var(--color-text-muted))">
|
||||
{{ content.content }}
|
||||
</p>
|
||||
@@ -73,7 +76,7 @@
|
||||
<div
|
||||
v-if="content?.links"
|
||||
id="section-links"
|
||||
class="flex flex-col gap-3 mb-10 max-w-2xl mx-auto section-anchor"
|
||||
class="relative flex flex-col gap-3 mb-10 max-w-2xl mx-auto section-anchor"
|
||||
>
|
||||
<a
|
||||
v-for="link in content.links"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="axis-scope axis-scope--eco section-padding">
|
||||
<div class="axis-scope axis-scope--eco oiseaux-scene relative section-padding">
|
||||
<div class="container-content">
|
||||
<div class="mx-auto max-w-2xl">
|
||||
<div class="section-icon mx-auto mb-6">
|
||||
@@ -10,11 +10,14 @@
|
||||
{{ content?.kicker }}
|
||||
</p>
|
||||
|
||||
<h1 class="font-display text-3xl font-bold mb-4 text-center" style="color: hsl(var(--color-text))">
|
||||
<!-- The workshop: each trade of the collective productions around the page -->
|
||||
<h1 class="relative font-display text-3xl font-bold mb-4 text-center" style="color: hsl(var(--color-text))">
|
||||
{{ content?.title }}
|
||||
<!-- In the margin, feet on the title's baseline (rule e) -->
|
||||
<UiOiseauPose nom="soudeuse" pose="marge-d" ecart="30px" bottom="4px" :w="112" retourne />
|
||||
</h1>
|
||||
|
||||
<p class="text-lg leading-relaxed mb-8 text-center" style="color: hsl(var(--color-text-muted))">
|
||||
<p class="relative text-lg leading-relaxed mb-8 text-center" style="color: hsl(var(--color-text-muted))">
|
||||
{{ content?.description }}
|
||||
</p>
|
||||
|
||||
@@ -24,12 +27,17 @@
|
||||
|
||||
<!-- Content -->
|
||||
<div v-if="content?.content" class="prose-block mb-10">
|
||||
<UiOiseauPose nom="menuisier" pose="perche" left="20px" :w="84" />
|
||||
<!-- Phone: a flatter bird at the same place, the text above leaves 89 px (rule f) -->
|
||||
<UiOiseauPose nom="cycliste-livreur" pose="perche" left="20px" :w="72" mobile-seul />
|
||||
<UiOiseauPose nom="forgeron" pose="marge-g" ecart="70px" :w="140" />
|
||||
<UiOiseauPose nom="berger" pose="derriere" left="calc(100% - 110px)" bottom="-46px" :w="230" />
|
||||
<p class="leading-relaxed whitespace-pre-line" style="color: hsl(var(--color-text-muted))">
|
||||
{{ content.content }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="text-center">
|
||||
<div class="relative text-center">
|
||||
<UiBaseButton variant="ghost" to="/economique">
|
||||
<div class="i-lucide-arrow-left mr-2 h-4 w-4" />
|
||||
Autonomie économique
|
||||
@@ -78,6 +86,7 @@ useSeoPage({
|
||||
}
|
||||
|
||||
.prose-block {
|
||||
position: relative;
|
||||
padding: 1.5rem;
|
||||
border-radius: 0.75rem;
|
||||
background: hsl(var(--color-surface));
|
||||
|
||||
@@ -1,75 +1,11 @@
|
||||
<template>
|
||||
<div class="relative overflow-hidden section-padding">
|
||||
<!-- Shadok danseur: character dancing with music notes -->
|
||||
<svg class="shadok-danseur" viewBox="0 0 240 300" fill="none" aria-hidden="true">
|
||||
<!-- Body (dynamic pose, leaning) -->
|
||||
<ellipse cx="120" cy="155" rx="38" ry="46" fill="currentColor" opacity="0.85"/>
|
||||
<!-- Head (tilted with joy) -->
|
||||
<ellipse cx="125" cy="92" rx="24" ry="23" fill="currentColor" opacity="0.8"/>
|
||||
<!-- Eyes (happy, squinted) -->
|
||||
<path d="M114 88 Q118 84 122 88" stroke="currentColor" stroke-width="2" stroke-linecap="round" fill="none" opacity="0.4"/>
|
||||
<path d="M130 88 Q134 84 138 88" stroke="currentColor" stroke-width="2" stroke-linecap="round" fill="none" opacity="0.4"/>
|
||||
<!-- Big smile -->
|
||||
<path d="M116 100 Q125 108 134 100" stroke="currentColor" stroke-width="2" stroke-linecap="round" fill="none" opacity="0.35"/>
|
||||
<!-- Arms thrown up (dancing) -->
|
||||
<line x1="85" y1="140" x2="50" y2="100" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" opacity="0.6"/>
|
||||
<line x1="155" y1="140" x2="190" y2="105" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" opacity="0.6"/>
|
||||
<!-- Hands -->
|
||||
<circle cx="50" cy="98" r="4" fill="currentColor" opacity="0.4"/>
|
||||
<circle cx="190" cy="103" r="4" fill="currentColor" opacity="0.4"/>
|
||||
<!-- Legs (one kicked up) -->
|
||||
<line x1="105" y1="198" x2="80" y2="255" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" opacity="0.6"/>
|
||||
<line x1="135" y1="198" x2="170" y2="240" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" opacity="0.6"/>
|
||||
<!-- Feet -->
|
||||
<path d="M80 255 L68 258 M80 255 L75 261" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" opacity="0.5"/>
|
||||
<path d="M170 240 L180 238 M170 240 L175 246" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" opacity="0.5"/>
|
||||
<!-- Music notes floating around -->
|
||||
<text x="42" y="82" fill="currentColor" opacity="0.3" font-size="18">♪</text>
|
||||
<text x="195" y="88" fill="currentColor" opacity="0.25" font-size="16">♫</text>
|
||||
<text x="60" y="65" fill="currentColor" opacity="0.2" font-size="14">♩</text>
|
||||
<text x="180" y="72" fill="currentColor" opacity="0.2" font-size="20">♪</text>
|
||||
</svg>
|
||||
|
||||
<!-- Shadok DJ: character with headphones behind a turntable -->
|
||||
<svg class="shadok-dj" viewBox="0 0 260 300" fill="none" aria-hidden="true">
|
||||
<!-- Body -->
|
||||
<ellipse cx="130" cy="155" rx="42" ry="50" fill="currentColor" opacity="0.85"/>
|
||||
<!-- Head -->
|
||||
<circle cx="130" cy="88" r="26" fill="currentColor" opacity="0.8"/>
|
||||
<!-- Headphones band -->
|
||||
<path d="M104 78 Q130 55 156 78" stroke="currentColor" stroke-width="4" stroke-linecap="round" fill="none" opacity="0.6"/>
|
||||
<!-- Headphone ear pads -->
|
||||
<ellipse cx="102" cy="85" rx="8" ry="12" fill="currentColor" opacity="0.5"/>
|
||||
<ellipse cx="158" cy="85" rx="8" ry="12" fill="currentColor" opacity="0.5"/>
|
||||
<!-- Eyes (cool, half-lidded) -->
|
||||
<ellipse cx="120" cy="85" rx="5" ry="3" fill="currentColor" opacity="0.25"/>
|
||||
<ellipse cx="140" cy="85" rx="5" ry="3" fill="currentColor" opacity="0.25"/>
|
||||
<circle cx="121" cy="86" r="1.8" fill="currentColor" opacity="0.5"/>
|
||||
<circle cx="141" cy="86" r="1.8" fill="currentColor" opacity="0.5"/>
|
||||
<!-- Mouth (grin) -->
|
||||
<path d="M122 98 Q130 104 138 98" stroke="currentColor" stroke-width="2" stroke-linecap="round" fill="none" opacity="0.35"/>
|
||||
<!-- Arms reaching to turntable -->
|
||||
<line x1="90" y1="150" x2="55" y2="195" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" opacity="0.6"/>
|
||||
<line x1="170" y1="150" x2="205" y2="195" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" opacity="0.6"/>
|
||||
<!-- Turntable body -->
|
||||
<rect x="30" y="200" width="200" height="18" rx="4" fill="currentColor" opacity="0.4"/>
|
||||
<!-- Turntable platter -->
|
||||
<ellipse cx="130" cy="200" rx="55" ry="15" fill="currentColor" opacity="0.25"/>
|
||||
<ellipse cx="130" cy="200" rx="55" ry="15" stroke="currentColor" stroke-width="1.5" fill="none" opacity="0.35"/>
|
||||
<!-- Record center -->
|
||||
<circle cx="130" cy="200" r="5" fill="currentColor" opacity="0.4"/>
|
||||
<!-- Tone arm -->
|
||||
<line x1="195" y1="188" x2="150" y2="195" stroke="currentColor" stroke-width="2" stroke-linecap="round" opacity="0.5"/>
|
||||
<circle cx="195" cy="188" r="3" fill="currentColor" opacity="0.35"/>
|
||||
<!-- Legs -->
|
||||
<line x1="115" y1="202" x2="105" y2="260" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" opacity="0.6"/>
|
||||
<line x1="145" y1="202" x2="155" y2="260" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" opacity="0.6"/>
|
||||
</svg>
|
||||
|
||||
<div class="container-content">
|
||||
<!-- Hero section with book cover -->
|
||||
<div class="mb-12 grid items-center gap-8 md:grid-cols-2">
|
||||
<div class="book-cover-wrapper">
|
||||
<div class="book-cover-wrapper relative">
|
||||
<!-- On the cover's base line, in the empty space between the book and the text -->
|
||||
<UiOiseauPose nom="trompettiste" pose="libre" left="calc(100% - 112px)" bottom="0px" :w="104" />
|
||||
<div class="book-cover-3d">
|
||||
<img
|
||||
:src="homeContent?.book.coverImage"
|
||||
@@ -80,8 +16,12 @@
|
||||
</div>
|
||||
|
||||
<div class="text-center md:text-left">
|
||||
<p class="mb-2 font-mono text-sm tracking-widest text-accent uppercase">{{ content?.kicker }}</p>
|
||||
<h1 class="page-title font-display font-bold tracking-tight text-white">
|
||||
<p class="relative mb-2 font-mono text-sm tracking-widest text-accent uppercase">
|
||||
<!-- The DJ stands at the end of this line, on its baseline: the title's box has no visible
|
||||
top edge (rule e) -->
|
||||
{{ content?.kicker }}<UiOiseauPose nom="dj" pose="perche" bout left="14px" :w="84" retourne ton="accent" :seuil="1200" />
|
||||
</p>
|
||||
<h1 class="page-title relative font-display font-bold tracking-tight text-white">
|
||||
{{ content?.title }}
|
||||
</h1>
|
||||
<p class="mt-4 text-white/60">
|
||||
@@ -131,7 +71,10 @@
|
||||
</div>
|
||||
|
||||
<!-- Song list -->
|
||||
<div v-if="viewMode === 'list'" class="flex flex-col gap-2">
|
||||
<div v-if="viewMode === 'list'" class="relative flex flex-col gap-2">
|
||||
<!-- S, not M: above it the buttons leave 150 px, and a perched bird keeps a void of its own
|
||||
height above its head (rule f, DA 03/11 after 17h35) -->
|
||||
<UiOiseauPose nom="danseur" pose="perche" left="calc(64% - 60px)" :w="80" :seuil="1200" />
|
||||
<SongItem
|
||||
v-for="song in filteredSongs"
|
||||
:key="song.id"
|
||||
@@ -237,42 +180,4 @@ const filteredSongs = computed(() => {
|
||||
object-fit: cover;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.shadok-danseur {
|
||||
position: absolute;
|
||||
left: 2%;
|
||||
top: 3%;
|
||||
width: clamp(100px, 14vw, 200px);
|
||||
opacity: 0.28;
|
||||
pointer-events: none;
|
||||
color: hsl(var(--color-primary));
|
||||
animation: shadok-float-danseur 7s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.shadok-dj {
|
||||
position: absolute;
|
||||
right: 2%;
|
||||
top: 3%;
|
||||
width: clamp(120px, 16vw, 230px);
|
||||
opacity: 0.3;
|
||||
pointer-events: none;
|
||||
color: hsl(var(--color-accent));
|
||||
animation: shadok-float-dj 8s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes shadok-float-danseur {
|
||||
0%, 100% { transform: translateY(0) rotate(0deg); }
|
||||
25% { transform: translateY(-8px) rotate(2deg); }
|
||||
75% { transform: translateY(-4px) rotate(-2deg); }
|
||||
}
|
||||
|
||||
@keyframes shadok-float-dj {
|
||||
0%, 100% { transform: translateY(0); }
|
||||
50% { transform: translateY(-10px); }
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.shadok-danseur { display: none; }
|
||||
.shadok-dj { display: none; }
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,343 +1,18 @@
|
||||
<template>
|
||||
<div class="relative overflow-hidden section-padding min-h-[70vh] flex items-center justify-center">
|
||||
<!-- 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="80" cy="120" rx="21" ry="27" fill="currentColor" opacity="0.25"/>
|
||||
<!-- Head -->
|
||||
<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>
|
||||
|
||||
<!-- 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="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="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">
|
||||
<!-- The stage of the hero: a tightrope walker and a drummer, at different heights -->
|
||||
<UiOiseauPose nom="funambule" pose="libre" left="5%" bottom="6%" :w="176" :op="0.2" />
|
||||
<UiOiseauPose nom="batteur" pose="libre" right="6%" bottom="34%" :w="116" retourne ton="accent" />
|
||||
<!-- Logo SVG vectoriel — inline pour currentColor -->
|
||||
<div class="evt-logo-wrap mx-auto mb-6" aria-hidden="true">
|
||||
<UiSectionSign class="evt-logo" />
|
||||
</div>
|
||||
|
||||
<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" style="color: hsl(var(--color-text))">
|
||||
{{ content?.title }}
|
||||
<h1 class="page-title relative font-display font-extrabold tracking-tight" style="color: hsl(var(--color-text))">
|
||||
<!-- The trapeze artist at the end of the title, on its baseline (rule e, docs/da-oiseaux.md) -->
|
||||
{{ content?.title }}<UiOiseauPose nom="trapeziste" pose="perche" bout left="16px" :w="96" ton="accent" :seuil="1200" />
|
||||
</h1>
|
||||
<p v-if="evtContent?.format" class="evt-format font-display">
|
||||
{{ evtContent.format }}
|
||||
@@ -354,8 +29,12 @@
|
||||
<p class="font-display text-xl font-bold" style="color: hsl(var(--color-text))">
|
||||
« {{ evtContent.leitmotiv }} »
|
||||
</p>
|
||||
<p v-if="evtContent?.tagline" class="font-mono text-sm tracking-widest uppercase" style="color: hsl(var(--color-primary))">
|
||||
<!-- The acrobat before the tagline, on its line: the tagline is narrower than the leitmotiv,
|
||||
28 px keep it out from under the leitmotiv, and above it the description ends higher
|
||||
(rule f, DA 03/11 after 17h35). By « Format & lieu », the stage manager stands alone -->
|
||||
<p v-if="evtContent?.tagline" class="relative font-mono text-sm tracking-widest uppercase" style="color: hsl(var(--color-primary))">
|
||||
{{ evtContent.tagline }}
|
||||
<UiOiseauPose nom="acrobate" pose="perche" right="calc(100% + 28px)" bottom="0px" :w="72" />
|
||||
</p>
|
||||
<span v-if="evtContent?.gestation" class="evt-gestation-badge">
|
||||
<div class="i-lucide-flask-conical h-3.5 w-3.5" />
|
||||
@@ -366,15 +45,26 @@
|
||||
</div>
|
||||
|
||||
<!-- ══ Contenu événement ══ -->
|
||||
<section v-if="evtContent?.axes || evtContent?.espaces || evtContent?.config" class="evt-content section-padding">
|
||||
<section v-if="evtContent?.axes || evtContent?.espaces || evtContent?.config" class="evt-content relative section-padding">
|
||||
<!-- The puppeteer stands on the footer's top rule, from 1200 px: under « Espaces & programme »
|
||||
the axes cards leave 100 px, half what it needs (rule f), and each row of cards holds one
|
||||
bird (rule c). The only bird of the page out of its block (orch, 03/11, option 1). Its
|
||||
offset is the seal's block of the layout (--seal-block), between this section and the
|
||||
footer; the bird check verifies it stands on the rule -->
|
||||
<UiOiseauPose nom="marionnettiste" pose="perche" left="18%" bottom="calc(-1 * var(--seal-block))" :w="112" retourne :seuil="1200" />
|
||||
<div class="container-content flex flex-col gap-14">
|
||||
|
||||
<!-- 3 axes -->
|
||||
<div v-if="evtContent?.axes">
|
||||
<h2 class="evt-section-title">Trois axes d'émancipation</h2>
|
||||
<h2 class="evt-section-title relative">
|
||||
<!-- The clown at the end of its title, on its baseline (rule e). M: between 768 and
|
||||
1199 px the four birds shown would all be S otherwise (rule a) -->
|
||||
Trois axes d'émancipation<UiOiseauPose nom="clown" pose="perche" bout left="14px" :w="108" retourne ton="accent" />
|
||||
</h2>
|
||||
<p class="evt-section-sub">« je subis — ou je m'affranchis »</p>
|
||||
<div class="axes-grid">
|
||||
<div v-for="(axe, i) in evtContent.axes" :key="i" class="axe-card">
|
||||
<UiOiseauPose v-for="o in OISEAUX_CARTE[axe.label] ?? []" :key="`${o.nom}-${o.pose}`" v-bind="o" />
|
||||
<div class="axe-icon">
|
||||
<div :class="`i-lucide-${axe.icon} h-5 w-5`" />
|
||||
</div>
|
||||
@@ -393,10 +83,13 @@
|
||||
|
||||
<!-- Espaces permanents -->
|
||||
<div v-if="evtContent?.espaces">
|
||||
<h2 class="evt-section-title">Espaces & programme</h2>
|
||||
<h2 class="evt-section-title relative">
|
||||
Espaces & programme
|
||||
</h2>
|
||||
<p class="evt-section-sub">Chorégraphie séquencée de plénières, ateliers et espaces permanents</p>
|
||||
<div class="espaces-grid">
|
||||
<div v-for="(esp, i) in evtContent.espaces" :key="i" class="espace-card">
|
||||
<UiOiseauPose v-for="o in OISEAUX_CARTE[esp.label] ?? []" :key="`${o.nom}-${o.pose}`" v-bind="o" />
|
||||
<div class="espace-icon">
|
||||
<div :class="`i-lucide-${esp.icon} h-4 w-4`" />
|
||||
</div>
|
||||
@@ -410,9 +103,16 @@
|
||||
|
||||
<!-- Config -->
|
||||
<div v-if="evtContent?.config">
|
||||
<h2 class="evt-section-title">Format & lieu</h2>
|
||||
<h2 class="evt-section-title relative">
|
||||
Format & lieu
|
||||
<!-- S at 72 px, feet on the cards' top edge (the title's 0.25rem margin under it): the
|
||||
espaces cards above leave 106 px at 768 px, and a perched bird keeps a void of its own
|
||||
height above it (rule f) -->
|
||||
<UiOiseauPose nom="regisseur" pose="perche" right="24%" bottom="-0.25rem" :w="72" retourne />
|
||||
</h2>
|
||||
<div class="config-grid">
|
||||
<div v-for="(cfg, i) in evtContent.config" :key="i" class="config-card">
|
||||
<UiOiseauPose v-for="o in OISEAUX_CARTE[cfg.label] ?? []" :key="`${o.nom}-${o.pose}`" v-bind="o" />
|
||||
<div class="config-icon">
|
||||
<div :class="`i-lucide-${cfg.icon} h-4 w-4`" />
|
||||
</div>
|
||||
@@ -429,6 +129,8 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { OiseauPlace } from '~/utils/oiseauPose'
|
||||
|
||||
definePageMeta({
|
||||
layout: 'default',
|
||||
})
|
||||
@@ -436,6 +138,12 @@ definePageMeta({
|
||||
const { data: content } = await usePageContent('evenement')
|
||||
const evtContent = computed(() => content.value as Record<string, any> | null)
|
||||
|
||||
// Birds standing on the cards, by card label: never two on the same row
|
||||
const OISEAUX_CARTE: Record<string, OiseauPlace[]> = {
|
||||
'Économique': [{ nom: 'jongleur', pose: 'perche', right: '22px', w: 76 }],
|
||||
'Ğ(marché)': [{ nom: 'cracheur-de-feu', pose: 'perche', right: '18px', w: 76, ton: 'accent', seuil: 1200 }],
|
||||
}
|
||||
|
||||
useSeoPage({
|
||||
title: evtContent.value?.meta?.title ?? 'Évènement — Le Librodrome',
|
||||
description: evtContent.value?.description as string ?? 'Prochains événements du Librodrome : rencontres, lectures et ateliers autour de l\'économie du don.',
|
||||
@@ -455,204 +163,6 @@ useSeoPage({
|
||||
font-size: clamp(2.5rem, 6vw, 3.5rem);
|
||||
}
|
||||
|
||||
/* 1. Funambule — top-left */
|
||||
.shadok-funambule {
|
||||
position: absolute;
|
||||
left: 4%;
|
||||
top: 5%;
|
||||
width: clamp(70px, 10vw, 140px);
|
||||
opacity: 0.24;
|
||||
pointer-events: none;
|
||||
color: hsl(var(--color-primary));
|
||||
animation: shadok-sway-funambule 9s ease-in-out infinite;
|
||||
}
|
||||
|
||||
/* 2. Accordeoniste — top-right */
|
||||
.shadok-accordeoniste {
|
||||
position: absolute;
|
||||
right: 3%;
|
||||
top: 4%;
|
||||
width: clamp(70px, 10vw, 140px);
|
||||
opacity: 0.22;
|
||||
pointer-events: none;
|
||||
color: hsl(var(--color-accent));
|
||||
animation: shadok-bounce-accordeon 8s ease-in-out infinite;
|
||||
}
|
||||
|
||||
/* 3. Jongleur — top-center */
|
||||
.shadok-jongleur {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 2%;
|
||||
transform: translateX(-50%);
|
||||
width: clamp(70px, 10vw, 130px);
|
||||
opacity: 0.2;
|
||||
pointer-events: none;
|
||||
color: hsl(var(--color-primary));
|
||||
animation: shadok-float-jongleur 7s ease-in-out infinite;
|
||||
}
|
||||
|
||||
/* 4. Cracheur de feu — left 5%, 40% */
|
||||
.shadok-cracheur {
|
||||
position: absolute;
|
||||
left: 5%;
|
||||
top: 40%;
|
||||
width: clamp(70px, 10vw, 140px);
|
||||
opacity: 0.22;
|
||||
pointer-events: none;
|
||||
color: hsl(var(--color-accent));
|
||||
animation: shadok-flicker-cracheur 8s ease-in-out infinite;
|
||||
}
|
||||
|
||||
/* 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(70px, 10vw, 140px);
|
||||
opacity: 0.22;
|
||||
pointer-events: none;
|
||||
color: hsl(var(--color-accent));
|
||||
animation: shadok-rush-regisseur 7s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@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); }
|
||||
40% { transform: translateY(-8px); }
|
||||
70% { transform: translateY(-3px); }
|
||||
}
|
||||
|
||||
@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(4deg); }
|
||||
75% { transform: rotate(-4deg); }
|
||||
}
|
||||
|
||||
@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); }
|
||||
50% { transform: translateX(-50%) rotate(10deg); }
|
||||
100% { transform: translateX(-50%) rotate(0deg); }
|
||||
}
|
||||
|
||||
@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-funambule,
|
||||
.shadok-accordeoniste,
|
||||
.shadok-jongleur,
|
||||
.shadok-cracheur,
|
||||
.shadok-trapeziste,
|
||||
.shadok-batteur,
|
||||
.shadok-marionnettiste,
|
||||
.shadok-clown,
|
||||
.shadok-acrobate,
|
||||
.shadok-regisseur {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Logo SVG ── */
|
||||
.evt-logo-wrap {
|
||||
width: 4.5rem;
|
||||
@@ -719,6 +229,7 @@ useSeoPage({
|
||||
}
|
||||
|
||||
.axe-card {
|
||||
position: relative;
|
||||
padding: 1.5rem;
|
||||
border-radius: 16px;
|
||||
background: hsl(var(--color-surface));
|
||||
@@ -777,6 +288,7 @@ useSeoPage({
|
||||
}
|
||||
|
||||
.espace-card {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 0.875rem;
|
||||
@@ -815,6 +327,7 @@ useSeoPage({
|
||||
}
|
||||
|
||||
.config-card {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 0.75rem;
|
||||
|
||||
@@ -1,47 +1,6 @@
|
||||
<template>
|
||||
<NuxtLayout>
|
||||
<div class="relative overflow-hidden section-padding">
|
||||
<!-- Shadok alchemist: character stirring a cauldron with sparkles -->
|
||||
<svg class="shadok-alchemist" viewBox="0 0 240 320" fill="none" aria-hidden="true">
|
||||
<!-- Body -->
|
||||
<ellipse cx="120" cy="145" rx="40" ry="48" fill="currentColor" opacity="0.85"/>
|
||||
<!-- Head -->
|
||||
<circle cx="120" cy="82" r="24" fill="currentColor" opacity="0.8"/>
|
||||
<!-- Wizard hat -->
|
||||
<polygon points="120,30 100,80 140,80" fill="currentColor" opacity="0.5"/>
|
||||
<line x1="100" y1="80" x2="140" y2="80" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.45"/>
|
||||
<!-- Hat star -->
|
||||
<circle cx="118" cy="55" r="3" fill="currentColor" opacity="0.25"/>
|
||||
<!-- Eyes (mischievous) -->
|
||||
<circle cx="111" cy="78" r="4.5" fill="currentColor" opacity="0.2"/>
|
||||
<circle cx="129" cy="78" r="4.5" fill="currentColor" opacity="0.2"/>
|
||||
<circle cx="112" cy="77" r="2" fill="currentColor" opacity="0.5"/>
|
||||
<circle cx="130" cy="77" r="2" fill="currentColor" opacity="0.5"/>
|
||||
<!-- Grin -->
|
||||
<path d="M112 92 Q120 98 128 92" stroke="currentColor" stroke-width="2" stroke-linecap="round" fill="none" opacity="0.35"/>
|
||||
<!-- Arm holding spoon/stick -->
|
||||
<line x1="158" y1="140" x2="175" y2="210" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" opacity="0.6"/>
|
||||
<!-- Other arm -->
|
||||
<path d="M82 145 Q65 165 70 185" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" fill="none" opacity="0.6"/>
|
||||
<!-- Cauldron -->
|
||||
<path d="M60 220 Q60 260 120 260 Q180 260 180 220" fill="currentColor" opacity="0.4"/>
|
||||
<ellipse cx="120" cy="220" rx="60" ry="15" fill="currentColor" opacity="0.3"/>
|
||||
<ellipse cx="120" cy="220" rx="60" ry="15" stroke="currentColor" stroke-width="2" fill="none" opacity="0.4"/>
|
||||
<!-- Cauldron legs -->
|
||||
<line x1="80" y1="258" x2="75" y2="280" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.5"/>
|
||||
<line x1="160" y1="258" x2="165" y2="280" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.5"/>
|
||||
<!-- Bubbles from cauldron -->
|
||||
<circle cx="100" cy="210" r="5" fill="currentColor" opacity="0.2"/>
|
||||
<circle cx="135" cy="205" r="4" fill="currentColor" opacity="0.18"/>
|
||||
<circle cx="115" cy="198" r="3" fill="currentColor" opacity="0.15"/>
|
||||
<!-- Sparkles -->
|
||||
<circle cx="90" cy="190" r="2" fill="currentColor" opacity="0.25"/>
|
||||
<circle cx="150" cy="195" r="2.5" fill="currentColor" opacity="0.2"/>
|
||||
<circle cx="105" cy="185" r="1.5" fill="currentColor" opacity="0.2"/>
|
||||
<!-- Character legs -->
|
||||
<line x1="105" y1="190" x2="95" y2="225" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" opacity="0.5"/>
|
||||
<line x1="135" y1="190" x2="145" y2="225" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" opacity="0.5"/>
|
||||
</svg>
|
||||
|
||||
<div class="container-content max-w-3xl mx-auto">
|
||||
<!-- Back link -->
|
||||
@@ -58,8 +17,9 @@
|
||||
<span class="inline-block mb-3 rounded-full bg-amber-400/15 px-3 py-0.5 font-mono text-xs tracking-widest text-amber-400 uppercase">
|
||||
{{ content?.kicker }}
|
||||
</span>
|
||||
<h1 class="page-title font-display font-bold text-white">
|
||||
{{ content?.title }}
|
||||
<h1 class="page-title relative font-display font-bold text-white">
|
||||
<!-- The alchemist at the end of the title, on its baseline (rule e) -->
|
||||
{{ content?.title }}<UiOiseauPose nom="alchimiste" pose="perche" bout left="16px" :w="98" retourne />
|
||||
</h1>
|
||||
<p class="mt-4 text-lg text-white/60 leading-relaxed max-w-xl mx-auto">
|
||||
{{ content?.description }}
|
||||
@@ -137,24 +97,4 @@ code {
|
||||
border-radius: 0.25em;
|
||||
background: hsl(40 80% 50% / 0.1);
|
||||
}
|
||||
|
||||
.shadok-alchemist {
|
||||
position: absolute;
|
||||
right: 2%;
|
||||
top: 10%;
|
||||
width: clamp(120px, 16vw, 230px);
|
||||
opacity: 0.3;
|
||||
pointer-events: none;
|
||||
color: hsl(var(--color-accent));
|
||||
animation: shadok-float-alchemist 10s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes shadok-float-alchemist {
|
||||
0%, 100% { transform: translateY(0) rotate(0deg); }
|
||||
50% { transform: translateY(-10px) rotate(1deg); }
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.shadok-alchemist { display: none; }
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,222 +1,5 @@
|
||||
<template>
|
||||
<div class="axis-scope axis-scope--num page-outer section-padding">
|
||||
|
||||
<!-- Shadok pinguin — tenant une clé USB, petit Tux à ses pieds -->
|
||||
<svg class="shadok-dl shadok-dl-pinguin" viewBox="0 0 170 210" fill="none" aria-hidden="true">
|
||||
<!-- Small Tux at bottom -->
|
||||
<ellipse cx="30" cy="175" rx="12" ry="16" fill="currentColor" opacity="0.2"/>
|
||||
<circle cx="30" cy="158" r="9" fill="currentColor" opacity="0.22"/>
|
||||
<ellipse cx="30" cy="173" rx="6" ry="9" fill="currentColor" opacity="0.1"/>
|
||||
<polygon points="30,157 25,154 30,161" fill="currentColor" opacity="0.3"/>
|
||||
<ellipse cx="25" cy="192" rx="6" ry="2.5" fill="currentColor" opacity="0.2"/>
|
||||
<ellipse cx="35" cy="192" rx="6" ry="2.5" fill="currentColor" opacity="0.2"/>
|
||||
<!-- Shadok body — upright -->
|
||||
<ellipse cx="105" cy="110" rx="20" ry="26" fill="currentColor" opacity="0.25"/>
|
||||
<!-- Head — looking at USB key -->
|
||||
<circle cx="100" cy="70" r="15" fill="currentColor" opacity="0.3"/>
|
||||
<circle cx="95" cy="68" r="1.8" fill="currentColor" opacity="0.5"/>
|
||||
<circle cx="104" cy="67" r="1.8" fill="currentColor" opacity="0.45"/>
|
||||
<polygon points="88,72 80,69 88,76" fill="currentColor" opacity="0.35"/>
|
||||
<!-- Left arm holding USB key -->
|
||||
<line x1="88" y1="100" x2="68" y2="90" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
|
||||
<rect x="42" y="82" width="26" height="14" rx="3" fill="currentColor" opacity="0.28"/>
|
||||
<rect x="32" y="86" width="10" height="6" rx="1" fill="currentColor" opacity="0.22"/>
|
||||
<line x1="50" y1="85" x2="60" y2="85" stroke="currentColor" stroke-width="1" opacity="0.2"/>
|
||||
<line x1="50" y1="89" x2="60" y2="89" stroke="currentColor" stroke-width="1" opacity="0.2"/>
|
||||
<!-- Right arm relaxed -->
|
||||
<line x1="122" y1="105" x2="140" y2="125" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.35"/>
|
||||
<!-- Legs -->
|
||||
<line x1="97" y1="133" x2="87" y2="192" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
|
||||
<line x1="113" y1="133" x2="123" y2="192" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
|
||||
<ellipse cx="85" cy="195" rx="9" ry="3" fill="currentColor" opacity="0.3"/>
|
||||
<ellipse cx="125" cy="195" rx="9" ry="3" fill="currentColor" opacity="0.3"/>
|
||||
</svg>
|
||||
|
||||
<!-- Shadok toile — au centre d'une web of trust, bras ouverts -->
|
||||
<svg class="shadok-dl shadok-dl-toile" viewBox="0 0 180 210" fill="none" aria-hidden="true">
|
||||
<!-- Nodes web -->
|
||||
<circle cx="30" cy="50" r="3" fill="currentColor" opacity="0.18"/>
|
||||
<circle cx="155" cy="55" r="3" fill="currentColor" opacity="0.16"/>
|
||||
<circle cx="18" cy="140" r="3" fill="currentColor" opacity="0.14"/>
|
||||
<circle cx="162" cy="142" r="3" fill="currentColor" opacity="0.16"/>
|
||||
<circle cx="80" cy="18" r="3" fill="currentColor" opacity="0.18"/>
|
||||
<circle cx="148" cy="185" r="2.5" fill="currentColor" opacity="0.14"/>
|
||||
<circle cx="15" cy="92" r="2.5" fill="currentColor" opacity="0.12"/>
|
||||
<!-- Connections from center -->
|
||||
<line x1="90" y1="105" x2="30" y2="50" stroke="currentColor" stroke-width="1" opacity="0.12"/>
|
||||
<line x1="90" y1="105" x2="155" y2="55" stroke="currentColor" stroke-width="1" opacity="0.12"/>
|
||||
<line x1="90" y1="105" x2="18" y2="140" stroke="currentColor" stroke-width="1" opacity="0.1"/>
|
||||
<line x1="90" y1="105" x2="162" y2="142" stroke="currentColor" stroke-width="1" opacity="0.1"/>
|
||||
<line x1="90" y1="105" x2="80" y2="18" stroke="currentColor" stroke-width="1" opacity="0.12"/>
|
||||
<line x1="90" y1="105" x2="15" y2="92" stroke="currentColor" stroke-width="0.8" opacity="0.1"/>
|
||||
<line x1="30" y1="50" x2="80" y2="18" stroke="currentColor" stroke-width="0.8" opacity="0.08"/>
|
||||
<line x1="155" y1="55" x2="162" y2="142" stroke="currentColor" stroke-width="0.8" opacity="0.08"/>
|
||||
<!-- Body — bras ouverts -->
|
||||
<ellipse cx="90" cy="118" rx="20" ry="26" fill="currentColor" opacity="0.25"/>
|
||||
<circle cx="90" cy="76" r="15" fill="currentColor" opacity="0.3"/>
|
||||
<circle cx="84" cy="74" r="1.8" fill="currentColor" opacity="0.5"/>
|
||||
<circle cx="95" cy="73" r="1.8" fill="currentColor" opacity="0.45"/>
|
||||
<!-- Beak front-facing proud -->
|
||||
<polygon points="90,83 85,87 95,87" fill="currentColor" opacity="0.35"/>
|
||||
<!-- Arms spread wide -->
|
||||
<line x1="72" y1="108" x2="32" y2="88" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
|
||||
<line x1="108" y1="108" x2="150" y2="90" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
|
||||
<circle cx="31" cy="88" r="2.5" fill="currentColor" opacity="0.3"/>
|
||||
<circle cx="151" cy="90" r="2.5" fill="currentColor" opacity="0.3"/>
|
||||
<!-- Legs -->
|
||||
<line x1="82" y1="141" x2="72" y2="196" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
|
||||
<line x1="98" y1="141" x2="110" y2="196" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
|
||||
<ellipse cx="70" cy="199" rx="9" ry="3" fill="currentColor" opacity="0.3"/>
|
||||
<ellipse cx="112" cy="199" rx="9" ry="3" fill="currentColor" opacity="0.3"/>
|
||||
</svg>
|
||||
|
||||
<!-- Shadok nas — ployant sous le poids d'un rack serveur -->
|
||||
<svg class="shadok-dl shadok-dl-nas" viewBox="0 0 170 210" fill="none" aria-hidden="true">
|
||||
<!-- Server rack on head, slightly tilted -->
|
||||
<rect x="32" y="10" width="82" height="52" rx="4" fill="currentColor" opacity="0.2" transform="rotate(-2 73 36)"/>
|
||||
<!-- Server units -->
|
||||
<rect x="38" y="15" width="68" height="7" rx="2" fill="currentColor" opacity="0.15" transform="rotate(-2 72 19)"/>
|
||||
<rect x="38" y="25" width="68" height="7" rx="2" fill="currentColor" opacity="0.15" transform="rotate(-2 72 29)"/>
|
||||
<rect x="38" y="35" width="68" height="7" rx="2" fill="currentColor" opacity="0.15" transform="rotate(-2 72 39)"/>
|
||||
<rect x="38" y="45" width="68" height="7" rx="2" fill="currentColor" opacity="0.15" transform="rotate(-2 72 49)"/>
|
||||
<!-- LED indicators -->
|
||||
<circle cx="46" cy="19" r="1.5" fill="currentColor" opacity="0.3"/>
|
||||
<circle cx="46" cy="29" r="1.5" fill="currentColor" opacity="0.28"/>
|
||||
<circle cx="46" cy="39" r="1.5" fill="currentColor" opacity="0.32"/>
|
||||
<circle cx="46" cy="49" r="1.5" fill="currentColor" opacity="0.25"/>
|
||||
<!-- Body — bent forward under weight -->
|
||||
<ellipse cx="85" cy="120" rx="22" ry="25" fill="currentColor" opacity="0.25" transform="rotate(8 85 120)"/>
|
||||
<!-- Head — strained under rack -->
|
||||
<circle cx="78" cy="82" r="15" fill="currentColor" opacity="0.3"/>
|
||||
<circle cx="73" cy="80" r="1.8" fill="currentColor" opacity="0.5"/>
|
||||
<circle cx="83" cy="79" r="1.8" fill="currentColor" opacity="0.45"/>
|
||||
<!-- Beak grimacing left -->
|
||||
<polygon points="65,84 57,81 65,88" fill="currentColor" opacity="0.35"/>
|
||||
<!-- Arms up holding rack -->
|
||||
<line x1="68" y1="110" x2="50" y2="70" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
|
||||
<line x1="104" y1="109" x2="112" y2="67" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
|
||||
<!-- Legs — pliant sous l'effort -->
|
||||
<line x1="75" y1="142" x2="58" y2="193" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" opacity="0.4"/>
|
||||
<line x1="97" y1="142" x2="114" y2="193" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" opacity="0.4"/>
|
||||
<ellipse cx="56" cy="196" rx="11" ry="3.5" fill="currentColor" opacity="0.3"/>
|
||||
<ellipse cx="116" cy="196" rx="11" ry="3.5" fill="currentColor" opacity="0.3"/>
|
||||
<!-- Sweat drops -->
|
||||
<ellipse cx="48" cy="90" rx="2" ry="3" fill="currentColor" opacity="0.2"/>
|
||||
<ellipse cx="44" cy="102" rx="1.5" ry="2.5" fill="currentColor" opacity="0.15"/>
|
||||
</svg>
|
||||
|
||||
<!-- Shadok debugger — loupe géante sur un minuscule bug, yeux écarquillés -->
|
||||
<svg class="shadok-dl shadok-dl-debugger" viewBox="0 0 180 215" fill="none" aria-hidden="true">
|
||||
<!-- Loupe — grande, tenue à bout de bras -->
|
||||
<circle cx="38" cy="55" r="30" stroke="currentColor" stroke-width="3.5" fill="none" opacity="0.28"/>
|
||||
<circle cx="38" cy="55" r="23" fill="currentColor" opacity="0.06"/>
|
||||
<!-- Manche loupe -->
|
||||
<line x1="62" y1="78" x2="80" y2="98" stroke="currentColor" stroke-width="4" stroke-linecap="round" opacity="0.3"/>
|
||||
<!-- Minuscule bug dans la loupe -->
|
||||
<ellipse cx="36" cy="53" rx="5" ry="4" fill="currentColor" opacity="0.35"/>
|
||||
<line x1="32" y1="50" x2="28" y2="46" stroke="currentColor" stroke-width="1" stroke-linecap="round" opacity="0.3"/>
|
||||
<line x1="36" y1="49" x2="36" y2="44" stroke="currentColor" stroke-width="1" stroke-linecap="round" opacity="0.3"/>
|
||||
<line x1="40" y1="50" x2="44" y2="46" stroke="currentColor" stroke-width="1" stroke-linecap="round" opacity="0.3"/>
|
||||
<line x1="32" y1="57" x2="28" y2="61" stroke="currentColor" stroke-width="1" stroke-linecap="round" opacity="0.3"/>
|
||||
<line x1="40" y1="57" x2="44" y2="61" stroke="currentColor" stroke-width="1" stroke-linecap="round" opacity="0.3"/>
|
||||
<!-- Exclamation dans la loupe -->
|
||||
<text x="50" y="45" font-size="10" fill="currentColor" opacity="0.4" font-family="monospace">!</text>
|
||||
<!-- Body — légèrement penché -->
|
||||
<ellipse cx="125" cy="118" rx="20" ry="26" fill="currentColor" opacity="0.25" transform="rotate(-5 125 118)"/>
|
||||
<!-- Head — très proche de la loupe, yeux écarquillés -->
|
||||
<circle cx="118" cy="75" r="16" fill="currentColor" opacity="0.3"/>
|
||||
<!-- Cercles sous les yeux (nuits blanches) -->
|
||||
<ellipse cx="112" cy="76" rx="5" ry="4" stroke="currentColor" stroke-width="1" fill="none" opacity="0.2"/>
|
||||
<ellipse cx="124" cy="75" rx="5" ry="4" stroke="currentColor" stroke-width="1" fill="none" opacity="0.2"/>
|
||||
<!-- Yeux très grands -->
|
||||
<circle cx="113" cy="75" r="2.5" fill="currentColor" opacity="0.55"/>
|
||||
<circle cx="123" cy="74" r="2.5" fill="currentColor" opacity="0.55"/>
|
||||
<!-- Beak — pointant vers la loupe -->
|
||||
<polygon points="104,78 96,75 104,83" fill="currentColor" opacity="0.35"/>
|
||||
<!-- Bras gauche tenant loupe -->
|
||||
<line x1="108" y1="108" x2="82" y2="95" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
|
||||
<!-- Bras droit -->
|
||||
<line x1="140" y1="110" x2="158" y2="130" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.35"/>
|
||||
<!-- Jambes -->
|
||||
<line x1="117" y1="141" x2="107" y2="198" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
|
||||
<line x1="133" y1="141" x2="143" y2="198" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
|
||||
<ellipse cx="105" cy="201" rx="9" ry="3" fill="currentColor" opacity="0.3"/>
|
||||
<ellipse cx="145" cy="201" rx="9" ry="3" fill="currentColor" opacity="0.3"/>
|
||||
</svg>
|
||||
|
||||
<!-- Shadok rubber duck — assis par terre, canard en caoutchouc face à lui, bras croisés, explication intense -->
|
||||
<svg class="shadok-dl shadok-dl-duck" viewBox="0 0 180 210" fill="none" aria-hidden="true">
|
||||
<!-- Rubber duck — stylisé, face au shadok -->
|
||||
<ellipse cx="145" cy="165" rx="18" ry="14" fill="currentColor" opacity="0.22"/>
|
||||
<circle cx="145" cy="148" r="11" fill="currentColor" opacity="0.25"/>
|
||||
<!-- Duck beak -->
|
||||
<path d="M155 148 L165 146 L155 152 Z" fill="currentColor" opacity="0.3"/>
|
||||
<!-- Duck eye -->
|
||||
<circle cx="150" cy="145" r="2" fill="currentColor" opacity="0.4"/>
|
||||
<!-- Duck wing -->
|
||||
<path d="M128 162 Q135 158 130 170" stroke="currentColor" stroke-width="1.5" fill="none" opacity="0.2"/>
|
||||
<!-- Shadok — assis, jambes en avant -->
|
||||
<ellipse cx="82" cy="130" rx="22" ry="20" fill="currentColor" opacity="0.25" transform="rotate(20 82 130)"/>
|
||||
<!-- Head — très expressif, se penche vers le canard -->
|
||||
<circle cx="95" cy="90" r="16" fill="currentColor" opacity="0.3"/>
|
||||
<circle cx="89" cy="88" r="2" fill="currentColor" opacity="0.5"/>
|
||||
<circle cx="100" cy="87" r="2" fill="currentColor" opacity="0.45"/>
|
||||
<!-- Beak pointing right at duck -->
|
||||
<polygon points="108,92 116,89 108,96" fill="currentColor" opacity="0.35"/>
|
||||
<!-- Bulle de dialogue -->
|
||||
<path d="M115 72 Q140 60 148 72 Q155 82 145 88 Q135 94 118 88 L115 94 L113 86 Q108 80 115 72Z" fill="currentColor" opacity="0.1"/>
|
||||
<text x="120" y="82" font-size="7" fill="currentColor" opacity="0.35" font-family="monospace">WTF?</text>
|
||||
<!-- Bras pointant vers duck -->
|
||||
<line x1="105" y1="118" x2="130" y2="140" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
|
||||
<!-- Bras gauche appuyé au sol -->
|
||||
<line x1="62" y1="122" x2="42" y2="148" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.35"/>
|
||||
<!-- Jambes en avant (assis) -->
|
||||
<line x1="72" y1="148" x2="42" y2="185" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
|
||||
<line x1="92" y1="152" x2="115" y2="185" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
|
||||
<ellipse cx="40" cy="188" rx="10" ry="3.5" fill="currentColor" opacity="0.3"/>
|
||||
<ellipse cx="117" cy="188" rx="10" ry="3.5" fill="currentColor" opacity="0.3"/>
|
||||
</svg>
|
||||
|
||||
<!-- Shadok caféine — énorme tasse, cernes, jambes qui tremblent -->
|
||||
<svg class="shadok-dl shadok-dl-cafeine" viewBox="0 0 170 215" fill="none" aria-hidden="true">
|
||||
<!-- Tasses vides au sol -->
|
||||
<ellipse cx="25" cy="195" rx="8" ry="4" fill="currentColor" opacity="0.18"/>
|
||||
<rect x="17" y="183" width="16" height="12" rx="2" fill="currentColor" opacity="0.15"/>
|
||||
<ellipse cx="52" cy="198" rx="6" ry="3" fill="currentColor" opacity="0.14"/>
|
||||
<rect x="46" y="188" width="12" height="10" rx="2" fill="currentColor" opacity="0.12"/>
|
||||
<!-- Énorme tasse à 2 mains -->
|
||||
<rect x="48" y="68" width="52" height="45" rx="5" fill="currentColor" opacity="0.25"/>
|
||||
<ellipse cx="74" cy="68" rx="26" ry="6" fill="currentColor" opacity="0.2"/>
|
||||
<!-- Vapeur -->
|
||||
<path d="M62 62 Q65 52 62 44" stroke="currentColor" stroke-width="1.5" fill="none" opacity="0.25" stroke-linecap="round"/>
|
||||
<path d="M74 58 Q78 47 74 38" stroke="currentColor" stroke-width="1.5" fill="none" opacity="0.22" stroke-linecap="round"/>
|
||||
<path d="M86 62 Q89 52 86 44" stroke="currentColor" stroke-width="1.5" fill="none" opacity="0.2" stroke-linecap="round"/>
|
||||
<!-- Anse tasse -->
|
||||
<path d="M100 78 Q118 78 118 90 Q118 102 100 102" stroke="currentColor" stroke-width="2.5" fill="none" opacity="0.22"/>
|
||||
<!-- Logo tasse (skull) -->
|
||||
<text x="62" y="95" font-size="14" fill="currentColor" opacity="0.18" font-family="monospace">☕</text>
|
||||
<!-- Body — tassé, fatigué -->
|
||||
<ellipse cx="90" cy="130" rx="20" ry="24" fill="currentColor" opacity="0.25" transform="rotate(5 90 130)"/>
|
||||
<!-- Head — yeux mi-clos, cernes -->
|
||||
<circle cx="88" cy="92" r="15" fill="currentColor" opacity="0.28"/>
|
||||
<!-- Cernes (demi-cercles sous yeux) -->
|
||||
<path d="M80 93 Q83 97 86 93" stroke="currentColor" stroke-width="1.2" fill="none" opacity="0.3"/>
|
||||
<path d="M90 92 Q93 96 96 92" stroke="currentColor" stroke-width="1.2" fill="none" opacity="0.3"/>
|
||||
<!-- Yeux mi-clos -->
|
||||
<line x1="79" y1="90" x2="85" y2="90" stroke="currentColor" stroke-width="2" stroke-linecap="round" opacity="0.5"/>
|
||||
<line x1="90" y1="89" x2="96" y2="89" stroke="currentColor" stroke-width="2" stroke-linecap="round" opacity="0.45"/>
|
||||
<!-- Beak -->
|
||||
<polygon points="78,97 70,94 78,101" fill="currentColor" opacity="0.35"/>
|
||||
<!-- Les deux bras tenant la tasse -->
|
||||
<line x1="72" y1="116" x2="56" y2="100" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
|
||||
<line x1="108" y1="118" x2="108" y2="100" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4"/>
|
||||
<!-- Jambes qui tremblent (légèrement en zigzag) -->
|
||||
<path d="M80 152 Q76 165 80 175 Q84 185 78 196" stroke="currentColor" stroke-width="3" stroke-linecap="round" fill="none" opacity="0.4"/>
|
||||
<path d="M100 152 Q104 165 100 175 Q96 185 102 196" stroke="currentColor" stroke-width="3" stroke-linecap="round" fill="none" opacity="0.4"/>
|
||||
<ellipse cx="76" cy="199" rx="10" ry="3.5" fill="currentColor" opacity="0.3"/>
|
||||
<ellipse cx="104" cy="199" rx="10" ry="3.5" fill="currentColor" opacity="0.3"/>
|
||||
</svg>
|
||||
|
||||
<div class="axis-scope axis-scope--num oiseaux-scene page-outer section-padding">
|
||||
<div class="container-content">
|
||||
|
||||
<!-- Header — centré, max-2xl -->
|
||||
@@ -238,6 +21,7 @@
|
||||
<!-- Project card (legacy pages) -->
|
||||
<div v-if="content?.project" class="mx-auto max-w-2xl mb-8">
|
||||
<div class="project-card">
|
||||
<UiOiseauPose v-for="o in oiseaux.projet ?? []" :key="`${o.nom}-${o.pose}`" v-bind="o" />
|
||||
<div class="project-icon">
|
||||
<div class="i-lucide-rocket h-5 w-5" />
|
||||
</div>
|
||||
@@ -316,10 +100,11 @@
|
||||
v-for="(section, si) in content.sections"
|
||||
:key="si"
|
||||
:id="`s${si}`"
|
||||
class="mb-12 section-anchor"
|
||||
class="relative mb-12 section-anchor"
|
||||
:class="['equivalents', 'tiers', 'territoire'].includes(section.type) ? 'max-w-4xl mx-auto' : 'max-w-2xl mx-auto'"
|
||||
>
|
||||
|
||||
<UiOiseauPose v-for="o in oiseaux.sections?.[si] ?? []" :key="`${o.nom}-${o.pose}`" v-bind="o" />
|
||||
<!-- ── ARGUMENTS ── -->
|
||||
<template v-if="section.type === 'arguments'">
|
||||
<h2 class="section-title">{{ section.title }}</h2>
|
||||
@@ -642,9 +427,54 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { OiseauPlace } from '~/utils/oiseauPose'
|
||||
|
||||
const route = useRoute()
|
||||
const slug = route.params.slug as string
|
||||
|
||||
// Birds of each page, by block: the project card and the sections (by index). Left side
|
||||
// only: the right margin holds the sticky summary on wide screens.
|
||||
interface OiseauxPage { projet?: OiseauPlace[], sections?: Record<number, OiseauPlace[]> }
|
||||
const OISEAUX_PAGE: Record<string, OiseauxPage> = {
|
||||
'logiciel-libre': {
|
||||
projet: [{ nom: 'cafeine', pose: 'derriere', left: '-130px', bottom: '-30px', w: 200 }],
|
||||
sections: {
|
||||
// feet on the last line of the steps: the block ends with the step's padding (16 px)
|
||||
1: [{ nom: 'debogueur', pose: 'marge-g', ecart: '24px', bottom: '16px', w: 132 }],
|
||||
// sections 2 (and cloud-libre 1 and 5) are wide: 152 px of margin at 1200 px
|
||||
2: [{ nom: 'le-manchot', pose: 'marge-g', ecart: '40px', w: 84 }],
|
||||
3: [{ nom: 'canard-en-plastique', pose: 'marge-g', ecart: '110px', w: 76 }],
|
||||
},
|
||||
},
|
||||
'authentification-wot': {
|
||||
projet: [
|
||||
// behind the card, out of its bottom-left corner: no painted pixel in the cut of the rounded
|
||||
// corner, inside the card's rectangle (rule g, orch 03/11 after 17h58)
|
||||
{ nom: 'porteur-de-serveur', pose: 'derriere', left: '-142px', bottom: '-24px', w: 210 },
|
||||
// the Ğ1 pump (Duniter) in the right margin, feet on the card's top edge: perched on the
|
||||
// card it stood 11 px under the third line of the introduction (rule f). The summary starts
|
||||
// under the card, and the left margin keeps no column of three (rule d)
|
||||
{ nom: 'pompeur-de-g1', pose: 'marge-d', ecart: '24px', bottom: '100%', w: 96 },
|
||||
],
|
||||
sections: {
|
||||
// the steps have no background: in the margin, on their last line, small and close to the
|
||||
// text, out of the band shared by the server carrier and the pump
|
||||
1: [{ nom: 'toile-de-confiance', pose: 'marge-g', ecart: '24px', bottom: '16px', w: 76 }],
|
||||
// not right under the server carrier and the web of trust: no column of three
|
||||
2: [{ nom: 'cryptographe', pose: 'marge-g', ecart: '96px', w: 84 }],
|
||||
},
|
||||
},
|
||||
'cloud-libre': {
|
||||
// both out of the cut of their card's rounded corner (rule g, as on the web of trust)
|
||||
projet: [{ nom: 'porteur-de-serveur', pose: 'derriere', left: '-142px', bottom: '-24px', w: 210 }],
|
||||
sections: {
|
||||
1: [{ nom: 'admin-reseau', pose: 'derriere', left: '-110px', bottom: '-32px', w: 220 }],
|
||||
5: [{ nom: 'imprimeuse-3d', pose: 'marge-g', ecart: '48px', w: 84 }],
|
||||
},
|
||||
},
|
||||
}
|
||||
const oiseaux = computed<OiseauxPage>(() => OISEAUX_PAGE[slug] ?? {})
|
||||
|
||||
const [{ data: content }, { data: homeData }] = await Promise.all([
|
||||
usePageContent(`numerique/${slug}`),
|
||||
usePageContent('home'),
|
||||
@@ -691,6 +521,7 @@ useSeoPage({
|
||||
}
|
||||
|
||||
.project-card {
|
||||
position: relative;
|
||||
padding: 1.5rem;
|
||||
border-radius: 0.75rem;
|
||||
border: 1px solid hsl(var(--color-primary) / 0.15);
|
||||
@@ -1913,95 +1744,6 @@ details[open] .territoire-chevron {
|
||||
overflow: clip;
|
||||
}
|
||||
|
||||
/* ── Shadoks décoratifs ── */
|
||||
.shadok-dl {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
color: hsl(var(--color-primary));
|
||||
}
|
||||
|
||||
.shadok-dl-pinguin {
|
||||
left: 1%;
|
||||
top: 18%;
|
||||
width: clamp(65px, 9vw, 130px);
|
||||
opacity: 0.22;
|
||||
animation: shadokfloat-a 11s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.shadok-dl-toile {
|
||||
right: 1%;
|
||||
top: 22%;
|
||||
width: clamp(65px, 9vw, 140px);
|
||||
opacity: 0.2;
|
||||
color: hsl(var(--color-accent));
|
||||
animation: shadokfloat-b 13s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.shadok-dl-nas {
|
||||
left: 2%;
|
||||
bottom: 6%;
|
||||
width: clamp(65px, 9vw, 135px);
|
||||
opacity: 0.22;
|
||||
color: hsl(var(--color-accent));
|
||||
animation: shadokfloat-c 10s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes shadokfloat-a {
|
||||
0%, 100% { transform: translateY(0) rotate(-2deg); }
|
||||
50% { transform: translateY(-8px) rotate(2deg); }
|
||||
}
|
||||
@keyframes shadokfloat-b {
|
||||
0%, 100% { transform: translateY(0) rotate(3deg); }
|
||||
50% { transform: translateY(-10px) rotate(-2deg); }
|
||||
}
|
||||
@keyframes shadokfloat-c {
|
||||
0%, 100% { transform: translateY(0) rotate(-1deg); }
|
||||
50% { transform: translateY(-7px) rotate(3deg); }
|
||||
}
|
||||
@keyframes shadokfloat-d {
|
||||
0%, 100% { transform: translateY(0) rotate(2deg); }
|
||||
50% { transform: translateY(-9px) rotate(-3deg); }
|
||||
}
|
||||
@keyframes shadokfloat-e {
|
||||
0%, 100% { transform: translateY(0) rotate(-3deg); }
|
||||
50% { transform: translateY(-6px) rotate(1deg); }
|
||||
}
|
||||
@keyframes shadokfloat-f {
|
||||
0%, 100% { transform: translateY(0) rotate(1deg); }
|
||||
50% { transform: translateY(-11px) rotate(-2deg); }
|
||||
}
|
||||
|
||||
.shadok-dl-debugger {
|
||||
right: 1%;
|
||||
bottom: 14%;
|
||||
width: clamp(65px, 9vw, 140px);
|
||||
opacity: 0.2;
|
||||
color: hsl(var(--color-primary));
|
||||
animation: shadokfloat-d 12s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.shadok-dl-duck {
|
||||
left: 38%;
|
||||
top: 6%;
|
||||
width: clamp(65px, 9vw, 145px);
|
||||
opacity: 0.2;
|
||||
color: hsl(var(--color-accent));
|
||||
animation: shadokfloat-e 14s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.shadok-dl-cafeine {
|
||||
right: 2%;
|
||||
top: 52%;
|
||||
width: clamp(65px, 9vw, 132px);
|
||||
opacity: 0.22;
|
||||
color: hsl(var(--color-primary));
|
||||
animation: shadokfloat-f 9s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.shadok-dl { display: none; }
|
||||
}
|
||||
|
||||
/* equiv-row without link */
|
||||
.equiv-row--no-link {
|
||||
cursor: default;
|
||||
|
||||
@@ -1,313 +1,16 @@
|
||||
<template>
|
||||
<div class="axis-scope axis-scope--num 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="axis-scope axis-scope--num oiseaux-scene relative overflow-hidden section-padding">
|
||||
<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 }}
|
||||
<p class="relative mb-2 font-mono text-sm tracking-widest text-primary uppercase">
|
||||
<!-- Phone: the title fills the width; the bird stands at the end of this line, on its baseline.
|
||||
A flat bird (37-44 px tall at 72 px wide): 93 px separate this baseline from the sticky
|
||||
header, and a perched bird keeps a void of its own height above it (rule f) -->
|
||||
{{ content?.kicker }}<UiOiseauPose nom="codeuse" pose="perche" bout :w="72" mobile-seul />
|
||||
</p>
|
||||
<h1 class="page-title relative font-display font-bold tracking-tight" style="color: hsl(var(--color-text))">
|
||||
<!-- At the end of the title, on its baseline (rule e, docs/da-oiseaux.md) -->
|
||||
{{ content?.title }}<UiOiseauPose nom="droneuse" pose="perche" bout left="16px" :w="76" :seuil="1200" />
|
||||
</h1>
|
||||
<p class="mt-4 mx-auto max-w-2xl leading-relaxed" style="color: hsl(var(--color-text-muted))">
|
||||
{{ content?.description }}
|
||||
@@ -317,6 +20,7 @@
|
||||
<div class="mx-auto max-w-3xl flex flex-col gap-6">
|
||||
<!-- LPK sert l'axe numérique -->
|
||||
<NuxtLink v-if="lpk" :to="lpk.to" class="lpk-card">
|
||||
<UiOiseauPose nom="electricien" pose="marge-g" ecart="40px" :w="150" />
|
||||
<span class="lpk-name">{{ lpk.name }}</span>
|
||||
<span class="lpk-body">
|
||||
<span class="lpk-full">{{ lpk.text }}</span>
|
||||
@@ -330,6 +34,8 @@
|
||||
:key="pillar.id"
|
||||
class="pillar-card"
|
||||
>
|
||||
<UiOiseauPose v-for="o in OISEAUX_PILIER[pillar.id] ?? []" :key="`${o.nom}-${o.pose}`" v-bind="o" />
|
||||
|
||||
<!-- Overlay lien — toute la carte cliquable -->
|
||||
<NuxtLink v-if="pillar.to" :to="pillar.to" class="pillar-overlay" :aria-label="pillar.label" tabindex="-1" />
|
||||
|
||||
@@ -382,11 +88,31 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { OiseauPlace } from '~/utils/oiseauPose'
|
||||
|
||||
definePageMeta({
|
||||
layout: 'default',
|
||||
})
|
||||
|
||||
const { data: content } = await usePageContent('numerique')
|
||||
|
||||
// Birds by pillar, each next to the trade it is about (composition of 03/11)
|
||||
const OISEAUX_PILIER: Record<string, OiseauPlace[]> = {
|
||||
// The cards stand 16 px apart: no room above them for a perched bird (DA rule of 03/11).
|
||||
// In the margins, no strict zigzag and several sizes (DA, 03/11 07h56): two stand at the
|
||||
// bottom of their card on the right, the network admin is large behind Cloud libre
|
||||
'logiciel-libre': [
|
||||
{ nom: 'codeuse', pose: 'marge-d', ecart: '28px', w: 84, retourne: true },
|
||||
],
|
||||
'authentification-wot': [
|
||||
{ nom: 'cryptographe', pose: 'marge-d', ecart: '64px', w: 112, retourne: true },
|
||||
],
|
||||
'cloud-libre': [
|
||||
// out by the side of the card, not by its corner: the card is a link, and its rounded corner
|
||||
// let 5 px of the drawing show on it (rule h)
|
||||
{ nom: 'admin-reseau', pose: 'derriere', left: '-150px', bottom: '20px', w: 236 },
|
||||
],
|
||||
}
|
||||
const { data: site } = await useSiteContent()
|
||||
const lpk = computed(() => site.value?.lpk)
|
||||
|
||||
@@ -398,6 +124,7 @@ useSeoPage({
|
||||
|
||||
<style scoped>
|
||||
.lpk-card {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1.25rem;
|
||||
@@ -578,143 +305,4 @@ useSeoPage({
|
||||
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>
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
// Placement of a bird on its block (props of UiOiseauPose), for the per-page tables.
|
||||
export interface OiseauPlace {
|
||||
nom: string
|
||||
pose?: 'perche' | 'marge-g' | 'marge-d' | 'derriere' | 'libre'
|
||||
w?: number
|
||||
left?: string
|
||||
right?: string
|
||||
top?: string
|
||||
bottom?: string
|
||||
ecart?: string
|
||||
retourne?: boolean
|
||||
mobile?: boolean
|
||||
mobileSeul?: boolean
|
||||
seuil?: 768 | 1024 | 1200
|
||||
ton?: 'primary' | 'accent'
|
||||
op?: number
|
||||
bout?: boolean
|
||||
}
|
||||
@@ -0,0 +1,316 @@
|
||||
// The 61 birds of the librodrome (studio DA, 2026-10-28) and the footer strip.
|
||||
// ratio : aspect ratio of the drawing's box ; sol : where its ground line is, in % of the box height
|
||||
// from the bottom (to stand a bird on an edge of the page : translateY(sol%)).
|
||||
// Files : /images/oiseaux/<name>.svg
|
||||
export const OISEAUX: Record<string, { ratio: string, sol: number, univers: string }> = {
|
||||
"calligraphe": {
|
||||
"ratio": "152.0 / 116.7",
|
||||
"sol": 5.0,
|
||||
"univers": "livre"
|
||||
},
|
||||
"cartonnier": {
|
||||
"ratio": "170.0 / 113.5",
|
||||
"sol": 5.2,
|
||||
"univers": "livre"
|
||||
},
|
||||
"colporteur": {
|
||||
"ratio": "152.0 / 119.6",
|
||||
"sol": 5.1,
|
||||
"univers": "livre"
|
||||
},
|
||||
"conteuse": {
|
||||
"ratio": "175.3 / 155.6",
|
||||
"sol": 3.9,
|
||||
"univers": "livre"
|
||||
},
|
||||
"correcteur": {
|
||||
"ratio": "152.0 / 112.8",
|
||||
"sol": 5.4,
|
||||
"univers": "livre"
|
||||
},
|
||||
"cycliste-livreur": {
|
||||
"ratio": "170.0 / 97.6",
|
||||
"sol": 6.0,
|
||||
"univers": "livre"
|
||||
},
|
||||
"empileur": {
|
||||
"ratio": "157.0 / 147.4",
|
||||
"sol": 4.0,
|
||||
"univers": "livre"
|
||||
},
|
||||
"factrice": {
|
||||
"ratio": "171.8 / 135.8",
|
||||
"sol": 4.5,
|
||||
"univers": "livre"
|
||||
},
|
||||
"illustratrice": {
|
||||
"ratio": "164.0 / 129.6",
|
||||
"sol": 4.6,
|
||||
"univers": "livre"
|
||||
},
|
||||
"lectrice": {
|
||||
"ratio": "137.0 / 133.5",
|
||||
"sol": 4.5,
|
||||
"univers": "livre"
|
||||
},
|
||||
"libraire": {
|
||||
"ratio": "170.0 / 133.6",
|
||||
"sol": 4.6,
|
||||
"univers": "livre"
|
||||
},
|
||||
"relieur": {
|
||||
"ratio": "152.0 / 129.6",
|
||||
"sol": 4.7,
|
||||
"univers": "livre"
|
||||
},
|
||||
"typographe": {
|
||||
"ratio": "157.0 / 131.6",
|
||||
"sol": 4.6,
|
||||
"univers": "livre"
|
||||
},
|
||||
"apicultrice": {
|
||||
"ratio": "167.0 / 155.6",
|
||||
"sol": 3.9,
|
||||
"univers": "bassin"
|
||||
},
|
||||
"berger": {
|
||||
"ratio": "172.0 / 133.0",
|
||||
"sol": 4.4,
|
||||
"univers": "bassin"
|
||||
},
|
||||
"boulangere": {
|
||||
"ratio": "157.0 / 147.4",
|
||||
"sol": 4.1,
|
||||
"univers": "bassin"
|
||||
},
|
||||
"forgeron": {
|
||||
"ratio": "158.0 / 160.7",
|
||||
"sol": 3.7,
|
||||
"univers": "bassin"
|
||||
},
|
||||
"maraichere": {
|
||||
"ratio": "164.0 / 112.1",
|
||||
"sol": 5.4,
|
||||
"univers": "bassin"
|
||||
},
|
||||
"menuisier": {
|
||||
"ratio": "164.0 / 115.1",
|
||||
"sol": 5.2,
|
||||
"univers": "bassin"
|
||||
},
|
||||
"pompeur-de-g1": {
|
||||
"ratio": "159.0 / 115.9",
|
||||
"sol": 5.1,
|
||||
"univers": "bassin"
|
||||
},
|
||||
"potier": {
|
||||
"ratio": "157.0 / 90.6",
|
||||
"sol": 6.5,
|
||||
"univers": "bassin"
|
||||
},
|
||||
"tisserand": {
|
||||
"ratio": "162.0 / 113.6",
|
||||
"sol": 5.3,
|
||||
"univers": "bassin"
|
||||
},
|
||||
"vigneronne": {
|
||||
"ratio": "157.0 / 138.6",
|
||||
"sol": 4.4,
|
||||
"univers": "bassin"
|
||||
},
|
||||
"admin-reseau": {
|
||||
"ratio": "137.0 / 138.6",
|
||||
"sol": 4.3,
|
||||
"univers": "numerique"
|
||||
},
|
||||
"cafeine": {
|
||||
"ratio": "166.0 / 137.4",
|
||||
"sol": 4.3,
|
||||
"univers": "numerique"
|
||||
},
|
||||
"canard-en-plastique": {
|
||||
"ratio": "166.0 / 126.2",
|
||||
"sol": 4.7,
|
||||
"univers": "numerique"
|
||||
},
|
||||
"codeuse": {
|
||||
"ratio": "164.0 / 95.6",
|
||||
"sol": 6.3,
|
||||
"univers": "numerique"
|
||||
},
|
||||
"cryptographe": {
|
||||
"ratio": "172.9 / 147.5",
|
||||
"sol": 4.0,
|
||||
"univers": "numerique"
|
||||
},
|
||||
"debogueur": {
|
||||
"ratio": "166.0 / 111.3",
|
||||
"sol": 5.3,
|
||||
"univers": "numerique"
|
||||
},
|
||||
"droneuse": {
|
||||
"ratio": "169.0 / 152.2",
|
||||
"sol": 3.9,
|
||||
"univers": "numerique"
|
||||
},
|
||||
"electricien": {
|
||||
"ratio": "174.0 / 152.8",
|
||||
"sol": 4.0,
|
||||
"univers": "numerique"
|
||||
},
|
||||
"imprimeuse-3d": {
|
||||
"ratio": "166.0 / 146.0",
|
||||
"sol": 4.1,
|
||||
"univers": "numerique"
|
||||
},
|
||||
"le-manchot": {
|
||||
"ratio": "166.0 / 109.6",
|
||||
"sol": 5.4,
|
||||
"univers": "numerique"
|
||||
},
|
||||
"porteur-de-serveur": {
|
||||
"ratio": "137.0 / 140.5",
|
||||
"sol": 4.3,
|
||||
"univers": "numerique"
|
||||
},
|
||||
"soudeuse": {
|
||||
"ratio": "166.0 / 114.6",
|
||||
"sol": 5.1,
|
||||
"univers": "numerique"
|
||||
},
|
||||
"toile-de-confiance": {
|
||||
"ratio": "137.3 / 159.8",
|
||||
"sol": 3.8,
|
||||
"univers": "numerique"
|
||||
},
|
||||
"avocate": {
|
||||
"ratio": "137.0 / 141.6",
|
||||
"sol": 4.3,
|
||||
"univers": "cite"
|
||||
},
|
||||
"capitaine": {
|
||||
"ratio": "157.0 / 116.5",
|
||||
"sol": 5.1,
|
||||
"univers": "cite"
|
||||
},
|
||||
"cartographe": {
|
||||
"ratio": "151.0 / 123.6",
|
||||
"sol": 4.9,
|
||||
"univers": "cite"
|
||||
},
|
||||
"comedien": {
|
||||
"ratio": "137.0 / 150.0",
|
||||
"sol": 3.9,
|
||||
"univers": "cite"
|
||||
},
|
||||
"juge": {
|
||||
"ratio": "137.0 / 154.9",
|
||||
"sol": 3.9,
|
||||
"univers": "cite"
|
||||
},
|
||||
"matelot": {
|
||||
"ratio": "172.0 / 115.7",
|
||||
"sol": 5.1,
|
||||
"univers": "cite"
|
||||
},
|
||||
"mime": {
|
||||
"ratio": "137.0 / 112.8",
|
||||
"sol": 5.3,
|
||||
"univers": "cite"
|
||||
},
|
||||
"vigie": {
|
||||
"ratio": "184.0 / 154.0",
|
||||
"sol": 3.9,
|
||||
"univers": "cite"
|
||||
},
|
||||
"accordeoniste": {
|
||||
"ratio": "146.3 / 135.6",
|
||||
"sol": 4.5,
|
||||
"univers": "scene"
|
||||
},
|
||||
"acrobate": {
|
||||
"ratio": "137.0 / 153.4",
|
||||
"sol": 3.9,
|
||||
"univers": "scene"
|
||||
},
|
||||
"batteur": {
|
||||
"ratio": "164.0 / 153.6",
|
||||
"sol": 3.9,
|
||||
"univers": "scene"
|
||||
},
|
||||
"clown": {
|
||||
"ratio": "159.4 / 127.9",
|
||||
"sol": 4.7,
|
||||
"univers": "scene"
|
||||
},
|
||||
"cracheur-de-feu": {
|
||||
"ratio": "137.0 / 141.0",
|
||||
"sol": 4.2,
|
||||
"univers": "scene"
|
||||
},
|
||||
"funambule": {
|
||||
"ratio": "170.0 / 162.9",
|
||||
"sol": 3.7,
|
||||
"univers": "scene"
|
||||
},
|
||||
"jongleur": {
|
||||
"ratio": "137.0 / 148.3",
|
||||
"sol": 4.0,
|
||||
"univers": "scene"
|
||||
},
|
||||
"marionnettiste": {
|
||||
"ratio": "164.0 / 133.6",
|
||||
"sol": 4.5,
|
||||
"univers": "scene"
|
||||
},
|
||||
"regisseur": {
|
||||
"ratio": "168.0 / 127.9",
|
||||
"sol": 4.7,
|
||||
"univers": "scene"
|
||||
},
|
||||
"trapeziste": {
|
||||
"ratio": "137.0 / 163.6",
|
||||
"sol": 3.7,
|
||||
"univers": "scene"
|
||||
},
|
||||
"alchimiste": {
|
||||
"ratio": "164.0 / 133.2",
|
||||
"sol": 4.6,
|
||||
"univers": "musique"
|
||||
},
|
||||
"danseur": {
|
||||
"ratio": "162.0 / 159.6",
|
||||
"sol": 3.8,
|
||||
"univers": "musique"
|
||||
},
|
||||
"dj": {
|
||||
"ratio": "164.0 / 155.6",
|
||||
"sol": 3.8,
|
||||
"univers": "musique"
|
||||
},
|
||||
"la-balance": {
|
||||
"ratio": "170.3 / 150.4",
|
||||
"sol": 3.9,
|
||||
"univers": "musique"
|
||||
},
|
||||
"penseur": {
|
||||
"ratio": "162.0 / 150.9",
|
||||
"sol": 3.9,
|
||||
"univers": "musique"
|
||||
},
|
||||
"philosophe": {
|
||||
"ratio": "162.0 / 155.2",
|
||||
"sol": 3.8,
|
||||
"univers": "musique"
|
||||
},
|
||||
"trompettiste": {
|
||||
"ratio": "164.0 / 158.1",
|
||||
"sol": 3.7,
|
||||
"univers": "musique"
|
||||
},
|
||||
"frise": {
|
||||
"ratio": "400 / 80",
|
||||
"sol": 6.3,
|
||||
"univers": "pied de page"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
# Consignes d'Yvv — design du site
|
||||
|
||||
Ce fichier rassemble les consignes qu'Yvv donne en conversation, avec leur date. Les consignes
|
||||
entre guillemets sont recopiées mot pour mot ; les autres le disent. Elles l'emportent sur les choix
|
||||
d'interprétation des sessions.
|
||||
|
||||
Pourquoi ce fichier : la graduation de la console, donnée le 29/09, s'est perdue avec le transcript
|
||||
où elle était écrite. Une consigne qui ne vit que dans une conversation finit par disparaître.
|
||||
|
||||
## Accueil — la console des trois curseurs
|
||||
|
||||
Code : `app/components/home/HomeConsole.vue`. Libellés : `site/pages/home.yml > console`.
|
||||
|
||||
### 29/09/2026
|
||||
|
||||
Le transcript a été effacé. Ces quatre consignes sont reconstituées d'après le commit `201cfbb` et le
|
||||
relais de la session orch le 03/11 : ce ne sont pas les mots d'Yvv.
|
||||
|
||||
- l'axe de la console a Asservissement en bas et Émancipation en haut ;
|
||||
- une animation où les curseurs bougent lentement, pour qu'on ne croie pas devoir les manipuler ;
|
||||
- « AUTONOMIE » devant les légendes des curseurs ;
|
||||
- « Autonomie numérique, économique, citoyenne » et « Code source, création monétaire, décision »
|
||||
mis en valeur.
|
||||
|
||||
### 03/11/2026, vers 03h58 (à la session orch)
|
||||
|
||||
Mot pour mot, d'après `~/harness/etat/reponses-yvv.md` :
|
||||
|
||||
> « Et retrouve les consignes, comme par exemple celles pour l'animation (curseurs qui montent au
|
||||
> rollover et ne fait pas descendre les autres, je t'avais donné une graduation). »
|
||||
|
||||
### 03/11/2026, entre 05h40 et 05h45 (relayée mot pour mot par la session orch)
|
||||
|
||||
> « Je t'avais dit déjà tout ça. Au rollover, numérique monte à 3/4, économie monte à 1/2, citoyenne
|
||||
> monte à 2/3. Les curseurs restent. Si on rollover à nouveau ils refont leur montée. Et le pavé
|
||||
> correspondant s'affiche bien sûr au même rollover. »
|
||||
|
||||
Ce que fait le code :
|
||||
- Un survol à la souris, un focus au clavier ou un tap sur un curseur arrête pour de bon la rotation
|
||||
automatique.
|
||||
- Le curseur touché repart d'en bas et monte en 1,1 s à son cran : numérique 0,75, économique 0,50,
|
||||
citoyenne 0,667. Il y reste.
|
||||
- Le panneau de l'axe s'affiche au même geste.
|
||||
- La main ne fait descendre aucun autre curseur :
|
||||
- un curseur déjà touché reste à son cran ;
|
||||
- au premier geste, un curseur jamais touché que la rotation faisait descendre s'arrête là où il
|
||||
est ;
|
||||
- un curseur jamais touché que la rotation faisait monter finit sa montée, au niveau que la
|
||||
rotation lui donnait (le cran pour l'axe mis en avant), puis il y reste.
|
||||
|
||||
Décision d'orch du 03/11 à 13h10, qui découle de « ne fait pas descendre les autres ».
|
||||
- Un nouveau survol rejoue la montée.
|
||||
- Avant tout geste, l'animation du 29/09 continue : la rotation et la dérive des curseurs bas. L'axe
|
||||
mis en avant monte à son cran, et non plus vers 0,9. C'est une décision d'orch du 03/11 à 05h51, qui
|
||||
découle de la graduation : l'animation et la main donnent les mêmes niveaux.
|
||||
- Sous `prefers-reduced-motion`, il n'y a aucune transition, mais les crans sont les mêmes.
|
||||
- Un défilement au doigt qui part d'un curseur ne compte pas comme un survol.
|
||||
|
||||
## Les oiseaux (identité du studio DA)
|
||||
|
||||
Code : `app/components/ui/OiseauPose.vue` (placement), `app/components/ui/Oiseau.vue` et
|
||||
`app/utils/oiseaux.ts` (kit du studio DA, copiés tels quels). Les places sont dans chaque page.
|
||||
|
||||
### 03/11/2026, 05h08 (relayée mot pour mot par la session orch)
|
||||
|
||||
> « fais un effort sur le positionnement des oiseaux, là ils sont en colonne, c'est trop régulier,
|
||||
> trop papier peint, fais mieux que ça, peuple le fond intelligemment par rapport aux contenus, varie
|
||||
> les tailles et les emplacements »
|
||||
|
||||
### 03/11/2026, vers 05h45 (réponses au formulaire, relayées par la session orch)
|
||||
|
||||
- « Posés sans flotter » : un oiseau posé sur une arête est immobile sur sa ligne de sol. Seul l'oiseau
|
||||
de fond, derrière une carte, peut garder un léger mouvement.
|
||||
- « Un oiseau par page » sous 768 px.
|
||||
- « Les 61 tout de suite » : les places du plan, plus les oiseaux restants sur les sous-pages.
|
||||
- Pour le kit : « Oui, à intégrer » (au formulaire du studio DA).
|
||||
|
||||
Ce que fait le code :
|
||||
- Chaque oiseau est accroché au bloc dont il fait le métier, et se tient sur une vraie arête :
|
||||
- perché sur le haut d'une carte ;
|
||||
- près d'un titre ;
|
||||
- en marge, à côté de son bloc ;
|
||||
- derrière une carte, en grand et pâle.
|
||||
- Jamais deux fois le même oiseau sur une page.
|
||||
- Entre 768 et 1199 px, seuls les oiseaux perchés restent.
|
||||
- Sous 768 px, au plus un oiseau par page.
|
||||
|
||||
Les avis du studio DA et des relectures du 03/11 (07h00, 07h56 et 08h14), relayés par la session
|
||||
orch, sont dans `docs/da-oiseaux.md` : ce ne sont pas les mots d'Yvv.
|
||||
@@ -0,0 +1,200 @@
|
||||
# Oiseaux — avis du studio DA et des relectures
|
||||
|
||||
Ce fichier garde les avis du studio DA et des relectures sur la place des oiseaux, relayés par la
|
||||
session orch. Ce ne sont pas les mots d'Yvv : ses consignes, datées et mot pour mot, sont dans
|
||||
`docs/consignes-yvv.md` (section « Les oiseaux »).
|
||||
|
||||
Code : `app/components/ui/OiseauPose.vue` (placement), `app/components/ui/Oiseau.vue` et
|
||||
`app/utils/oiseaux.ts` (kit du studio DA). Les places sont dans chaque page.
|
||||
|
||||
### 03/11/2026, 07h00 : avis du studio DA (relayé par la session orch, ce ne sont pas les mots d'Yvv)
|
||||
|
||||
« kit : ok », avec trois constats de composition :
|
||||
- trop d'oiseaux autour du seul bloc de productions-collectives ;
|
||||
- des perchés qui mordent sur la carte du dessus (numérique) ;
|
||||
- l'un d'eux mord aussi sur le coin d'une carte à 375 px.
|
||||
|
||||
La règle de DA, pour toutes les pages : un oiseau perché n'a sa place que s'il y a, au-dessus de
|
||||
l'arête, un vide au moins égal à sa hauteur. Sinon, il va en marge ou derrière la carte.
|
||||
|
||||
La relecture du même jour ajoute : pas trois oiseaux sur la même verticale dans une marge.
|
||||
|
||||
Ce que fait le code depuis :
|
||||
- Aucun oiseau perché entre deux cartes rapprochées. Ceux qui y étaient sont passés en marge ou
|
||||
derrière la carte.
|
||||
- Au téléphone, l'oiseau de la page se tient au bout de la ligne de l'intitulé, au-dessus du titre.
|
||||
- Un perché dont le bloc n'a de vide au-dessus qu'une fois la grille en deux colonnes ne s'affiche
|
||||
qu'à partir de 1024 px (`seuil`).
|
||||
- Productions-collectives garde 4 oiseaux. Les autres sont partis sur des pages qui en avaient peu :
|
||||
- monnaie-libre : boulangère, vigneronne ;
|
||||
- tarifs-eau : potier ;
|
||||
- décision : tisserand ;
|
||||
- cloud-libre : imprimeuse 3D.
|
||||
- Toujours les 61 oiseaux rendus sur le site.
|
||||
|
||||
### 03/11/2026, 07h56 : second avis du studio DA, et relecture de 08h14 (relayés par la session orch)
|
||||
|
||||
DA : « composition : à reprendre (numérique et commande seulement) ».
|
||||
- Numérique : les 4 oiseaux de marge font un zigzag gauche, droite, gauche, droite, à pas égal et
|
||||
de même taille. C'est le « trop régulier, papier peint » d'Yvv sous une autre forme.
|
||||
- Commande : chaque carte a un oiseau à gauche et un à droite, à la même hauteur.
|
||||
|
||||
Deux règles de DA en découlent :
|
||||
- pas plus de deux alternances gauche-droite de suite à pas presque égal (moins de 20 % d'écart) ;
|
||||
- dès qu'une page a trois oiseaux ou plus, au moins deux tailles différentes.
|
||||
|
||||
La relecture ajoute qu'un oiseau ne se pose pas sur le haut de la boîte d'un titre : ce n'est pas
|
||||
une arête visible. Un oiseau de marge ne se tient pas non plus à mi-hauteur du texte.
|
||||
|
||||
Ce que fait le code depuis :
|
||||
- Un oiseau de marge a les pieds sur une vraie ligne : le bas ou le haut de son bloc, ou d'une carte
|
||||
de ce bloc. C'est la consigne d'Yvv « posés sans flotter ».
|
||||
- Un oiseau près d'un titre se tient sur sa dernière ligne, ou au bout de la ligne de l'intitulé.
|
||||
- Numérique : électricien en M, codeuse et cryptographe au bas de leur carte, à droite,
|
||||
administrateur réseau en grand derrière Cloud libre.
|
||||
- Commande : un seul côté par carte. Le cycliste est à gauche de la première, le libraire en grand
|
||||
derrière la seconde, à droite. La lectrice se tient sur le sac de l'icône. Le cartonnier est parti
|
||||
sur la ligne qui clôt chaque chapitre, l'empileur sur le sommaire du livre.
|
||||
|
||||
### 03/11/2026, 13h10 : relecture (relayée par la session orch, ce ne sont pas les mots d'Yvv)
|
||||
|
||||
La relecture de fb8c71f à 16d6607 conclut « à reprendre », avec cinq constats :
|
||||
- /economique : la maraîchère et la balance font une paire, aux sols presque à la même hauteur ;
|
||||
- /numerique/authentification-wot : la toile de confiance est « derrière » un bloc sans fond, et du
|
||||
texte la traverse ;
|
||||
- les chapitres : le cartonnier est posé sur un filet que rien ne trace ;
|
||||
- /evenement : le régisseur et l'acrobate font une paire ;
|
||||
- le sommaire du livre : trois oiseaux dans une même bande de la marge droite.
|
||||
|
||||
Trois règles en découlent :
|
||||
- pas deux oiseaux de part et d'autre dont les sols sont à moins de 40 px l'un de l'autre ;
|
||||
- une ligne visible, c'est une bordure, un fond différent de ce qu'il y a dessous, le bas d'une
|
||||
carte qui a un fond, ou une ligne de texte ;
|
||||
- « derrière une carte » demande une carte au fond opaque.
|
||||
|
||||
Ce que fait le code depuis :
|
||||
- /economique : la balance sort de derrière le bloc du livre, à gauche, à mi-hauteur.
|
||||
- Authentification : la toile de confiance est petite, en marge, sur la dernière ligne des étapes.
|
||||
- Logiciel libre : le débogueur se tient aussi sur la dernière ligne des étapes.
|
||||
- Chapitres : le filet au-dessus de précédent et suivant est tracé ; le cartonnier s'y pose.
|
||||
- /evenement : le régisseur reste seul près de « Format & lieu ». L'acrobate se tient sur la ligne
|
||||
du leitmotiv, dans l'en-tête, avant le texte, à gauche.
|
||||
- Sommaire du livre : l'empileur est plus loin dans la marge, l'illustratrice dépasse moins de sa
|
||||
carte.
|
||||
- Monnaie libre : la vigneronne sort de derrière le haut du bloc de texte, qui a un fond.
|
||||
|
||||
## Règles mesurées (liste fermée, 03/11 à 14h41)
|
||||
|
||||
Liste fixée par la session orch le 03/11 à 14h41, seuils confirmés par DA le 03/11. Ce ne sont pas
|
||||
les mots d'Yvv. Une composition qui tient ces règles est finie : on n'en ajoute pas d'autre.
|
||||
|
||||
Comment on mesure :
|
||||
- Largeurs : 375 px, de 376 à 767 px par pas de 16, de 768 à 1600 px par pas de 8.
|
||||
- Un oiseau compte quand il est affiché.
|
||||
- Ses pixels peints : son dessin (le SVG du kit) rendu à sa taille et retourné s'il est en miroir.
|
||||
Un pixel compte s'il a au moins 32/255 d'opacité. Pour un oiseau derrière une carte, on retire
|
||||
les pixels cachés par un fond opaque, coins arrondis compris.
|
||||
- Son sol : la ligne de sol du dessin (`sol` du kit, en % de la hauteur depuis le bas). Pour un
|
||||
oiseau derrière une carte qui en cache les pieds, le bas de ce qui se voit de lui.
|
||||
- Une ligne de texte se mesure à sa ligne de base, d'après les métriques de sa police.
|
||||
- Côté : gauche ou droite du milieu de la fenêtre, selon le centre de l'oiseau.
|
||||
- Oiseau de marge : posé en marge, ou dont le centre est hors de son bloc. Un oiseau « libre »
|
||||
dont le centre sort de son bloc est donc un oiseau de marge. Un oiseau derrière une carte reste
|
||||
« derrière », même si son centre sort de la carte : son sol est le bas de ce qui se voit de lui
|
||||
(décisions d'orch, 03/11 après 17h58).
|
||||
|
||||
Les règles :
|
||||
- a. Tailles : S sous 100 px de large, M de 100 à 179 px, L à partir de 180 px. Dès que trois
|
||||
oiseaux sont visibles, au moins deux tailles.
|
||||
- b. Zigzag : pas trois alternances gauche-droite de suite (de haut en bas) quand chaque pas est à
|
||||
moins de 20 % du pas moyen.
|
||||
- c. Paire : deux oiseaux de part et d'autre du milieu n'ont pas leurs sols à moins de 40 px l'un
|
||||
de l'autre. Où qu'ils soient, deux sols ne sont jamais à moins de 12 px.
|
||||
- d. Colonne : trois oiseaux de marge du même côté ne partagent aucune bande verticale de plus de
|
||||
12 px, mesurée sur leurs dessins.
|
||||
- e. Sol : un oiseau de marge ou perché a son sol à 12 px au plus d'une ligne visible. Cela vaut
|
||||
pour un oiseau « libre » dont le centre sort de son bloc (orch, 03/11 après 17h58).
|
||||
- Ligne visible : une bordure non nulle, un fond différent de celui du dessous (le bas d'une
|
||||
carte qui a un fond, le haut du bloc sur lequel il se tient, une image), ou une ligne de texte
|
||||
de son bloc.
|
||||
- Un oiseau « au bout du titre » est à 48 px au plus de la fin du texte de cette ligne (ou de
|
||||
son début s'il se tient avant), et son sol à 12 px au plus de la ligne de base du titre.
|
||||
- Au-delà, il relève d'une autre règle : la marge ; perché sur le bloc d'en dessous, le sol à
|
||||
12 px au plus de son arête haute ; ou derrière une carte.
|
||||
- f. Perché : jamais sur la moitié haute d'un titre ou d'un paragraphe. Au-dessus de l'oiseau, un
|
||||
vide au moins égal à sa hauteur. Il se mesure du haut de son dessin à la plus basse des lignes de
|
||||
texte ou des blocs dont la boîte passe au-dessus de sa largeur peinte (précision de DA, 03/11
|
||||
après 17h35 : l'oiseau ne tient pas seulement dans le vide, il en garde autant au-dessus de lui).
|
||||
Les lignes du titre où il se tient comptent aussi, celles au-dessus de sa ligne. En haut de page,
|
||||
l'en-tête collant compte parmi ces blocs.
|
||||
- g. Derrière : la part de l'oiseau qui est dans son bloc est couverte par un fond opaque (opacité
|
||||
du fond ≥ 0,9). « Dans son bloc » s'entend du rectangle du bloc : aucun pixel peint ne reste à
|
||||
découvert dans ce rectangle, découpe d'un coin arrondi comprise (décision d'orch, 03/11 après
|
||||
17h58).
|
||||
- h. Aucun pixel peint sur un texte ou un lien, trait de sol compris.
|
||||
- i. Sous 768 px, au plus un oiseau par page. Les 61 oiseaux visibles au moins une fois à 1440 px.
|
||||
Jamais deux fois le même sur une page. Aucun sous 72 px de large.
|
||||
|
||||
Hors liste, le contrôle garde les règles techniques du kit :
|
||||
- aucun oiseau rogné ;
|
||||
- aucun oiseau hors de la page ;
|
||||
- les opacités ;
|
||||
- jamais de miroir pour le calligraphe et le typographe ;
|
||||
- ni dégradé ni filtre ;
|
||||
- aucun débordement de la page ou de l'en-tête ;
|
||||
- un oiseau ne mord pas sur un bloc plein ;
|
||||
- aucune erreur console.
|
||||
|
||||
Code : `app/components/ui/OiseauPose.vue`. La pose `bout` sert à l'oiseau au bout d'un titre : une
|
||||
ancre sans taille dans le texte, sur sa ligne de base.
|
||||
|
||||
Ce que la liste a changé (03/11, après b6143c5) :
|
||||
- Les oiseaux près d'un titre se tiennent au bout de sa ligne, sur sa ligne de base (pose `bout`) :
|
||||
- trapéziste, clown et marionnettiste (/evenement) ;
|
||||
- DJ (/en-musique) ;
|
||||
- droneuse (/numerique) ;
|
||||
- vigie (/citoyenne) ;
|
||||
- alchimiste (/gratewizard) ;
|
||||
- relieur (/economique/modele-eco), avant le titre ;
|
||||
- l'oiseau de l'intitulé au téléphone sur chaque page.
|
||||
- Accueil : l'accordéoniste passe au bout du titre « Le librodrome est une invitation ». Sur la
|
||||
carte de l'événement, il se tenait à la hauteur de la lectrice et son trait de sol touchait le
|
||||
lien.
|
||||
- Authentification : le pompeur de Ğ1 est perché sur la carte du projet. Il y a 74 px de vide
|
||||
au-dessus, à toutes les largeurs.
|
||||
- Productions collectives : la soudeuse a les pieds sur la ligne de base du titre.
|
||||
- /numerique et /citoyenne : l'administrateur réseau et le cartographe sortent par le côté de leur
|
||||
carte, plus par un coin (la carte est un lien).
|
||||
|
||||
Ce que la liste a changé (03/11, après bb8fad9 ; la règle f mesurée au-dessus du dessin) :
|
||||
- Accueil :
|
||||
- le DJ se tient derrière le pupitre de la console, comme derrière ses platines : seule sa tête
|
||||
dépasse. Perché sur le pupitre, sous l'en-tête, il n'avait pas sa hauteur de vide au-dessus ;
|
||||
- l'accordéoniste passe en S (72 px), à partir de 1200 px, 8 px sous la ligne de base ;
|
||||
- la factrice passe à 76 px ;
|
||||
- la lectrice ne sort plus de derrière la carte du livre. Descendue de 20 px, loin de la fin de
|
||||
la première ligne du chapeau (DA, 03/11), elle ne montrait plus que sa bulle. Ni la marge de
|
||||
page ni la gouttière ne la laissent sortir par le côté (DA, 03/11 après 19h : « Je ne veux pas
|
||||
d'un oiseau réduit à un morceau »). Elle reste sur la page commande.
|
||||
- Au téléphone, l'oiseau de l'intitulé est un oiseau plat, l'en-tête collant laissant 93 px :
|
||||
codeuse (/numerique), maraîchère (/economique), potier (/citoyenne). Sur productions
|
||||
collectives, le cycliste livreur remplace le menuisier sous 768 px.
|
||||
- Authentification : le pompeur de Ğ1 passe en marge droite, les pieds sur l'arête haute de la
|
||||
carte du projet. Perché sur la carte, il n'avait que 11 px de vide au-dessus de sa tête (DA).
|
||||
- Tarifs de l'eau : le comédien, de même, en marge droite sur l'arête haute de la carte.
|
||||
- Commande : la lectrice quitte le sac, sous l'en-tête, pour la marge droite, sur l'arête haute de
|
||||
la première carte.
|
||||
- /en-musique : le danseur passe en S (80 px).
|
||||
- /evenement :
|
||||
- l'acrobate se tient avant la devise, plus étroite que le leitmotiv ;
|
||||
- le régisseur passe en S, les pieds sur l'arête des cartes ;
|
||||
- le clown passe en M (108 px), pour deux tailles entre 768 et 1199 px ;
|
||||
- le marionnettiste se perche sur le filet du pied de page, à partir de 1200 px : sous
|
||||
« Espaces & programme », il n'y a pas la place, et chaque rangée de cartes ne porte qu'un
|
||||
oiseau. C'est le seul oiseau de la page hors de son bloc (orch, option 1, 03/11). Son décalage
|
||||
vient du bloc du sceau du layout (`--seal-block`), et le contrôle vérifie qu'il est sur le filet.
|
||||
- Chapitres : le calligraphe se tient sur le filet du titre, le typographe sur le filet de la
|
||||
navigation, tous deux dans la marge gauche ; le cartonnier, en marge droite, au pied des boutons
|
||||
PDF et chansons. Ils n'ont plus de hauteur en %.
|
||||
- Porteur de serveur (authentification, cloud libre) et administrateur réseau (cloud libre) :
|
||||
décalés hors de la découpe du coin arrondi de leur carte.
|
||||
@@ -92,6 +92,8 @@ export default defineNuxtConfig({
|
||||
htmlAttrs: { lang: 'fr' },
|
||||
link: [
|
||||
{ rel: 'icon', type: 'image/svg+xml', href: '/favicon.svg' },
|
||||
{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico', sizes: '48x48' },
|
||||
{ rel: 'apple-touch-icon', href: '/apple-touch-icon.png' },
|
||||
{ rel: 'preconnect', href: 'https://fonts.bunny.net' },
|
||||
{
|
||||
rel: 'stylesheet',
|
||||
|
||||
|
After Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 5.5 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 45 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 112 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 19 KiB |