Compare commits
12 Commits
b9e6b4a96c
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 98fab43fe5 | |||
| a334c97434 | |||
| 84e843479d | |||
| 2f444ea7c7 | |||
| 8408fd6466 | |||
| dcf64cc924 | |||
| f6339400fa | |||
| 8fd000a153 | |||
| 95f82e4fee | |||
| a038851895 | |||
| efed0b9033 | |||
| 99a8b84164 |
@@ -1,3 +1,9 @@
|
|||||||
# Admin authentication
|
# Admin authentication
|
||||||
NUXT_ADMIN_PASSWORD=changeme
|
NUXT_ADMIN_PASSWORD=changeme
|
||||||
NUXT_ADMIN_SECRET=change-this-to-a-random-secret-at-least-32-chars
|
NUXT_ADMIN_SECRET=change-this-to-a-random-secret-at-least-32-chars
|
||||||
|
|
||||||
|
# Umami analytics — hébergé sur stats.open.us.org
|
||||||
|
# NUXT_UMAMI_API_KEY : Settings → API Keys dans l'interface Umami (pour /api/stats)
|
||||||
|
NUXT_PUBLIC_UMAMI_URL=https://stats.open.us.org
|
||||||
|
NUXT_PUBLIC_UMAMI_WEBSITE_ID=95ff616d-9ce1-47d9-bca2-f6ddc344a99a
|
||||||
|
NUXT_UMAMI_API_KEY=
|
||||||
|
|||||||
@@ -25,6 +25,9 @@ public/pdfjs/pdf.worker.min.mjs
|
|||||||
# Sources originales (PDF, JPG — pas servies par l'appli)
|
# Sources originales (PDF, JPG — pas servies par l'appli)
|
||||||
sources/
|
sources/
|
||||||
|
|
||||||
|
# Runtime data (Docker volume — never committed)
|
||||||
|
data/
|
||||||
|
|
||||||
# Local env files
|
# Local env files
|
||||||
.env
|
.env
|
||||||
.env.*
|
.env.*
|
||||||
|
|||||||
@@ -2,81 +2,130 @@
|
|||||||
|
|
||||||
Site vitrine du projet Le Librodrome — livre + chansons sur l'économie du don.
|
Site vitrine du projet Le Librodrome — livre + chansons sur l'économie du don.
|
||||||
|
|
||||||
|
## Protocole de début de session
|
||||||
|
|
||||||
|
1. `git pull --rebase origin main` (récupère les commits admin git sync YAML prod)
|
||||||
|
2. Vérifier que `data/messages.yml` existe — si absent, signaler avant toute opération
|
||||||
|
3. Si l'objectif de la session n'est pas précisé, le demander
|
||||||
|
|
||||||
## Stack
|
## Stack
|
||||||
|
|
||||||
- **Nuxt 4** (Vue 3, TypeScript, Nitro)
|
- Nuxt 4 (Vue 3, TypeScript, Nitro) + Nuxt Content, Pinia, UnoCSS, VueUse, Nuxt Image
|
||||||
- **Modules** : Nuxt Content, Pinia, UnoCSS, VueUse, Nuxt Image
|
- Icônes : Lucide + Phosphor (via @iconify-json) ; Package manager : pnpm
|
||||||
- **Icônes** : Lucide + Phosphor (via @iconify-json)
|
- Déploiement : Docker + Traefik, CI via Woodpecker
|
||||||
- **Package manager** : pnpm
|
|
||||||
- **Déploiement** : Docker + Traefik, CI via Woodpecker
|
|
||||||
|
|
||||||
## Structure
|
## Structure
|
||||||
|
|
||||||
```
|
```
|
||||||
app/
|
app/
|
||||||
pages/
|
pages/
|
||||||
numerique/ # Autonomie numérique (index + [slug] détail)
|
numerique/ # index + [slug]
|
||||||
economique/ # Autonomie économique (index, monnaie-libre, commande, productions-collectives)
|
economique/ # index, monnaie-libre, commande, productions-collectives
|
||||||
modele-eco/ # Livre : sommaire + chapitres [slug]
|
modele-eco/ # sommaire + chapitres [slug]
|
||||||
citoyenne/ # Autonomie citoyenne (index + [slug] détail)
|
citoyenne/ # index + [slug]
|
||||||
en-musique/ # Player audio
|
en-musique/ # player audio
|
||||||
evenement.vue # Événement
|
admin/ # back-office (pages/, book/, songs, messages, media)
|
||||||
admin/ # Back-office (pages/, book/, songs, messages, media)
|
|
||||||
components/
|
components/
|
||||||
book/Actions.vue # Boutons partagés livre (player, PDF, chapitres, commande)
|
book/Actions.vue # boutons partagés (player, PDF, chapitres, commande)
|
||||||
home/ # BookSection, AxisBlock, AxisGrid, HeroSection, Messages
|
home/ # BookSection, AxisBlock, AxisGrid, HeroSection, HomeMessages
|
||||||
admin/, player/, song/, ui/
|
admin/, player/, song/, ui/
|
||||||
composables/ # useAudioPlayer, useBookData, useGrateWizard, usePageContent...
|
composables/ # useAudioPlayer, useBookData, useGrateWizard, usePageContent
|
||||||
stores/palette.ts # 4 palettes saisonnières (été par défaut, persisté localStorage)
|
stores/palette.ts # 4 palettes saisonnières (été par défaut, persisté localStorage)
|
||||||
assets/css/ # main.css (UnoCSS + overrides light mode)
|
assets/css/main.css # UnoCSS + overrides light mode
|
||||||
site/
|
site/
|
||||||
pages/ # Contenu YAML par section (numerique/, economique/, citoyenne/)
|
pages/ # Contenu YAML administrable par section (sous-dossiers)
|
||||||
site.yml # Config globale (nav, footer, GrateWizard)
|
site.yml # Config globale (nav, footer, GrateWizard)
|
||||||
bookplayer.config.yml # Config player/chapitres
|
bookplayer.config.yml # Config player/chapitres
|
||||||
|
data/
|
||||||
|
messages.yml # Runtime — volume Docker ../data:/src/data — JAMAIS dans git
|
||||||
server/
|
server/
|
||||||
api/content/pages/[...path].get.ts # GET pages YAML (chemins imbriqués)
|
api/content/ # GET pages YAML (chemins imbriqués)
|
||||||
api/admin/content/pages/[...path].put.ts # PUT pages YAML
|
api/admin/content/ # PUT pages YAML + liste
|
||||||
api/admin/content/pages.get.ts # Liste toutes les pages
|
api/messages/ # GET (publiés) + POST (nouveau message)
|
||||||
middleware/redirects.ts # 301 : /gestation, /modele-eco, /decision, /lire
|
api/admin/messages/ # GET tous + PUT (type, reply, published) + DELETE
|
||||||
|
utils/content.ts # readDataYaml/writeDataYaml (data/) + readYaml/writeYaml (site/)
|
||||||
|
middleware/redirects.ts # 301 : /gestation, /modele-eco, /decision, /lire
|
||||||
docker/
|
docker/
|
||||||
Dockerfile, docker-compose.yml, docker-compose.dev.yml
|
Dockerfile, docker-compose.yml, docker-compose.dev.yml
|
||||||
```
|
```
|
||||||
|
|
||||||
## Ports dev (CRITIQUE)
|
## Données runtime (CRITIQUE)
|
||||||
|
|
||||||
| Projet | Port | Config |
|
- `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**
|
||||||
| **librodrome** | **3000** | `nuxt.config.ts` → `devServer.port: 3000` |
|
- Structure message : `{ id, author, email, text, type, published, createdAt, reply: { text, publishedAt } | null }`
|
||||||
| **GrateWizard** | **3001** | `package.json` → `next dev --port 3001` |
|
- Types : `reaction` (ancien, affiché "Réaction", plus proposé dans les formulaires) | `question` | `suggestion` | `retour`
|
||||||
| **SejeteralO frontend** | **3009** | `frontend/nuxt.config.ts` → `devServer.port: 3009` |
|
- En dev local : `<racine>/data/messages.yml`
|
||||||
| **SejeteralO backend** | **8000** | Makefile → `uvicorn --port 8000` |
|
- **Avant toute migration de chemin ou écriture sur data/ : demander confirmation**
|
||||||
|
|
||||||
**Ne jamais changer ces ports.**
|
|
||||||
|
|
||||||
## Intégration GrateWizard
|
## Intégration GrateWizard
|
||||||
|
|
||||||
- URL dev : `app/app.config.ts` → `localhost:3001`
|
- URL dev : `app/app.config.ts` → `localhost:3001`
|
||||||
- URL prod : `https://gratewizard.axiom-team.fr`
|
- URL prod : `https://gratewizard.axiom-team.fr`
|
||||||
- Bloc GrateWizard dans la section économique de la home
|
|
||||||
|
|
||||||
## Contenu administrable
|
## Contenu administrable
|
||||||
|
|
||||||
- YAML dans `site/pages/` organisé par section (sous-dossiers)
|
- YAML dans `site/pages/` par section ; API supporte les chemins imbriqués
|
||||||
- API supporte les chemins imbriqués (`numerique/logiciel-libre`)
|
- Admin : `/admin/pages` liste, `/admin/pages/{path}` édite en YAML
|
||||||
- Admin : `/admin/pages` liste toutes les pages, `/admin/pages/{path}` édite en YAML
|
- Git sync auto en prod (`ADMIN_GIT_SYNC=true`) → d'où le `git pull --rebase` obligatoire en début de session
|
||||||
- Git sync auto en prod (ADMIN_GIT_SYNC=true)
|
|
||||||
|
|
||||||
## Commandes
|
## Commandes
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pnpm dev # Dev server sur :3000
|
pnpm dev # Dev server :3000
|
||||||
pnpm build # Build production
|
pnpm build # Build production
|
||||||
|
PORT=3099 node .output/server/index.mjs # Test build prod (toujours avant commit)
|
||||||
```
|
```
|
||||||
|
|
||||||
## Conventions
|
## Conventions
|
||||||
|
|
||||||
- Langue du site : français
|
- CSS via UnoCSS + variables CSS palettes ; composants Vue SFC `<script setup lang="ts">`
|
||||||
- Commits en français, style concis
|
- Shadoks SVG inline thématiques sur chaque page (hidden mobile, opacity 0.18–0.28)
|
||||||
- CSS via UnoCSS (utility-first) + variables CSS palettes
|
- Hexagramme 益 (#42 Yi, Augmentation) dans `layouts/default.vue`
|
||||||
- Composants Vue SFC avec `<script setup lang="ts">`
|
- Signature § (logo calligraphique SVG gradient) dans `TheHeader.vue` — ne pas modifier sans demander
|
||||||
- Shadoks SVG inline thématiques sur chaque page (hidden mobile)
|
|
||||||
|
## SEO & Recherche IA — Checklist permanente
|
||||||
|
|
||||||
|
### À chaque nouvelle page Vue
|
||||||
|
|
||||||
|
1. **Appeler `useSeoPage()`** — jamais `useHead({ title })` seul
|
||||||
|
```ts
|
||||||
|
useSeoPage({
|
||||||
|
title: 'Titre page — Le Librodrome', // 50–60 chars
|
||||||
|
description: 'Description...', // 120–155 chars
|
||||||
|
image: '/images/og-specifique.jpg', // optionnel, sinon og-default.png (logo §)
|
||||||
|
type: 'website' | 'article' | 'book', // défaut: website
|
||||||
|
})
|
||||||
|
```
|
||||||
|
2. **og:image** : utiliser la couverture `/images/Couv-Economie-du-don.jpg` pour les pages livre/musique ; `og-default.png` (logo §) pour les pages site générales
|
||||||
|
3. **type: 'article'** pour les chapitres du modele-eco ; **type: 'book'** pour les index livre
|
||||||
|
|
||||||
|
### À chaque nouveau fichier YAML dans `site/pages/`
|
||||||
|
|
||||||
|
Ajouter le bloc `seo:` en tête :
|
||||||
|
```yaml
|
||||||
|
seo:
|
||||||
|
title: "Titre SEO — 50-60 chars max"
|
||||||
|
description: "Description 120-155 chars, avec mots-clés naturels"
|
||||||
|
# image: /images/og-specifique.jpg # optionnel
|
||||||
|
# keywords: [monnaie libre, TRM, économie du don]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Règles description SEO
|
||||||
|
|
||||||
|
- Commence par un verbe d'action ou une affirmation forte
|
||||||
|
- Contient les mots-clés : économie du don, monnaie libre, TRM, June, bassin de vie, autonomie
|
||||||
|
- 120–155 caractères (ni trop court, ni tronqué)
|
||||||
|
- Différente du titre et du H1
|
||||||
|
|
||||||
|
### JSON-LD automatique
|
||||||
|
|
||||||
|
- JSON-LD `Organization` + `Book` : injecté globalement dans `app.vue` — ne pas redupliquer
|
||||||
|
- Chapitres → og:type `article` via `useSeoPage({ type: 'article' })`
|
||||||
|
- Sitemap : géré par `@nuxtjs/sitemap` — ajouter manuellement les nouvelles routes statiques dans `nuxt.config.ts > sitemap.urls`
|
||||||
|
|
||||||
|
### Analytics — Nouveaux composants
|
||||||
|
|
||||||
|
- Tout bouton CTA externe : `trackCta(label, url)` depuis `useTracking()`
|
||||||
|
- Tout `<a target="_blank">` : capturé automatiquement par `useScrollTracking()` dans `default.vue`
|
||||||
|
- Tout nouveau lecteur média : appeler `trackAudioPlay` / `trackPdfOpen` depuis `useTracking()`
|
||||||
|
|||||||
+2
-3
@@ -81,9 +81,8 @@ Parent avec `overflow: clip` (pas `overflow: hidden` qui casserait `position: st
|
|||||||
|
|
||||||
## Analytics
|
## Analytics
|
||||||
|
|
||||||
`app/composables/useTracking.ts` — wrapper Umami. Activé si `NUXT_PUBLIC_UMAMI_WEBSITE_ID` est défini.
|
`app/composables/useTracking.ts` — wrapper Umami. Module `nuxt-umami` configuré dans `nuxt.config.ts` (host : `stats.open.us.org`).
|
||||||
`server/api/stats/index.get.ts` — endpoint public pour la fédération inter-instances (observatoires territoire).
|
`server/api/stats/index.get.ts` — endpoint public stats (nécessite `NUXT_UMAMI_API_KEY`).
|
||||||
`docker/docker-compose.umami.yml` — stack Umami + PostgreSQL avec labels Traefik.
|
|
||||||
|
|
||||||
## Redirections
|
## Redirections
|
||||||
|
|
||||||
|
|||||||
@@ -64,9 +64,8 @@ Port réservé : **3000** (ne pas changer).
|
|||||||
|
|
||||||
## Analytics
|
## Analytics
|
||||||
|
|
||||||
Umami self-hosted (optionnel). Configurer `NUXT_PUBLIC_UMAMI_WEBSITE_ID` et `NUXT_PUBLIC_UMAMI_URL` dans l'environnement.
|
Umami hébergé sur `stats.open.us.org`. Module `nuxt-umami` configuré dans `nuxt.config.ts`.
|
||||||
Déploiement séparé : `docker/docker-compose.umami.yml` → `stats.librodrome.org`.
|
Stats publiques exposées via `/api/stats` (nécessite `NUXT_UMAMI_API_KEY`).
|
||||||
Stats publiques exposées via `/api/stats` pour la fédération inter-instances.
|
|
||||||
|
|
||||||
## Déploiement
|
## Déploiement
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -21,7 +21,7 @@ export default defineAppConfig({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
libredecision: {
|
libredecision: {
|
||||||
url: import.meta.dev ? 'http://localhost:3002' : 'https://decision.laplank.org',
|
url: import.meta.dev ? 'http://localhost:3002' : 'https://decision.librodrome.org',
|
||||||
},
|
},
|
||||||
sejeteral0: {
|
sejeteral0: {
|
||||||
url: import.meta.dev ? 'http://localhost:3009' : 'https://collectivites.librodrome.org',
|
url: import.meta.dev ? 'http://localhost:3009' : 'https://collectivites.librodrome.org',
|
||||||
|
|||||||
+52
-16
@@ -17,24 +17,60 @@
|
|||||||
const paletteStore = usePaletteStore()
|
const paletteStore = usePaletteStore()
|
||||||
onMounted(() => paletteStore.applyToDOM())
|
onMounted(() => paletteStore.applyToDOM())
|
||||||
|
|
||||||
// Umami analytics — inject script only when configured
|
const config = useRuntimeConfig()
|
||||||
const runtimeConfig = useRuntimeConfig()
|
const siteUrl = (config.public.siteUrl as string) || 'https://librodrome.org'
|
||||||
if (runtimeConfig.public.umamiWebsiteId && runtimeConfig.public.umamiUrl) {
|
|
||||||
useHead({
|
|
||||||
script: [{
|
|
||||||
src: `${runtimeConfig.public.umamiUrl}/script.js`,
|
|
||||||
defer: true,
|
|
||||||
'data-website-id': runtimeConfig.public.umamiWebsiteId,
|
|
||||||
}],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Global SEO defaults — surchargeables page par page via useSeoPage()
|
||||||
useHead({
|
useHead({
|
||||||
titleTemplate: (title) => {
|
titleTemplate: (title) => title ? `${title} — Le Librodrome` : 'Le Librodrome',
|
||||||
return title ? `${title} — Le Librodrome` : 'Le librodrome'
|
})
|
||||||
},
|
|
||||||
meta: [
|
useSeoMeta({
|
||||||
{ name: 'description', content: 'Une économie du don — enfin concevable. Un livre et des chansons, lecture guidée et écoute libre.' },
|
ogSiteName: 'Le Librodrome',
|
||||||
|
ogType: 'website',
|
||||||
|
ogLocale: 'fr_FR',
|
||||||
|
ogImage: `${siteUrl}/og-default.png`,
|
||||||
|
ogImageWidth: 1200,
|
||||||
|
ogImageHeight: 630,
|
||||||
|
twitterCard: 'summary_large_image',
|
||||||
|
twitterSite: '@librodrome',
|
||||||
|
})
|
||||||
|
|
||||||
|
// JSON-LD — Organisation + Livre
|
||||||
|
useHead({
|
||||||
|
script: [
|
||||||
|
{
|
||||||
|
type: 'application/ld+json',
|
||||||
|
innerHTML: JSON.stringify({
|
||||||
|
'@context': 'https://schema.org',
|
||||||
|
'@graph': [
|
||||||
|
{
|
||||||
|
'@type': 'Organization',
|
||||||
|
'@id': `${siteUrl}/#organization`,
|
||||||
|
name: 'Le Librodrome',
|
||||||
|
url: siteUrl,
|
||||||
|
logo: {
|
||||||
|
'@type': 'ImageObject',
|
||||||
|
url: `${siteUrl}/images/og-default.png`,
|
||||||
|
},
|
||||||
|
description: 'Plateforme d\'autonomie numérique, économique et citoyenne à l\'échelle des bassins de vie.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'@type': 'Book',
|
||||||
|
'@id': `${siteUrl}/economique/modele-eco#book`,
|
||||||
|
name: 'Une économie du don — enfin concevable',
|
||||||
|
author: { '@type': 'Person', name: 'Yvv' },
|
||||||
|
publisher: { '@id': `${siteUrl}/#organization` },
|
||||||
|
isbn: '979-1-042-45206-3',
|
||||||
|
inLanguage: 'fr',
|
||||||
|
image: `${siteUrl}/images/Couv-Economie-du-don.jpg`,
|
||||||
|
url: `${siteUrl}/economique/modele-eco`,
|
||||||
|
description: 'Un livre et 9 chansons pour explorer les fondements d\'une économie fondée sur le don.',
|
||||||
|
license: 'https://creativecommons.org/licenses/by-nc/4.0/',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
},
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<Teleport to="body">
|
<Teleport to="body">
|
||||||
<Transition name="pdf-overlay">
|
<div
|
||||||
<div
|
v-if="isOpen"
|
||||||
v-if="isOpen"
|
class="pdf-reader"
|
||||||
class="pdf-reader"
|
@keydown.escape="close"
|
||||||
@keydown.escape="close"
|
tabindex="0"
|
||||||
tabindex="0"
|
ref="overlayRef"
|
||||||
ref="overlayRef"
|
>
|
||||||
>
|
|
||||||
<!-- Top bar -->
|
<!-- Top bar -->
|
||||||
<div class="pdf-bar">
|
<div class="pdf-bar">
|
||||||
<div class="pdf-bar-title">
|
<div class="pdf-bar-title">
|
||||||
@@ -26,10 +25,11 @@
|
|||||||
:src="pdfUrl"
|
:src="pdfUrl"
|
||||||
class="pdf-frame"
|
class="pdf-frame"
|
||||||
:title="bpContent?.pdf.iframeTitle"
|
:title="bpContent?.pdf.iframeTitle"
|
||||||
|
ref="iframeRef"
|
||||||
|
@load="iframeRef?.focus()"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Transition>
|
|
||||||
</Teleport>
|
</Teleport>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -40,8 +40,10 @@ const emit = defineEmits<{ 'update:modelValue': [value: boolean] }>()
|
|||||||
const { data: bpContent } = await usePageContent('book-player')
|
const { data: bpContent } = await usePageContent('book-player')
|
||||||
const bookData = useBookData()
|
const bookData = useBookData()
|
||||||
await bookData.init()
|
await bookData.init()
|
||||||
|
const { trackPdfOpen, trackPdfClose } = useTracking()
|
||||||
|
|
||||||
const overlayRef = ref<HTMLElement>()
|
const overlayRef = ref<HTMLElement>()
|
||||||
|
const iframeRef = ref<HTMLIFrameElement>()
|
||||||
|
|
||||||
const isOpen = computed({
|
const isOpen = computed({
|
||||||
get: () => props.modelValue,
|
get: () => props.modelValue,
|
||||||
@@ -59,9 +61,18 @@ function close() {
|
|||||||
isOpen.value = false
|
isOpen.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tracking state
|
||||||
|
let pdfOpenedAt = 0
|
||||||
|
|
||||||
watch(isOpen, (open) => {
|
watch(isOpen, (open) => {
|
||||||
if (open) {
|
if (open) {
|
||||||
nextTick(() => overlayRef.value?.focus())
|
nextTick(() => overlayRef.value?.focus())
|
||||||
|
trackPdfOpen(props.page ? `chapter-p${props.page}` : 'direct')
|
||||||
|
pdfOpenedAt = Date.now()
|
||||||
|
}
|
||||||
|
else if (pdfOpenedAt > 0) {
|
||||||
|
trackPdfClose(0, Date.now() - pdfOpenedAt)
|
||||||
|
pdfOpenedAt = 0
|
||||||
}
|
}
|
||||||
if (import.meta.client) {
|
if (import.meta.client) {
|
||||||
document.body.style.overflow = open ? 'hidden' : ''
|
document.body.style.overflow = open ? 'hidden' : ''
|
||||||
@@ -138,15 +149,4 @@ onUnmounted(() => {
|
|||||||
background: white;
|
background: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Overlay transitions */
|
|
||||||
.pdf-overlay-enter-active {
|
|
||||||
animation: pdf-enter 0.4s cubic-bezier(0.16, 1, 0.3, 1) both;
|
|
||||||
}
|
|
||||||
.pdf-overlay-leave-active {
|
|
||||||
animation: pdf-enter 0.3s cubic-bezier(0.7, 0, 0.84, 0) reverse both;
|
|
||||||
}
|
|
||||||
@keyframes pdf-enter {
|
|
||||||
from { opacity: 0; transform: translateY(8px); }
|
|
||||||
to { opacity: 1; transform: translateY(0); }
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -102,7 +102,7 @@
|
|||||||
<!-- Song disc -->
|
<!-- Song disc -->
|
||||||
<div v-if="currentSong" class="reader-song">
|
<div v-if="currentSong" class="reader-song">
|
||||||
<div class="reader-disc" :class="{ spinning: playerStore.isPlaying }">
|
<div class="reader-disc" :class="{ spinning: playerStore.isPlaying }">
|
||||||
<img src="/images/book-cover-spread.jpg" alt="" class="reader-disc-img" />
|
<img src="/images/Couv-Economie-du-don.jpg" alt="" class="reader-disc-img" />
|
||||||
<div class="reader-disc-hole" />
|
<div class="reader-disc-hole" />
|
||||||
</div>
|
</div>
|
||||||
<span class="reader-song-name">{{ currentSong.title }}</span>
|
<span class="reader-song-name">{{ currentSong.title }}</span>
|
||||||
@@ -138,6 +138,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const { data: bpContent } = await usePageContent('book-player')
|
const { data: bpContent } = await usePageContent('book-player')
|
||||||
|
const { trackPlayerOpen, trackPlayerChapter, trackPlayerMode } = useTracking()
|
||||||
|
|
||||||
const COL_GAP = 80
|
const COL_GAP = 80
|
||||||
|
|
||||||
@@ -167,6 +168,7 @@ const scrollPercent = ref(0)
|
|||||||
|
|
||||||
// When switching back to paginated, recalc pages
|
// When switching back to paginated, recalc pages
|
||||||
watch(readingMode, async (mode) => {
|
watch(readingMode, async (mode) => {
|
||||||
|
trackPlayerMode(mode === 'scroll' ? 'scroll' : 'guided')
|
||||||
if (mode === 'paginated') {
|
if (mode === 'paginated') {
|
||||||
await nextTick()
|
await nextTick()
|
||||||
await nextTick()
|
await nextTick()
|
||||||
@@ -288,6 +290,7 @@ function goToTrack(idx: number) {
|
|||||||
// Play the song
|
// Play the song
|
||||||
const song = tracks.value[idx]?.song
|
const song = tracks.value[idx]?.song
|
||||||
if (song) {
|
if (song) {
|
||||||
|
trackPlayerChapter(song.id)
|
||||||
_skipSongWatch = true
|
_skipSongWatch = true
|
||||||
audioPlayer.loadAndPlay(song)
|
audioPlayer.loadAndPlay(song)
|
||||||
}
|
}
|
||||||
@@ -378,6 +381,7 @@ function onTouchEnd(e: TouchEvent) {
|
|||||||
watch(isOpen, async (open) => {
|
watch(isOpen, async (open) => {
|
||||||
if (open) {
|
if (open) {
|
||||||
showSommaire.value = false
|
showSommaire.value = false
|
||||||
|
trackPlayerOpen()
|
||||||
await initBookData()
|
await initBookData()
|
||||||
await nextTick()
|
await nextTick()
|
||||||
overlayRef.value?.focus()
|
overlayRef.value?.focus()
|
||||||
|
|||||||
@@ -16,12 +16,17 @@
|
|||||||
class="axis-item card-surface"
|
class="axis-item card-surface"
|
||||||
:class="{ 'axis-item--gestation': item.gestation }"
|
:class="{ 'axis-item--gestation': item.gestation }"
|
||||||
>
|
>
|
||||||
<!-- Clickable card body -->
|
<!-- Overlay link — full card clickable (z-index 0) -->
|
||||||
<component
|
<component
|
||||||
:is="itemTag(item)"
|
:is="itemTag(item)"
|
||||||
v-bind="itemAttrs(item)"
|
v-bind="itemAttrs(item)"
|
||||||
class="axis-item-body"
|
class="axis-item-overlay"
|
||||||
>
|
:aria-label="item.label"
|
||||||
|
tabindex="-1"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Card body (position relative, z-index 1 — above overlay) -->
|
||||||
|
<div class="axis-item-body">
|
||||||
<!-- Item icon -->
|
<!-- Item icon -->
|
||||||
<div v-if="item.icon" class="axis-item-icon" :class="`axis-item-icon--${color}`">
|
<div v-if="item.icon" class="axis-item-icon" :class="`axis-item-icon--${color}`">
|
||||||
<span v-if="item.icon === 'g1'" class="axis-item-icon-g1">Ğ1</span>
|
<span v-if="item.icon === 'g1'" class="axis-item-icon-g1">Ğ1</span>
|
||||||
@@ -46,37 +51,47 @@
|
|||||||
</div>
|
</div>
|
||||||
<p class="axis-pi-text">{{ item.presentation.text }}</p>
|
<p class="axis-pi-text">{{ item.presentation.text }}</p>
|
||||||
</div>
|
</div>
|
||||||
</component>
|
</div>
|
||||||
|
|
||||||
<!-- Actions zone (separate from card link) -->
|
<!-- Actions zone — z-index 1, above overlay, boutons ouvrent liens externes -->
|
||||||
<div v-if="item.actions?.length" class="axis-actions">
|
<div v-if="item.actions?.length" class="axis-actions">
|
||||||
<!-- Primary row -->
|
<!-- Primary row -->
|
||||||
<div class="axis-actions-row">
|
<div class="axis-actions-row">
|
||||||
<button
|
<component
|
||||||
|
:is="action.href ? 'a' : 'button'"
|
||||||
v-for="action in primaryActions(item.actions)"
|
v-for="action in primaryActions(item.actions)"
|
||||||
:key="action.id"
|
:key="action.id"
|
||||||
|
v-bind="action.href ? { href: action.href, target: '_blank', rel: 'noopener noreferrer' } : {}"
|
||||||
class="axis-action-btn"
|
class="axis-action-btn"
|
||||||
:class="{ 'axis-action-btn--highlight': action.highlight }"
|
:class="{ 'axis-action-btn--highlight': action.highlight }"
|
||||||
@click.stop="handleAction(action.id)"
|
@click.stop="!action.href && handleAction(action.id)"
|
||||||
>
|
|
||||||
<div :class="iconClass(action.icon)" class="h-3.5 w-3.5" />
|
|
||||||
{{ action.label }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<!-- Secondary row -->
|
|
||||||
<div v-if="secondaryActions(item.actions).length" class="axis-actions-secondary">
|
|
||||||
<component
|
|
||||||
:is="action.to ? resolveComponent('NuxtLink') : 'button'"
|
|
||||||
v-for="action in secondaryActions(item.actions)"
|
|
||||||
:key="action.label"
|
|
||||||
:to="action.to"
|
|
||||||
class="axis-action-btn axis-action-btn--secondary"
|
|
||||||
@click.stop="!action.to && handleAction(action.id)"
|
|
||||||
>
|
>
|
||||||
<div :class="iconClass(action.icon)" class="h-3.5 w-3.5" />
|
<div :class="iconClass(action.icon)" class="h-3.5 w-3.5" />
|
||||||
{{ action.label }}
|
{{ action.label }}
|
||||||
</component>
|
</component>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- Secondary row -->
|
||||||
|
<div v-if="secondaryActions(item.actions).length" class="axis-actions-secondary">
|
||||||
|
<template v-for="action in secondaryActions(item.actions)" :key="action.label">
|
||||||
|
<NuxtLink
|
||||||
|
v-if="action.to"
|
||||||
|
:to="action.to"
|
||||||
|
class="axis-action-btn axis-action-btn--secondary"
|
||||||
|
@click.stop
|
||||||
|
>
|
||||||
|
<div :class="iconClass(action.icon)" class="h-3.5 w-3.5" />
|
||||||
|
{{ action.label }}
|
||||||
|
</NuxtLink>
|
||||||
|
<button
|
||||||
|
v-else
|
||||||
|
class="axis-action-btn axis-action-btn--secondary"
|
||||||
|
@click.stop="handleAction(action.id)"
|
||||||
|
>
|
||||||
|
<div :class="iconClass(action.icon)" class="h-3.5 w-3.5" />
|
||||||
|
{{ action.label }}
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -91,6 +106,7 @@ interface AxisAction {
|
|||||||
highlight?: boolean
|
highlight?: boolean
|
||||||
secondary?: boolean
|
secondary?: boolean
|
||||||
to?: string
|
to?: string
|
||||||
|
href?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AxisItem {
|
interface AxisItem {
|
||||||
@@ -182,9 +198,18 @@ function itemAttrs(item: AxisItem) {
|
|||||||
border: 1px solid hsl(var(--color-text) / 0.08);
|
border: 1px solid hsl(var(--color-text) / 0.08);
|
||||||
background: hsl(var(--color-surface));
|
background: hsl(var(--color-surface));
|
||||||
transition: border-color 0.2s, box-shadow 0.2s;
|
transition: border-color 0.2s, box-shadow 0.2s;
|
||||||
|
position: relative;
|
||||||
/* overflow: visible pour laisser le tooltip sortir du cadre */
|
/* overflow: visible pour laisser le tooltip sortir du cadre */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Lien overlay — couvre toute la carte, AU-DESSUS du contenu */
|
||||||
|
.axis-item-overlay {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 1;
|
||||||
|
border-radius: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
.axis-item:hover {
|
.axis-item:hover {
|
||||||
border-color: hsl(var(--color-primary) / 0.25);
|
border-color: hsl(var(--color-primary) / 0.25);
|
||||||
box-shadow: 0 4px 24px hsl(var(--color-primary) / 0.06);
|
box-shadow: 0 4px 24px hsl(var(--color-primary) / 0.06);
|
||||||
@@ -203,7 +228,6 @@ function itemAttrs(item: AxisItem) {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding: 1.25rem;
|
padding: 1.25rem;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
text-decoration: none;
|
|
||||||
color: inherit;
|
color: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -259,6 +283,8 @@ function itemAttrs(item: AxisItem) {
|
|||||||
background: hsl(var(--color-bg) / 0.4);
|
background: hsl(var(--color-bg) / 0.4);
|
||||||
border-bottom-left-radius: 0.75rem;
|
border-bottom-left-radius: 0.75rem;
|
||||||
border-bottom-right-radius: 0.75rem;
|
border-bottom-right-radius: 0.75rem;
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.axis-actions-row {
|
.axis-actions-row {
|
||||||
|
|||||||
@@ -131,10 +131,9 @@ const { data: content } = await usePageContent('home')
|
|||||||
}
|
}
|
||||||
|
|
||||||
.book-cover-img {
|
.book-cover-img {
|
||||||
width: 200%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
object-fit: cover;
|
object-fit: contain;
|
||||||
transform: translateX(-50%);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.heading-section {
|
.heading-section {
|
||||||
|
|||||||
@@ -1,31 +1,37 @@
|
|||||||
<template>
|
<template>
|
||||||
<section class="section-padding">
|
<section :class="compact ? 'section-book-compact' : 'section-padding'">
|
||||||
<div class="container-content">
|
<div class="container-content">
|
||||||
<div class="grid items-center gap-12 md:grid-cols-2">
|
<div :class="['grid items-center', compact ? 'gap-5 md:grid-cols-[auto_1fr]' : 'gap-12 md:grid-cols-2']">
|
||||||
<!-- Book cover -->
|
<!-- Book cover -->
|
||||||
<UiScrollReveal>
|
<UiScrollReveal>
|
||||||
<div class="book-cover-wrapper relative">
|
<div class="book-cover-wrapper relative">
|
||||||
<div class="book-cover-3d">
|
<div
|
||||||
<img
|
:class="['book-cover-3d', compact && 'book-cover-3d--compact']"
|
||||||
:src="content?.book.coverImage"
|
:style="{ backgroundImage: `url(${content?.book.coverImage})` }"
|
||||||
:alt="content?.book.coverAlt"
|
role="img"
|
||||||
class="book-cover-img"
|
:aria-label="content?.book.coverAlt"
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</UiScrollReveal>
|
</UiScrollReveal>
|
||||||
|
|
||||||
<!-- Content + CTAs -->
|
<!-- Content + CTAs -->
|
||||||
<div>
|
<div>
|
||||||
<UiScrollReveal>
|
<UiScrollReveal>
|
||||||
<p class="mb-2 font-mono text-sm tracking-widest text-accent uppercase">{{ content?.book.kicker }}</p>
|
<p :class="['font-mono tracking-widest text-accent uppercase', compact ? 'text-xs mb-1' : 'text-sm mb-2']">
|
||||||
<h2 class="heading-section font-display font-bold tracking-tight text-white">
|
{{ content?.book.kicker }}
|
||||||
{{ content?.book.title }}
|
</p>
|
||||||
|
<h2 :class="['book-heading font-display font-bold tracking-tight text-white', compact && 'book-heading--compact']">
|
||||||
|
<template v-if="compact">
|
||||||
|
<span class="block">{{ titleLine1 }}</span>
|
||||||
|
<span class="block" style="color: hsl(var(--color-text) / 0.55)">{{ titleLine2 }}</span>
|
||||||
|
</template>
|
||||||
|
<template v-else>{{ content?.book.title }}</template>
|
||||||
</h2>
|
</h2>
|
||||||
</UiScrollReveal>
|
</UiScrollReveal>
|
||||||
|
|
||||||
<UiScrollReveal :delay="100">
|
<UiScrollReveal :delay="100">
|
||||||
<p class="mt-4 text-lg leading-relaxed text-white/60">
|
<p :class="['leading-relaxed', compact ? 'mt-2 text-sm' : 'mt-4 text-lg text-white/60']"
|
||||||
|
:style="compact ? 'color: hsl(var(--color-text-muted)); font-size: 0.85rem' : ''">
|
||||||
{{ content?.book.description }}
|
{{ content?.book.description }}
|
||||||
</p>
|
</p>
|
||||||
</UiScrollReveal>
|
</UiScrollReveal>
|
||||||
@@ -46,8 +52,10 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
withDefaults(defineProps<{
|
withDefaults(defineProps<{
|
||||||
showChapters?: boolean
|
showChapters?: boolean
|
||||||
|
compact?: boolean
|
||||||
}>(), {
|
}>(), {
|
||||||
showChapters: true,
|
showChapters: true,
|
||||||
|
compact: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
defineEmits<{
|
defineEmits<{
|
||||||
@@ -56,9 +64,22 @@ defineEmits<{
|
|||||||
}>()
|
}>()
|
||||||
|
|
||||||
const { data: content } = await usePageContent('home')
|
const { data: content } = await usePageContent('home')
|
||||||
|
|
||||||
|
const titleParts = computed(() => {
|
||||||
|
const title = content.value?.book.title ?? ''
|
||||||
|
const idx = title.indexOf('—')
|
||||||
|
if (idx === -1) return [title, '']
|
||||||
|
return [title.slice(0, idx).trim(), '— ' + title.slice(idx + 1).trim()]
|
||||||
|
})
|
||||||
|
const titleLine1 = computed(() => titleParts.value[0])
|
||||||
|
const titleLine2 = computed(() => titleParts.value[1])
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
.section-book-compact {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.book-cover-wrapper {
|
.book-cover-wrapper {
|
||||||
perspective: 800px;
|
perspective: 800px;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -66,7 +87,12 @@ const { data: content } = await usePageContent('home')
|
|||||||
}
|
}
|
||||||
|
|
||||||
.book-cover-3d {
|
.book-cover-3d {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 360px;
|
||||||
aspect-ratio: 3 / 4;
|
aspect-ratio: 3 / 4;
|
||||||
|
background-size: 200% auto;
|
||||||
|
background-position: right center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
border-radius: 0.75rem;
|
border-radius: 0.75rem;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border: 1px solid hsl(var(--color-text) / 0.1);
|
border: 1px solid hsl(var(--color-text) / 0.1);
|
||||||
@@ -75,7 +101,10 @@ const { data: content } = await usePageContent('home')
|
|||||||
0 0 0 1px hsl(var(--color-text) / 0.08);
|
0 0 0 1px hsl(var(--color-text) / 0.08);
|
||||||
transition: transform 0.5s cubic-bezier(0.645, 0.045, 0.355, 1),
|
transition: transform 0.5s cubic-bezier(0.645, 0.045, 0.355, 1),
|
||||||
box-shadow 0.5s ease;
|
box-shadow 0.5s ease;
|
||||||
max-width: 360px;
|
}
|
||||||
|
|
||||||
|
.book-cover-3d--compact {
|
||||||
|
max-width: 120px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.book-cover-3d:hover {
|
.book-cover-3d:hover {
|
||||||
@@ -85,14 +114,13 @@ const { data: content } = await usePageContent('home')
|
|||||||
0 0 0 1px hsl(var(--color-primary) / 0.2);
|
0 0 0 1px hsl(var(--color-primary) / 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.book-cover-img {
|
|
||||||
width: 200%;
|
|
||||||
height: 100%;
|
|
||||||
object-fit: cover;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.heading-section {
|
.book-heading {
|
||||||
font-size: clamp(1.625rem, 4vw, 2.125rem);
|
font-size: clamp(1.625rem, 4vw, 2.125rem);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.book-heading--compact {
|
||||||
|
font-size: clamp(0.95rem, 2.5vw, 1.15rem);
|
||||||
|
line-height: 1.35;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -3,13 +3,21 @@
|
|||||||
<div class="container-content flex h-[var(--header-height)] items-center justify-between px-4">
|
<div class="container-content flex h-[var(--header-height)] items-center justify-between px-4">
|
||||||
<!-- Logo -->
|
<!-- Logo -->
|
||||||
<NuxtLink to="/" class="logo-link flex items-center gap-2.5">
|
<NuxtLink to="/" class="logo-link flex items-center gap-2.5">
|
||||||
<svg class="logo-icon" viewBox="0 0 64 80" fill="none" aria-hidden="true">
|
<svg class="logo-icon" viewBox="0 0 46 78" fill="none" aria-hidden="true">
|
||||||
<path d="M38 8 C28 6 18 10 18 20 C18 28 26 32 34 34 C42 36 48 40 48 48 C48 52 46 55 42 57 L44 40 C44 36 40 32 34 30 C28 28 22 24 22 18 C22 14 24 11 28 10Z" fill="currentColor" opacity="0.9"/>
|
<defs>
|
||||||
<path d="M26 72 C36 74 46 70 46 60 C46 52 38 48 30 46 C22 44 16 40 16 32 C16 28 18 25 22 23 L20 40 C20 44 24 48 30 50 C36 52 42 56 42 62 C42 66 40 69 36 70Z" fill="currentColor" opacity="0.9"/>
|
<linearGradient id="sect-grad" x1="0" y1="0" x2="0" y2="1">
|
||||||
<path d="M20 16 C20 8 28 4 36 6 C42 8 46 14 44 20" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" fill="none" opacity="0.7"/>
|
<stop offset="0%" stop-color="hsl(var(--color-primary))"/>
|
||||||
<path d="M44 64 C44 72 36 76 28 74 C22 72 18 66 20 60" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" fill="none" opacity="0.7"/>
|
<stop offset="100%" stop-color="hsl(var(--color-accent))"/>
|
||||||
<path d="M36 4 Q42 2 46 6" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" fill="none" opacity="0.5"/>
|
</linearGradient>
|
||||||
<path d="M28 76 Q22 78 18 74" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" fill="none" opacity="0.5"/>
|
</defs>
|
||||||
|
<path
|
||||||
|
d="M 33 10 C 26 7 18 7 14 11 C 8 15 7 24 10 31 C 13 38 21 40 27 44 C 33 48 38 55 35 62 C 32 69 24 72 17 70 C 10 68 8 72 10 75 C 12 78 20 79 28 76"
|
||||||
|
stroke="url(#sect-grad)"
|
||||||
|
stroke-width="5"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
fill="none"
|
||||||
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
<span class="logo-text">{{ site?.identity.name }}</span>
|
<span class="logo-text">{{ site?.identity.name }}</span>
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
@@ -78,7 +86,6 @@ const allNav = computed(() => [...axes.value, ...extra.value])
|
|||||||
.logo-icon {
|
.logo-icon {
|
||||||
width: 1.6rem;
|
width: 1.6rem;
|
||||||
height: 2rem;
|
height: 2rem;
|
||||||
color: hsl(var(--color-primary));
|
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,12 @@ import type { Song } from '~/types/song'
|
|||||||
|
|
||||||
let audio: HTMLAudioElement | null = null
|
let audio: HTMLAudioElement | null = null
|
||||||
let animationFrameId: number | null = null
|
let animationFrameId: number | null = null
|
||||||
|
// Track which milestones have been fired for the current song
|
||||||
|
const firedMilestones = new Set<number>()
|
||||||
|
|
||||||
export function useAudioPlayer() {
|
export function useAudioPlayer() {
|
||||||
const store = usePlayerStore()
|
const store = usePlayerStore()
|
||||||
|
const { trackAudioPlay, trackAudioComplete, trackAudioProgress } = useTracking()
|
||||||
|
|
||||||
function getAudio(): HTMLAudioElement {
|
function getAudio(): HTMLAudioElement {
|
||||||
if (!audio) {
|
if (!audio) {
|
||||||
@@ -17,6 +20,10 @@ export function useAudioPlayer() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
audio.addEventListener('ended', () => {
|
audio.addEventListener('ended', () => {
|
||||||
|
if (store.currentSong) {
|
||||||
|
trackAudioComplete(store.currentSong.id, store.currentSong.title)
|
||||||
|
}
|
||||||
|
firedMilestones.clear()
|
||||||
const next = store.nextSong()
|
const next = store.nextSong()
|
||||||
if (next) {
|
if (next) {
|
||||||
loadAndPlay(next)
|
loadAndPlay(next)
|
||||||
@@ -36,6 +43,16 @@ export function useAudioPlayer() {
|
|||||||
const update = () => {
|
const update = () => {
|
||||||
if (audio && !audio.paused) {
|
if (audio && !audio.paused) {
|
||||||
store.setCurrentTime(audio.currentTime)
|
store.setCurrentTime(audio.currentTime)
|
||||||
|
// Track progress milestones (25 / 50 / 75 %)
|
||||||
|
if (audio.duration > 0 && store.currentSong) {
|
||||||
|
const pct = (audio.currentTime / audio.duration) * 100
|
||||||
|
for (const milestone of [25, 50, 75] as const) {
|
||||||
|
if (pct >= milestone && !firedMilestones.has(milestone)) {
|
||||||
|
firedMilestones.add(milestone)
|
||||||
|
trackAudioProgress(store.currentSong.id, milestone)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
animationFrameId = requestAnimationFrame(update)
|
animationFrameId = requestAnimationFrame(update)
|
||||||
}
|
}
|
||||||
@@ -52,6 +69,7 @@ export function useAudioPlayer() {
|
|||||||
async function loadAndPlay(song: Song) {
|
async function loadAndPlay(song: Song) {
|
||||||
const el = getAudio()
|
const el = getAudio()
|
||||||
store.setSong(song)
|
store.setSong(song)
|
||||||
|
firedMilestones.clear()
|
||||||
|
|
||||||
// Try OGG first, fall back to MP3
|
// Try OGG first, fall back to MP3
|
||||||
const oggPath = song.file.replace(/\.mp3$/, '.ogg')
|
const oggPath = song.file.replace(/\.mp3$/, '.ogg')
|
||||||
@@ -64,6 +82,7 @@ export function useAudioPlayer() {
|
|||||||
await el.play()
|
await el.play()
|
||||||
store.play()
|
store.play()
|
||||||
startTimeUpdate()
|
startTimeUpdate()
|
||||||
|
trackAudioPlay(song.id, song.title)
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
// If OGG failed, try MP3
|
// If OGG failed, try MP3
|
||||||
@@ -73,6 +92,7 @@ export function useAudioPlayer() {
|
|||||||
await el.play()
|
await el.play()
|
||||||
store.play()
|
store.play()
|
||||||
startTimeUpdate()
|
startTimeUpdate()
|
||||||
|
trackAudioPlay(song.id, song.title)
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
console.error('Playback failed:', err)
|
console.error('Playback failed:', err)
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
/**
|
||||||
|
* Tracks scroll depth milestones (25 / 50 / 75 / 100 %) on each page.
|
||||||
|
* Also intercepts external link clicks and tracks them.
|
||||||
|
*
|
||||||
|
* Usage: call once in default.vue layout.
|
||||||
|
*/
|
||||||
|
export function useScrollTracking() {
|
||||||
|
const route = useRoute()
|
||||||
|
const { trackScrollDepth, trackExternalLink } = useTracking()
|
||||||
|
|
||||||
|
// ── Scroll depth ────────────────────────────────────────────────────────
|
||||||
|
const fired = new Set<number>()
|
||||||
|
|
||||||
|
function onScroll() {
|
||||||
|
const el = document.documentElement
|
||||||
|
const scrolled = el.scrollTop + el.clientHeight
|
||||||
|
const total = el.scrollHeight
|
||||||
|
if (total <= el.clientHeight) return
|
||||||
|
const pct = (scrolled / total) * 100
|
||||||
|
|
||||||
|
for (const milestone of [25, 50, 75, 100] as const) {
|
||||||
|
if (pct >= milestone && !fired.has(milestone)) {
|
||||||
|
fired.add(milestone)
|
||||||
|
trackScrollDepth(route.path, milestone)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset fired milestones on route change
|
||||||
|
watch(() => route.path, () => fired.clear())
|
||||||
|
|
||||||
|
// ── External link tracking ───────────────────────────────────────────────
|
||||||
|
function onLinkClick(e: MouseEvent) {
|
||||||
|
const target = (e.target as HTMLElement).closest('a')
|
||||||
|
if (!target) return
|
||||||
|
const href = target.getAttribute('href') || ''
|
||||||
|
if (!href.startsWith('http') && !href.startsWith('//')) return
|
||||||
|
try {
|
||||||
|
const url = new URL(href)
|
||||||
|
if (url.hostname === 'librodrome.org') return
|
||||||
|
trackExternalLink(href, target.textContent?.trim() || '')
|
||||||
|
}
|
||||||
|
catch { /* invalid URL, ignore */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
window.addEventListener('scroll', onScroll, { passive: true })
|
||||||
|
document.addEventListener('click', onLinkClick)
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
window.removeEventListener('scroll', onScroll)
|
||||||
|
document.removeEventListener('click', onLinkClick)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
/**
|
||||||
|
* Applique toutes les balises SEO (og:*, Twitter Cards, canonical, description)
|
||||||
|
* à partir du contenu YAML d'une page.
|
||||||
|
*
|
||||||
|
* Usage dans les pages :
|
||||||
|
* useSeoPage({ title: content.value?.meta?.title, description: content.value?.description })
|
||||||
|
*
|
||||||
|
* L'og:image par défaut est /og-default.png (logo §).
|
||||||
|
* Chaque section peut surcharger avec son propre image via le champ seo.image du YAML.
|
||||||
|
*/
|
||||||
|
export function useSeoPage(opts: {
|
||||||
|
title?: string | null
|
||||||
|
description?: string | null
|
||||||
|
image?: string | null
|
||||||
|
type?: 'website' | 'article' | 'book'
|
||||||
|
}) {
|
||||||
|
const config = useRuntimeConfig()
|
||||||
|
const route = useRoute()
|
||||||
|
const siteUrl = (config.public.siteUrl as string) || 'https://librodrome.org'
|
||||||
|
|
||||||
|
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'
|
||||||
|
const image = rawImage.startsWith('http') ? rawImage : `${siteUrl}${rawImage}`
|
||||||
|
const canonical = `${siteUrl}${route.path}`
|
||||||
|
const type = opts.type || 'website'
|
||||||
|
|
||||||
|
useSeoMeta({
|
||||||
|
// Open Graph
|
||||||
|
ogTitle: title,
|
||||||
|
ogDescription: description,
|
||||||
|
ogImage: image,
|
||||||
|
ogImageWidth: 1200,
|
||||||
|
ogImageHeight: 630,
|
||||||
|
ogUrl: canonical,
|
||||||
|
ogType: type,
|
||||||
|
ogSiteName: 'Le Librodrome',
|
||||||
|
ogLocale: 'fr_FR',
|
||||||
|
// Twitter Cards
|
||||||
|
twitterCard: 'summary_large_image',
|
||||||
|
twitterTitle: title,
|
||||||
|
twitterDescription: description,
|
||||||
|
twitterImage: image,
|
||||||
|
// Standard
|
||||||
|
description,
|
||||||
|
})
|
||||||
|
|
||||||
|
useHead({
|
||||||
|
link: [{ rel: 'canonical', href: canonical }],
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,13 +1,19 @@
|
|||||||
/**
|
/**
|
||||||
* Umami analytics wrapper — safe server-side, no-op when not configured.
|
* Umami analytics wrapper — safe server-side, no-op when not configured.
|
||||||
* Usage: const { track } = useTracking()
|
*
|
||||||
* track('player:open')
|
* Events convention:
|
||||||
* track('axis:navigate', { axis: 'numerique' })
|
* audio:play | audio:pause | audio:complete | audio:progress
|
||||||
|
* pdf:open | pdf:close
|
||||||
|
* player:open | player:chapter | player:mode
|
||||||
|
* scroll:depth
|
||||||
|
* link:external
|
||||||
|
* cta:click
|
||||||
*/
|
*/
|
||||||
export function useTracking() {
|
export function useTracking() {
|
||||||
const runtimeConfig = useRuntimeConfig()
|
const runtimeConfig = useRuntimeConfig()
|
||||||
const enabled = !!runtimeConfig.public.umamiWebsiteId
|
const enabled = !!runtimeConfig.public.umamiWebsiteId
|
||||||
|
|
||||||
|
// ── Core ────────────────────────────────────────────────────────────────
|
||||||
function track(event: string, data?: Record<string, unknown>) {
|
function track(event: string, data?: Record<string, unknown>) {
|
||||||
if (!import.meta.client || !enabled) return
|
if (!import.meta.client || !enabled) return
|
||||||
const umami = (window as Record<string, unknown>).umami as
|
const umami = (window as Record<string, unknown>).umami as
|
||||||
@@ -16,5 +22,75 @@ export function useTracking() {
|
|||||||
umami?.track(event, data)
|
umami?.track(event, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
return { track, enabled }
|
// ── Audio ────────────────────────────────────────────────────────────────
|
||||||
|
function trackAudioPlay(songId: string, songTitle: string, context?: string) {
|
||||||
|
track('audio:play', { song_id: songId, song_title: songTitle, context })
|
||||||
|
}
|
||||||
|
|
||||||
|
function trackAudioPause(songId: string, progressPct: number) {
|
||||||
|
track('audio:pause', { song_id: songId, progress_pct: progressPct })
|
||||||
|
}
|
||||||
|
|
||||||
|
function trackAudioComplete(songId: string, songTitle: string) {
|
||||||
|
track('audio:complete', { song_id: songId, song_title: songTitle })
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Fired at 25 / 50 / 75 % milestones */
|
||||||
|
function trackAudioProgress(songId: string, milestone: 25 | 50 | 75) {
|
||||||
|
track('audio:progress', { song_id: songId, milestone })
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── PDF ──────────────────────────────────────────────────────────────────
|
||||||
|
function trackPdfOpen(trigger?: string) {
|
||||||
|
track('pdf:open', { trigger })
|
||||||
|
}
|
||||||
|
|
||||||
|
function trackPdfClose(pagesVisited: number, durationMs: number) {
|
||||||
|
track('pdf:close', { pages_visited: pagesVisited, duration_ms: durationMs })
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── BookPlayer ───────────────────────────────────────────────────────────
|
||||||
|
function trackPlayerOpen(trigger?: string) {
|
||||||
|
track('player:open', { trigger })
|
||||||
|
}
|
||||||
|
|
||||||
|
function trackPlayerChapter(chapterSlug: string) {
|
||||||
|
track('player:chapter', { chapter_slug: chapterSlug })
|
||||||
|
}
|
||||||
|
|
||||||
|
function trackPlayerMode(mode: 'guided' | 'scroll') {
|
||||||
|
track('player:mode', { mode })
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Navigation & UX ──────────────────────────────────────────────────────
|
||||||
|
function trackScrollDepth(page: string, depth: 25 | 50 | 75 | 100) {
|
||||||
|
track('scroll:depth', { page, depth })
|
||||||
|
}
|
||||||
|
|
||||||
|
function trackExternalLink(url: string, label?: string) {
|
||||||
|
const route = useRoute()
|
||||||
|
track('link:external', { url, label, from_page: route.path })
|
||||||
|
}
|
||||||
|
|
||||||
|
function trackCta(label: string, target?: string) {
|
||||||
|
const route = useRoute()
|
||||||
|
track('cta:click', { label, target, from_page: route.path })
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
track,
|
||||||
|
enabled,
|
||||||
|
trackAudioPlay,
|
||||||
|
trackAudioPause,
|
||||||
|
trackAudioComplete,
|
||||||
|
trackAudioProgress,
|
||||||
|
trackPdfOpen,
|
||||||
|
trackPdfClose,
|
||||||
|
trackPlayerOpen,
|
||||||
|
trackPlayerChapter,
|
||||||
|
trackPlayerMode,
|
||||||
|
trackScrollDepth,
|
||||||
|
trackExternalLink,
|
||||||
|
trackCta,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
useScrollTracking()
|
||||||
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.app-layout {
|
.app-layout {
|
||||||
grid-template-rows: auto 1fr auto;
|
grid-template-rows: auto 1fr auto;
|
||||||
|
|||||||
@@ -40,8 +40,9 @@ definePageMeta({
|
|||||||
layout: 'default',
|
layout: 'default',
|
||||||
})
|
})
|
||||||
|
|
||||||
useHead({
|
useSeoPage({
|
||||||
title: 'À propos',
|
title: 'À propos — Le Librodrome',
|
||||||
|
description: 'Le Librodrome : l\'histoire du projet, la démarche et les personnes derrière le livre et les outils d\'autonomie.',
|
||||||
})
|
})
|
||||||
|
|
||||||
const { data: page } = await useAsyncData('about', () =>
|
const { data: page } = await useAsyncData('about', () =>
|
||||||
|
|||||||
+236
-19
@@ -1,25 +1,67 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="section-padding">
|
<div class="section-padding">
|
||||||
<div class="container-content">
|
<div class="container-content">
|
||||||
<div class="mx-auto max-w-2xl">
|
|
||||||
|
<!-- Header -->
|
||||||
|
<div class="mx-auto max-w-2xl text-center mb-8">
|
||||||
<div class="section-icon mx-auto mb-6">
|
<div class="section-icon mx-auto mb-6">
|
||||||
<div :class="`i-lucide-${content?.icon ?? 'landmark'}`" class="h-12 w-12" />
|
<div :class="`i-lucide-${content?.icon ?? 'landmark'}`" class="h-12 w-12" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="mb-2 font-mono text-sm tracking-widest text-primary uppercase text-center">
|
<p class="mb-2 font-mono text-sm tracking-widest text-primary uppercase text-center">
|
||||||
{{ content?.kicker }}
|
{{ content?.kicker }}
|
||||||
</p>
|
</p>
|
||||||
|
<h1 class="font-display text-3xl font-bold mb-4" style="color: hsl(var(--color-text))">
|
||||||
<h1 class="font-display text-3xl font-bold mb-4 text-center" style="color: hsl(var(--color-text))">
|
|
||||||
{{ content?.title ?? slug }}
|
{{ content?.title ?? slug }}
|
||||||
</h1>
|
</h1>
|
||||||
|
<p class="text-lg leading-relaxed" style="color: hsl(var(--color-text-muted))">
|
||||||
<p class="text-lg leading-relaxed mb-8 text-center" style="color: hsl(var(--color-text-muted))">
|
|
||||||
{{ content?.description }}
|
{{ content?.description }}
|
||||||
</p>
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Features grid (for decision page) -->
|
<!-- Navigation prev / index / next -->
|
||||||
<div v-if="content?.features" class="grid gap-4 sm:grid-cols-2 mb-12">
|
<nav class="ctx-nav mx-auto max-w-2xl mb-10">
|
||||||
|
<NuxtLink v-if="prevItem?.to" :to="prevItem.to" class="ctx-nav-btn ctx-nav-prev">
|
||||||
|
<div class="i-lucide-arrow-left h-4 w-4 shrink-0" />
|
||||||
|
<span>{{ prevItem.label }}</span>
|
||||||
|
</NuxtLink>
|
||||||
|
<div v-else class="ctx-nav-spacer" />
|
||||||
|
|
||||||
|
<NuxtLink to="/citoyenne" class="ctx-nav-btn ctx-nav-index">
|
||||||
|
<div class="i-lucide-layout-grid h-4 w-4" />
|
||||||
|
<span>Citoyenne</span>
|
||||||
|
</NuxtLink>
|
||||||
|
|
||||||
|
<NuxtLink v-if="nextItem?.to" :to="nextItem.to" class="ctx-nav-btn ctx-nav-next">
|
||||||
|
<span>{{ nextItem.label }}</span>
|
||||||
|
<div class="i-lucide-arrow-right h-4 w-4 shrink-0" />
|
||||||
|
</NuxtLink>
|
||||||
|
<div v-else class="ctx-nav-spacer" />
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<!-- Zone sections — relative pour ancrer le sidebar -->
|
||||||
|
<div class="sections-area">
|
||||||
|
|
||||||
|
<!-- Sidebar sommaire -->
|
||||||
|
<aside v-if="sommaire.length > 1" class="page-sidebar">
|
||||||
|
<nav class="sommaire-sidebar">
|
||||||
|
<p class="sommaire-sidebar-title">Sur cette page</p>
|
||||||
|
<ol class="sommaire-sidebar-list">
|
||||||
|
<li v-for="(entry, ei) in sommaire" :key="ei">
|
||||||
|
<a :href="`#${entry.id}`" class="sommaire-sidebar-link">
|
||||||
|
<span class="sommaire-n">{{ ei + 1 }}</span>
|
||||||
|
<span>{{ entry.title }}</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
</nav>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<!-- Features grid -->
|
||||||
|
<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"
|
||||||
|
>
|
||||||
<div v-for="feature in content.features" :key="feature.title" class="feature-card">
|
<div v-for="feature in content.features" :key="feature.title" class="feature-card">
|
||||||
<div class="feature-icon">
|
<div class="feature-icon">
|
||||||
<div :class="`i-lucide-${feature.icon} h-5 w-5`" />
|
<div :class="`i-lucide-${feature.icon} h-5 w-5`" />
|
||||||
@@ -30,7 +72,11 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Project card -->
|
<!-- Project card -->
|
||||||
<div v-if="content?.project" class="project-card mb-8">
|
<div
|
||||||
|
v-if="content?.project"
|
||||||
|
id="section-project"
|
||||||
|
class="project-card mb-8 max-w-2xl mx-auto section-anchor"
|
||||||
|
>
|
||||||
<div class="project-icon">
|
<div class="project-icon">
|
||||||
<div class="i-lucide-rocket h-5 w-5" />
|
<div class="i-lucide-rocket h-5 w-5" />
|
||||||
</div>
|
</div>
|
||||||
@@ -47,27 +93,30 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Extended content -->
|
<!-- Extended content -->
|
||||||
<div v-if="content?.content" class="prose-block mb-10">
|
<div
|
||||||
|
v-if="content?.content"
|
||||||
|
id="section-content"
|
||||||
|
class="prose-block mb-10 max-w-2xl mx-auto section-anchor"
|
||||||
|
>
|
||||||
<p class="leading-relaxed whitespace-pre-line" style="color: hsl(var(--color-text-muted))">
|
<p class="leading-relaxed whitespace-pre-line" style="color: hsl(var(--color-text-muted))">
|
||||||
{{ content.content }}
|
{{ content.content }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="text-center flex flex-col items-center gap-3 sm:flex-row sm:justify-center">
|
<!-- Actions -->
|
||||||
|
<div class="text-center flex flex-col items-center gap-3 sm:flex-row sm:justify-center mb-4">
|
||||||
<UiBaseButton v-if="slug === 'decision'" :href="decisionUrl" target="_blank">
|
<UiBaseButton v-if="slug === 'decision'" :href="decisionUrl" target="_blank">
|
||||||
<div class="i-lucide-external-link mr-2 h-4 w-4" />
|
<div class="i-lucide-external-link mr-2 h-4 w-4" />
|
||||||
Ouvrir Glibredecision
|
Ouvrir libreDecision
|
||||||
</UiBaseButton>
|
</UiBaseButton>
|
||||||
<UiBaseButton v-if="slug === 'tarifs-eau'" :href="sejeteral0Url" target="_blank">
|
<UiBaseButton v-if="slug === 'tarifs-eau'" :href="sejeteral0Url" target="_blank">
|
||||||
<div class="i-lucide-external-link mr-2 h-4 w-4" />
|
<div class="i-lucide-external-link mr-2 h-4 w-4" />
|
||||||
Lancer SejeteralO
|
Lancer SejeteralO
|
||||||
</UiBaseButton>
|
</UiBaseButton>
|
||||||
<UiBaseButton variant="ghost" to="/citoyenne">
|
|
||||||
<div class="i-lucide-arrow-left mr-2 h-4 w-4" />
|
|
||||||
Autonomie citoyenne
|
|
||||||
</UiBaseButton>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
</div><!-- /sections-area -->
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -76,14 +125,37 @@
|
|||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const slug = route.params.slug as string
|
const slug = route.params.slug as string
|
||||||
|
|
||||||
const { data: content } = await usePageContent(`citoyenne/${slug}`)
|
const [{ data: content }, { data: homeData }] = await Promise.all([
|
||||||
|
usePageContent(`citoyenne/${slug}`),
|
||||||
|
usePageContent('home'),
|
||||||
|
])
|
||||||
|
|
||||||
const appConfig = useAppConfig()
|
const appConfig = useAppConfig()
|
||||||
const decisionUrl = (appConfig.libredecision as { url: string })?.url ?? '#'
|
const decisionUrl = (appConfig.libredecision as { url: string })?.url ?? '#'
|
||||||
const sejeteral0Url = (appConfig.sejeteral0 as { url: string })?.url ?? '#'
|
const sejeteral0Url = (appConfig.sejeteral0 as { url: string })?.url ?? '#'
|
||||||
|
|
||||||
useHead({
|
// Sommaire
|
||||||
|
const sommaire = computed(() => {
|
||||||
|
const entries: { title: string; id: string }[] = []
|
||||||
|
if ((content.value?.features as unknown[])?.length) entries.push({ title: 'Fonctionnalités', id: 'section-features' })
|
||||||
|
if (content.value?.project) entries.push({ title: (content.value.project as { name: string }).name, id: 'section-project' })
|
||||||
|
if (content.value?.content) entries.push({ title: 'En savoir plus', id: 'section-content' })
|
||||||
|
return entries
|
||||||
|
})
|
||||||
|
|
||||||
|
// Prev / next dans la section citoyenne
|
||||||
|
interface AxisItem { label: string; to?: string; icon?: string }
|
||||||
|
const politiqueItems = computed<AxisItem[]>(
|
||||||
|
() => (homeData.value as Record<string, unknown> | null)?.axes?.politique?.items as AxisItem[] ?? [],
|
||||||
|
)
|
||||||
|
const currentPath = `/citoyenne/${slug}`
|
||||||
|
const currentIdx = computed(() => politiqueItems.value.findIndex(i => i.to === currentPath))
|
||||||
|
const prevItem = computed(() => currentIdx.value > 0 ? politiqueItems.value[currentIdx.value - 1] : null)
|
||||||
|
const nextItem = computed(() => currentIdx.value < politiqueItems.value.length - 1 ? politiqueItems.value[currentIdx.value + 1] : null)
|
||||||
|
|
||||||
|
useSeoPage({
|
||||||
title: content.value?.meta?.title ?? slug,
|
title: content.value?.meta?.title ?? slug,
|
||||||
|
description: content.value?.description as string ?? undefined,
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -156,4 +228,149 @@ useHead({
|
|||||||
border-radius: 0.75rem;
|
border-radius: 0.75rem;
|
||||||
background: hsl(var(--color-surface));
|
background: hsl(var(--color-surface));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.section-anchor {
|
||||||
|
scroll-margin-top: 5.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Navigation prev/next ── */
|
||||||
|
.ctx-nav {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr auto 1fr;
|
||||||
|
gap: 0.5rem;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ctx-nav-spacer { min-width: 0; }
|
||||||
|
|
||||||
|
.ctx-nav-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.45rem;
|
||||||
|
padding: 0.5rem 0.875rem;
|
||||||
|
border-radius: 20px;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: 500;
|
||||||
|
color: hsl(var(--color-text-muted));
|
||||||
|
background: hsl(var(--color-surface));
|
||||||
|
border: 1px solid hsl(var(--color-text) / 0.08);
|
||||||
|
transition: all 0.15s;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ctx-nav-btn:hover {
|
||||||
|
color: hsl(var(--color-primary));
|
||||||
|
border-color: hsl(var(--color-primary) / 0.25);
|
||||||
|
background: hsl(var(--color-primary) / 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ctx-nav-prev { justify-content: flex-start; }
|
||||||
|
.ctx-nav-next { justify-content: flex-end; }
|
||||||
|
|
||||||
|
.ctx-nav-prev span,
|
||||||
|
.ctx-nav-next span {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ctx-nav-index {
|
||||||
|
justify-content: center;
|
||||||
|
background: hsl(var(--color-primary) / 0.08);
|
||||||
|
border-color: hsl(var(--color-primary) / 0.15);
|
||||||
|
color: hsl(var(--color-primary));
|
||||||
|
font-size: 0.75rem;
|
||||||
|
padding: 0.45rem 0.75rem;
|
||||||
|
white-space: nowrap;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ctx-nav-index:hover { background: hsl(var(--color-primary) / 0.15); }
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.ctx-nav { grid-template-columns: auto 1fr auto; }
|
||||||
|
.ctx-nav-prev span, .ctx-nav-next span { display: none; }
|
||||||
|
.ctx-nav-prev, .ctx-nav-next { padding: 0.5rem; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Zone sections ── */
|
||||||
|
.sections-area {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Sidebar sommaire ── */
|
||||||
|
.page-sidebar {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
width: 10.5rem;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1300px) {
|
||||||
|
.page-sidebar { display: block; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.sommaire-sidebar {
|
||||||
|
position: sticky;
|
||||||
|
top: 5.5rem;
|
||||||
|
padding: 0.875rem;
|
||||||
|
border-radius: 14px;
|
||||||
|
background: hsl(var(--color-surface));
|
||||||
|
border: 1px solid hsl(var(--color-text) / 0.07);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sommaire-sidebar-title {
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: 0.65rem;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.1em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: hsl(var(--color-text-muted));
|
||||||
|
margin: 0 0 0.6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sommaire-sidebar-list {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sommaire-sidebar-link {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
gap: 0.45rem;
|
||||||
|
padding: 0.28rem 0.4rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: hsl(var(--color-text-muted));
|
||||||
|
line-height: 1.4;
|
||||||
|
transition: color 0.12s, background 0.12s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sommaire-sidebar-link:hover {
|
||||||
|
color: hsl(var(--color-primary));
|
||||||
|
background: hsl(var(--color-primary) / 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sommaire-n {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 1.2rem;
|
||||||
|
height: 1.2rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: hsl(var(--color-primary) / 0.1);
|
||||||
|
color: hsl(var(--color-primary));
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: 0.6rem;
|
||||||
|
font-weight: 700;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -343,7 +343,8 @@
|
|||||||
<div class="mx-auto max-w-3xl flex flex-col gap-6">
|
<div class="mx-auto max-w-3xl flex flex-col gap-6">
|
||||||
<!-- Decision collective -->
|
<!-- Decision collective -->
|
||||||
<div class="item-card">
|
<div class="item-card">
|
||||||
<NuxtLink to="/citoyenne/decision" class="item-body group">
|
<NuxtLink to="/citoyenne/decision" class="item-overlay" aria-label="Décision collective" tabindex="-1" />
|
||||||
|
<div class="item-body group">
|
||||||
<div class="item-header">
|
<div class="item-header">
|
||||||
<div class="item-icon">
|
<div class="item-icon">
|
||||||
<div class="i-lucide-gavel h-5 w-5" />
|
<div class="i-lucide-gavel h-5 w-5" />
|
||||||
@@ -355,22 +356,19 @@
|
|||||||
<p class="leading-relaxed mt-3" style="color: hsl(var(--color-text-muted))">
|
<p class="leading-relaxed mt-3" style="color: hsl(var(--color-text-muted))">
|
||||||
Se donner les moyens de la décision collective.
|
Se donner les moyens de la décision collective.
|
||||||
</p>
|
</p>
|
||||||
<div class="mt-3 inline-flex items-center gap-1 text-sm text-primary group-hover:text-primary/80 transition-colors">
|
</div>
|
||||||
En savoir plus
|
|
||||||
<div class="i-lucide-arrow-right h-3.5 w-3.5" />
|
|
||||||
</div>
|
|
||||||
</NuxtLink>
|
|
||||||
<div class="item-actions">
|
<div class="item-actions">
|
||||||
<a :href="decisionUrl" target="_blank" rel="noopener" class="action-btn action-btn--primary">
|
<a :href="decisionUrl" target="_blank" rel="noopener" class="action-btn action-btn--primary">
|
||||||
<div class="i-lucide-external-link h-3.5 w-3.5" />
|
<div class="i-lucide-external-link h-3.5 w-3.5" />
|
||||||
Ouvrir Glibredecision
|
Ouvrir libreDecision
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Tarifs de l'eau -->
|
<!-- Tarifs de l'eau -->
|
||||||
<div class="item-card">
|
<div class="item-card">
|
||||||
<NuxtLink to="/citoyenne/tarifs-eau" class="item-body group">
|
<NuxtLink to="/citoyenne/tarifs-eau" class="item-overlay" aria-label="Tarifs de l'eau" tabindex="-1" />
|
||||||
|
<div class="item-body group">
|
||||||
<div class="item-header">
|
<div class="item-header">
|
||||||
<div class="item-icon">
|
<div class="item-icon">
|
||||||
<div class="i-lucide-droplets h-5 w-5" />
|
<div class="i-lucide-droplets h-5 w-5" />
|
||||||
@@ -387,11 +385,7 @@
|
|||||||
Application pour obtenir justice sociale et incitation dynamique à la réduction.
|
Application pour obtenir justice sociale et incitation dynamique à la réduction.
|
||||||
Permet de confier la décision à la population des communes.
|
Permet de confier la décision à la population des communes.
|
||||||
</p>
|
</p>
|
||||||
<div class="mt-3 inline-flex items-center gap-1 text-sm text-primary group-hover:text-primary/80 transition-colors">
|
</div>
|
||||||
En savoir plus
|
|
||||||
<div class="i-lucide-arrow-right h-3.5 w-3.5" />
|
|
||||||
</div>
|
|
||||||
</NuxtLink>
|
|
||||||
<div class="item-actions">
|
<div class="item-actions">
|
||||||
<a :href="sejeteral0Url" target="_blank" rel="noopener" class="action-btn action-btn--primary">
|
<a :href="sejeteral0Url" target="_blank" rel="noopener" class="action-btn action-btn--primary">
|
||||||
<div class="i-lucide-external-link h-3.5 w-3.5" />
|
<div class="i-lucide-external-link h-3.5 w-3.5" />
|
||||||
@@ -415,8 +409,9 @@ const appConfig = useAppConfig()
|
|||||||
const decisionUrl = (appConfig.libredecision as { url: string })?.url ?? '#'
|
const decisionUrl = (appConfig.libredecision as { url: string })?.url ?? '#'
|
||||||
const sejeteral0Url = (appConfig.sejeteral0 as { url: string })?.url ?? '#'
|
const sejeteral0Url = (appConfig.sejeteral0 as { url: string })?.url ?? '#'
|
||||||
|
|
||||||
useHead({
|
useSeoPage({
|
||||||
title: content.value?.meta?.title ?? 'Autonomie citoyenne',
|
title: content.value?.meta?.title ?? 'Autonomie citoyenne',
|
||||||
|
description: (content.value as Record<string, unknown> | null)?.description as string ?? 'Décision collective, gouvernance locale, outils citoyens — l\'autonomie politique à l\'échelle des bassins de vie.',
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -431,16 +426,24 @@ useHead({
|
|||||||
background: hsl(var(--color-surface));
|
background: hsl(var(--color-surface));
|
||||||
transition: border-color 0.2s;
|
transition: border-color 0.2s;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-card:hover {
|
.item-card:hover {
|
||||||
border-color: hsl(var(--color-primary) / 0.2);
|
border-color: hsl(var(--color-primary) / 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Lien overlay — couvre toute la carte, AU-DESSUS du contenu */
|
||||||
|
.item-overlay {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.item-body {
|
.item-body {
|
||||||
display: block;
|
display: block;
|
||||||
padding: 1.5rem;
|
padding: 1.5rem;
|
||||||
text-decoration: none;
|
|
||||||
color: inherit;
|
color: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -484,6 +487,8 @@ useHead({
|
|||||||
padding: 0.75rem 1.5rem;
|
padding: 0.75rem 1.5rem;
|
||||||
border-top: 1px solid hsl(var(--color-text) / 0.06);
|
border-top: 1px solid hsl(var(--color-text) / 0.06);
|
||||||
background: hsl(var(--color-bg) / 0.4);
|
background: hsl(var(--color-bg) / 0.4);
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-btn {
|
.action-btn {
|
||||||
|
|||||||
@@ -304,8 +304,10 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const { data: content } = await usePageContent('economique/commande')
|
const { data: content } = await usePageContent('economique/commande')
|
||||||
|
|
||||||
useHead({
|
useSeoPage({
|
||||||
title: content.value?.meta?.title ?? 'Commander le livre',
|
title: content.value?.meta?.title ?? 'Commander le livre — Une économie du don',
|
||||||
|
description: 'Commander l\'édition papier du livre « Une économie du don — enfin concevable » d\'Yvv.',
|
||||||
|
image: '/images/Couv-Economie-du-don.jpg',
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -387,7 +387,8 @@
|
|||||||
|
|
||||||
<div class="mx-auto max-w-3xl flex flex-col gap-8">
|
<div class="mx-auto max-w-3xl flex flex-col gap-8">
|
||||||
<!-- Monnaie libre -->
|
<!-- Monnaie libre -->
|
||||||
<NuxtLink to="/economique/monnaie-libre" class="item-card group">
|
<NuxtLink to="/economique/monnaie-libre" class="item-card g1-card group">
|
||||||
|
<span class="g1-watermark" aria-hidden="true">Ğ1</span>
|
||||||
<div class="item-header">
|
<div class="item-header">
|
||||||
<div class="item-icon">
|
<div class="item-icon">
|
||||||
<span class="g1-icon">Ğ1</span>
|
<span class="g1-icon">Ğ1</span>
|
||||||
@@ -408,6 +409,7 @@
|
|||||||
<!-- Modèle économique — bloc livre -->
|
<!-- Modèle économique — bloc livre -->
|
||||||
<div class="book-block">
|
<div class="book-block">
|
||||||
<HomeBookSection
|
<HomeBookSection
|
||||||
|
compact
|
||||||
@open-player="showBookPlayer = true"
|
@open-player="showBookPlayer = true"
|
||||||
@open-pdf="showPdfReader = true"
|
@open-pdf="showPdfReader = true"
|
||||||
/>
|
/>
|
||||||
@@ -450,8 +452,9 @@ definePageMeta({
|
|||||||
|
|
||||||
const { data: content } = await usePageContent('economique')
|
const { data: content } = await usePageContent('economique')
|
||||||
|
|
||||||
useHead({
|
useSeoPage({
|
||||||
title: content.value?.meta?.title ?? 'Autonomie économique',
|
title: content.value?.meta?.title ?? 'Autonomie économique',
|
||||||
|
description: (content.value as Record<string, unknown> | null)?.description as string ?? 'Comprendre et expérimenter une économie fondée sur le don, la monnaie libre et la réciprocité.',
|
||||||
})
|
})
|
||||||
|
|
||||||
const showBookPlayer = ref(false)
|
const showBookPlayer = ref(false)
|
||||||
@@ -520,12 +523,48 @@ const showPdfReader = ref(false)
|
|||||||
}
|
}
|
||||||
|
|
||||||
.book-block {
|
.book-block {
|
||||||
padding: 1.5rem;
|
padding: 1.25rem;
|
||||||
border-radius: 0.75rem;
|
border-radius: 0.75rem;
|
||||||
border: 1px solid hsl(var(--color-text) / 0.08);
|
border: 1px solid hsl(var(--color-text) / 0.08);
|
||||||
background: hsl(var(--color-surface));
|
background: hsl(var(--color-surface));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Monnaie libre card */
|
||||||
|
.g1-card {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
background: linear-gradient(
|
||||||
|
135deg,
|
||||||
|
hsl(var(--color-surface)) 60%,
|
||||||
|
hsl(var(--color-primary) / 0.06) 100%
|
||||||
|
);
|
||||||
|
border-color: hsl(var(--color-primary) / 0.14);
|
||||||
|
}
|
||||||
|
|
||||||
|
.g1-card:hover {
|
||||||
|
border-color: hsl(var(--color-primary) / 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.g1-watermark {
|
||||||
|
position: absolute;
|
||||||
|
right: -0.25rem;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
font-family: var(--font-display);
|
||||||
|
font-weight: 900;
|
||||||
|
font-size: 5.5rem;
|
||||||
|
line-height: 1;
|
||||||
|
color: hsl(var(--color-primary) / 0.07);
|
||||||
|
pointer-events: none;
|
||||||
|
user-select: none;
|
||||||
|
letter-spacing: -0.02em;
|
||||||
|
transition: color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.g1-card:hover .g1-watermark {
|
||||||
|
color: hsl(var(--color-primary) / 0.11);
|
||||||
|
}
|
||||||
|
|
||||||
/* Shadok illustrations */
|
/* Shadok illustrations */
|
||||||
.shadok {
|
.shadok {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|||||||
@@ -43,6 +43,11 @@ definePageMeta({
|
|||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const slug = route.params.slug as string
|
const slug = route.params.slug as string
|
||||||
|
|
||||||
|
// Scroll to top when navigating between chapters (component reused by Vue Router)
|
||||||
|
watch(() => slug, () => {
|
||||||
|
if (import.meta.client) window.scrollTo({ top: 0, behavior: 'instant' })
|
||||||
|
})
|
||||||
|
|
||||||
// Initialize guided mode
|
// Initialize guided mode
|
||||||
useGuidedMode()
|
useGuidedMode()
|
||||||
|
|
||||||
@@ -54,8 +59,11 @@ if (!chapter.value) {
|
|||||||
throw createError({ statusCode: 404, statusMessage: 'Chapitre non trouvé' })
|
throw createError({ statusCode: 404, statusMessage: 'Chapitre non trouvé' })
|
||||||
}
|
}
|
||||||
|
|
||||||
useHead({
|
useSeoPage({
|
||||||
title: chapter.value?.title,
|
title: chapter.value?.title ?? slug,
|
||||||
|
description: chapter.value?.description as string ?? `Chapitre « ${chapter.value?.title} » — Une économie du don, enfin concevable.`,
|
||||||
|
image: '/images/Couv-Economie-du-don.jpg',
|
||||||
|
type: 'article',
|
||||||
})
|
})
|
||||||
|
|
||||||
// Get adjacent chapters for navigation
|
// Get adjacent chapters for navigation
|
||||||
|
|||||||
@@ -424,8 +424,11 @@ definePageMeta({
|
|||||||
|
|
||||||
const { data: content } = await usePageContent('economique/modele-eco')
|
const { data: content } = await usePageContent('economique/modele-eco')
|
||||||
|
|
||||||
useHead({
|
useSeoPage({
|
||||||
title: content.value?.meta?.title ?? 'Table des matières',
|
title: content.value?.meta?.title ?? 'Une économie du don — Table des matières',
|
||||||
|
description: 'Onze chapitres pour explorer les fondements d\'une économie fondée sur le don, la mesure et la monnaie libre.',
|
||||||
|
image: '/images/Couv-Economie-du-don.jpg',
|
||||||
|
type: 'book',
|
||||||
})
|
})
|
||||||
|
|
||||||
const { data: chapters } = await useAsyncData('book-toc', () =>
|
const { data: chapters } = await useAsyncData('book-toc', () =>
|
||||||
|
|||||||
@@ -1,33 +1,79 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="section-padding">
|
<div class="section-padding">
|
||||||
<div class="container-content">
|
<div class="container-content">
|
||||||
<div class="mx-auto max-w-2xl">
|
|
||||||
|
<!-- Header -->
|
||||||
|
<div class="mx-auto max-w-2xl text-center mb-8">
|
||||||
<div class="section-icon mx-auto mb-6">
|
<div class="section-icon mx-auto mb-6">
|
||||||
<span v-if="content?.icon === 'g1'" class="g1-icon">Ğ1</span>
|
<span v-if="content?.icon === 'g1'" class="g1-icon">Ğ1</span>
|
||||||
<div v-else :class="`i-lucide-${content?.icon ?? 'coins'}`" class="h-12 w-12" />
|
<div v-else :class="`i-lucide-${content?.icon ?? 'coins'}`" class="h-12 w-12" />
|
||||||
</div>
|
</div>
|
||||||
|
<p class="mb-2 font-mono text-sm tracking-widest text-primary uppercase">
|
||||||
<p class="mb-2 font-mono text-sm tracking-widest text-primary uppercase text-center">
|
|
||||||
{{ content?.kicker }}
|
{{ content?.kicker }}
|
||||||
</p>
|
</p>
|
||||||
|
<h1 class="font-display text-3xl font-bold mb-4" style="color: hsl(var(--color-text))">
|
||||||
<h1 class="font-display text-3xl font-bold mb-4 text-center" style="color: hsl(var(--color-text))">
|
|
||||||
{{ content?.title }}
|
{{ content?.title }}
|
||||||
</h1>
|
</h1>
|
||||||
|
<p class="text-lg leading-relaxed" style="color: hsl(var(--color-text-muted))">
|
||||||
<p class="text-lg leading-relaxed mb-8 text-center" style="color: hsl(var(--color-text-muted))">
|
|
||||||
{{ content?.description }}
|
{{ content?.description }}
|
||||||
</p>
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Navigation prev / index / next -->
|
||||||
|
<nav class="ctx-nav mx-auto max-w-2xl mb-10">
|
||||||
|
<NuxtLink v-if="prevItem?.to" :to="prevItem.to" class="ctx-nav-btn ctx-nav-prev">
|
||||||
|
<div class="i-lucide-arrow-left h-4 w-4 shrink-0" />
|
||||||
|
<span>{{ prevItem.label }}</span>
|
||||||
|
</NuxtLink>
|
||||||
|
<div v-else class="ctx-nav-spacer" />
|
||||||
|
|
||||||
|
<NuxtLink to="/economique" class="ctx-nav-btn ctx-nav-index">
|
||||||
|
<div class="i-lucide-layout-grid h-4 w-4" />
|
||||||
|
<span>Économique</span>
|
||||||
|
</NuxtLink>
|
||||||
|
|
||||||
|
<NuxtLink v-if="nextItem?.to" :to="nextItem.to" class="ctx-nav-btn ctx-nav-next">
|
||||||
|
<span>{{ nextItem.label }}</span>
|
||||||
|
<div class="i-lucide-arrow-right h-4 w-4 shrink-0" />
|
||||||
|
</NuxtLink>
|
||||||
|
<div v-else class="ctx-nav-spacer" />
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<!-- Zone sections — relative pour ancrer le sidebar -->
|
||||||
|
<div class="sections-area">
|
||||||
|
|
||||||
|
<!-- Sidebar sommaire -->
|
||||||
|
<aside v-if="sommaire.length > 1" class="page-sidebar">
|
||||||
|
<nav class="sommaire-sidebar">
|
||||||
|
<p class="sommaire-sidebar-title">Sur cette page</p>
|
||||||
|
<ol class="sommaire-sidebar-list">
|
||||||
|
<li v-for="(entry, ei) in sommaire" :key="ei">
|
||||||
|
<a :href="`#${entry.id}`" class="sommaire-sidebar-link">
|
||||||
|
<span class="sommaire-n">{{ ei + 1 }}</span>
|
||||||
|
<span>{{ entry.title }}</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
</nav>
|
||||||
|
</aside>
|
||||||
|
|
||||||
<!-- Content -->
|
<!-- Content -->
|
||||||
<div v-if="content?.content" class="prose-block mb-8">
|
<div
|
||||||
|
v-if="content?.content"
|
||||||
|
id="section-content"
|
||||||
|
class="prose-block mb-8 max-w-2xl mx-auto section-anchor"
|
||||||
|
>
|
||||||
<p class="leading-relaxed whitespace-pre-line" style="color: hsl(var(--color-text-muted))">
|
<p class="leading-relaxed whitespace-pre-line" style="color: hsl(var(--color-text-muted))">
|
||||||
{{ content.content }}
|
{{ content.content }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- External links -->
|
<!-- External links -->
|
||||||
<div v-if="content?.links" class="flex flex-col gap-3 mb-10">
|
<div
|
||||||
|
v-if="content?.links"
|
||||||
|
id="section-links"
|
||||||
|
class="flex flex-col gap-3 mb-10 max-w-2xl mx-auto section-anchor"
|
||||||
|
>
|
||||||
<a
|
<a
|
||||||
v-for="link in content.links"
|
v-for="link in content.links"
|
||||||
:key="link.href"
|
:key="link.href"
|
||||||
@@ -44,22 +90,39 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="text-center">
|
</div><!-- /sections-area -->
|
||||||
<UiBaseButton variant="ghost" to="/economique">
|
|
||||||
<div class="i-lucide-arrow-left mr-2 h-4 w-4" />
|
|
||||||
Autonomie économique
|
|
||||||
</UiBaseButton>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const { data: content } = await usePageContent('economique/monnaie-libre')
|
const [{ data: content }, { data: homeData }] = await Promise.all([
|
||||||
|
usePageContent('economique/monnaie-libre'),
|
||||||
|
usePageContent('home'),
|
||||||
|
])
|
||||||
|
|
||||||
useHead({
|
// Sommaire
|
||||||
title: content.value?.meta?.title ?? 'Monnaie libre',
|
const sommaire = computed(() => {
|
||||||
|
const entries: { title: string; id: string }[] = []
|
||||||
|
if (content.value?.content) entries.push({ title: 'La théorie', id: 'section-content' })
|
||||||
|
if ((content.value?.links as unknown[])?.length) entries.push({ title: 'Ressources', id: 'section-links' })
|
||||||
|
return entries
|
||||||
|
})
|
||||||
|
|
||||||
|
// Prev / next dans la section économique
|
||||||
|
interface AxisItem { label: string; to?: string; icon?: string }
|
||||||
|
const economieItems = computed<AxisItem[]>(
|
||||||
|
() => (homeData.value as Record<string, unknown> | null)?.axes?.economie?.items as AxisItem[] ?? [],
|
||||||
|
)
|
||||||
|
const currentPath = '/economique/monnaie-libre'
|
||||||
|
const currentIdx = computed(() => economieItems.value.findIndex(i => i.to === currentPath))
|
||||||
|
const prevItem = computed(() => currentIdx.value > 0 ? economieItems.value[currentIdx.value - 1] : null)
|
||||||
|
const nextItem = computed(() => currentIdx.value < economieItems.value.length - 1 ? economieItems.value[currentIdx.value + 1] : null)
|
||||||
|
|
||||||
|
useSeoPage({
|
||||||
|
title: content.value?.meta?.title ?? 'Monnaie libre — Autonomie économique',
|
||||||
|
description: content.value?.description as string ?? 'La monnaie libre (June / G1) : comprendre la théorie relative de la monnaie et expérimenter une monnaie équitable.',
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -116,4 +179,149 @@ useHead({
|
|||||||
color: hsl(var(--color-primary));
|
color: hsl(var(--color-primary));
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.section-anchor {
|
||||||
|
scroll-margin-top: 5.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Navigation prev/next ── */
|
||||||
|
.ctx-nav {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr auto 1fr;
|
||||||
|
gap: 0.5rem;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ctx-nav-spacer { min-width: 0; }
|
||||||
|
|
||||||
|
.ctx-nav-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.45rem;
|
||||||
|
padding: 0.5rem 0.875rem;
|
||||||
|
border-radius: 20px;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: 500;
|
||||||
|
color: hsl(var(--color-text-muted));
|
||||||
|
background: hsl(var(--color-surface));
|
||||||
|
border: 1px solid hsl(var(--color-text) / 0.08);
|
||||||
|
transition: all 0.15s;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ctx-nav-btn:hover {
|
||||||
|
color: hsl(var(--color-primary));
|
||||||
|
border-color: hsl(var(--color-primary) / 0.25);
|
||||||
|
background: hsl(var(--color-primary) / 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ctx-nav-prev { justify-content: flex-start; }
|
||||||
|
.ctx-nav-next { justify-content: flex-end; }
|
||||||
|
|
||||||
|
.ctx-nav-prev span,
|
||||||
|
.ctx-nav-next span {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ctx-nav-index {
|
||||||
|
justify-content: center;
|
||||||
|
background: hsl(var(--color-primary) / 0.08);
|
||||||
|
border-color: hsl(var(--color-primary) / 0.15);
|
||||||
|
color: hsl(var(--color-primary));
|
||||||
|
font-size: 0.75rem;
|
||||||
|
padding: 0.45rem 0.75rem;
|
||||||
|
white-space: nowrap;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ctx-nav-index:hover { background: hsl(var(--color-primary) / 0.15); }
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.ctx-nav { grid-template-columns: auto 1fr auto; }
|
||||||
|
.ctx-nav-prev span, .ctx-nav-next span { display: none; }
|
||||||
|
.ctx-nav-prev, .ctx-nav-next { padding: 0.5rem; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Zone sections ── */
|
||||||
|
.sections-area {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Sidebar sommaire ── */
|
||||||
|
.page-sidebar {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
width: 10.5rem;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1300px) {
|
||||||
|
.page-sidebar { display: block; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.sommaire-sidebar {
|
||||||
|
position: sticky;
|
||||||
|
top: 5.5rem;
|
||||||
|
padding: 0.875rem;
|
||||||
|
border-radius: 14px;
|
||||||
|
background: hsl(var(--color-surface));
|
||||||
|
border: 1px solid hsl(var(--color-text) / 0.07);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sommaire-sidebar-title {
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: 0.65rem;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.1em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: hsl(var(--color-text-muted));
|
||||||
|
margin: 0 0 0.6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sommaire-sidebar-list {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sommaire-sidebar-link {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
gap: 0.45rem;
|
||||||
|
padding: 0.28rem 0.4rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: hsl(var(--color-text-muted));
|
||||||
|
line-height: 1.4;
|
||||||
|
transition: color 0.12s, background 0.12s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sommaire-sidebar-link:hover {
|
||||||
|
color: hsl(var(--color-primary));
|
||||||
|
background: hsl(var(--color-primary) / 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sommaire-n {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 1.2rem;
|
||||||
|
height: 1.2rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: hsl(var(--color-primary) / 0.1);
|
||||||
|
color: hsl(var(--color-primary));
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: 0.6rem;
|
||||||
|
font-weight: 700;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -44,8 +44,9 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const { data: content } = await usePageContent('economique/productions-collectives')
|
const { data: content } = await usePageContent('economique/productions-collectives')
|
||||||
|
|
||||||
useHead({
|
useSeoPage({
|
||||||
title: content.value?.meta?.title ?? 'Productions collectives',
|
title: content.value?.meta?.title ?? 'Productions collectives',
|
||||||
|
description: content.value?.description as string ?? 'Initiatives et productions collectives dans le cadre de l\'économie du don.',
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -166,8 +166,10 @@ definePageMeta({
|
|||||||
const { data: content } = await usePageContent('en-musique')
|
const { data: content } = await usePageContent('en-musique')
|
||||||
const { data: homeContent } = await usePageContent('home')
|
const { data: homeContent } = await usePageContent('home')
|
||||||
|
|
||||||
useHead({
|
useSeoPage({
|
||||||
title: content.value?.meta?.title ?? 'En musique',
|
title: content.value?.meta?.title ?? 'En musique — Le Librodrome',
|
||||||
|
description: 'Neuf chansons qui racontent le livre « Une économie du don ». Écoute libre, paroles et présentation musicale guidée.',
|
||||||
|
image: '/images/Couv-Economie-du-don.jpg',
|
||||||
})
|
})
|
||||||
|
|
||||||
const store = usePlayerStore()
|
const store = usePlayerStore()
|
||||||
|
|||||||
+279
-3
@@ -330,15 +330,106 @@
|
|||||||
</svg>
|
</svg>
|
||||||
|
|
||||||
<div class="container-content relative z-10 text-center">
|
<div class="container-content relative z-10 text-center">
|
||||||
|
<!-- Logo SVG vectoriel — inline pour currentColor -->
|
||||||
|
<div class="evt-logo-wrap mx-auto mb-6" aria-hidden="true">
|
||||||
|
<svg viewBox="0 0 64 80" fill="none" class="evt-logo">
|
||||||
|
<path d="M 28 6 C 44 2,52 12,47 14 C 40 18,16 18,12 28 C 8 38,16 50,28 54 C 38 56,52 60,52 68 C 52 76,42 78,34 74"
|
||||||
|
stroke="currentColor" stroke-width="16" stroke-linecap="round" stroke-linejoin="round" opacity="0.12"/>
|
||||||
|
<path d="M 28 6 C 44 2,52 12,47 14 C 40 18,16 18,12 28 C 8 38,16 50,28 54 C 38 56,52 60,52 68 C 52 76,42 78,34 74"
|
||||||
|
stroke="currentColor" stroke-width="9" stroke-linecap="round" stroke-linejoin="round" opacity="0.38"/>
|
||||||
|
<path d="M 28 6 C 44 2,52 12,47 14 C 40 18,16 18,12 28 C 8 38,16 50,28 54 C 38 56,52 60,52 68 C 52 76,42 78,34 74"
|
||||||
|
stroke="currentColor" stroke-width="5" stroke-linecap="round" stroke-linejoin="round" opacity="0.92"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
|
||||||
<p class="mb-3 font-mono text-sm tracking-widest text-accent uppercase">{{ content?.kicker }}</p>
|
<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))">
|
<h1 class="page-title font-display font-extrabold tracking-tight" style="color: hsl(var(--color-text))">
|
||||||
{{ content?.title }}
|
{{ content?.title }}
|
||||||
</h1>
|
</h1>
|
||||||
<p class="mt-4 text-lg" style="color: hsl(var(--color-text-muted))">
|
<p v-if="evtContent?.subtitle" class="mt-2 text-base italic" style="color: hsl(var(--color-text-muted))">
|
||||||
|
{{ evtContent.subtitle }}
|
||||||
|
</p>
|
||||||
|
<p class="mt-4 text-lg leading-relaxed max-w-xl mx-auto" style="color: hsl(var(--color-text-muted))">
|
||||||
{{ content?.description }}
|
{{ content?.description }}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<!-- Leitmotiv -->
|
||||||
|
<div v-if="evtContent?.leitmotiv" class="mt-8 flex flex-col items-center gap-3">
|
||||||
|
<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))">
|
||||||
|
{{ evtContent.tagline }}
|
||||||
|
</p>
|
||||||
|
<span v-if="evtContent?.gestation" class="evt-gestation-badge">
|
||||||
|
<div class="i-lucide-flask-conical h-3.5 w-3.5" />
|
||||||
|
En gestation
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- ══ Contenu événement ══ -->
|
||||||
|
<section v-if="evtContent?.axes || evtContent?.espaces || evtContent?.config" class="evt-content section-padding">
|
||||||
|
<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>
|
||||||
|
<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">
|
||||||
|
<div class="axe-icon">
|
||||||
|
<div :class="`i-lucide-${axe.icon} h-5 w-5`" />
|
||||||
|
</div>
|
||||||
|
<h3 class="font-display text-lg font-bold mb-3" style="color: hsl(var(--color-text))">
|
||||||
|
{{ axe.label }}
|
||||||
|
</h3>
|
||||||
|
<ul class="axe-list">
|
||||||
|
<li v-for="(item, j) in axe.items" :key="j">
|
||||||
|
<div class="i-lucide-arrow-right h-3.5 w-3.5 shrink-0 mt-0.5" style="color: hsl(var(--color-primary))" />
|
||||||
|
<span>{{ item }}</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Espaces permanents -->
|
||||||
|
<div v-if="evtContent?.espaces">
|
||||||
|
<h2 class="evt-section-title">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">
|
||||||
|
<div class="espace-icon">
|
||||||
|
<div :class="`i-lucide-${esp.icon} h-4 w-4`" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p class="font-semibold text-sm" style="color: hsl(var(--color-text))">{{ esp.label }}</p>
|
||||||
|
<p class="text-xs mt-0.5 leading-relaxed" style="color: hsl(var(--color-text-muted))">{{ esp.desc }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Config -->
|
||||||
|
<div v-if="evtContent?.config">
|
||||||
|
<h2 class="evt-section-title">Format & lieu</h2>
|
||||||
|
<div class="config-grid">
|
||||||
|
<div v-for="(cfg, i) in evtContent.config" :key="i" class="config-card">
|
||||||
|
<div class="config-icon">
|
||||||
|
<div :class="`i-lucide-${cfg.icon} h-4 w-4`" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p class="font-semibold text-sm" style="color: hsl(var(--color-text))">{{ cfg.label }}</p>
|
||||||
|
<p class="text-xs mt-0.5" style="color: hsl(var(--color-text-muted))">{{ cfg.detail }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
@@ -347,9 +438,11 @@ definePageMeta({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const { data: content } = await usePageContent('evenement')
|
const { data: content } = await usePageContent('evenement')
|
||||||
|
const evtContent = computed(() => content.value as Record<string, any> | null)
|
||||||
|
|
||||||
useHead({
|
useSeoPage({
|
||||||
title: content.value?.meta?.title ?? 'Évènement',
|
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.',
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -555,4 +648,187 @@ useHead({
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── Logo SVG ── */
|
||||||
|
.evt-logo-wrap {
|
||||||
|
width: 4.5rem;
|
||||||
|
height: 4.5rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: hsl(var(--color-primary));
|
||||||
|
filter: drop-shadow(0 0 14px hsl(var(--color-primary) / 0.25));
|
||||||
|
}
|
||||||
|
|
||||||
|
.evt-logo {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Badge gestation hero ── */
|
||||||
|
.evt-gestation-badge {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.35rem;
|
||||||
|
padding: 0.25rem 0.75rem;
|
||||||
|
border-radius: 9999px;
|
||||||
|
background: hsl(var(--color-accent) / 0.12);
|
||||||
|
border: 1px solid hsl(var(--color-accent) / 0.25);
|
||||||
|
color: hsl(var(--color-accent));
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Section contenus événement ── */
|
||||||
|
.evt-content {
|
||||||
|
background: hsl(var(--color-bg));
|
||||||
|
}
|
||||||
|
|
||||||
|
.evt-section-title {
|
||||||
|
font-family: var(--font-display);
|
||||||
|
font-size: clamp(1.25rem, 3vw, 1.75rem);
|
||||||
|
font-weight: 800;
|
||||||
|
color: hsl(var(--color-text));
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.evt-section-sub {
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: 0.8rem;
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: hsl(var(--color-primary));
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Axes ── */
|
||||||
|
.axes-grid {
|
||||||
|
display: grid;
|
||||||
|
gap: 1rem;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 640px) {
|
||||||
|
.axes-grid { grid-template-columns: repeat(3, 1fr); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.axe-card {
|
||||||
|
padding: 1.5rem;
|
||||||
|
border-radius: 16px;
|
||||||
|
background: hsl(var(--color-surface));
|
||||||
|
border: 1px solid hsl(var(--color-text) / 0.07);
|
||||||
|
transition: border-color 0.2s, box-shadow 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.axe-card:hover {
|
||||||
|
border-color: hsl(var(--color-primary) / 0.25);
|
||||||
|
box-shadow: 0 4px 24px hsl(var(--color-primary) / 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.axe-icon {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 2.5rem;
|
||||||
|
height: 2.5rem;
|
||||||
|
border-radius: 0.625rem;
|
||||||
|
background: hsl(var(--color-primary) / 0.1);
|
||||||
|
color: hsl(var(--color-primary));
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.axe-list {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.axe-list li {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 0.5rem;
|
||||||
|
font-size: 0.82rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
color: hsl(var(--color-text-muted));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Espaces ── */
|
||||||
|
.espaces-grid {
|
||||||
|
display: grid;
|
||||||
|
gap: 0.75rem;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 640px) {
|
||||||
|
.espaces-grid { grid-template-columns: repeat(2, 1fr); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1024px) {
|
||||||
|
.espaces-grid { grid-template-columns: repeat(3, 1fr); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.espace-card {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 0.875rem;
|
||||||
|
padding: 1rem 1.25rem;
|
||||||
|
border-radius: 12px;
|
||||||
|
background: hsl(var(--color-surface));
|
||||||
|
border: 1px solid hsl(var(--color-text) / 0.06);
|
||||||
|
transition: border-color 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.espace-card:hover {
|
||||||
|
border-color: hsl(var(--color-accent) / 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.espace-icon {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 2rem;
|
||||||
|
height: 2rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
background: hsl(var(--color-accent) / 0.1);
|
||||||
|
color: hsl(var(--color-accent));
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Config ── */
|
||||||
|
.config-grid {
|
||||||
|
display: grid;
|
||||||
|
gap: 0.75rem;
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 640px) {
|
||||||
|
.config-grid { grid-template-columns: repeat(4, 1fr); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-card {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 0.75rem;
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 12px;
|
||||||
|
background: hsl(var(--color-surface));
|
||||||
|
border: 1px solid hsl(var(--color-text) / 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-icon {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 1.875rem;
|
||||||
|
height: 1.875rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
background: hsl(var(--color-primary) / 0.1);
|
||||||
|
color: hsl(var(--color-primary));
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -104,8 +104,9 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const { data: content } = await usePageContent('gratewizard')
|
const { data: content } = await usePageContent('gratewizard')
|
||||||
|
|
||||||
useHead({
|
useSeoPage({
|
||||||
title: content.value?.meta?.title ?? 'grateWizard \u2014 Coefficients relatifs',
|
title: content.value?.meta?.title ?? 'grateWizard — Coefficients relatifs',
|
||||||
|
description: 'Calculateur de coefficients relatifs pour l\'économie du don et la monnaie libre (DU/June).',
|
||||||
})
|
})
|
||||||
|
|
||||||
const { url, launch } = useGrateWizard()
|
const { url, launch } = useGrateWizard()
|
||||||
|
|||||||
+3
-2
@@ -9,8 +9,9 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
useHead({
|
useSeoPage({
|
||||||
title: 'Accueil',
|
title: 'Le Librodrome — autonomie numérique, économique, citoyenne',
|
||||||
|
description: 'Construire une autonomie collective à l\'échelle des bassins de vie. Un livre, des chansons et des outils pour l\'émancipation.',
|
||||||
})
|
})
|
||||||
|
|
||||||
const showBookPlayer = ref(false)
|
const showBookPlayer = ref(false)
|
||||||
|
|||||||
@@ -41,8 +41,9 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
useHead({
|
useSeoPage({
|
||||||
title: 'Messages',
|
title: 'Messages — Le Librodrome',
|
||||||
|
description: 'Questions, retours et réactions des lecteurs et visiteurs du Librodrome.',
|
||||||
})
|
})
|
||||||
|
|
||||||
const { data: messages } = await useFetch('/api/messages')
|
const { data: messages } = await useFetch('/api/messages')
|
||||||
|
|||||||
@@ -599,6 +599,13 @@
|
|||||||
|
|
||||||
</div><!-- /sections-area -->
|
</div><!-- /sections-area -->
|
||||||
|
|
||||||
|
<div v-if="slug === 'logiciel-libre'" class="text-center mt-6">
|
||||||
|
<UiBaseButton href="https://axiom-team.fr/dons" target="_blank">
|
||||||
|
<div class="i-lucide-external-link mr-2 h-4 w-4" />
|
||||||
|
Voir la v1 bridée (premier flux)
|
||||||
|
</UiBaseButton>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div v-if="slug === 'tarifs-eau'" class="text-center mt-6">
|
<div v-if="slug === 'tarifs-eau'" class="text-center mt-6">
|
||||||
<UiBaseButton :href="sejeteral0Url" target="_blank">
|
<UiBaseButton :href="sejeteral0Url" target="_blank">
|
||||||
<div class="i-lucide-external-link mr-2 h-4 w-4" />
|
<div class="i-lucide-external-link mr-2 h-4 w-4" />
|
||||||
@@ -640,8 +647,9 @@ const currentIdx = computed(() => sectionItems.value.findIndex(i => i.to === cur
|
|||||||
const prevItem = computed(() => currentIdx.value > 0 ? sectionItems.value[currentIdx.value - 1] : null)
|
const prevItem = computed(() => currentIdx.value > 0 ? sectionItems.value[currentIdx.value - 1] : null)
|
||||||
const nextItem = computed(() => currentIdx.value < sectionItems.value.length - 1 ? sectionItems.value[currentIdx.value + 1] : null)
|
const nextItem = computed(() => currentIdx.value < sectionItems.value.length - 1 ? sectionItems.value[currentIdx.value + 1] : null)
|
||||||
|
|
||||||
useHead({
|
useSeoPage({
|
||||||
title: content.value?.meta?.title ?? slug,
|
title: content.value?.meta?.title ?? slug,
|
||||||
|
description: content.value?.description as string ?? undefined,
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
+100
-30
@@ -320,40 +320,52 @@
|
|||||||
:key="pillar.id"
|
:key="pillar.id"
|
||||||
class="pillar-card"
|
class="pillar-card"
|
||||||
>
|
>
|
||||||
<div class="pillar-header">
|
<!-- Overlay lien — toute la carte cliquable -->
|
||||||
<div class="pillar-icon">
|
<NuxtLink v-if="pillar.to" :to="pillar.to" class="pillar-overlay" :aria-label="pillar.label" tabindex="-1" />
|
||||||
<div :class="`i-lucide-${pillar.icon}`" class="h-5 w-5" />
|
|
||||||
</div>
|
|
||||||
<h2 class="font-display text-xl font-bold" style="color: hsl(var(--color-text))">
|
|
||||||
{{ pillar.label }}
|
|
||||||
</h2>
|
|
||||||
<span v-if="pillar.gestation" class="gestation-badge">
|
|
||||||
<div class="i-lucide-flask-conical h-3 w-3" />
|
|
||||||
En gestation
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p class="leading-relaxed whitespace-pre-line mt-3" style="color: hsl(var(--color-text-muted))">{{ pillar.text }}</p>
|
<!-- Corps de la carte -->
|
||||||
|
<div class="pillar-body">
|
||||||
<!-- Project card -->
|
<div class="pillar-header">
|
||||||
<div v-if="pillar.project" class="project-card mt-4">
|
<div class="pillar-icon">
|
||||||
<div class="project-icon">
|
<div :class="`i-lucide-${pillar.icon}`" class="h-5 w-5" />
|
||||||
<div class="i-lucide-rocket h-4 w-4" />
|
</div>
|
||||||
|
<h2 class="font-display text-xl font-bold" style="color: hsl(var(--color-text))">
|
||||||
|
{{ pillar.label }}
|
||||||
|
</h2>
|
||||||
|
<span v-if="pillar.gestation" class="gestation-badge">
|
||||||
|
<div class="i-lucide-flask-conical h-3 w-3" />
|
||||||
|
En gestation
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
<span class="font-display font-semibold text-sm" style="color: hsl(var(--color-text))">{{ pillar.project.name }}</span>
|
<p class="leading-relaxed whitespace-pre-line mt-3" style="color: hsl(var(--color-text-muted))">{{ pillar.text }}</p>
|
||||||
<span class="text-sm ml-2" style="color: hsl(var(--color-text-muted))">{{ pillar.project.text }}</span>
|
|
||||||
|
<!-- Project card -->
|
||||||
|
<div v-if="pillar.project" class="project-card mt-4">
|
||||||
|
<div class="project-icon">
|
||||||
|
<div class="i-lucide-rocket h-4 w-4" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span class="font-display font-semibold text-sm" style="color: hsl(var(--color-text))">{{ pillar.project.name }}</span>
|
||||||
|
<span class="text-sm ml-2" style="color: hsl(var(--color-text-muted))">{{ pillar.project.text }}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="pillar.to" class="mt-4">
|
<!-- Zone actions — z-index au-dessus de l'overlay -->
|
||||||
<NuxtLink
|
<div v-if="(pillar as any).actions?.length" class="pillar-actions">
|
||||||
:to="pillar.to"
|
<a
|
||||||
class="inline-flex items-center gap-1 text-sm text-primary hover:text-primary/80 transition-colors"
|
v-for="action in (pillar as any).actions"
|
||||||
|
:key="action.label"
|
||||||
|
:href="action.href"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
class="pillar-action-btn"
|
||||||
|
:class="{ 'pillar-action-btn--highlight': action.highlight }"
|
||||||
>
|
>
|
||||||
En savoir plus
|
<div :class="`i-lucide-${action.icon}`" class="h-3.5 w-3.5" />
|
||||||
<div class="i-lucide-arrow-right h-3.5 w-3.5" />
|
{{ action.label }}
|
||||||
</NuxtLink>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -368,8 +380,9 @@ definePageMeta({
|
|||||||
|
|
||||||
const { data: content } = await usePageContent('numerique')
|
const { data: content } = await usePageContent('numerique')
|
||||||
|
|
||||||
useHead({
|
useSeoPage({
|
||||||
title: content.value?.meta?.title ?? 'Autonomie numérique',
|
title: content.value?.meta?.title ?? 'Autonomie numérique',
|
||||||
|
description: (content.value as Record<string, unknown> | null)?.description as string ?? 'Logiciel libre, authentification Web of Trust, cloud souverain — reprendre le contrôle de ses outils numériques.',
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -379,17 +392,74 @@ useHead({
|
|||||||
}
|
}
|
||||||
|
|
||||||
.pillar-card {
|
.pillar-card {
|
||||||
padding: 1.5rem;
|
|
||||||
border-radius: 0.75rem;
|
border-radius: 0.75rem;
|
||||||
border: 1px solid hsl(var(--color-text) / 0.08);
|
border: 1px solid hsl(var(--color-text) / 0.08);
|
||||||
background: hsl(var(--color-surface));
|
background: hsl(var(--color-surface));
|
||||||
transition: border-color 0.2s;
|
transition: border-color 0.2s;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pillar-card:hover {
|
.pillar-card:hover {
|
||||||
border-color: hsl(var(--color-primary) / 0.2);
|
border-color: hsl(var(--color-primary) / 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pillar-overlay {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 1;
|
||||||
|
border-radius: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pillar-body {
|
||||||
|
padding: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pillar-actions {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.375rem;
|
||||||
|
padding: 0.75rem 1.5rem;
|
||||||
|
border-top: 1px solid hsl(var(--color-text) / 0.06);
|
||||||
|
background: hsl(var(--color-bg) / 0.4);
|
||||||
|
border-bottom-left-radius: 0.75rem;
|
||||||
|
border-bottom-right-radius: 0.75rem;
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pillar-action-btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.375rem;
|
||||||
|
padding: 0.375rem 0.75rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: 500;
|
||||||
|
color: hsl(var(--color-text) / 0.7);
|
||||||
|
background: hsl(var(--color-text) / 0.05);
|
||||||
|
border: 1px solid hsl(var(--color-text) / 0.1);
|
||||||
|
transition: all 0.2s;
|
||||||
|
text-decoration: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pillar-action-btn:hover {
|
||||||
|
color: hsl(var(--color-text));
|
||||||
|
background: hsl(var(--color-primary) / 0.12);
|
||||||
|
border-color: hsl(var(--color-primary) / 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pillar-action-btn--highlight {
|
||||||
|
color: hsl(var(--color-primary));
|
||||||
|
background: hsl(var(--color-primary) / 0.12);
|
||||||
|
border-color: hsl(var(--color-primary) / 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pillar-action-btn--highlight:hover {
|
||||||
|
background: hsl(var(--color-primary) / 0.2);
|
||||||
|
border-color: hsl(var(--color-primary) / 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
.pillar-header {
|
.pillar-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import type { RouterConfig } from '@nuxt/schema'
|
||||||
|
|
||||||
|
export default <RouterConfig>{
|
||||||
|
scrollBehavior(to, _from, savedPosition) {
|
||||||
|
if (savedPosition) return savedPosition
|
||||||
|
if (to.hash) return { el: to.hash, behavior: 'smooth' }
|
||||||
|
return { top: 0, behavior: 'instant' }
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
## Umami analytics — déployer avec le compose principal
|
|
||||||
## Usage : docker compose -f docker-compose.yml -f docker-compose.umami.yml up -d
|
|
||||||
##
|
|
||||||
## Variables à définir dans .env :
|
|
||||||
## UMAMI_DB_PASSWORD — mot de passe PostgreSQL Umami (générer avec openssl rand -hex 32)
|
|
||||||
## UMAMI_APP_SECRET — secret applicatif (générer avec openssl rand -hex 32)
|
|
||||||
## UMAMI_DOMAIN — ex: stats.librodrome.org
|
|
||||||
|
|
||||||
name: librodrome
|
|
||||||
|
|
||||||
services:
|
|
||||||
umami:
|
|
||||||
image: ghcr.io/umami-software/umami:postgresql-latest
|
|
||||||
environment:
|
|
||||||
DATABASE_URL: postgresql://umami:${UMAMI_DB_PASSWORD}@umami-db:5432/umami
|
|
||||||
DATABASE_TYPE: postgresql
|
|
||||||
APP_SECRET: ${UMAMI_APP_SECRET}
|
|
||||||
depends_on:
|
|
||||||
umami-db:
|
|
||||||
condition: service_healthy
|
|
||||||
restart: always
|
|
||||||
labels:
|
|
||||||
- "traefik.enable=true"
|
|
||||||
- "traefik.http.routers.umami.rule=Host(`${UMAMI_DOMAIN:-stats.librodrome.org}`)"
|
|
||||||
- "traefik.http.routers.umami.entrypoints=websecure"
|
|
||||||
- "traefik.http.routers.umami.tls.certresolver=letsencrypt"
|
|
||||||
- "traefik.http.services.umami.loadbalancer.server.port=3000"
|
|
||||||
networks:
|
|
||||||
- default
|
|
||||||
- traefik
|
|
||||||
|
|
||||||
umami-db:
|
|
||||||
image: postgres:15-alpine
|
|
||||||
environment:
|
|
||||||
POSTGRES_DB: umami
|
|
||||||
POSTGRES_USER: umami
|
|
||||||
POSTGRES_PASSWORD: ${UMAMI_DB_PASSWORD}
|
|
||||||
volumes:
|
|
||||||
- umami-db-data:/var/lib/postgresql/data
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD-SHELL", "pg_isready -U umami"]
|
|
||||||
interval: 5s
|
|
||||||
timeout: 5s
|
|
||||||
retries: 10
|
|
||||||
restart: always
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
umami-db-data:
|
|
||||||
|
|
||||||
networks:
|
|
||||||
traefik:
|
|
||||||
external: true
|
|
||||||
@@ -11,10 +11,13 @@ services:
|
|||||||
NUXT_ADMIN_PASSWORD: ${NUXT_ADMIN_PASSWORD}
|
NUXT_ADMIN_PASSWORD: ${NUXT_ADMIN_PASSWORD}
|
||||||
NUXT_ADMIN_SECRET: ${NUXT_ADMIN_SECRET}
|
NUXT_ADMIN_SECRET: ${NUXT_ADMIN_SECRET}
|
||||||
ADMIN_GIT_SYNC: ${ADMIN_GIT_SYNC:-false}
|
ADMIN_GIT_SYNC: ${ADMIN_GIT_SYNC:-false}
|
||||||
|
NUXT_PUBLIC_UMAMI_URL: ${NUXT_PUBLIC_UMAMI_URL:-}
|
||||||
|
NUXT_PUBLIC_UMAMI_WEBSITE_ID: ${NUXT_PUBLIC_UMAMI_WEBSITE_ID:-}
|
||||||
|
NUXT_UMAMI_API_KEY: ${NUXT_UMAMI_API_KEY:-}
|
||||||
ports:
|
ports:
|
||||||
- 3000
|
- 3000
|
||||||
volumes:
|
volumes:
|
||||||
- ./data:/src/data
|
- ../data:/src/data
|
||||||
- ./public:/src/public
|
- ./public:/src/public
|
||||||
restart: always
|
restart: always
|
||||||
labels:
|
labels:
|
||||||
|
|||||||
@@ -14,8 +14,56 @@ export default defineNuxtConfig({
|
|||||||
'@unocss/nuxt',
|
'@unocss/nuxt',
|
||||||
'@vueuse/nuxt',
|
'@vueuse/nuxt',
|
||||||
'@nuxt/image',
|
'@nuxt/image',
|
||||||
|
'@nuxtjs/sitemap',
|
||||||
|
'nuxt-umami',
|
||||||
],
|
],
|
||||||
|
|
||||||
|
umami: {
|
||||||
|
host: 'https://stats.open.us.org',
|
||||||
|
id: '95ff616d-9ce1-47d9-bca2-f6ddc344a99a',
|
||||||
|
autoTrack: true,
|
||||||
|
ignoreLocalhost: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
site: {
|
||||||
|
url: 'https://librodrome.org',
|
||||||
|
name: 'Le Librodrome',
|
||||||
|
},
|
||||||
|
|
||||||
|
sitemap: {
|
||||||
|
strictNuxtContentPaths: false,
|
||||||
|
sources: ['/api/__sitemap__/urls'],
|
||||||
|
urls: [
|
||||||
|
{ loc: '/', changefreq: 'weekly', priority: 1.0 },
|
||||||
|
{ loc: '/economique', changefreq: 'monthly', priority: 0.9 },
|
||||||
|
{ loc: '/economique/modele-eco', changefreq: 'monthly', priority: 0.9 },
|
||||||
|
{ loc: '/economique/monnaie-libre', changefreq: 'monthly', priority: 0.8 },
|
||||||
|
{ loc: '/economique/productions-collectives', changefreq: 'monthly', priority: 0.7 },
|
||||||
|
{ loc: '/economique/commande', changefreq: 'monthly', priority: 0.8 },
|
||||||
|
{ loc: '/numerique', changefreq: 'monthly', priority: 0.8 },
|
||||||
|
{ loc: '/numerique/logiciel-libre', changefreq: 'monthly', priority: 0.8 },
|
||||||
|
{ loc: '/numerique/authentification-wot', changefreq: 'monthly', priority: 0.7 },
|
||||||
|
{ loc: '/numerique/cloud-libre', changefreq: 'monthly', priority: 0.7 },
|
||||||
|
{ loc: '/citoyenne', changefreq: 'monthly', priority: 0.8 },
|
||||||
|
{ loc: '/citoyenne/decision', changefreq: 'monthly', priority: 0.8 },
|
||||||
|
{ loc: '/citoyenne/tarifs-eau', changefreq: 'monthly', priority: 0.7 },
|
||||||
|
{ loc: '/en-musique', changefreq: 'monthly', priority: 0.8 },
|
||||||
|
{ loc: '/a-propos', changefreq: 'yearly', priority: 0.5 },
|
||||||
|
// Chapitres modele-eco
|
||||||
|
{ loc: '/economique/modele-eco/01-introduction', changefreq: 'monthly', priority: 0.7 },
|
||||||
|
{ loc: '/economique/modele-eco/02-don', changefreq: 'monthly', priority: 0.7 },
|
||||||
|
{ loc: '/economique/modele-eco/03-mesure', changefreq: 'monthly', priority: 0.7 },
|
||||||
|
{ loc: '/economique/modele-eco/04-monnaie', changefreq: 'monthly', priority: 0.7 },
|
||||||
|
{ loc: '/economique/modele-eco/05-trm', changefreq: 'monthly', priority: 0.7 },
|
||||||
|
{ loc: '/economique/modele-eco/06-produire', changefreq: 'monthly', priority: 0.7 },
|
||||||
|
{ loc: '/economique/modele-eco/07-echanger', changefreq: 'monthly', priority: 0.7 },
|
||||||
|
{ loc: '/economique/modele-eco/08-institution', changefreq: 'monthly', priority: 0.7 },
|
||||||
|
{ loc: '/economique/modele-eco/09-greffes', changefreq: 'monthly', priority: 0.7 },
|
||||||
|
{ loc: '/economique/modele-eco/10-maintenant', changefreq: 'monthly', priority: 0.7 },
|
||||||
|
{ loc: '/economique/modele-eco/11-annexes', changefreq: 'monthly', priority: 0.6 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
unocss: {
|
unocss: {
|
||||||
safelist: [
|
safelist: [
|
||||||
// Axis block icons (dynamic from YAML)
|
// Axis block icons (dynamic from YAML)
|
||||||
|
|||||||
@@ -12,11 +12,13 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nuxt/content": "^3.11.2",
|
"@nuxt/content": "^3.11.2",
|
||||||
"@nuxt/image": "^2.0.0",
|
"@nuxt/image": "^2.0.0",
|
||||||
|
"@nuxtjs/sitemap": "^8.0.12",
|
||||||
"@pinia/nuxt": "^0.11.3",
|
"@pinia/nuxt": "^0.11.3",
|
||||||
"@unocss/nuxt": "^66.6.0",
|
"@unocss/nuxt": "^66.6.0",
|
||||||
"@vueuse/nuxt": "^14.2.1",
|
"@vueuse/nuxt": "^14.2.1",
|
||||||
"better-sqlite3": "^12.6.2",
|
"better-sqlite3": "^12.6.2",
|
||||||
"nuxt": "^4.3.1",
|
"nuxt": "^4.3.1",
|
||||||
|
"nuxt-umami": "^3.2.1",
|
||||||
"pdfjs-dist": "^5.4.624",
|
"pdfjs-dist": "^5.4.624",
|
||||||
"vue": "^3.5.28",
|
"vue": "^3.5.28",
|
||||||
"vue-router": "^4.6.4",
|
"vue-router": "^4.6.4",
|
||||||
|
|||||||
Generated
+330
-4
@@ -14,6 +14,9 @@ importers:
|
|||||||
'@nuxt/image':
|
'@nuxt/image':
|
||||||
specifier: ^2.0.0
|
specifier: ^2.0.0
|
||||||
version: 2.0.0(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.3)(magicast@0.5.2)
|
version: 2.0.0(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.3)(magicast@0.5.2)
|
||||||
|
'@nuxtjs/sitemap':
|
||||||
|
specifier: ^8.0.12
|
||||||
|
version: 8.0.12(@nuxt/schema@4.3.1)(magicast@0.5.2)(nuxt@4.3.1(@parcel/watcher@2.5.6)(@types/node@25.2.3)(@vue/compiler-sfc@3.5.28)(better-sqlite3@12.6.2)(cac@6.7.14)(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.3)(magicast@0.5.2)(rollup@4.57.1)(terser@5.46.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2))(yaml@2.8.2))(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2))(vue@3.5.28(typescript@5.9.3))(zod@3.25.76)
|
||||||
'@pinia/nuxt':
|
'@pinia/nuxt':
|
||||||
specifier: ^0.11.3
|
specifier: ^0.11.3
|
||||||
version: 0.11.3(magicast@0.5.2)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.28(typescript@5.9.3)))
|
version: 0.11.3(magicast@0.5.2)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.28(typescript@5.9.3)))
|
||||||
@@ -29,6 +32,9 @@ importers:
|
|||||||
nuxt:
|
nuxt:
|
||||||
specifier: ^4.3.1
|
specifier: ^4.3.1
|
||||||
version: 4.3.1(@parcel/watcher@2.5.6)(@types/node@25.2.3)(@vue/compiler-sfc@3.5.28)(better-sqlite3@12.6.2)(cac@6.7.14)(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.3)(magicast@0.5.2)(rollup@4.57.1)(terser@5.46.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2))(yaml@2.8.2)
|
version: 4.3.1(@parcel/watcher@2.5.6)(@types/node@25.2.3)(@vue/compiler-sfc@3.5.28)(better-sqlite3@12.6.2)(cac@6.7.14)(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.3)(magicast@0.5.2)(rollup@4.57.1)(terser@5.46.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2))(yaml@2.8.2)
|
||||||
|
nuxt-umami:
|
||||||
|
specifier: ^3.2.1
|
||||||
|
version: 3.2.1(magicast@0.5.2)
|
||||||
pdfjs-dist:
|
pdfjs-dist:
|
||||||
specifier: ^5.4.624
|
specifier: ^5.4.624
|
||||||
version: 5.4.624
|
version: 5.4.624
|
||||||
@@ -210,9 +216,15 @@ packages:
|
|||||||
'@clack/core@1.0.1':
|
'@clack/core@1.0.1':
|
||||||
resolution: {integrity: sha512-WKeyK3NOBwDOzagPR5H08rFk9D/WuN705yEbuZvKqlkmoLM2woKtXb10OO2k1NoSU4SFG947i2/SCYh+2u5e4g==}
|
resolution: {integrity: sha512-WKeyK3NOBwDOzagPR5H08rFk9D/WuN705yEbuZvKqlkmoLM2woKtXb10OO2k1NoSU4SFG947i2/SCYh+2u5e4g==}
|
||||||
|
|
||||||
|
'@clack/core@1.2.0':
|
||||||
|
resolution: {integrity: sha512-qfxof/3T3t9DPU/Rj3OmcFyZInceqj/NVtO9rwIuJqCUgh32gwPjpFQQp/ben07qKlhpwq7GzfWpST4qdJ5Drg==}
|
||||||
|
|
||||||
'@clack/prompts@1.0.1':
|
'@clack/prompts@1.0.1':
|
||||||
resolution: {integrity: sha512-/42G73JkuYdyWZ6m8d/CJtBrGl1Hegyc7Fy78m5Ob+jF85TOUmLR5XLce/U3LxYAw0kJ8CT5aI99RIvPHcGp/Q==}
|
resolution: {integrity: sha512-/42G73JkuYdyWZ6m8d/CJtBrGl1Hegyc7Fy78m5Ob+jF85TOUmLR5XLce/U3LxYAw0kJ8CT5aI99RIvPHcGp/Q==}
|
||||||
|
|
||||||
|
'@clack/prompts@1.2.0':
|
||||||
|
resolution: {integrity: sha512-4jmztR9fMqPMjz6H/UZXj0zEmE43ha1euENwkckKKel4XpSfokExPo5AiVStdHSAlHekz4d0CA/r45Ok1E4D3w==}
|
||||||
|
|
||||||
'@cloudflare/kv-asset-handler@0.4.2':
|
'@cloudflare/kv-asset-handler@0.4.2':
|
||||||
resolution: {integrity: sha512-SIOD2DxrRRwQ+jgzlXCqoEFiKOFqaPjhnNTGKXSRLvp1HiOvapLaFG2kEr9dYQTYe8rKrd9uvDUzmAITeNyaHQ==}
|
resolution: {integrity: sha512-SIOD2DxrRRwQ+jgzlXCqoEFiKOFqaPjhnNTGKXSRLvp1HiOvapLaFG2kEr9dYQTYe8rKrd9uvDUzmAITeNyaHQ==}
|
||||||
engines: {node: '>=18.0.0'}
|
engines: {node: '>=18.0.0'}
|
||||||
@@ -733,6 +745,11 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
vite: '>=6.0'
|
vite: '>=6.0'
|
||||||
|
|
||||||
|
'@nuxt/devtools-kit@4.0.0-alpha.3':
|
||||||
|
resolution: {integrity: sha512-ymp4jqS3hFfwRw8uDkv8cpu4kWvhQrX+S4jnA/oOc76s4AXf2HCZZJgrncKxh+txqi1NJj8nsQNBbaqRAo3g4w==}
|
||||||
|
peerDependencies:
|
||||||
|
vite: '>=6.0'
|
||||||
|
|
||||||
'@nuxt/devtools-wizard@3.2.1':
|
'@nuxt/devtools-wizard@3.2.1':
|
||||||
resolution: {integrity: sha512-NKUg54cLQSDeBWaNwAPkVIpwXtd1CrxLr0inl9Z7OdLwsidqMrncNObO6K3HgV0PEdAcqY4IwE2hkON2dlRLYw==}
|
resolution: {integrity: sha512-NKUg54cLQSDeBWaNwAPkVIpwXtd1CrxLr0inl9Z7OdLwsidqMrncNObO6K3HgV0PEdAcqY4IwE2hkON2dlRLYw==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
@@ -751,10 +768,18 @@ packages:
|
|||||||
resolution: {integrity: sha512-otHi6gAoYXKLrp8m27ZjX1PjxOPaltQ4OiUs/BhkW995mF/vXf8SWQTw68fww+Uric0v+XgoVrP9icDi+yT6zw==}
|
resolution: {integrity: sha512-otHi6gAoYXKLrp8m27ZjX1PjxOPaltQ4OiUs/BhkW995mF/vXf8SWQTw68fww+Uric0v+XgoVrP9icDi+yT6zw==}
|
||||||
engines: {node: '>=18.20.6'}
|
engines: {node: '>=18.20.6'}
|
||||||
|
|
||||||
|
'@nuxt/kit@3.21.2':
|
||||||
|
resolution: {integrity: sha512-Bd6m6mrDrqpBEbX+g0rc66/ALd1sxlgdx5nfK9MAYO0yKLTOSK7McSYz1KcOYn3LQFCXOWfvXwaqih/b+REI1g==}
|
||||||
|
engines: {node: '>=18.12.0'}
|
||||||
|
|
||||||
'@nuxt/kit@4.3.1':
|
'@nuxt/kit@4.3.1':
|
||||||
resolution: {integrity: sha512-UjBFt72dnpc+83BV3OIbCT0YHLevJtgJCHpxMX0YRKWLDhhbcDdUse87GtsQBrjvOzK7WUNUYLDS/hQLYev5rA==}
|
resolution: {integrity: sha512-UjBFt72dnpc+83BV3OIbCT0YHLevJtgJCHpxMX0YRKWLDhhbcDdUse87GtsQBrjvOzK7WUNUYLDS/hQLYev5rA==}
|
||||||
engines: {node: '>=18.12.0'}
|
engines: {node: '>=18.12.0'}
|
||||||
|
|
||||||
|
'@nuxt/kit@4.4.2':
|
||||||
|
resolution: {integrity: sha512-5+IPRNX2CjkBhuWUwz0hBuLqiaJPRoKzQ+SvcdrQDbAyE+VDeFt74VpSFr5/R0ujrK4b+XnSHUJWdS72w6hsog==}
|
||||||
|
engines: {node: '>=18.12.0'}
|
||||||
|
|
||||||
'@nuxt/nitro-server@4.3.1':
|
'@nuxt/nitro-server@4.3.1':
|
||||||
resolution: {integrity: sha512-4aNiM69Re02gI1ywnDND0m6QdVKXhWzDdtvl/16veytdHZj3FSq57ZCwOClNJ7HQkEMqXgS+bi6S2HmJX+et+g==}
|
resolution: {integrity: sha512-4aNiM69Re02gI1ywnDND0m6QdVKXhWzDdtvl/16veytdHZj3FSq57ZCwOClNJ7HQkEMqXgS+bi6S2HmJX+et+g==}
|
||||||
engines: {node: ^20.19.0 || >=22.12.0}
|
engines: {node: ^20.19.0 || >=22.12.0}
|
||||||
@@ -786,6 +811,15 @@ packages:
|
|||||||
'@nuxtjs/mdc@0.20.1':
|
'@nuxtjs/mdc@0.20.1':
|
||||||
resolution: {integrity: sha512-fGmtLDQAmmDHF5Z37Apfc3k806rb2XWDOQFhb5xlDSL7+HiiUjyFy3ctx3qWdlC08dRzfAetmsGOYbfDqYsB/w==}
|
resolution: {integrity: sha512-fGmtLDQAmmDHF5Z37Apfc3k806rb2XWDOQFhb5xlDSL7+HiiUjyFy3ctx3qWdlC08dRzfAetmsGOYbfDqYsB/w==}
|
||||||
|
|
||||||
|
'@nuxtjs/sitemap@8.0.12':
|
||||||
|
resolution: {integrity: sha512-1lFrk7FW/+3vtWRNnAyVjhyEsQN3xwau9hQI/cTmUKyxbImY0d10ZXeicR+amCU4wSnayVeaysBjM2KREAODTA==}
|
||||||
|
engines: {node: '>=18.0.0'}
|
||||||
|
peerDependencies:
|
||||||
|
zod: '>=3'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
zod:
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@oxc-minify/binding-android-arm-eabi@0.112.0':
|
'@oxc-minify/binding-android-arm-eabi@0.112.0':
|
||||||
resolution: {integrity: sha512-m7TGBR2hjsBJIN9UJ909KBoKsuogo6CuLsHKvUIBXdjI0JVHP8g4ZHeB+BJpGn5LJdeSGDfz9MWiuXrZDRzunw==}
|
resolution: {integrity: sha512-m7TGBR2hjsBJIN9UJ909KBoKsuogo6CuLsHKvUIBXdjI0JVHP8g4ZHeB+BJpGn5LJdeSGDfz9MWiuXrZDRzunw==}
|
||||||
engines: {node: ^20.19.0 || >=22.12.0}
|
engines: {node: ^20.19.0 || >=22.12.0}
|
||||||
@@ -1906,6 +1940,11 @@ packages:
|
|||||||
engines: {node: '>=0.4.0'}
|
engines: {node: '>=0.4.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
acorn@8.16.0:
|
||||||
|
resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==}
|
||||||
|
engines: {node: '>=0.4.0'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
agent-base@7.1.4:
|
agent-base@7.1.4:
|
||||||
resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==}
|
resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==}
|
||||||
engines: {node: '>= 14'}
|
engines: {node: '>= 14'}
|
||||||
@@ -2215,6 +2254,9 @@ packages:
|
|||||||
cookie-es@1.2.2:
|
cookie-es@1.2.2:
|
||||||
resolution: {integrity: sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg==}
|
resolution: {integrity: sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg==}
|
||||||
|
|
||||||
|
cookie-es@1.2.3:
|
||||||
|
resolution: {integrity: sha512-lXVyvUvrNXblMqzIRrxHb57UUVmqsSWlxqt3XIjCkUP0wDAf6uicO6KMbEgYrMNtEvWgWHwe42CKxPu9MYAnWw==}
|
||||||
|
|
||||||
cookie-es@2.0.0:
|
cookie-es@2.0.0:
|
||||||
resolution: {integrity: sha512-RAj4E421UYRgqokKUmotqAwuplYw15qtdXfY+hGzgCJ/MBjCVZcSoHK/kH9kocfjRjcDME7IiDWR/1WX1TM2Pg==}
|
resolution: {integrity: sha512-RAj4E421UYRgqokKUmotqAwuplYw15qtdXfY+hGzgCJ/MBjCVZcSoHK/kH9kocfjRjcDME7IiDWR/1WX1TM2Pg==}
|
||||||
|
|
||||||
@@ -2372,6 +2414,9 @@ packages:
|
|||||||
defu@6.1.4:
|
defu@6.1.4:
|
||||||
resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==}
|
resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==}
|
||||||
|
|
||||||
|
defu@6.1.7:
|
||||||
|
resolution: {integrity: sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==}
|
||||||
|
|
||||||
denque@2.1.0:
|
denque@2.1.0:
|
||||||
resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==}
|
resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==}
|
||||||
engines: {node: '>=0.10'}
|
engines: {node: '>=0.10'}
|
||||||
@@ -2587,9 +2632,25 @@ packages:
|
|||||||
fast-npm-meta@1.2.1:
|
fast-npm-meta@1.2.1:
|
||||||
resolution: {integrity: sha512-vTHOCEbzcbQEfYL0sPzcz+HF5asxoy60tPBVaiYzsCfuyhbXZCSqXL+LgPGV22nuAYimoGMeDpywMQB4aOw8HQ==}
|
resolution: {integrity: sha512-vTHOCEbzcbQEfYL0sPzcz+HF5asxoy60tPBVaiYzsCfuyhbXZCSqXL+LgPGV22nuAYimoGMeDpywMQB4aOw8HQ==}
|
||||||
|
|
||||||
|
fast-string-truncated-width@1.2.1:
|
||||||
|
resolution: {integrity: sha512-Q9acT/+Uu3GwGj+5w/zsGuQjh9O1TyywhIwAxHudtWrgF09nHOPrvTLhQevPbttcxjr/SNN7mJmfOw/B1bXgow==}
|
||||||
|
|
||||||
|
fast-string-width@1.1.0:
|
||||||
|
resolution: {integrity: sha512-O3fwIVIH5gKB38QNbdg+3760ZmGz0SZMgvwJbA1b2TGXceKE6A2cOlfogh1iw8lr049zPyd7YADHy+B7U4W9bQ==}
|
||||||
|
|
||||||
fast-uri@3.1.0:
|
fast-uri@3.1.0:
|
||||||
resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==}
|
resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==}
|
||||||
|
|
||||||
|
fast-wrap-ansi@0.1.6:
|
||||||
|
resolution: {integrity: sha512-HlUwET7a5gqjURj70D5jl7aC3Zmy4weA1SHUfM0JFI0Ptq987NH2TwbBFLoERhfwk+E+eaq4EK3jXoT+R3yp3w==}
|
||||||
|
|
||||||
|
fast-xml-builder@1.1.4:
|
||||||
|
resolution: {integrity: sha512-f2jhpN4Eccy0/Uz9csxh3Nu6q4ErKxf0XIsasomfOihuSUa3/xw6w8dnOtCDgEItQFJG8KyXPzQXzcODDrrbOg==}
|
||||||
|
|
||||||
|
fast-xml-parser@5.5.11:
|
||||||
|
resolution: {integrity: sha512-QL0eb0YbSTVWF6tTf1+LEMSgtCEjBYPpnAjoLC8SscESlAjXEIRJ7cHtLG0pLeDFaZLa4VKZLArtA/60ZS7vyA==}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
fastq@1.20.1:
|
fastq@1.20.1:
|
||||||
resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==}
|
resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==}
|
||||||
|
|
||||||
@@ -2733,6 +2794,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-O1Ld7Dr+nqPnmGpdhzLmMTQ4vAsD+rHwMm1NLUmoUFFymBOMKxCCrtDxqdBRYXdeEPEi3SyoR4TizJLQrnKBNA==}
|
resolution: {integrity: sha512-O1Ld7Dr+nqPnmGpdhzLmMTQ4vAsD+rHwMm1NLUmoUFFymBOMKxCCrtDxqdBRYXdeEPEi3SyoR4TizJLQrnKBNA==}
|
||||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||||
|
|
||||||
|
h3@1.15.11:
|
||||||
|
resolution: {integrity: sha512-L3THSe2MPeBwgIZVSH5zLdBBU90TOxarvhK9d04IDY2AmVS8j2Jz2LIWtwsGOU3lu2I5jCN7FNvVfY2+XyF+mg==}
|
||||||
|
|
||||||
h3@1.15.5:
|
h3@1.15.5:
|
||||||
resolution: {integrity: sha512-xEyq3rSl+dhGX2Lm0+eFQIAzlDN6Fs0EcC4f7BNUmzaRX/PTzeuM+Tr2lHB8FoXggsQIeXLj8EDVgs5ywxyxmg==}
|
resolution: {integrity: sha512-xEyq3rSl+dhGX2Lm0+eFQIAzlDN6Fs0EcC4f7BNUmzaRX/PTzeuM+Tr2lHB8FoXggsQIeXLj8EDVgs5ywxyxmg==}
|
||||||
|
|
||||||
@@ -3338,6 +3402,9 @@ packages:
|
|||||||
mlly@1.8.0:
|
mlly@1.8.0:
|
||||||
resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==}
|
resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==}
|
||||||
|
|
||||||
|
mlly@1.8.2:
|
||||||
|
resolution: {integrity: sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==}
|
||||||
|
|
||||||
mocked-exports@0.1.1:
|
mocked-exports@0.1.1:
|
||||||
resolution: {integrity: sha512-aF7yRQr/Q0O2/4pIXm6PZ5G+jAd7QS4Yu8m+WEeEHGnbo+7mE36CbLSDQiXYV8bVL3NfmdeqPJct0tUlnjVSnA==}
|
resolution: {integrity: sha512-aF7yRQr/Q0O2/4pIXm6PZ5G+jAd7QS4Yu8m+WEeEHGnbo+7mE36CbLSDQiXYV8bVL3NfmdeqPJct0tUlnjVSnA==}
|
||||||
|
|
||||||
@@ -3444,6 +3511,15 @@ packages:
|
|||||||
resolution: {integrity: sha512-5pVCzWXqg9HP159JDhdfQJtFvgmS/KouEVpyYLPEBXWMrQoJBwujsczmLeIKXKI2BTy4RqfXy8N1GfGTZNb57g==}
|
resolution: {integrity: sha512-5pVCzWXqg9HP159JDhdfQJtFvgmS/KouEVpyYLPEBXWMrQoJBwujsczmLeIKXKI2BTy4RqfXy8N1GfGTZNb57g==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
nuxt-site-config-kit@4.0.8:
|
||||||
|
resolution: {integrity: sha512-7g3giKXt0M2vssCUg8XFfR6+u4U0zywQ8p8i4msy4p+9etteFNrkrCmVHZ83xiWGFbnoTgiaymPjbaQH3KZqAg==}
|
||||||
|
|
||||||
|
nuxt-site-config@4.0.8:
|
||||||
|
resolution: {integrity: sha512-H7wHoOJ5Z6ZnTqD5vUugaKkWZbejZ9kGmzpr2dheOaC6RdT8JafCfMrmJG7W+cyJiJJ3YmzL+bzPBW2bW6MExA==}
|
||||||
|
|
||||||
|
nuxt-umami@3.2.1:
|
||||||
|
resolution: {integrity: sha512-82cf3kcrMn4Iq0rJ2Blfl48AqLWqRubEpxOinOoxqW7taZAd5SgZcCdCj7y4qXSt0W5DhBYgaq4IboyGFHoVUQ==}
|
||||||
|
|
||||||
nuxt@4.3.1:
|
nuxt@4.3.1:
|
||||||
resolution: {integrity: sha512-bl+0rFcT5Ax16aiWFBFPyWcsTob19NTZaDL5P6t0MQdK63AtgS6fN6fwvwdbXtnTk6/YdCzlmuLzXhSM22h0OA==}
|
resolution: {integrity: sha512-bl+0rFcT5Ax16aiWFBFPyWcsTob19NTZaDL5P6t0MQdK63AtgS6fN6fwvwdbXtnTk6/YdCzlmuLzXhSM22h0OA==}
|
||||||
engines: {node: ^20.19.0 || >=22.12.0}
|
engines: {node: ^20.19.0 || >=22.12.0}
|
||||||
@@ -3457,6 +3533,20 @@ packages:
|
|||||||
'@types/node':
|
'@types/node':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
nuxtseo-shared@5.1.2:
|
||||||
|
resolution: {integrity: sha512-L8nYZCmdFh2w9wNf4dxQy5Vzv2JTWd661zAg3D0h9HRm3chUkMZNgWQbodE7rK6jpitydONyvi7uHXOEHbGIuA==}
|
||||||
|
peerDependencies:
|
||||||
|
'@nuxt/schema': ^3.16.0 || ^4.0.0
|
||||||
|
nuxt: ^3.16.0 || ^4.0.0
|
||||||
|
nuxt-site-config: ^3.2.0 || ^4.0.0
|
||||||
|
vue: ^3.5.0
|
||||||
|
zod: ^3.23.0 || ^4.0.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
nuxt-site-config:
|
||||||
|
optional: true
|
||||||
|
zod:
|
||||||
|
optional: true
|
||||||
|
|
||||||
nypm@0.6.5:
|
nypm@0.6.5:
|
||||||
resolution: {integrity: sha512-K6AJy1GMVyfyMXRVB88700BJqNUkByijGJM8kEHpLdcAt+vSQAVfkWWHYzuRXHSY6xA2sNc5RjTj0p9rE2izVQ==}
|
resolution: {integrity: sha512-K6AJy1GMVyfyMXRVB88700BJqNUkByijGJM8kEHpLdcAt+vSQAVfkWWHYzuRXHSY6xA2sNc5RjTj0p9rE2izVQ==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
@@ -3552,6 +3642,10 @@ packages:
|
|||||||
path-browserify@1.0.1:
|
path-browserify@1.0.1:
|
||||||
resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==}
|
resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==}
|
||||||
|
|
||||||
|
path-expression-matcher@1.5.0:
|
||||||
|
resolution: {integrity: sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ==}
|
||||||
|
engines: {node: '>=14.0.0'}
|
||||||
|
|
||||||
path-key@3.1.1:
|
path-key@3.1.1:
|
||||||
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
|
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@@ -3935,6 +4029,9 @@ packages:
|
|||||||
remark-stringify@11.0.0:
|
remark-stringify@11.0.0:
|
||||||
resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==}
|
resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==}
|
||||||
|
|
||||||
|
request-ip@3.3.0:
|
||||||
|
resolution: {integrity: sha512-cA6Xh6e0fDBBBwH77SLJaJPBmD3nWVAcF9/XAcsrIHdjhFzFiB5aNQFytdjCGPezU3ROwrR11IddKAM08vohxA==}
|
||||||
|
|
||||||
require-directory@2.1.1:
|
require-directory@2.1.1:
|
||||||
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
|
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
@@ -4085,6 +4182,11 @@ packages:
|
|||||||
sisteransi@1.0.5:
|
sisteransi@1.0.5:
|
||||||
resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==}
|
resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==}
|
||||||
|
|
||||||
|
site-config-stack@4.0.8:
|
||||||
|
resolution: {integrity: sha512-Su+57p7CGqd3QSMmaDV+qU9EqWmgAT3SGX4Wurb5VsEBMFC3oXvai8BlrXVUnH1ay9hA1WOn0g0i6+y/RJX5Yw==}
|
||||||
|
peerDependencies:
|
||||||
|
vue: ^3.5.30
|
||||||
|
|
||||||
skin-tone@2.0.0:
|
skin-tone@2.0.0:
|
||||||
resolution: {integrity: sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA==}
|
resolution: {integrity: sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@@ -4146,6 +4248,9 @@ packages:
|
|||||||
std-env@3.10.0:
|
std-env@3.10.0:
|
||||||
resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==}
|
resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==}
|
||||||
|
|
||||||
|
std-env@4.0.0:
|
||||||
|
resolution: {integrity: sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ==}
|
||||||
|
|
||||||
streamx@2.23.0:
|
streamx@2.23.0:
|
||||||
resolution: {integrity: sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==}
|
resolution: {integrity: sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==}
|
||||||
|
|
||||||
@@ -4185,6 +4290,9 @@ packages:
|
|||||||
strip-literal@3.1.0:
|
strip-literal@3.1.0:
|
||||||
resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==}
|
resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==}
|
||||||
|
|
||||||
|
strnum@2.2.3:
|
||||||
|
resolution: {integrity: sha512-oKx6RUCuHfT3oyVjtnrmn19H1SiCqgJSg+54XqURKp5aCMbrXrhLjRN9TjuwMjiYstZ0MzDrHqkGZ5dFTKd+zg==}
|
||||||
|
|
||||||
structured-clone-es@1.0.0:
|
structured-clone-es@1.0.0:
|
||||||
resolution: {integrity: sha512-FL8EeKFFyNQv5cMnXI31CIMCsFarSVI2bF0U0ImeNE3g/F1IvJQyqzOXxPBRXiwQfyBTlbNe88jh1jFW0O/jiQ==}
|
resolution: {integrity: sha512-FL8EeKFFyNQv5cMnXI31CIMCsFarSVI2bF0U0ImeNE3g/F1IvJQyqzOXxPBRXiwQfyBTlbNe88jh1jFW0O/jiQ==}
|
||||||
|
|
||||||
@@ -4272,6 +4380,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==}
|
resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
|
tinyexec@1.1.1:
|
||||||
|
resolution: {integrity: sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
tinyglobby@0.2.15:
|
tinyglobby@0.2.15:
|
||||||
resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
|
resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
|
||||||
engines: {node: '>=12.0.0'}
|
engines: {node: '>=12.0.0'}
|
||||||
@@ -5007,12 +5119,24 @@ snapshots:
|
|||||||
picocolors: 1.1.1
|
picocolors: 1.1.1
|
||||||
sisteransi: 1.0.5
|
sisteransi: 1.0.5
|
||||||
|
|
||||||
|
'@clack/core@1.2.0':
|
||||||
|
dependencies:
|
||||||
|
fast-wrap-ansi: 0.1.6
|
||||||
|
sisteransi: 1.0.5
|
||||||
|
|
||||||
'@clack/prompts@1.0.1':
|
'@clack/prompts@1.0.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@clack/core': 1.0.1
|
'@clack/core': 1.0.1
|
||||||
picocolors: 1.1.1
|
picocolors: 1.1.1
|
||||||
sisteransi: 1.0.5
|
sisteransi: 1.0.5
|
||||||
|
|
||||||
|
'@clack/prompts@1.2.0':
|
||||||
|
dependencies:
|
||||||
|
'@clack/core': 1.2.0
|
||||||
|
fast-string-width: 1.1.0
|
||||||
|
fast-wrap-ansi: 0.1.6
|
||||||
|
sisteransi: 1.0.5
|
||||||
|
|
||||||
'@cloudflare/kv-asset-handler@0.4.2': {}
|
'@cloudflare/kv-asset-handler@0.4.2': {}
|
||||||
|
|
||||||
'@dxup/nuxt@0.3.2(magicast@0.5.2)':
|
'@dxup/nuxt@0.3.2(magicast@0.5.2)':
|
||||||
@@ -5474,6 +5598,14 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- magicast
|
- magicast
|
||||||
|
|
||||||
|
'@nuxt/devtools-kit@4.0.0-alpha.3(magicast@0.5.2)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2))':
|
||||||
|
dependencies:
|
||||||
|
'@nuxt/kit': 4.4.2(magicast@0.5.2)
|
||||||
|
tinyexec: 1.1.1
|
||||||
|
vite: 7.3.1(@types/node@25.2.3)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2)
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- magicast
|
||||||
|
|
||||||
'@nuxt/devtools-wizard@3.2.1':
|
'@nuxt/devtools-wizard@3.2.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
consola: 3.4.2
|
consola: 3.4.2
|
||||||
@@ -5562,6 +5694,32 @@ snapshots:
|
|||||||
- magicast
|
- magicast
|
||||||
- uploadthing
|
- uploadthing
|
||||||
|
|
||||||
|
'@nuxt/kit@3.21.2(magicast@0.5.2)':
|
||||||
|
dependencies:
|
||||||
|
c12: 3.3.3(magicast@0.5.2)
|
||||||
|
consola: 3.4.2
|
||||||
|
defu: 6.1.7
|
||||||
|
destr: 2.0.5
|
||||||
|
errx: 0.1.0
|
||||||
|
exsolve: 1.0.8
|
||||||
|
ignore: 7.0.5
|
||||||
|
jiti: 2.6.1
|
||||||
|
klona: 2.0.6
|
||||||
|
knitwork: 1.3.0
|
||||||
|
mlly: 1.8.2
|
||||||
|
ohash: 2.0.11
|
||||||
|
pathe: 2.0.3
|
||||||
|
pkg-types: 2.3.0
|
||||||
|
rc9: 3.0.0
|
||||||
|
scule: 1.3.0
|
||||||
|
semver: 7.7.4
|
||||||
|
tinyglobby: 0.2.15
|
||||||
|
ufo: 1.6.3
|
||||||
|
unctx: 2.5.0
|
||||||
|
untyped: 2.0.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- magicast
|
||||||
|
|
||||||
'@nuxt/kit@4.3.1(magicast@0.5.2)':
|
'@nuxt/kit@4.3.1(magicast@0.5.2)':
|
||||||
dependencies:
|
dependencies:
|
||||||
c12: 3.3.3(magicast@0.5.2)
|
c12: 3.3.3(magicast@0.5.2)
|
||||||
@@ -5587,6 +5745,31 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- magicast
|
- magicast
|
||||||
|
|
||||||
|
'@nuxt/kit@4.4.2(magicast@0.5.2)':
|
||||||
|
dependencies:
|
||||||
|
c12: 3.3.3(magicast@0.5.2)
|
||||||
|
consola: 3.4.2
|
||||||
|
defu: 6.1.7
|
||||||
|
destr: 2.0.5
|
||||||
|
errx: 0.1.0
|
||||||
|
exsolve: 1.0.8
|
||||||
|
ignore: 7.0.5
|
||||||
|
jiti: 2.6.1
|
||||||
|
klona: 2.0.6
|
||||||
|
mlly: 1.8.2
|
||||||
|
ohash: 2.0.11
|
||||||
|
pathe: 2.0.3
|
||||||
|
pkg-types: 2.3.0
|
||||||
|
rc9: 3.0.0
|
||||||
|
scule: 1.3.0
|
||||||
|
semver: 7.7.4
|
||||||
|
tinyglobby: 0.2.15
|
||||||
|
ufo: 1.6.3
|
||||||
|
unctx: 2.5.0
|
||||||
|
untyped: 2.0.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- magicast
|
||||||
|
|
||||||
'@nuxt/nitro-server@4.3.1(better-sqlite3@12.6.2)(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.3)(magicast@0.5.2)(nuxt@4.3.1(@parcel/watcher@2.5.6)(@types/node@25.2.3)(@vue/compiler-sfc@3.5.28)(better-sqlite3@12.6.2)(cac@6.7.14)(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.3)(magicast@0.5.2)(rollup@4.57.1)(terser@5.46.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2))(yaml@2.8.2))(typescript@5.9.3)':
|
'@nuxt/nitro-server@4.3.1(better-sqlite3@12.6.2)(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.3)(magicast@0.5.2)(nuxt@4.3.1(@parcel/watcher@2.5.6)(@types/node@25.2.3)(@vue/compiler-sfc@3.5.28)(better-sqlite3@12.6.2)(cac@6.7.14)(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.3)(magicast@0.5.2)(rollup@4.57.1)(terser@5.46.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2))(yaml@2.8.2))(typescript@5.9.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@nuxt/devalue': 2.0.2
|
'@nuxt/devalue': 2.0.2
|
||||||
@@ -5776,6 +5959,29 @@ snapshots:
|
|||||||
- magicast
|
- magicast
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
'@nuxtjs/sitemap@8.0.12(@nuxt/schema@4.3.1)(magicast@0.5.2)(nuxt@4.3.1(@parcel/watcher@2.5.6)(@types/node@25.2.3)(@vue/compiler-sfc@3.5.28)(better-sqlite3@12.6.2)(cac@6.7.14)(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.3)(magicast@0.5.2)(rollup@4.57.1)(terser@5.46.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2))(yaml@2.8.2))(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2))(vue@3.5.28(typescript@5.9.3))(zod@3.25.76)':
|
||||||
|
dependencies:
|
||||||
|
'@nuxt/kit': 4.4.2(magicast@0.5.2)
|
||||||
|
consola: 3.4.2
|
||||||
|
defu: 6.1.7
|
||||||
|
fast-xml-parser: 5.5.11
|
||||||
|
nuxt-site-config: 4.0.8(@nuxt/schema@4.3.1)(magicast@0.5.2)(nuxt@4.3.1(@parcel/watcher@2.5.6)(@types/node@25.2.3)(@vue/compiler-sfc@3.5.28)(better-sqlite3@12.6.2)(cac@6.7.14)(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.3)(magicast@0.5.2)(rollup@4.57.1)(terser@5.46.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2))(yaml@2.8.2))(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2))(vue@3.5.28(typescript@5.9.3))(zod@3.25.76)
|
||||||
|
nuxtseo-shared: 5.1.2(@nuxt/schema@4.3.1)(magicast@0.5.2)(nuxt-site-config@4.0.8(@nuxt/schema@4.3.1)(magicast@0.5.2)(nuxt@4.3.1(@parcel/watcher@2.5.6)(@types/node@25.2.3)(@vue/compiler-sfc@3.5.28)(better-sqlite3@12.6.2)(cac@6.7.14)(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.3)(magicast@0.5.2)(rollup@4.57.1)(terser@5.46.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2))(yaml@2.8.2))(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2))(vue@3.5.28(typescript@5.9.3))(zod@3.25.76))(nuxt@4.3.1(@parcel/watcher@2.5.6)(@types/node@25.2.3)(@vue/compiler-sfc@3.5.28)(better-sqlite3@12.6.2)(cac@6.7.14)(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.3)(magicast@0.5.2)(rollup@4.57.1)(terser@5.46.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2))(yaml@2.8.2))(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2))(vue@3.5.28(typescript@5.9.3))(zod@3.25.76)
|
||||||
|
ofetch: 1.5.1
|
||||||
|
pathe: 2.0.3
|
||||||
|
pkg-types: 2.3.0
|
||||||
|
radix3: 1.1.2
|
||||||
|
ufo: 1.6.3
|
||||||
|
ultrahtml: 1.6.0
|
||||||
|
optionalDependencies:
|
||||||
|
zod: 3.25.76
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- '@nuxt/schema'
|
||||||
|
- magicast
|
||||||
|
- nuxt
|
||||||
|
- vite
|
||||||
|
- vue
|
||||||
|
|
||||||
'@oxc-minify/binding-android-arm-eabi@0.112.0':
|
'@oxc-minify/binding-android-arm-eabi@0.112.0':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
@@ -6808,12 +7014,14 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
acorn: 8.15.0
|
acorn: 8.15.0
|
||||||
|
|
||||||
acorn-import-phases@1.0.4(acorn@8.15.0):
|
acorn-import-phases@1.0.4(acorn@8.16.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
acorn: 8.15.0
|
acorn: 8.16.0
|
||||||
|
|
||||||
acorn@8.15.0: {}
|
acorn@8.15.0: {}
|
||||||
|
|
||||||
|
acorn@8.16.0: {}
|
||||||
|
|
||||||
agent-base@7.1.4: {}
|
agent-base@7.1.4: {}
|
||||||
|
|
||||||
ajv-formats@2.1.1(ajv@8.18.0):
|
ajv-formats@2.1.1(ajv@8.18.0):
|
||||||
@@ -7111,6 +7319,8 @@ snapshots:
|
|||||||
|
|
||||||
cookie-es@1.2.2: {}
|
cookie-es@1.2.2: {}
|
||||||
|
|
||||||
|
cookie-es@1.2.3: {}
|
||||||
|
|
||||||
cookie-es@2.0.0: {}
|
cookie-es@2.0.0: {}
|
||||||
|
|
||||||
copy-anything@4.0.5:
|
copy-anything@4.0.5:
|
||||||
@@ -7260,6 +7470,8 @@ snapshots:
|
|||||||
|
|
||||||
defu@6.1.4: {}
|
defu@6.1.4: {}
|
||||||
|
|
||||||
|
defu@6.1.7: {}
|
||||||
|
|
||||||
denque@2.1.0: {}
|
denque@2.1.0: {}
|
||||||
|
|
||||||
depd@2.0.0: {}
|
depd@2.0.0: {}
|
||||||
@@ -7471,8 +7683,28 @@ snapshots:
|
|||||||
|
|
||||||
fast-npm-meta@1.2.1: {}
|
fast-npm-meta@1.2.1: {}
|
||||||
|
|
||||||
|
fast-string-truncated-width@1.2.1: {}
|
||||||
|
|
||||||
|
fast-string-width@1.1.0:
|
||||||
|
dependencies:
|
||||||
|
fast-string-truncated-width: 1.2.1
|
||||||
|
|
||||||
fast-uri@3.1.0: {}
|
fast-uri@3.1.0: {}
|
||||||
|
|
||||||
|
fast-wrap-ansi@0.1.6:
|
||||||
|
dependencies:
|
||||||
|
fast-string-width: 1.1.0
|
||||||
|
|
||||||
|
fast-xml-builder@1.1.4:
|
||||||
|
dependencies:
|
||||||
|
path-expression-matcher: 1.5.0
|
||||||
|
|
||||||
|
fast-xml-parser@5.5.11:
|
||||||
|
dependencies:
|
||||||
|
fast-xml-builder: 1.1.4
|
||||||
|
path-expression-matcher: 1.5.0
|
||||||
|
strnum: 2.2.3
|
||||||
|
|
||||||
fastq@1.20.1:
|
fastq@1.20.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
reusify: 1.1.0
|
reusify: 1.1.0
|
||||||
@@ -7611,6 +7843,18 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
duplexer: 0.1.2
|
duplexer: 0.1.2
|
||||||
|
|
||||||
|
h3@1.15.11:
|
||||||
|
dependencies:
|
||||||
|
cookie-es: 1.2.3
|
||||||
|
crossws: 0.3.5
|
||||||
|
defu: 6.1.7
|
||||||
|
destr: 2.0.5
|
||||||
|
iron-webcrypto: 1.2.1
|
||||||
|
node-mock-http: 1.0.4
|
||||||
|
radix3: 1.1.2
|
||||||
|
ufo: 1.6.3
|
||||||
|
uncrypto: 0.1.3
|
||||||
|
|
||||||
h3@1.15.5:
|
h3@1.15.5:
|
||||||
dependencies:
|
dependencies:
|
||||||
cookie-es: 1.2.2
|
cookie-es: 1.2.2
|
||||||
@@ -8500,6 +8744,13 @@ snapshots:
|
|||||||
pkg-types: 1.3.1
|
pkg-types: 1.3.1
|
||||||
ufo: 1.6.3
|
ufo: 1.6.3
|
||||||
|
|
||||||
|
mlly@1.8.2:
|
||||||
|
dependencies:
|
||||||
|
acorn: 8.16.0
|
||||||
|
pathe: 2.0.3
|
||||||
|
pkg-types: 1.3.1
|
||||||
|
ufo: 1.6.3
|
||||||
|
|
||||||
mocked-exports@0.1.1: {}
|
mocked-exports@0.1.1: {}
|
||||||
|
|
||||||
mrmime@2.0.1: {}
|
mrmime@2.0.1: {}
|
||||||
@@ -8682,6 +8933,41 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- magicast
|
- magicast
|
||||||
|
|
||||||
|
nuxt-site-config-kit@4.0.8(magicast@0.5.2)(vue@3.5.28(typescript@5.9.3)):
|
||||||
|
dependencies:
|
||||||
|
'@nuxt/kit': 4.4.2(magicast@0.5.2)
|
||||||
|
site-config-stack: 4.0.8(vue@3.5.28(typescript@5.9.3))
|
||||||
|
std-env: 4.0.0
|
||||||
|
ufo: 1.6.3
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- magicast
|
||||||
|
- vue
|
||||||
|
|
||||||
|
nuxt-site-config@4.0.8(@nuxt/schema@4.3.1)(magicast@0.5.2)(nuxt@4.3.1(@parcel/watcher@2.5.6)(@types/node@25.2.3)(@vue/compiler-sfc@3.5.28)(better-sqlite3@12.6.2)(cac@6.7.14)(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.3)(magicast@0.5.2)(rollup@4.57.1)(terser@5.46.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2))(yaml@2.8.2))(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2))(vue@3.5.28(typescript@5.9.3))(zod@3.25.76):
|
||||||
|
dependencies:
|
||||||
|
'@nuxt/kit': 4.4.2(magicast@0.5.2)
|
||||||
|
h3: 1.15.11
|
||||||
|
nuxt-site-config-kit: 4.0.8(magicast@0.5.2)(vue@3.5.28(typescript@5.9.3))
|
||||||
|
nuxtseo-shared: 5.1.2(@nuxt/schema@4.3.1)(magicast@0.5.2)(nuxt-site-config@4.0.8(@nuxt/schema@4.3.1)(magicast@0.5.2)(nuxt@4.3.1(@parcel/watcher@2.5.6)(@types/node@25.2.3)(@vue/compiler-sfc@3.5.28)(better-sqlite3@12.6.2)(cac@6.7.14)(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.3)(magicast@0.5.2)(rollup@4.57.1)(terser@5.46.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2))(yaml@2.8.2))(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2))(vue@3.5.28(typescript@5.9.3))(zod@3.25.76))(nuxt@4.3.1(@parcel/watcher@2.5.6)(@types/node@25.2.3)(@vue/compiler-sfc@3.5.28)(better-sqlite3@12.6.2)(cac@6.7.14)(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.3)(magicast@0.5.2)(rollup@4.57.1)(terser@5.46.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2))(yaml@2.8.2))(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2))(vue@3.5.28(typescript@5.9.3))(zod@3.25.76)
|
||||||
|
pathe: 2.0.3
|
||||||
|
pkg-types: 2.3.0
|
||||||
|
site-config-stack: 4.0.8(vue@3.5.28(typescript@5.9.3))
|
||||||
|
ufo: 1.6.3
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- '@nuxt/schema'
|
||||||
|
- magicast
|
||||||
|
- nuxt
|
||||||
|
- vite
|
||||||
|
- vue
|
||||||
|
- zod
|
||||||
|
|
||||||
|
nuxt-umami@3.2.1(magicast@0.5.2):
|
||||||
|
dependencies:
|
||||||
|
'@nuxt/kit': 3.21.2(magicast@0.5.2)
|
||||||
|
request-ip: 3.3.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- magicast
|
||||||
|
|
||||||
nuxt@4.3.1(@parcel/watcher@2.5.6)(@types/node@25.2.3)(@vue/compiler-sfc@3.5.28)(better-sqlite3@12.6.2)(cac@6.7.14)(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.3)(magicast@0.5.2)(rollup@4.57.1)(terser@5.46.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2))(yaml@2.8.2):
|
nuxt@4.3.1(@parcel/watcher@2.5.6)(@types/node@25.2.3)(@vue/compiler-sfc@3.5.28)(better-sqlite3@12.6.2)(cac@6.7.14)(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.3)(magicast@0.5.2)(rollup@4.57.1)(terser@5.46.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2))(yaml@2.8.2):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@dxup/nuxt': 0.3.2(magicast@0.5.2)
|
'@dxup/nuxt': 0.3.2(magicast@0.5.2)
|
||||||
@@ -8805,6 +9091,31 @@ snapshots:
|
|||||||
- xml2js
|
- xml2js
|
||||||
- yaml
|
- yaml
|
||||||
|
|
||||||
|
nuxtseo-shared@5.1.2(@nuxt/schema@4.3.1)(magicast@0.5.2)(nuxt-site-config@4.0.8(@nuxt/schema@4.3.1)(magicast@0.5.2)(nuxt@4.3.1(@parcel/watcher@2.5.6)(@types/node@25.2.3)(@vue/compiler-sfc@3.5.28)(better-sqlite3@12.6.2)(cac@6.7.14)(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.3)(magicast@0.5.2)(rollup@4.57.1)(terser@5.46.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2))(yaml@2.8.2))(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2))(vue@3.5.28(typescript@5.9.3))(zod@3.25.76))(nuxt@4.3.1(@parcel/watcher@2.5.6)(@types/node@25.2.3)(@vue/compiler-sfc@3.5.28)(better-sqlite3@12.6.2)(cac@6.7.14)(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.3)(magicast@0.5.2)(rollup@4.57.1)(terser@5.46.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2))(yaml@2.8.2))(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2))(vue@3.5.28(typescript@5.9.3))(zod@3.25.76):
|
||||||
|
dependencies:
|
||||||
|
'@clack/prompts': 1.2.0
|
||||||
|
'@nuxt/devtools-kit': 4.0.0-alpha.3(magicast@0.5.2)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2))
|
||||||
|
'@nuxt/kit': 4.4.2(magicast@0.5.2)
|
||||||
|
'@nuxt/schema': 4.3.1
|
||||||
|
birpc: 4.0.0
|
||||||
|
consola: 3.4.2
|
||||||
|
defu: 6.1.7
|
||||||
|
nuxt: 4.3.1(@parcel/watcher@2.5.6)(@types/node@25.2.3)(@vue/compiler-sfc@3.5.28)(better-sqlite3@12.6.2)(cac@6.7.14)(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.3)(magicast@0.5.2)(rollup@4.57.1)(terser@5.46.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2))(yaml@2.8.2)
|
||||||
|
ofetch: 1.5.1
|
||||||
|
pathe: 2.0.3
|
||||||
|
pkg-types: 2.3.0
|
||||||
|
radix3: 1.1.2
|
||||||
|
sirv: 3.0.2
|
||||||
|
std-env: 4.0.0
|
||||||
|
ufo: 1.6.3
|
||||||
|
vue: 3.5.28(typescript@5.9.3)
|
||||||
|
optionalDependencies:
|
||||||
|
nuxt-site-config: 4.0.8(@nuxt/schema@4.3.1)(magicast@0.5.2)(nuxt@4.3.1(@parcel/watcher@2.5.6)(@types/node@25.2.3)(@vue/compiler-sfc@3.5.28)(better-sqlite3@12.6.2)(cac@6.7.14)(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.3)(magicast@0.5.2)(rollup@4.57.1)(terser@5.46.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2))(yaml@2.8.2))(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2))(vue@3.5.28(typescript@5.9.3))(zod@3.25.76)
|
||||||
|
zod: 3.25.76
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- magicast
|
||||||
|
- vite
|
||||||
|
|
||||||
nypm@0.6.5:
|
nypm@0.6.5:
|
||||||
dependencies:
|
dependencies:
|
||||||
citty: 0.2.1
|
citty: 0.2.1
|
||||||
@@ -8971,6 +9282,8 @@ snapshots:
|
|||||||
|
|
||||||
path-browserify@1.0.1: {}
|
path-browserify@1.0.1: {}
|
||||||
|
|
||||||
|
path-expression-matcher@1.5.0: {}
|
||||||
|
|
||||||
path-key@3.1.1: {}
|
path-key@3.1.1: {}
|
||||||
|
|
||||||
path-key@4.0.0: {}
|
path-key@4.0.0: {}
|
||||||
@@ -9419,6 +9732,8 @@ snapshots:
|
|||||||
mdast-util-to-markdown: 2.1.2
|
mdast-util-to-markdown: 2.1.2
|
||||||
unified: 11.0.5
|
unified: 11.0.5
|
||||||
|
|
||||||
|
request-ip@3.3.0: {}
|
||||||
|
|
||||||
require-directory@2.1.1: {}
|
require-directory@2.1.1: {}
|
||||||
|
|
||||||
require-from-string@2.0.2: {}
|
require-from-string@2.0.2: {}
|
||||||
@@ -9633,6 +9948,11 @@ snapshots:
|
|||||||
|
|
||||||
sisteransi@1.0.5: {}
|
sisteransi@1.0.5: {}
|
||||||
|
|
||||||
|
site-config-stack@4.0.8(vue@3.5.28(typescript@5.9.3)):
|
||||||
|
dependencies:
|
||||||
|
ufo: 1.6.3
|
||||||
|
vue: 3.5.28(typescript@5.9.3)
|
||||||
|
|
||||||
skin-tone@2.0.0:
|
skin-tone@2.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
unicode-emoji-modifier-base: 1.0.0
|
unicode-emoji-modifier-base: 1.0.0
|
||||||
@@ -9684,6 +10004,8 @@ snapshots:
|
|||||||
|
|
||||||
std-env@3.10.0: {}
|
std-env@3.10.0: {}
|
||||||
|
|
||||||
|
std-env@4.0.0: {}
|
||||||
|
|
||||||
streamx@2.23.0:
|
streamx@2.23.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
events-universal: 1.0.1
|
events-universal: 1.0.1
|
||||||
@@ -9734,6 +10056,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
js-tokens: 9.0.1
|
js-tokens: 9.0.1
|
||||||
|
|
||||||
|
strnum@2.2.3: {}
|
||||||
|
|
||||||
structured-clone-es@1.0.0: {}
|
structured-clone-es@1.0.0: {}
|
||||||
|
|
||||||
stylehacks@7.0.7(postcss@8.5.6):
|
stylehacks@7.0.7(postcss@8.5.6):
|
||||||
@@ -9828,6 +10152,8 @@ snapshots:
|
|||||||
|
|
||||||
tinyexec@1.0.2: {}
|
tinyexec@1.0.2: {}
|
||||||
|
|
||||||
|
tinyexec@1.1.1: {}
|
||||||
|
|
||||||
tinyglobby@0.2.15:
|
tinyglobby@0.2.15:
|
||||||
dependencies:
|
dependencies:
|
||||||
fdir: 6.5.0(picomatch@4.0.3)
|
fdir: 6.5.0(picomatch@4.0.3)
|
||||||
@@ -10255,8 +10581,8 @@ snapshots:
|
|||||||
'@webassemblyjs/ast': 1.14.1
|
'@webassemblyjs/ast': 1.14.1
|
||||||
'@webassemblyjs/wasm-edit': 1.14.1
|
'@webassemblyjs/wasm-edit': 1.14.1
|
||||||
'@webassemblyjs/wasm-parser': 1.14.1
|
'@webassemblyjs/wasm-parser': 1.14.1
|
||||||
acorn: 8.15.0
|
acorn: 8.16.0
|
||||||
acorn-import-phases: 1.0.4(acorn@8.15.0)
|
acorn-import-phases: 1.0.4(acorn@8.16.0)
|
||||||
browserslist: 4.28.1
|
browserslist: 4.28.1
|
||||||
chrome-trace-event: 1.0.4
|
chrome-trace-event: 1.0.4
|
||||||
enhanced-resolve: 5.19.0
|
enhanced-resolve: 5.19.0
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 327 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
@@ -0,0 +1,25 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 80" fill="none" role="img" aria-label="Librodrome">
|
||||||
|
<!--
|
||||||
|
Logotype calligraphique Librodrome — § (section sign) brush-stroke.
|
||||||
|
Vectorisé depuis le PNG source (analyse pixel, 200×200 px).
|
||||||
|
Tracé S calligraphique : crochet haut-droit → arc gauche → arc droit → fin centre.
|
||||||
|
3 couches opacité : effet pinceau naturaliste.
|
||||||
|
currentColor → s'adapte automatiquement aux 4 palettes.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<!-- Halo — diffusion pinceau -->
|
||||||
|
<path
|
||||||
|
d="M 28 6 C 44 2,52 12,47 14 C 40 18,16 18,12 28 C 8 38,16 50,28 54 C 38 56,52 60,52 68 C 52 76,42 78,34 74"
|
||||||
|
stroke="currentColor" stroke-width="16" stroke-linecap="round" stroke-linejoin="round" opacity="0.12"
|
||||||
|
/>
|
||||||
|
<!-- Corps — épaisseur principale -->
|
||||||
|
<path
|
||||||
|
d="M 28 6 C 44 2,52 12,47 14 C 40 18,16 18,12 28 C 8 38,16 50,28 54 C 38 56,52 60,52 68 C 52 76,42 78,34 74"
|
||||||
|
stroke="currentColor" stroke-width="9" stroke-linecap="round" stroke-linejoin="round" opacity="0.38"
|
||||||
|
/>
|
||||||
|
<!-- Trait vif — ligne calligraphique -->
|
||||||
|
<path
|
||||||
|
d="M 28 6 C 44 2,52 12,47 14 C 40 18,16 18,12 28 C 8 38,16 50,28 54 C 38 56,52 60,52 68 C 52 76,42 78,34 74"
|
||||||
|
stroke="currentColor" stroke-width="5" stroke-linecap="round" stroke-linejoin="round" opacity="0.92"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.3 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 46 KiB |
Binary file not shown.
Binary file not shown.
@@ -158,24 +158,6 @@ html, body {
|
|||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Page turn transition */
|
|
||||||
.page-slot canvas {
|
|
||||||
transition: opacity 1s ease-out, transform 1s ease-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-slot canvas.entering {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateX(var(--enter-dir, 20px));
|
|
||||||
transition: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-slot canvas.leaving {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateX(var(--leave-dir, -20px));
|
|
||||||
position: absolute;
|
|
||||||
transition: opacity 1.2s ease-in, transform 1.2s ease-in;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Navigation bar */
|
/* Navigation bar */
|
||||||
.nav-bar {
|
.nav-bar {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -300,8 +282,6 @@ let currentSpread = 0; // index into spreads[]
|
|||||||
let spreads = []; // [[1], [2,3], [4,5], ...] — page 1 alone (cover), then pairs
|
let spreads = []; // [[1], [2,3], [4,5], ...] — page 1 alone (cover), then pairs
|
||||||
let pageCanvasCache = new Map();
|
let pageCanvasCache = new Map();
|
||||||
let outlinePageMap = []; // [{item, pageNum}] for highlighting
|
let outlinePageMap = []; // [{item, pageNum}] for highlighting
|
||||||
let isAnimating = false;
|
|
||||||
|
|
||||||
function buildSpreads(numPages) {
|
function buildSpreads(numPages) {
|
||||||
spreads = [];
|
spreads = [];
|
||||||
// Page 1 = couverture seule
|
// Page 1 = couverture seule
|
||||||
@@ -347,33 +327,14 @@ async function renderPageCanvas(pageNum) {
|
|||||||
return canvas;
|
return canvas;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function showSpread(index, animate = true) {
|
async function showSpread(index) {
|
||||||
if (index < 0 || index >= spreads.length) return;
|
if (index < 0 || index >= spreads.length) return;
|
||||||
if (isAnimating) return;
|
|
||||||
|
|
||||||
const prevIndex = currentSpread;
|
|
||||||
const direction = index > prevIndex ? 1 : -1; // 1 = forward, -1 = back
|
|
||||||
currentSpread = index;
|
currentSpread = index;
|
||||||
|
|
||||||
const pages = spreads[index];
|
const pages = spreads[index];
|
||||||
const slotLeft = document.getElementById('slotLeft');
|
const slotLeft = document.getElementById('slotLeft');
|
||||||
const slotRight = document.getElementById('slotRight');
|
const slotRight = document.getElementById('slotRight');
|
||||||
|
|
||||||
// Collect old canvases for fade-out
|
|
||||||
const oldCanvases = [...slotLeft.querySelectorAll('canvas'), ...slotRight.querySelectorAll('canvas')];
|
|
||||||
const shouldAnimate = animate && oldCanvases.length > 0;
|
|
||||||
|
|
||||||
if (shouldAnimate) {
|
|
||||||
isAnimating = true;
|
|
||||||
// Fade out old canvases with directional slide
|
|
||||||
oldCanvases.forEach(c => {
|
|
||||||
c.style.setProperty('--leave-dir', `${-direction * 20}px`);
|
|
||||||
c.classList.add('leaving');
|
|
||||||
});
|
|
||||||
// Wait for fade-out to mostly complete
|
|
||||||
await new Promise(r => setTimeout(r, 500));
|
|
||||||
}
|
|
||||||
|
|
||||||
slotLeft.innerHTML = '';
|
slotLeft.innerHTML = '';
|
||||||
slotRight.innerHTML = '';
|
slotRight.innerHTML = '';
|
||||||
slotLeft.className = 'page-slot';
|
slotLeft.className = 'page-slot';
|
||||||
@@ -381,40 +342,17 @@ async function showSpread(index, animate = true) {
|
|||||||
|
|
||||||
if (pages.length === 1) {
|
if (pages.length === 1) {
|
||||||
const canvas = await renderPageCanvas(pages[0]);
|
const canvas = await renderPageCanvas(pages[0]);
|
||||||
if (shouldAnimate) {
|
|
||||||
canvas.style.setProperty('--enter-dir', `${direction * 20}px`);
|
|
||||||
canvas.classList.add('entering');
|
|
||||||
}
|
|
||||||
slotLeft.appendChild(canvas);
|
slotLeft.appendChild(canvas);
|
||||||
slotRight.className = 'page-slot empty';
|
slotRight.className = 'page-slot empty';
|
||||||
if (shouldAnimate) {
|
|
||||||
// Double rAF ensures the browser has painted the initial state before transitioning
|
|
||||||
requestAnimationFrame(() => requestAnimationFrame(() => canvas.classList.remove('entering')));
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
const [left, right] = await Promise.all([
|
const [left, right] = await Promise.all([
|
||||||
renderPageCanvas(pages[0]),
|
renderPageCanvas(pages[0]),
|
||||||
renderPageCanvas(pages[1]),
|
renderPageCanvas(pages[1]),
|
||||||
]);
|
]);
|
||||||
if (shouldAnimate) {
|
|
||||||
left.style.setProperty('--enter-dir', `${direction * 20}px`);
|
|
||||||
right.style.setProperty('--enter-dir', `${direction * 20}px`);
|
|
||||||
left.classList.add('entering');
|
|
||||||
right.classList.add('entering');
|
|
||||||
}
|
|
||||||
slotLeft.appendChild(left);
|
slotLeft.appendChild(left);
|
||||||
slotRight.appendChild(right);
|
slotRight.appendChild(right);
|
||||||
if (shouldAnimate) {
|
|
||||||
requestAnimationFrame(() => requestAnimationFrame(() => {
|
|
||||||
left.classList.remove('entering');
|
|
||||||
right.classList.remove('entering');
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shouldAnimate) setTimeout(() => { isAnimating = false; }, 1100);
|
|
||||||
else isAnimating = false;
|
|
||||||
|
|
||||||
updateNav();
|
updateNav();
|
||||||
highlightOutline();
|
highlightOutline();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { existsSync } from 'node:fs'
|
||||||
|
import { readFile, writeFile, mkdir } from 'node:fs/promises'
|
||||||
|
import { join } from 'node:path'
|
||||||
|
|
||||||
|
// Seeds data/messages.yml from site/messages.yml on first boot (or after data loss).
|
||||||
|
// Only runs if data/messages.yml is absent — never overwrites existing runtime data.
|
||||||
|
export default defineNitroPlugin(async () => {
|
||||||
|
const dataFile = join(process.cwd(), 'data', 'messages.yml')
|
||||||
|
if (existsSync(dataFile)) return
|
||||||
|
|
||||||
|
const seedFile = join(process.cwd(), 'site', 'messages.yml')
|
||||||
|
if (!existsSync(seedFile)) return
|
||||||
|
|
||||||
|
await mkdir(join(process.cwd(), 'data'), { recursive: true })
|
||||||
|
const seed = await readFile(seedFile, 'utf-8')
|
||||||
|
await writeFile(dataFile, seed, 'utf-8')
|
||||||
|
console.log('[seed-messages] data/messages.yml initialisé depuis site/messages.yml')
|
||||||
|
})
|
||||||
@@ -3,7 +3,7 @@ book:
|
|||||||
author: Yvv
|
author: Yvv
|
||||||
pdfFile: /pdf/une-economie-du-don.pdf
|
pdfFile: /pdf/une-economie-du-don.pdf
|
||||||
description: Un livre et 9 chansons pour explorer ensemble les fondements d'une économie fondée sur le don.
|
description: Un livre et 9 chansons pour explorer ensemble les fondements d'une économie fondée sur le don.
|
||||||
coverImage: /images/book-cover.jpg
|
coverImage: /images/Couv-Economie-du-don.jpg
|
||||||
license: CC-BY-NC
|
license: CC-BY-NC
|
||||||
isbn: 979-1-042-45206-3
|
isbn: 979-1-042-45206-3
|
||||||
songs:
|
songs:
|
||||||
|
|||||||
+23
-3
@@ -1,7 +1,27 @@
|
|||||||
messages:
|
messages:
|
||||||
- id: 1
|
- id: 1
|
||||||
author: test
|
author: Yvv
|
||||||
email: ""
|
email: ""
|
||||||
text: test
|
text: >-
|
||||||
|
Bienvenue dans le librodrome. Le message que vous écrivez est le tout premier pas pour prendre contact et
|
||||||
|
potentiellement embarquer dans la démarche. Encore un énorme boulot de préparation des outils et du fonctionnement
|
||||||
|
de la plateforme.
|
||||||
|
|
||||||
|
|
||||||
|
Ensuite le propos est d'identifier quelques bassins de vie pionniers qui se lancent dans des productions
|
||||||
|
collectives directement, ou bien qui souhaitent passer par un événement librodrome, pour cristaliser des envies et
|
||||||
|
passer à l'action à cette occasion.
|
||||||
|
|
||||||
|
|
||||||
|
N'hésitez pas à laisser un moyen de vous répondre dans votre message ; n'hésitez pas à nommer votre bassin de vie.
|
||||||
|
Nous prendrons contact avec vous si vous le souhaitez, nous ferons connaissance et verrons ensemble comment
|
||||||
|
factoriser nos efforts et nos mobilisations.
|
||||||
|
|
||||||
|
|
||||||
|
A bientôt.
|
||||||
|
|
||||||
|
Merci pour la patience en attendant l'aboutissement d'un outil opérationnel.
|
||||||
|
type: suggestion
|
||||||
published: true
|
published: true
|
||||||
createdAt: 2026-02-20T01:23:38.633Z
|
createdAt: 2026-03-19T04:09:22.881Z
|
||||||
|
reply: null
|
||||||
|
|||||||
@@ -1,5 +1,69 @@
|
|||||||
kicker: Bientôt
|
|
||||||
title: En gestation
|
|
||||||
description: Cette rubrique est en cours de préparation.
|
|
||||||
meta:
|
meta:
|
||||||
title: Évènement
|
title: Le Librodrome — L'événement
|
||||||
|
|
||||||
|
kicker: Performance d'émancipation civile
|
||||||
|
title: Le Librodrome
|
||||||
|
subtitle: "Une paece — performance artistique d'émancipation civile et économique"
|
||||||
|
leitmotiv: Construire une autonomie collective.
|
||||||
|
tagline: Rendre possible. Passer la seconde.
|
||||||
|
gestation: true
|
||||||
|
|
||||||
|
description: >
|
||||||
|
Synergies entre collectifs, associations et coopératives — autour de trois axes :
|
||||||
|
numérique, économique, politique. « Je subis ou je m'affranchis. »
|
||||||
|
|
||||||
|
axes:
|
||||||
|
- icon: cpu
|
||||||
|
label: Numérique
|
||||||
|
items:
|
||||||
|
- Affranchissement GAFAM — logiciels libres, Linux, migration
|
||||||
|
- Cloud local décentralisé — Nextcloud, IPFS, Matrix, PeerTube
|
||||||
|
- IA collective locale — Mistral, UPlanet
|
||||||
|
|
||||||
|
- icon: coins
|
||||||
|
label: Économique
|
||||||
|
items:
|
||||||
|
- Monnaie libre (June Ğ1) vs monnaie-dette
|
||||||
|
- Économie du don — amorcer les filières, couvrir besoins et plaisirs
|
||||||
|
- Productions versatiles — énergie, alimentaire, agricole
|
||||||
|
|
||||||
|
- icon: landmark
|
||||||
|
label: Politique
|
||||||
|
items:
|
||||||
|
- Autonomie d'un bassin de vie — accessible et reproductible
|
||||||
|
- "Pragmatique : on parle chiffres, terrain, zéro étiquette"
|
||||||
|
- Feuilles de route et graphe des synergies
|
||||||
|
|
||||||
|
espaces:
|
||||||
|
- icon: cpu
|
||||||
|
label: Salle des machines
|
||||||
|
desc: Install Linux, on-boarding June Ğ1 et cryptos, FabLab ouvert, visios
|
||||||
|
- icon: shopping-bag
|
||||||
|
label: "Ğ(marché)"
|
||||||
|
desc: Expérience laboratoire in vivo — liberté de choisir sa monnaie, construction collective d'échelles de valeurs relatives
|
||||||
|
- icon: users
|
||||||
|
label: Ateliers & feuilles de route
|
||||||
|
desc: Animateurs initiés, préparés en amont — cartographies, synergies, restitutions collectives
|
||||||
|
- icon: music-2
|
||||||
|
label: Scène musicale & théâtrale
|
||||||
|
desc: Performances, concerts, lectures — chill out en préau
|
||||||
|
- icon: utensils
|
||||||
|
label: Buvette & restauration
|
||||||
|
desc: Lié au Ğ(marché) — expérience de l'économie du don en direct
|
||||||
|
- icon: gamepad-2
|
||||||
|
label: Géconomicus
|
||||||
|
desc: "Jeu économique — 3 parties dont 1 sans monnaie, 15 joueurs, 3 animateurs, spectateurs"
|
||||||
|
|
||||||
|
config:
|
||||||
|
- icon: calendar
|
||||||
|
label: Ven → Sam → Dim
|
||||||
|
detail: "5 jours, 4 nuits (+ jeudi install & répétitions + lundi after)"
|
||||||
|
- icon: users
|
||||||
|
label: 100 à 200 personnes
|
||||||
|
detail: Public 2 jours — junistes 3 jours
|
||||||
|
- icon: map-pin
|
||||||
|
label: Lieu privé — Drôme
|
||||||
|
detail: Événement sur invitation, lieu en cours de négociation
|
||||||
|
- icon: wifi
|
||||||
|
label: Fibre + LAN wifi
|
||||||
|
detail: Salle des machines, salle de conf, salles de production, campement 40-60 tentes
|
||||||
|
|||||||
+28
-1
@@ -31,7 +31,7 @@ book:
|
|||||||
title: Une économie du don — enfin concevable
|
title: Une économie du don — enfin concevable
|
||||||
description: Un livre et quelques chansons pour une proposition de modèle économique fondé sur le don. Le livre est
|
description: Un livre et quelques chansons pour une proposition de modèle économique fondé sur le don. Le livre est
|
||||||
accompagné de chansons qui le racontent, un peu autrement.
|
accompagné de chansons qui le racontent, un peu autrement.
|
||||||
coverImage: /images/book-cover-spread.jpg
|
coverImage: /images/Couv-Economie-du-don.jpg
|
||||||
coverAlt: Couverture — Une économie du don, enfin concevable
|
coverAlt: Couverture — Une économie du don, enfin concevable
|
||||||
cta:
|
cta:
|
||||||
player: Présentation musicale
|
player: Présentation musicale
|
||||||
@@ -49,6 +49,12 @@ axes:
|
|||||||
presentation:
|
presentation:
|
||||||
title: wishBounty
|
title: wishBounty
|
||||||
text: Application pour le financement fléché des développements.
|
text: Application pour le financement fléché des développements.
|
||||||
|
actions:
|
||||||
|
- id: open-dons
|
||||||
|
label: Voir la v1 bridée (premier flux)
|
||||||
|
icon: external-link
|
||||||
|
highlight: true
|
||||||
|
href: https://axiom-team.fr/dons
|
||||||
- label: Authentification — WoT
|
- label: Authentification — WoT
|
||||||
description: "Une clé pour les accès, une signature pour les actes. Le seul système sans autorité centrale ni biométrie — DID W3C, Duniter, EUDI Wallet 2026."
|
description: "Une clé pour les accès, une signature pour les actes. Le seul système sans autorité centrale ni biométrie — DID W3C, Duniter, EUDI Wallet 2026."
|
||||||
to: /numerique/authentification-wot
|
to: /numerique/authentification-wot
|
||||||
@@ -101,6 +107,15 @@ axes:
|
|||||||
to: /economique/productions-collectives
|
to: /economique/productions-collectives
|
||||||
gestation: true
|
gestation: true
|
||||||
icon: users
|
icon: users
|
||||||
|
presentation:
|
||||||
|
title: Ğ1flux
|
||||||
|
text: Observatoire des transactions, premier élément d'un observatoire de l'économie monnaie-libriste.
|
||||||
|
actions:
|
||||||
|
- id: open-g1flux
|
||||||
|
label: Ouvrir Ğ1flux
|
||||||
|
icon: external-link
|
||||||
|
highlight: true
|
||||||
|
href: https://g1flux.syoul.fr
|
||||||
politique:
|
politique:
|
||||||
title: Autonomie citoyenne
|
title: Autonomie citoyenne
|
||||||
icon: landmark
|
icon: landmark
|
||||||
@@ -109,12 +124,24 @@ axes:
|
|||||||
description: Se donner les moyens de la décision collective.
|
description: Se donner les moyens de la décision collective.
|
||||||
to: /citoyenne/decision
|
to: /citoyenne/decision
|
||||||
icon: gavel
|
icon: gavel
|
||||||
|
actions:
|
||||||
|
- id: open-libredecision
|
||||||
|
label: Ouvrir libreDecision
|
||||||
|
icon: external-link
|
||||||
|
highlight: true
|
||||||
|
href: https://decision.librodrome.org
|
||||||
- label: Tarifs de l'eau
|
- label: Tarifs de l'eau
|
||||||
description: Application pour obtenir justice sociale et incitation dynamique à la réduction. Permet de confier la
|
description: Application pour obtenir justice sociale et incitation dynamique à la réduction. Permet de confier la
|
||||||
décision à la population des communes.
|
décision à la population des communes.
|
||||||
to: /citoyenne/tarifs-eau
|
to: /citoyenne/tarifs-eau
|
||||||
gestation: true
|
gestation: true
|
||||||
icon: droplets
|
icon: droplets
|
||||||
|
actions:
|
||||||
|
- id: open-sejeteral0
|
||||||
|
label: Ouvrir SejeteralO
|
||||||
|
icon: external-link
|
||||||
|
highlight: true
|
||||||
|
href: https://collectivites.librodrome.org
|
||||||
gratewizard:
|
gratewizard:
|
||||||
title: grateWizard
|
title: grateWizard
|
||||||
subtitle: Un utilitaire pratique pour estimer les valeurs de façon relative
|
subtitle: Un utilitaire pratique pour estimer les valeurs de façon relative
|
||||||
|
|||||||
@@ -18,6 +18,11 @@ pillars:
|
|||||||
text: Application pour le financement fléché des développements libres.
|
text: Application pour le financement fléché des développements libres.
|
||||||
gestation: true
|
gestation: true
|
||||||
to: /numerique/logiciel-libre
|
to: /numerique/logiciel-libre
|
||||||
|
actions:
|
||||||
|
- label: Voir la v1 bridée (premier flux)
|
||||||
|
icon: external-link
|
||||||
|
highlight: true
|
||||||
|
href: https://axiom-team.fr/dons
|
||||||
|
|
||||||
- id: authentification-wot
|
- id: authentification-wot
|
||||||
label: "Authentification — WoT"
|
label: "Authentification — WoT"
|
||||||
|
|||||||
@@ -24,19 +24,19 @@ sections:
|
|||||||
title: Mots de passe
|
title: Mots de passe
|
||||||
text: >
|
text: >
|
||||||
Stockés côté serveur, volés par millions. Have I Been Pwned recense
|
Stockés côté serveur, volés par millions. Have I Been Pwned recense
|
||||||
des milliards de comptes compromis. Ta sécurité dépend intégralement
|
des milliards de comptes compromis. Votre sécurité dépend intégralement
|
||||||
de la sécurité d'un tiers que tu ne contrôles pas.
|
de la sécurité d'un tiers que vous ne contrôlez pas.
|
||||||
- icon: chrome
|
- icon: chrome
|
||||||
title: "Se connecter avec Google"
|
title: "Se connecter avec Google"
|
||||||
text: >
|
text: >
|
||||||
Déléguer l'authentification à Google ou Meta, c'est en faire l'infrastructure
|
Déléguer l'authentification à Google ou Meta, c'est en faire l'infrastructure
|
||||||
d'identité mondiale. Un compte suspendu = toutes tes connexions coupées.
|
d'identité mondiale. Un compte suspendu = toutes vos connexions coupées.
|
||||||
Ces entreprises savent quand, où et à quoi tu te connectes.
|
Ces entreprises savent quand, où et à quoi vous vous connectez.
|
||||||
- icon: fingerprint
|
- icon: fingerprint
|
||||||
title: Biométrie — irrévocable
|
title: Biométrie — irrévocable
|
||||||
text: >
|
text: >
|
||||||
Un mot de passe compromis se change. Une empreinte digitale jamais.
|
Un mot de passe compromis se change. Une empreinte digitale jamais.
|
||||||
Si tes données biométriques sont volées — et elles l'ont été
|
Si vos données biométriques sont volées — et elles l'ont été
|
||||||
(OPM breach USA 2015, 5,6 millions d'empreintes) — la compromission est définitive.
|
(OPM breach USA 2015, 5,6 millions d'empreintes) — la compromission est définitive.
|
||||||
- icon: building-2
|
- icon: building-2
|
||||||
title: Autorités de Certification (X.509)
|
title: Autorités de Certification (X.509)
|
||||||
@@ -49,22 +49,22 @@ sections:
|
|||||||
title: Comment fonctionne une Web of Trust
|
title: Comment fonctionne une Web of Trust
|
||||||
steps:
|
steps:
|
||||||
- n: 1
|
- n: 1
|
||||||
title: Générer une paire de clés sur ta machine
|
title: Générer une paire de clés sur votre machine
|
||||||
text: >
|
text: >
|
||||||
Clé privée (secrète, ne quitte jamais ton appareil) et clé publique
|
Clé privée (secrète, ne quitte jamais votre appareil) et clé publique
|
||||||
(diffusée librement). Ce qui est signé avec la clé privée peut être
|
(diffusée librement). Ce qui est signé avec la clé privée peut être
|
||||||
vérifié par n'importe qui avec la clé publique.
|
vérifié par n'importe qui avec la clé publique.
|
||||||
tip: "Algorithme recommandé : Ed25519 — clé courte, rapide, sans paramètres douteux"
|
tip: "Algorithme recommandé : Ed25519 — clé courte, rapide, sans paramètres douteux"
|
||||||
- n: 2
|
- n: 2
|
||||||
title: Publier ta clé publique
|
title: Publier votre clé publique
|
||||||
text: >
|
text: >
|
||||||
Dans un DID Document, sur un keyserver, dans ton profil Duniter.
|
Dans un DID Document, sur un keyserver, dans votre profil Duniter.
|
||||||
N'importe qui peut vérifier tes signatures. Personne ne peut usurper
|
N'importe qui peut vérifier vos signatures. Personne ne peut usurper
|
||||||
ton identité sans ta clé privée.
|
votre identité sans votre clé privée.
|
||||||
- n: 3
|
- n: 3
|
||||||
title: Être certifié par des membres certifiés
|
title: Être certifié par des membres certifiés
|
||||||
text: >
|
text: >
|
||||||
Dans Duniter, 5 membres certifiés (sigQty=5) certifient que tu es une personne réelle,
|
Dans Duniter, 5 membres certifiés (sigQty=5) certifient que vous êtes une personne réelle,
|
||||||
dans un rayon de 3 sauts du nœud sentinelle (stepMax=3).
|
dans un rayon de 3 sauts du nœud sentinelle (stepMax=3).
|
||||||
La confiance émerge du graphe — pas d'un serveur central.
|
La confiance émerge du graphe — pas d'un serveur central.
|
||||||
- n: 4
|
- n: 4
|
||||||
@@ -78,7 +78,7 @@ sections:
|
|||||||
text: >
|
text: >
|
||||||
La même clé permet de s'authentifier à des services
|
La même clé permet de s'authentifier à des services
|
||||||
et de signer des actes (documents, transactions, votes).
|
et de signer des actes (documents, transactions, votes).
|
||||||
Tout ce que tu fais est vérifiable. Rien n'est transmissible à ton insu.
|
Tout ce que vous faites est vérifiable. Rien n'est transmissible à votre insu.
|
||||||
|
|
||||||
- type: insight
|
- type: insight
|
||||||
title: Le seul système sans autorité centrale ni biométrie
|
title: Le seul système sans autorité centrale ni biométrie
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ title: Cloud libre
|
|||||||
icon: cloud
|
icon: cloud
|
||||||
|
|
||||||
description: >
|
description: >
|
||||||
Tes données vivent dans des datacenters dont tu ne connais pas l'adresse,
|
Vos données vivent dans des datacenters dont vous ne connaissez pas l'adresse,
|
||||||
sous des juridictions que tu ne contrôles pas. Sortir de là est possible —
|
sous des juridictions que vous ne contrôlez pas. Sortir de là est possible —
|
||||||
en trois paliers : auto-hébergement, services fédérés, architectures décentralisées.
|
en trois paliers : auto-hébergement, services fédérés, architectures décentralisées.
|
||||||
Ces dernières émergent à peine.
|
Ces dernières émergent à peine.
|
||||||
|
|
||||||
@@ -23,13 +23,13 @@ sections:
|
|||||||
- icon: shield
|
- icon: shield
|
||||||
title: RGPD — conformité structurelle
|
title: RGPD — conformité structurelle
|
||||||
text: >
|
text: >
|
||||||
Google, Amazon, Microsoft stockent tes données sous juridiction américaine (CLOUD Act).
|
Google, Amazon, Microsoft stockent vos données sous juridiction américaine (CLOUD Act).
|
||||||
L'auto-hébergement ou un hébergeur associatif européen offre
|
L'auto-hébergement ou un hébergeur associatif européen offre
|
||||||
une conformité RGPD structurelle — pas seulement déclarative.
|
une conformité RGPD structurelle — pas seulement déclarative.
|
||||||
- icon: eye-off
|
- icon: eye-off
|
||||||
title: Vie privée des données
|
title: Vie privée des données
|
||||||
text: >
|
text: >
|
||||||
Dans un cloud commercial, tes documents, photos et communications
|
Dans un cloud commercial, vos documents, photos et communications
|
||||||
sont analysés, profilés, monétisés. Ce n'est pas une théorie —
|
sont analysés, profilés, monétisés. Ce n'est pas une théorie —
|
||||||
c'est le modèle économique déclaré.
|
c'est le modèle économique déclaré.
|
||||||
- icon: zap
|
- icon: zap
|
||||||
@@ -40,10 +40,10 @@ sections:
|
|||||||
Héberger chez soi ou mutualiser dans un hébergeur associatif
|
Héberger chez soi ou mutualiser dans un hébergeur associatif
|
||||||
réduit l'empreinte réelle.
|
réduit l'empreinte réelle.
|
||||||
- icon: key
|
- icon: key
|
||||||
title: Propriété réelle de tes données
|
title: Propriété réelle de vos données
|
||||||
text: >
|
text: >
|
||||||
Un service cloud peut fermer, changer ses CGU, couper ton compte.
|
Un service cloud peut fermer, changer ses CGU, couper votre compte.
|
||||||
Tes données te reviennent quand tu les héberges toi-même —
|
Vos données vous reviennent quand vous les hébergez vous-même —
|
||||||
portabilité réelle, pas juste un bouton "Exporter".
|
portabilité réelle, pas juste un bouton "Exporter".
|
||||||
- icon: git-branch
|
- icon: git-branch
|
||||||
title: Résilience décentralisée
|
title: Résilience décentralisée
|
||||||
@@ -60,7 +60,7 @@ sections:
|
|||||||
icon: server
|
icon: server
|
||||||
badge: Maîtrise totale
|
badge: Maîtrise totale
|
||||||
text: >
|
text: >
|
||||||
Ton propre serveur chez toi ou chez un hébergeur associatif.
|
Votre propre serveur chez vous ou chez un hébergeur associatif.
|
||||||
Contrôle absolu, RGPD pleinement compatible.
|
Contrôle absolu, RGPD pleinement compatible.
|
||||||
YunoHost installe plus de 200 applications libres en un clic.
|
YunoHost installe plus de 200 applications libres en un clic.
|
||||||
tools:
|
tools:
|
||||||
@@ -74,8 +74,8 @@ sections:
|
|||||||
badge: Interconnecté
|
badge: Interconnecté
|
||||||
text: >
|
text: >
|
||||||
Des instances indépendantes interconnectées via des protocoles ouverts
|
Des instances indépendantes interconnectées via des protocoles ouverts
|
||||||
(ActivityPub, Matrix). Tu rejoins un serveur de confiance
|
(ActivityPub, Matrix). Vous rejoignez un serveur de confiance
|
||||||
ou héberges le tien. Les données restent chez l'opérateur choisi.
|
ou hébergez le vôtre. Les données restent chez l'opérateur choisi.
|
||||||
tools:
|
tools:
|
||||||
- "Mastodon — réseau social fédéré"
|
- "Mastodon — réseau social fédéré"
|
||||||
- "PeerTube — vidéo hébergée et fédérée"
|
- "PeerTube — vidéo hébergée et fédérée"
|
||||||
@@ -175,7 +175,7 @@ sections:
|
|||||||
C'est une tension réelle, non résolue, que le législateur européen commence à instruire
|
C'est une tension réelle, non résolue, que le législateur européen commence à instruire
|
||||||
(lignes directrices CEPD 02/2025).
|
(lignes directrices CEPD 02/2025).
|
||||||
points:
|
points:
|
||||||
- "Auto-hébergement : conformité RGPD totale — tu effaces ce que tu veux"
|
- "Auto-hébergement : conformité RGPD totale — vous effacez ce que vous voulez"
|
||||||
- "Services fédérés : dépend de l'opérateur de l'instance — choisir un hébergeur CHATONS"
|
- "Services fédérés : dépend de l'opérateur de l'instance — choisir un hébergeur CHATONS"
|
||||||
- "IPFS / blockchain : chiffrer + détruire la clé (la donnée devient inintelligible). Accepté par la CNIL sous conditions strictes."
|
- "IPFS / blockchain : chiffrer + détruire la clé (la donnée devient inintelligible). Accepté par la CNIL sous conditions strictes."
|
||||||
- "Off-chain data : stocker uniquement un hash sur la chaîne, les données personnelles sur un serveur effaçable — approche la plus propre"
|
- "Off-chain data : stocker uniquement un hash sur la chaîne, les données personnelles sur un serveur effaçable — approche la plus propre"
|
||||||
|
|||||||
@@ -39,20 +39,20 @@ sections:
|
|||||||
- icon: users
|
- icon: users
|
||||||
title: Bien commun
|
title: Bien commun
|
||||||
text: >
|
text: >
|
||||||
Le logiciel libre est un commun numérique. Tu n'es pas client, tu participes.
|
Le logiciel libre est un commun numérique. Vous n'êtes pas client, vous participez.
|
||||||
Cohérent avec l'économie du don — l'argent économisé sur les licences
|
Cohérent avec l'économie du don — l'argent économisé sur les licences
|
||||||
peut financer des communs.
|
peut financer des communs.
|
||||||
- icon: lock-open
|
- icon: lock-open
|
||||||
title: Indépendance réelle
|
title: Indépendance réelle
|
||||||
text: >
|
text: >
|
||||||
Pas de mise à jour forcée qui casse le système. Pas d'abonnement qui expire
|
Pas de mise à jour forcée qui casse le système. Pas d'abonnement qui expire
|
||||||
et verrouille tes fichiers. Ton OS t'appartient vraiment.
|
et verrouille vos fichiers. Votre OS vous appartient vraiment.
|
||||||
- icon: bot
|
- icon: bot
|
||||||
title: Assistance permanente gratuite
|
title: Assistance permanente gratuite
|
||||||
text: >
|
text: >
|
||||||
Un LLM open source installé localement (Ollama + phi3:mini, 4 Go de RAM)
|
Un LLM open source installé localement (Ollama + phi3:mini, 4 Go de RAM)
|
||||||
peut rédiger les commandes shell dans ton terminal sur simple demande en français.
|
peut rédiger les commandes shell dans votre terminal sur simple demande en français.
|
||||||
Sans connexion à aucun serveur. Sans abonnement. Sans envoyer tes données.
|
Sans connexion à aucun serveur. Sans abonnement. Sans envoyer vos données.
|
||||||
|
|
||||||
- type: fiche
|
- type: fiche
|
||||||
title: "Fiche de migration — 6 étapes"
|
title: "Fiche de migration — 6 étapes"
|
||||||
@@ -65,8 +65,8 @@ sections:
|
|||||||
- n: 2
|
- n: 2
|
||||||
title: Tester sans risque — Live USB
|
title: Tester sans risque — Live USB
|
||||||
text: >
|
text: >
|
||||||
Démarre Linux Mint depuis une clé USB. Tu explores, tu testes LibreOffice,
|
Démarrez Linux Mint depuis une clé USB. Vous explorez, testez LibreOffice,
|
||||||
tu vérifies le WiFi — sans toucher à ton Windows existant.
|
vérifiez le WiFi — sans toucher à votre Windows existant.
|
||||||
tip: "Créer la clé : balenaEtcher (gratuit) + image Linux Mint sur linuxmint.com"
|
tip: "Créer la clé : balenaEtcher (gratuit) + image Linux Mint sur linuxmint.com"
|
||||||
- n: 3
|
- n: 3
|
||||||
title: Choisir sa distribution
|
title: Choisir sa distribution
|
||||||
@@ -77,8 +77,8 @@ sections:
|
|||||||
- n: 4
|
- n: 4
|
||||||
title: Installer — double-boot ou remplacement
|
title: Installer — double-boot ou remplacement
|
||||||
text: >
|
text: >
|
||||||
Double-boot si tu veux garder Windows le temps de la transition.
|
Double-boot si vous voulez garder Windows le temps de la transition.
|
||||||
Remplacement complet quand tu es prêt·e — c'est le vrai départ.
|
Remplacement complet quand vous êtes prêt·e — c'est le vrai départ.
|
||||||
- n: 5
|
- n: 5
|
||||||
title: Installer ses équivalents libres
|
title: Installer ses équivalents libres
|
||||||
text: >
|
text: >
|
||||||
@@ -175,9 +175,9 @@ sections:
|
|||||||
- type: llm
|
- type: llm
|
||||||
title: Un assistant shell local — sans cloud
|
title: Un assistant shell local — sans cloud
|
||||||
text: >
|
text: >
|
||||||
Un petit LLM open source sur ta machine répond à tes questions sur les commandes Linux
|
Un petit LLM open source sur votre machine répond à vos questions sur les commandes Linux
|
||||||
en langage naturel, sans envoyer aucune donnée à un serveur distant.
|
en langage naturel, sans envoyer aucune donnée à un serveur distant.
|
||||||
Il suggère. Toi tu décides.
|
Il suggère. Vous décidez.
|
||||||
tool: Ollama
|
tool: Ollama
|
||||||
tool_url: https://ollama.com
|
tool_url: https://ollama.com
|
||||||
model: "phi3:mini"
|
model: "phi3:mini"
|
||||||
@@ -187,7 +187,7 @@ sections:
|
|||||||
- "ollama pull phi3:mini"
|
- "ollama pull phi3:mini"
|
||||||
- "ollama run phi3:mini \"Comment trouver les fichiers modifiés depuis 7 jours ?\""
|
- "ollama run phi3:mini \"Comment trouver les fichiers modifiés depuis 7 jours ?\""
|
||||||
rules:
|
rules:
|
||||||
- Lis chaque commande avant de l'exécuter — le LLM suggère, tu valides
|
- Lisez chaque commande avant de l'exécuter — le LLM suggère, vous validez
|
||||||
- "Utilise --dry-run ou -n quand disponible (rsync, make…)"
|
- "Utilise --dry-run ou -n quand disponible (rsync, make…)"
|
||||||
- Pour les commandes root/sudo, vérification manuelle obligatoire
|
- Pour les commandes root/sudo, vérification manuelle obligatoire
|
||||||
- "Préfère phi3:mini ou qwen2.5-coder — pas les modèles uncensored"
|
- "Préfère phi3:mini ou qwen2.5-coder — pas les modèles uncensored"
|
||||||
|
|||||||
Reference in New Issue
Block a user