diff --git a/app/app.config.ts b/app/app.config.ts
index f899a6e..4cab6a7 100644
--- a/app/app.config.ts
+++ b/app/app.config.ts
@@ -21,9 +21,9 @@ export default defineAppConfig({
},
},
libredecision: {
- url: import.meta.dev ? 'http://localhost:3002' : 'https://decision.librodrome.org',
+ url: 'https://decision.librodrome.org',
},
sejeteral0: {
- url: import.meta.dev ? 'http://localhost:3009' : 'https://collectivites.librodrome.org',
+ url: 'https://collectivites.librodrome.org',
},
})
diff --git a/app/assets/css/main.css b/app/assets/css/main.css
index c20a663..fb2a669 100644
--- a/app/assets/css/main.css
+++ b/app/assets/css/main.css
@@ -16,7 +16,7 @@
--sidebar-width: 280px;
--font-display: 'Bricolage Grotesque', system-ui, sans-serif;
- --font-sans: 'Atkinson Hyperlegible', system-ui, sans-serif;
+ --font-sans: 'Public Sans', system-ui, sans-serif;
--font-logo: 'Syne', sans-serif;
/* One hue per axis — overridden by each palette (stores/palette.ts) */
@@ -29,6 +29,13 @@
--ease-in-out-expo: cubic-bezier(0.87, 0, 0.13, 1);
}
+/* Fader level of the home console — registered so it can transition smoothly */
+@property --level {
+ syntax: '';
+ inherits: true;
+ initial-value: 0;
+}
+
*,
*::before,
*::after {
diff --git a/app/components/book/BookPlayer.vue b/app/components/book/BookPlayer.vue
index 5ac31a5..3e8cf4e 100644
--- a/app/components/book/BookPlayer.vue
+++ b/app/components/book/BookPlayer.vue
@@ -378,6 +378,15 @@ function onTouchEnd(e: TouchEvent) {
}
// ── Lifecycle ──
+// While the reader is open, the persistent mini player (above it) lifts up
+// so it does not cover the reader navigation.
+if (import.meta.client) {
+ watch(isOpen, (open) => {
+ document.documentElement.classList.toggle('book-player-open', open)
+ }, { immediate: true })
+ onUnmounted(() => document.documentElement.classList.remove('book-player-open'))
+}
+
watch(isOpen, async (open) => {
if (open) {
showSommaire.value = false
diff --git a/app/components/home/AxisBlock.vue b/app/components/home/AxisBlock.vue
deleted file mode 100644
index a235edc..0000000
--- a/app/components/home/AxisBlock.vue
+++ /dev/null
@@ -1,379 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ item.label }}
-
-
- En gestation
-
-
-
-
{{ item.description }}
-
-
-
-
-
{{ item.presentation.text }}
-
-
-
-
-
-
-
-
-
- {{ action.label }}
-
-
-
-
-
-
-
- {{ action.label }}
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/components/home/AxisGrid.vue b/app/components/home/AxisGrid.vue
deleted file mode 100644
index 92ea63b..0000000
--- a/app/components/home/AxisGrid.vue
+++ /dev/null
@@ -1,237 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ evenement.title }}
-
-
- {{ evenement.subtitle }}
-
-
-
-
-
- En gestation
-
-
-
-
-
-
-
-
-
-
diff --git a/app/components/home/BookSection.vue b/app/components/home/BookSection.vue
index 5ebe77c..ac835ae 100644
--- a/app/components/home/BookSection.vue
+++ b/app/components/home/BookSection.vue
@@ -89,9 +89,9 @@ const titleLine2 = computed(() => titleParts.value[1])
.book-cover-3d {
width: 100%;
max-width: 360px;
- aspect-ratio: 3 / 4;
- background-size: 200% auto;
- background-position: right center;
+ aspect-ratio: 1756 / 2480;
+ background-size: cover;
+ background-position: center;
background-repeat: no-repeat;
border-radius: 0.75rem;
overflow: hidden;
diff --git a/app/components/home/HeroSection.vue b/app/components/home/HeroSection.vue
deleted file mode 100644
index 99fcb82..0000000
--- a/app/components/home/HeroSection.vue
+++ /dev/null
@@ -1,120 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/components/home/HomeConsole.vue b/app/components/home/HomeConsole.vue
index 00574f4..d74fba4 100644
--- a/app/components/home/HomeConsole.vue
+++ b/app/components/home/HomeConsole.vue
@@ -1,17 +1,25 @@
-
+
-
+
- je m'affranchis
-
- je subis
+ {{ bottomLabel }}
+
+ {{ topLabel }}
@@ -61,7 +72,7 @@
{{ tool.label }}
{{ tool.presentation.title }}
- en gestation
+ {{ (tool.status || 'En gestation').toLowerCase() }}
@@ -89,6 +100,9 @@ import type { AxisKey, HomeAxis, HomeStack } from '~/composables/useHomeData'
const props = withDefaults(defineProps<{
axes: HomeAxis[]
+ prefix?: string
+ topLabel?: string
+ bottomLabel?: string
upstreamLabel?: string
stacks?: HomeStack[]
stacksLabel?: string
@@ -96,6 +110,9 @@ const props = withDefaults(defineProps<{
maxStacks?: number
compact?: boolean
}>(), {
+ prefix: 'Autonomie',
+ topLabel: 'Émancipation',
+ bottomLabel: 'Asservissement',
maxStacks: 8,
upstreamLabel: 'en amont',
stacksLabel: 'En service',
@@ -104,21 +121,58 @@ const props = withDefaults(defineProps<{
const active = defineModel
({ default: 'numerique' })
const uid = useId()
+const root = ref(null)
const tabs = ref([])
-const ready = ref(false)
const current = computed(() => props.axes.find(a => a.key === active.value) ?? props.axes[0])
-// Faders rise from « je subis » once mounted; the selected one is pushed to the top.
-function level(key: AxisKey) {
- if (!ready.value) return 0
- return key === active.value ? 0.94 : 0.3
+// ── Motion ──────────────────────────────────────────────────────────────
+// The cursors move on their own, slowly: each one drifts, and every few
+// beats another axis is pushed up towards émancipation. Visitors watch it;
+// they can still pick an axis (click, arrows), which stops the rotation.
+// SSR renders every cursor at the bottom, so the first move is the rise.
+const BEAT_MS = 2600
+const BEATS_PER_AXIS = 3
+
+const levels = reactive>>({})
+const reduced = ref(false)
+const hovered = ref(false)
+const visible = ref(true)
+const chosen = ref(false)
+let beat = 0
+let timer: ReturnType | undefined
+
+function place() {
+ props.axes.forEach((axis, i) => {
+ const isActive = axis.key === active.value
+ if (reduced.value) {
+ levels[axis.key] = isActive ? 0.94 : 0.3
+ return
+ }
+ const wave = Math.sin(beat * 0.9 + i * 2.1)
+ levels[axis.key] = isActive ? 0.87 + 0.06 * wave : 0.3 + 0.14 * wave
+ })
}
-function select(key: AxisKey) {
+function advance() {
+ const idx = props.axes.findIndex(a => a.key === active.value)
+ active.value = props.axes[(idx + 1) % props.axes.length]!.key
+}
+
+function onBeat() {
+ if (!visible.value) return
+ beat++
+ if (!chosen.value && !hovered.value && beat % BEATS_PER_AXIS === 0) advance()
+ place()
+}
+
+function choose(key: AxisKey) {
+ chosen.value = true
active.value = key
}
+watch(active, place)
+
function onKeydown(e: KeyboardEvent) {
const keys = ['ArrowRight', 'ArrowLeft', 'ArrowUp', 'ArrowDown', 'Home', 'End']
if (!keys.includes(e.key)) return
@@ -130,12 +184,23 @@ function onKeydown(e: KeyboardEvent) {
if (e.key === 'ArrowLeft' || e.key === 'ArrowDown') next = idx === 0 ? last : idx - 1
if (e.key === 'Home') next = 0
if (e.key === 'End') next = last
- active.value = props.axes[next]!.key
+ choose(props.axes[next]!.key)
nextTick(() => tabs.value[next]?.focus())
}
onMounted(() => {
- requestAnimationFrame(() => { ready.value = true })
+ reduced.value = window.matchMedia('(prefers-reduced-motion: reduce)').matches
+ requestAnimationFrame(place)
+ if (reduced.value) return
+
+ timer = setInterval(onBeat, BEAT_MS)
+ const io = new IntersectionObserver(([entry]) => { visible.value = !!entry?.isIntersecting })
+ if (root.value) io.observe(root.value)
+
+ onUnmounted(() => {
+ clearInterval(timer)
+ io.disconnect()
+ })
})
@@ -155,7 +220,7 @@ onMounted(() => {
.console-board {
display: grid;
grid-template-columns: auto 1fr;
- gap: 0.75rem;
+ gap: 0.9rem;
padding: 1.25rem 1.25rem 1rem;
border-radius: 16px;
background: hsl(var(--color-surface));
@@ -164,32 +229,34 @@ onMounted(() => {
0 18px 40px -24px hsl(var(--color-text) / 0.35);
}
+/* Vertical scale, read from bottom to top */
.console-scale {
display: flex;
- flex-direction: column;
+ align-items: center;
justify-content: space-between;
- align-items: flex-end;
+ gap: 0.5rem;
height: var(--track-h);
- font-family: var(--font-mono);
- font-size: 0.62rem;
- line-height: 1.2;
- color: hsl(var(--color-text) / 0.45);
- text-align: right;
+ writing-mode: vertical-rl;
+ transform: rotate(180deg);
+ font-family: var(--font-display);
+ font-size: 0.72rem;
+ letter-spacing: 0.02em;
white-space: nowrap;
}
-.console-ticks {
- display: flex;
- flex-direction: column;
- gap: calc(var(--track-h) / 9);
- align-items: flex-end;
+.console-scale-bottom {
+ color: hsl(var(--color-text) / 0.45);
}
-.console-ticks i {
- display: block;
- width: 0.5rem;
- height: 1px;
- background: hsl(var(--color-text) / 0.2);
+.console-scale-top {
+ font-weight: 800;
+ color: hsl(var(--color-text));
+}
+
+.console-scale-line {
+ flex: 1;
+ width: 1px;
+ background: linear-gradient(to bottom, hsl(var(--color-text) / 0.12), hsl(var(--color-text) / 0.45));
}
.console-faders {
@@ -203,15 +270,13 @@ onMounted(() => {
display: flex;
flex-direction: column;
align-items: center;
- gap: 0.5rem;
+ gap: 0.55rem;
padding: 0 0.25rem 0.25rem;
border-radius: 12px;
cursor: pointer;
- transition: background 0.2s ease;
-}
-
-.fader:hover {
- background: hsl(var(--hue) / 0.06);
+ /* --level is a registered property (main.css): the cursors glide */
+ transition: --level 2.6s cubic-bezier(0.45, 0.05, 0.55, 0.95);
+ transition-delay: calc(var(--i) * 160ms);
}
.fader:focus-visible {
@@ -233,6 +298,7 @@ onMounted(() => {
height: calc(var(--level) * (var(--track-h) - var(--cap-h)) + var(--cap-h) / 2);
border-radius: inherit;
background: hsl(var(--hue) / 0.45);
+ transition: background 0.6s ease;
}
.fader--active .fader-fill {
@@ -267,25 +333,28 @@ onMounted(() => {
background: hsl(var(--hue));
}
-.console--ready .fader-fill,
-.console--ready .fader-cap {
- transition:
- height 0.7s var(--ease-out-expo),
- transform 0.7s var(--ease-out-expo);
+.fader-label {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ line-height: 1.1;
}
-/* Orchestrated first rise: one fader after the other */
-.console--ready .fader-fill,
-.console--ready .fader-cap {
- transition-delay: calc(var(--i) * 90ms);
+.fader-prefix {
+ font-family: var(--font-mono);
+ font-size: 0.6rem;
+ letter-spacing: 0.14em;
+ text-transform: uppercase;
+ color: hsl(var(--color-text) / 0.5);
}
.fader-name {
font-family: var(--font-display);
- font-weight: 700;
- font-size: 0.95rem;
+ font-weight: 800;
+ font-size: 1.05rem;
letter-spacing: -0.01em;
- color: hsl(var(--color-text) / 0.6);
+ color: hsl(var(--color-text) / 0.65);
+ transition: color 0.6s ease;
}
.fader--active .fader-name {
@@ -293,11 +362,21 @@ onMounted(() => {
}
.fader-lever {
- font-family: var(--font-mono);
- font-size: 0.64rem;
- line-height: 1.25;
+ padding: 0.2rem 0.55rem;
+ border-radius: 20px;
+ font-family: var(--font-display);
+ font-weight: 600;
+ font-size: 0.8rem;
+ line-height: 1.2;
text-align: center;
- color: hsl(var(--color-text) / 0.45);
+ color: hsl(var(--color-text) / 0.8);
+ background: hsl(var(--hue) / 0.1);
+ transition: background 0.6s ease, color 0.6s ease;
+}
+
+.fader--active .fader-lever {
+ color: hsl(var(--color-bg));
+ background: hsl(var(--hue));
}
/* ── Panel ── */
@@ -455,15 +534,19 @@ onMounted(() => {
@media (max-width: 480px) {
.console { --track-h: 8.5rem; }
- .console-board { padding: 1rem 0.75rem 0.75rem; gap: 0.4rem; }
- .console-scale { white-space: normal; max-width: 3.4rem; font-size: 0.56rem; }
+ .console-board { padding: 1rem 0.6rem 0.75rem; gap: 0.35rem; }
+ .console-scale { font-size: 0.62rem; }
.fader-cap { width: 2.2rem; margin-left: -1.1rem; }
- .fader-name { font-size: 0.85rem; }
+ .fader-name { font-size: 0.88rem; }
+ .fader-prefix { font-size: 0.52rem; letter-spacing: 0.08em; }
+ .fader-lever { font-size: 0.68rem; padding: 0.2rem 0.4rem; }
}
@media (prefers-reduced-motion: reduce) {
- .console--ready .fader-fill,
- .console--ready .fader-cap,
+ .fader,
+ .fader-fill,
+ .fader-name,
+ .fader-lever,
.panel-enter-active,
.panel-leave-active {
transition: none;
diff --git a/app/components/home/HomeVariantConsole.vue b/app/components/home/HomeLanding.vue
similarity index 77%
rename from app/components/home/HomeVariantConsole.vue
rename to app/components/home/HomeLanding.vue
index c2491ad..411f35c 100644
--- a/app/components/home/HomeVariantConsole.vue
+++ b/app/components/home/HomeLanding.vue
@@ -9,7 +9,12 @@
{{ heading[0] }}
{{ heading[1] }}
- {{ heading[3] }}
+
+
+ {{ seg.text }}
+ {{ seg.text }}
+
+
{{ heading[4] }}
{{ hero.approach }}
@@ -19,6 +24,9 @@
@@ -113,16 +116,15 @@ a.tool-name:hover {
color: hsl(var(--hue));
}
-.tool-state {
+.tool-status {
margin-left: 0.4rem;
- font-family: var(--font-mono);
- font-size: 0.64rem;
- color: hsl(var(--color-text) / 0.5);
- white-space: nowrap;
}
-.tool-state::before {
- content: '◌ ';
+.tool-note {
+ align-self: center;
+ font-family: var(--font-mono);
+ font-size: 0.66rem;
+ color: hsl(var(--axis-cit));
}
.tool-desc {
diff --git a/app/components/home/HomeToolbox.vue b/app/components/home/HomeToolbox.vue
index f24a9f9..cc67fa5 100644
--- a/app/components/home/HomeToolbox.vue
+++ b/app/components/home/HomeToolbox.vue
@@ -23,14 +23,14 @@
-
+
- {{ lpk.name }} {{ lpk.fullName }} — {{ lpk.text }}
+ {{ lpk.name }} {{ lpk.text }}
{{ s.name }}
@@ -65,7 +65,7 @@ import type { HomeAxis } from '~/composables/useHomeData'
withDefaults(defineProps<{
axes: HomeAxis[]
- lpk?: { name: string, fullName: string, text: string, to: string }
+ lpk?: { name: string, text: string, to: string }
bouquet?: { title: string, inService: { name?: string }[], stacks: unknown[] } | null
gratewizard?: { title: string, subtitle: string }
showPromise?: boolean
@@ -153,7 +153,7 @@ function onAction(id: string) {
gap: 0.5rem;
}
-/* LPK */
+/* LaPlanK */
.toolbox-lpk {
display: flex;
flex-direction: column;
diff --git a/app/components/home/HomeVariantEtabli.vue b/app/components/home/HomeVariantEtabli.vue
deleted file mode 100644
index 34cd621..0000000
--- a/app/components/home/HomeVariantEtabli.vue
+++ /dev/null
@@ -1,492 +0,0 @@
-
-
-
-
-
-
- {{ heading[0] }} {{ heading[1] }}
-
-
{{ format.text }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ axis.lever }}
-
- {{ axis.title }}
-
- {{ axis.promise }}
-
-
-
-
-
-
- Tout est en gestation sur cet axe —
- .
-
-
-
-
-
-
- {{ lpk.name }} {{ lpk.fullName }} — {{ lpk.text }}
-
-
-
-
-
-
- {{ cat.label }}
-
-
- -
-
- {{ s.role }}
- {{ s.name }}
- {{ s.name }}
- — {{ bouquet.statuses[s.status] }}
-
-
-
-
-
- {{ bouquet.title }}
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/components/home/HomeVariantParcours.vue b/app/components/home/HomeVariantParcours.vue
deleted file mode 100644
index d59dff9..0000000
--- a/app/components/home/HomeVariantParcours.vue
+++ /dev/null
@@ -1,321 +0,0 @@
-
-
-
-
-
-
-
-
-
-
- 1 / {{ steps.length }}
-
-
-
-
- 2 / {{ steps.length }}
- {{ steps[1]!.label }}
-
-
-
-
- 3 / {{ steps.length }}
- {{ steps[2]!.label }}
-
-
-
-
- 4 / {{ steps.length }}
-
-
-
-
-
-
-
-
-
-
diff --git a/app/components/home/HomeVariantSwitch.vue b/app/components/home/HomeVariantSwitch.vue
deleted file mode 100644
index 6d0b85b..0000000
--- a/app/components/home/HomeVariantSwitch.vue
+++ /dev/null
@@ -1,71 +0,0 @@
-
-
-
-
-
-
-
diff --git a/app/components/home/TypewriterText.vue b/app/components/home/TypewriterText.vue
deleted file mode 100644
index 49e82ca..0000000
--- a/app/components/home/TypewriterText.vue
+++ /dev/null
@@ -1,279 +0,0 @@
-
-
-
-
-
- {{ line }}
-
-
-
-
-
- En savoir plus
-
-
-
-
-
- {{ cite }}
-
-
-
-
-
-
{{ hero.approach }}
-
-
-
- {{ axis.label }}
- - {{ axis.value }}
-
-
-
-
-
-
-
{{ hero.audience }}
-
-
-
- {{ item.label }}
- - {{ item.value }}
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/components/layout/TheHeader.vue b/app/components/layout/TheHeader.vue
index e2ddaf1..0742f6d 100644
--- a/app/components/layout/TheHeader.vue
+++ b/app/components/layout/TheHeader.vue
@@ -3,22 +3,7 @@
diff --git a/app/pages/citoyenne/index.vue b/app/pages/citoyenne/index.vue
index 9769066..ba23bca 100644
--- a/app/pages/citoyenne/index.vue
+++ b/app/pages/citoyenne/index.vue
@@ -352,6 +352,7 @@
Décision collective
+
Se donner les moyens de la décision collective.
@@ -376,10 +377,7 @@
Tarifs de l'eau
-
-
- En gestation
-
+
Application pour obtenir justice sociale et incitation dynamique à la réduction.
@@ -404,6 +402,10 @@ definePageMeta({
})
const { data: content } = await usePageContent('citoyenne')
+const statusOf = (id: string) => {
+ const item = ((content.value as any)?.items ?? []).find((i: any) => i.id === id) ?? {}
+ return { status: item.status, since: item.since, gestation: item.gestation }
+}
const appConfig = useAppConfig()
const decisionUrl = (appConfig.libredecision as { url: string })?.url ?? '#'
@@ -449,6 +451,7 @@ useSeoPage({
.item-header {
display: flex;
+ flex-wrap: wrap;
align-items: center;
gap: 0.75rem;
}
diff --git a/app/pages/economique/index.vue b/app/pages/economique/index.vue
index 819ef1a..b1de775 100644
--- a/app/pages/economique/index.vue
+++ b/app/pages/economique/index.vue
@@ -396,6 +396,7 @@
Monnaie libre
+
La Ğ1 (June) : une monnaie co-créée par ses membres, sans dette ni intérêt. Le dividende universel comme base.
@@ -424,10 +425,7 @@
Productions collectives
-
-
- En gestation
-
+
Une plateforme pour faciliter la création d'équipes et la réalisation de productions à l'échelle des bassins de vie. Passer la seconde.
@@ -451,6 +449,10 @@ definePageMeta({
})
const { data: content } = await usePageContent('economique')
+const statusOf = (id: string) => {
+ const item = ((content.value as any)?.items ?? []).find((i: any) => i.id === id) ?? {}
+ return { status: item.status, since: item.since, gestation: item.gestation }
+}
useSeoPage({
title: content.value?.meta?.title ?? 'Autonomie économique',
@@ -484,6 +486,7 @@ const showPdfReader = ref(false)
.item-header {
display: flex;
+ flex-wrap: wrap;
align-items: center;
gap: 0.75rem;
}
diff --git a/app/pages/economique/monnaie-libre.vue b/app/pages/economique/monnaie-libre.vue
index 64cabff..de52be7 100644
--- a/app/pages/economique/monnaie-libre.vue
+++ b/app/pages/economique/monnaie-libre.vue
@@ -17,6 +17,7 @@
{{ content?.description }}
+
diff --git a/app/pages/economique/productions-collectives.vue b/app/pages/economique/productions-collectives.vue
index ebe62e6..c3b8932 100644
--- a/app/pages/economique/productions-collectives.vue
+++ b/app/pages/economique/productions-collectives.vue
@@ -18,10 +18,9 @@
{{ content?.description }}
-
-
- En gestation
-
+
+
+
diff --git a/app/pages/evenement.vue b/app/pages/evenement.vue
index 641c063..ba82940 100644
--- a/app/pages/evenement.vue
+++ b/app/pages/evenement.vue
@@ -332,20 +332,16 @@
{{ content?.kicker }}
+
+ {{ evtContent.format }}
+
{{ evtContent.subtitle }}
@@ -447,6 +443,14 @@ useSeoPage({