Refonte accueil : hero typo statique, axes icônes, menu italic, page numérique
- Hero : 5 lignes typographiques alternées (bold/light/accent/caps/italic), citations et axes dans un bloc discret dépliable - Icônes axes : Ğ1 custom, balance (éco don), graphe (WoT), marteau (décision), pictos plus lumineux (glow) - Menu : Autonomie en italique + grand, Événement majuscule - Page /autonomie renommée /numerique avec redirect 301 - Sceau hexagramme 益 Yì dans le layout, BookSection dans /modele-eco - Fonts Syne + Space Grotesk, dark theme éclairci - Popup GrateWizard agrandie (480×860) - Actions AxisBlock : primary côte à côte, secondary séparé dessous Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -16,8 +16,8 @@ export default defineAppConfig({
|
||||
gratewizard: {
|
||||
url: import.meta.dev ? 'http://localhost:3001' : 'https://gratewizard.axiom-team.fr',
|
||||
popup: {
|
||||
width: 420,
|
||||
height: 720,
|
||||
width: 480,
|
||||
height: 860,
|
||||
},
|
||||
},
|
||||
libredecision: {
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
/* This file provides fallback and utility classes */
|
||||
|
||||
.font-display {
|
||||
font-family: 'Outfit', system-ui, sans-serif;
|
||||
font-family: 'Syne', system-ui, sans-serif;
|
||||
}
|
||||
|
||||
.font-sans {
|
||||
font-family: 'Inter', system-ui, sans-serif;
|
||||
font-family: 'Space Grotesk', system-ui, sans-serif;
|
||||
}
|
||||
|
||||
.font-mono {
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
:root {
|
||||
--color-primary: 18 80% 45%;
|
||||
--color-accent: 32 85% 50%;
|
||||
--color-bg: 220 12% 15%;
|
||||
--color-surface: 220 10% 19%;
|
||||
--color-surface-light: 220 8% 24%;
|
||||
--color-bg: 215 8% 22%;
|
||||
--color-surface: 213 7% 27%;
|
||||
--color-surface-light: 210 6% 32%;
|
||||
--color-text: 0 0% 100%;
|
||||
--color-text-muted: 0 0% 65%;
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
--player-height: 0rem;
|
||||
--sidebar-width: 280px;
|
||||
|
||||
--font-display: 'Outfit', sans-serif;
|
||||
--font-sans: 'Inter', sans-serif;
|
||||
--font-display: 'Syne', sans-serif;
|
||||
--font-sans: 'Space Grotesk', sans-serif;
|
||||
--font-mono: 'JetBrains Mono', monospace;
|
||||
|
||||
--ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
|
||||
|
||||
@@ -24,7 +24,8 @@
|
||||
>
|
||||
<!-- Item icon -->
|
||||
<div v-if="item.icon" class="axis-item-icon" :class="`axis-item-icon--${color}`">
|
||||
<div :class="iconClass(item.icon)" class="h-5 w-5" />
|
||||
<span v-if="item.icon === 'g1'" class="axis-item-icon-g1">Ğ1</span>
|
||||
<div v-else :class="iconClass(item.icon)" class="h-5 w-5" />
|
||||
</div>
|
||||
|
||||
<h3 class="font-display text-lg font-semibold text-white mb-1">
|
||||
@@ -40,15 +41,31 @@
|
||||
|
||||
<!-- Actions zone (separate from card link) -->
|
||||
<div v-if="item.actions?.length" class="axis-actions">
|
||||
<button
|
||||
v-for="action in item.actions"
|
||||
:key="action.id"
|
||||
class="axis-action-btn"
|
||||
@click.stop="handleAction(action.id)"
|
||||
>
|
||||
<div :class="iconClass(action.icon)" class="h-3.5 w-3.5" />
|
||||
{{ action.label }}
|
||||
</button>
|
||||
<!-- Primary row -->
|
||||
<div class="axis-actions-row">
|
||||
<button
|
||||
v-for="action in primaryActions(item.actions)"
|
||||
:key="action.id"
|
||||
class="axis-action-btn"
|
||||
:class="{ 'axis-action-btn--highlight': action.highlight }"
|
||||
@click.stop="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">
|
||||
<button
|
||||
v-for="action in secondaryActions(item.actions)"
|
||||
:key="action.id"
|
||||
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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -60,6 +77,8 @@ interface AxisAction {
|
||||
id: string
|
||||
label: string
|
||||
icon: string
|
||||
highlight?: boolean
|
||||
secondary?: boolean
|
||||
}
|
||||
|
||||
interface AxisItem {
|
||||
@@ -86,6 +105,14 @@ const emit = defineEmits<{
|
||||
'launch-gratewizard': []
|
||||
}>()
|
||||
|
||||
function primaryActions(actions: AxisAction[]) {
|
||||
return actions.filter(a => !a.secondary)
|
||||
}
|
||||
|
||||
function secondaryActions(actions: AxisAction[]) {
|
||||
return actions.filter(a => a.secondary)
|
||||
}
|
||||
|
||||
function handleAction(id: string) {
|
||||
if (id === 'open-player') emit('open-player')
|
||||
else if (id === 'open-pdf') emit('open-pdf')
|
||||
@@ -179,13 +206,22 @@ function itemAttrs(item: AxisItem) {
|
||||
}
|
||||
|
||||
.axis-item-icon--primary {
|
||||
background: hsl(var(--color-primary) / 0.1);
|
||||
background: hsl(var(--color-primary) / 0.18);
|
||||
color: hsl(var(--color-primary));
|
||||
box-shadow: 0 0 14px hsl(var(--color-primary) / 0.15);
|
||||
}
|
||||
|
||||
.axis-item-icon--accent {
|
||||
background: hsl(var(--color-accent) / 0.1);
|
||||
background: hsl(var(--color-accent) / 0.18);
|
||||
color: hsl(var(--color-accent));
|
||||
box-shadow: 0 0 14px hsl(var(--color-accent) / 0.15);
|
||||
}
|
||||
|
||||
.axis-item-icon-g1 {
|
||||
font-family: var(--font-display);
|
||||
font-weight: 700;
|
||||
font-size: 1.1rem;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.gestation-badge {
|
||||
@@ -204,12 +240,26 @@ function itemAttrs(item: AxisItem) {
|
||||
}
|
||||
|
||||
.axis-actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
border-top: 1px solid hsl(var(--color-text) / 0.06);
|
||||
background: hsl(var(--color-bg) / 0.4);
|
||||
}
|
||||
|
||||
.axis-actions-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.375rem;
|
||||
padding: 0.75rem 1.25rem;
|
||||
border-top: 1px solid hsl(var(--color-text) / 0.06);
|
||||
background: hsl(var(--color-bg) / 0.4);
|
||||
}
|
||||
|
||||
.axis-actions-secondary {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.375rem;
|
||||
padding: 0.5rem 1.25rem 0.75rem;
|
||||
border-top: 1px solid hsl(var(--color-text) / 0.04);
|
||||
}
|
||||
|
||||
.axis-action-btn {
|
||||
@@ -232,4 +282,28 @@ function itemAttrs(item: AxisItem) {
|
||||
background: hsl(var(--color-primary) / 0.12);
|
||||
border-color: hsl(var(--color-primary) / 0.3);
|
||||
}
|
||||
|
||||
.axis-action-btn--highlight {
|
||||
color: hsl(var(--color-primary));
|
||||
background: hsl(var(--color-primary) / 0.12);
|
||||
border-color: hsl(var(--color-primary) / 0.25);
|
||||
}
|
||||
|
||||
.axis-action-btn--highlight:hover {
|
||||
background: hsl(var(--color-primary) / 0.2);
|
||||
border-color: hsl(var(--color-primary) / 0.4);
|
||||
}
|
||||
|
||||
.axis-action-btn--secondary {
|
||||
color: hsl(var(--color-text) / 0.45);
|
||||
background: transparent;
|
||||
border-color: hsl(var(--color-text) / 0.06);
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.axis-action-btn--secondary:hover {
|
||||
color: hsl(var(--color-accent));
|
||||
background: hsl(var(--color-accent) / 0.08);
|
||||
border-color: hsl(var(--color-accent) / 0.2);
|
||||
}
|
||||
</style>
|
||||
|
||||
135
app/components/home/BookSection.vue
Normal file
135
app/components/home/BookSection.vue
Normal file
@@ -0,0 +1,135 @@
|
||||
<template>
|
||||
<section class="section-padding">
|
||||
<div class="container-content">
|
||||
<div class="grid items-center gap-12 md:grid-cols-2">
|
||||
<!-- Book cover -->
|
||||
<UiScrollReveal>
|
||||
<div class="book-cover-wrapper relative">
|
||||
<!-- Shadok pumper -->
|
||||
<svg class="shadok-pumper" viewBox="0 0 200 240" fill="none" aria-hidden="true">
|
||||
<ellipse cx="100" cy="130" rx="55" ry="65" fill="currentColor" opacity="0.9"/>
|
||||
<ellipse cx="100" cy="60" rx="30" ry="28" fill="currentColor" opacity="0.85"/>
|
||||
<circle cx="88" cy="54" r="6" fill="currentColor" opacity="0.2"/>
|
||||
<circle cx="112" cy="54" r="6" fill="currentColor" opacity="0.2"/>
|
||||
<circle cx="90" cy="53" r="2.5" fill="currentColor" opacity="0.5"/>
|
||||
<circle cx="114" cy="53" r="2.5" fill="currentColor" opacity="0.5"/>
|
||||
<polygon points="100,68 115,78 85,78" fill="currentColor" opacity="0.6"/>
|
||||
<line x1="80" y1="192" x2="70" y2="230" stroke="currentColor" stroke-width="4" stroke-linecap="round" opacity="0.7"/>
|
||||
<line x1="120" y1="192" x2="130" y2="230" stroke="currentColor" stroke-width="4" stroke-linecap="round" opacity="0.7"/>
|
||||
<line x1="70" y1="230" x2="55" y2="232" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.5"/>
|
||||
<line x1="130" y1="230" x2="145" y2="232" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.5"/>
|
||||
<line x1="155" y1="110" x2="190" y2="90" stroke="currentColor" stroke-width="4" stroke-linecap="round" opacity="0.6"/>
|
||||
<line x1="190" y1="90" x2="190" y2="120" stroke="currentColor" stroke-width="4" stroke-linecap="round" opacity="0.6"/>
|
||||
<rect x="180" y="118" width="18" height="40" rx="3" fill="currentColor" opacity="0.4"/>
|
||||
</svg>
|
||||
<div class="book-cover-3d">
|
||||
<img
|
||||
:src="content?.book.coverImage"
|
||||
:alt="content?.book.coverAlt"
|
||||
class="book-cover-img"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</UiScrollReveal>
|
||||
|
||||
<!-- Content + CTAs -->
|
||||
<div>
|
||||
<UiScrollReveal>
|
||||
<p class="mb-2 font-mono text-sm tracking-widest text-accent uppercase">{{ content?.book.kicker }}</p>
|
||||
<h2 class="heading-section font-display font-bold tracking-tight text-white">
|
||||
{{ content?.book.title }}
|
||||
</h2>
|
||||
</UiScrollReveal>
|
||||
|
||||
<UiScrollReveal :delay="100">
|
||||
<p class="mt-4 text-lg leading-relaxed text-white/60">
|
||||
{{ content?.book.description }}
|
||||
</p>
|
||||
</UiScrollReveal>
|
||||
|
||||
<UiScrollReveal :delay="200">
|
||||
<div class="mt-8 flex flex-col gap-3 sm:flex-row sm:gap-4">
|
||||
<UiBaseButton @click="$emit('open-player')">
|
||||
<div class="i-lucide-play mr-2 h-5 w-5" />
|
||||
{{ content?.book.cta.player }}
|
||||
</UiBaseButton>
|
||||
<UiBaseButton variant="accent" @click="$emit('open-pdf')">
|
||||
<div class="i-lucide-book-open mr-2 h-5 w-5" />
|
||||
{{ content?.book.cta.pdf }}
|
||||
</UiBaseButton>
|
||||
</div>
|
||||
</UiScrollReveal>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineEmits<{
|
||||
'open-player': []
|
||||
'open-pdf': []
|
||||
}>()
|
||||
|
||||
const { data: content } = await usePageContent('home')
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.book-cover-wrapper {
|
||||
perspective: 800px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.book-cover-3d {
|
||||
aspect-ratio: 3 / 4;
|
||||
border-radius: 0.75rem;
|
||||
overflow: hidden;
|
||||
border: 1px solid hsl(var(--color-text) / 0.1);
|
||||
box-shadow:
|
||||
0 12px 40px hsl(var(--color-text) / 0.15),
|
||||
0 0 0 1px hsl(var(--color-text) / 0.08);
|
||||
transition: transform 0.5s cubic-bezier(0.645, 0.045, 0.355, 1),
|
||||
box-shadow 0.5s ease;
|
||||
max-width: 360px;
|
||||
}
|
||||
|
||||
.book-cover-3d:hover {
|
||||
transform: rotateY(-8deg) rotateX(3deg) scale(1.02);
|
||||
box-shadow:
|
||||
12px 16px 48px hsl(var(--color-text) / 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 {
|
||||
font-size: clamp(1.625rem, 4vw, 2.125rem);
|
||||
}
|
||||
|
||||
.shadok-pumper {
|
||||
position: absolute;
|
||||
right: 3%;
|
||||
bottom: 8%;
|
||||
width: clamp(90px, 12vw, 180px);
|
||||
opacity: 0.28;
|
||||
pointer-events: none;
|
||||
color: hsl(var(--color-primary));
|
||||
animation: shadok-float 10s ease-in-out infinite;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
@keyframes shadok-float {
|
||||
0%, 100% { transform: translateY(0); }
|
||||
50% { transform: translateY(-10px); }
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.shadok-pumper { display: none; }
|
||||
}
|
||||
</style>
|
||||
@@ -2,7 +2,7 @@
|
||||
<section class="relative overflow-hidden section-padding hero-section">
|
||||
<!-- Background gradient -->
|
||||
<div class="absolute inset-0 bg-gradient-to-b from-primary/10 via-transparent to-surface-bg" />
|
||||
<div class="absolute inset-0 bg-[radial-gradient(ellipse_at_top,hsl(12_76%_48%/0.15),transparent_70%)]" />
|
||||
<div class="absolute inset-0 bg-[radial-gradient(ellipse_at_top,hsl(12_76%_48%/0.12),transparent_70%)]" />
|
||||
|
||||
<!-- Shadok bird decoration -->
|
||||
<svg class="shadok-bird" viewBox="0 0 180 260" fill="none" aria-hidden="true">
|
||||
@@ -46,8 +46,8 @@
|
||||
<div class="container-content relative z-10 px-4">
|
||||
<div class="mx-auto max-w-2xl">
|
||||
<HomeTypewriterText
|
||||
v-if="sentences.length"
|
||||
:sentences="sentences"
|
||||
v-if="hero"
|
||||
:hero="hero"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -55,19 +55,17 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { TypewriterSentence } from '~/composables/useTypewriter'
|
||||
|
||||
const { data: content } = await usePageContent('home')
|
||||
|
||||
const sentences = computed<TypewriterSentence[]>(() => {
|
||||
const raw = (content.value as any)?.hero?.typewriter?.sentences
|
||||
if (!Array.isArray(raw)) return []
|
||||
return raw.map((s: any) => ({
|
||||
text: s.text,
|
||||
style: s.style || 'title',
|
||||
stays: !!s.stays,
|
||||
separator: !!s.separator,
|
||||
}))
|
||||
const hero = computed(() => {
|
||||
const raw = (content.value as any)?.hero
|
||||
if (!raw) return null
|
||||
return {
|
||||
heading: Array.isArray(raw.heading) ? raw.heading : [],
|
||||
citations: Array.isArray(raw.citations) ? raw.citations : [],
|
||||
approach: raw.approach || '',
|
||||
axes: Array.isArray(raw.axes) ? raw.axes : [],
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,171 +1,260 @@
|
||||
<template>
|
||||
<ClientOnly>
|
||||
<div class="hero-text" @click="handleClick">
|
||||
<!-- Locked sentences (stays: true, already revealed) -->
|
||||
<TransitionGroup name="lock">
|
||||
<div v-for="(item, i) in lockedSentences" :key="`lock-${i}`">
|
||||
<div v-if="item.separator" class="hero-separator" />
|
||||
<p v-else class="hero-line" :class="styleClass(item.style)">
|
||||
{{ item.text }}
|
||||
</p>
|
||||
</div>
|
||||
</TransitionGroup>
|
||||
|
||||
<!-- Active sentence — pure CSS opacity fade -->
|
||||
<div class="hero-active-zone">
|
||||
<p
|
||||
v-show="currentText"
|
||||
class="hero-line hero-active"
|
||||
:class="[styleClass(currentStyle), { 'is-visible': isVisible }]"
|
||||
>
|
||||
{{ currentText }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- SEO / no-JS fallback -->
|
||||
<template #fallback>
|
||||
<div class="hero-text">
|
||||
<p
|
||||
v-for="(sentence, i) in stayingSentences"
|
||||
<div class="hero-content">
|
||||
<!-- 5-line heading with alternating typography -->
|
||||
<div class="hero-heading">
|
||||
<h1 v-if="hero.heading?.length" class="hero-lines">
|
||||
<span
|
||||
v-for="(line, i) in hero.heading"
|
||||
:key="i"
|
||||
class="hero-line"
|
||||
:class="styleClass(sentence.style)"
|
||||
>
|
||||
{{ sentence.text }}
|
||||
</p>
|
||||
:class="`hero-line--${i + 1}`"
|
||||
>{{ line }}</span>
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<!-- Discrete aside block -->
|
||||
<details class="hero-aside">
|
||||
<summary class="hero-aside-toggle">En savoir plus</summary>
|
||||
|
||||
<div class="hero-aside-body">
|
||||
<!-- Citations -->
|
||||
<blockquote v-if="hero.citations?.length" class="hero-citations">
|
||||
<p v-for="(cite, i) in hero.citations" :key="i" class="hero-cite">
|
||||
{{ cite }}
|
||||
</p>
|
||||
</blockquote>
|
||||
|
||||
<!-- Approach + Axes -->
|
||||
<div v-if="hero.approach" class="hero-approach">
|
||||
<p class="hero-approach-text">{{ hero.approach }}</p>
|
||||
<dl v-if="hero.axes?.length" class="hero-axes">
|
||||
<div v-for="(axis, i) in hero.axes" :key="i" class="hero-axis">
|
||||
<dt>{{ axis.label }}</dt>
|
||||
<dd>{{ axis.value }}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</ClientOnly>
|
||||
</details>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { TypewriterSentence } from '~/composables/useTypewriter'
|
||||
interface HeroAxis {
|
||||
label: string
|
||||
value: string
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
sentences: TypewriterSentence[]
|
||||
interface HeroData {
|
||||
heading: string[]
|
||||
citations: string[]
|
||||
approach: string
|
||||
axes: HeroAxis[]
|
||||
}
|
||||
|
||||
defineProps<{
|
||||
hero: HeroData
|
||||
}>()
|
||||
|
||||
const stayingSentences = computed(() => props.sentences.filter(s => s.stays))
|
||||
|
||||
const {
|
||||
currentText,
|
||||
currentStyle,
|
||||
isVisible,
|
||||
lockedSentences,
|
||||
isComplete,
|
||||
start,
|
||||
skipToEnd,
|
||||
} = useTypewriter(props.sentences)
|
||||
|
||||
onMounted(() => {
|
||||
start()
|
||||
})
|
||||
|
||||
function handleClick() {
|
||||
if (!isComplete.value) {
|
||||
skipToEnd()
|
||||
}
|
||||
}
|
||||
|
||||
function styleClass(style?: string) {
|
||||
if (style === 'citation') return 'hero-line--citation'
|
||||
if (style === 'text') return 'hero-line--body'
|
||||
return 'hero-line--title'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.hero-text {
|
||||
.hero-content {
|
||||
text-align: center;
|
||||
min-height: 16rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
align-items: center;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
/* ── Lines ── */
|
||||
/* ── Heading — 5 lines ── */
|
||||
|
||||
.hero-heading {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.hero-lines {
|
||||
margin: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 0.1em;
|
||||
}
|
||||
|
||||
.hero-line {
|
||||
margin: 0;
|
||||
padding: 0.3em 0;
|
||||
display: block;
|
||||
font-family: var(--font-display);
|
||||
line-height: 1.4;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.hero-line--title {
|
||||
font-weight: 600;
|
||||
font-size: clamp(1.3rem, 3.5vw, 2.2rem);
|
||||
/* l1: Construire une autonomie collective. — bold, large */
|
||||
.hero-line--1 {
|
||||
font-weight: 700;
|
||||
font-size: clamp(1.45rem, 3.8vw, 2.5rem);
|
||||
color: hsl(var(--color-text) / 0.95);
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
/* l2: à l'échelle des bassins de vie — light, same size, softer */
|
||||
.hero-line--2 {
|
||||
font-weight: 300;
|
||||
font-size: clamp(1.3rem, 3.2vw, 2.1rem);
|
||||
color: hsl(var(--color-text) / 0.55);
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
|
||||
.hero-line--citation {
|
||||
/* l3: Pousser les curseurs — accent color, medium */
|
||||
.hero-line--3 {
|
||||
font-weight: 600;
|
||||
font-size: clamp(1.1rem, 2.6vw, 1.6rem);
|
||||
color: hsl(var(--color-accent));
|
||||
margin-top: 0.5em;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
/* l4: Autonomie numérique, économique, citoyenne. — small-caps feel */
|
||||
.hero-line--4 {
|
||||
font-family: var(--font-sans);
|
||||
font-weight: 500;
|
||||
font-size: clamp(0.9rem, 2vw, 1.15rem);
|
||||
color: hsl(var(--color-text) / 0.65);
|
||||
letter-spacing: 0.06em;
|
||||
text-transform: uppercase;
|
||||
margin-top: 0.15em;
|
||||
}
|
||||
|
||||
/* l5: — s'en donner les moyens — — italic, same tone as l4 */
|
||||
.hero-line--5 {
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
font-size: clamp(1.05rem, 2.8vw, 1.5rem);
|
||||
color: hsl(var(--color-text) / 0.7);
|
||||
max-width: 40ch;
|
||||
margin-inline: auto;
|
||||
font-size: clamp(0.95rem, 2.2vw, 1.2rem);
|
||||
color: hsl(var(--color-text) / 0.65);
|
||||
margin-top: 0.3em;
|
||||
}
|
||||
|
||||
.hero-line--body {
|
||||
font-weight: 400;
|
||||
font-size: clamp(1rem, 2.5vw, 1.3rem);
|
||||
color: hsl(var(--color-text) / 0.6);
|
||||
max-width: 46ch;
|
||||
margin-inline: auto;
|
||||
/* ── Discrete aside block ── */
|
||||
|
||||
.hero-aside {
|
||||
width: 100%;
|
||||
max-width: 36em;
|
||||
}
|
||||
|
||||
/* ── Separator ── */
|
||||
|
||||
.hero-separator {
|
||||
width: 4rem;
|
||||
height: 2px;
|
||||
margin: 1.25rem auto;
|
||||
background: linear-gradient(
|
||||
to right,
|
||||
transparent,
|
||||
hsl(var(--color-primary) / 0.45),
|
||||
transparent
|
||||
);
|
||||
border-radius: 1px;
|
||||
}
|
||||
|
||||
/* ── Active zone ── */
|
||||
|
||||
.hero-active-zone {
|
||||
min-height: 5rem;
|
||||
display: flex;
|
||||
.hero-aside-toggle {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
margin: 0 auto;
|
||||
padding: 0.35rem 0.85rem;
|
||||
border-radius: 9999px;
|
||||
font-family: var(--font-sans);
|
||||
font-size: 0.78rem;
|
||||
font-weight: 500;
|
||||
color: hsl(var(--color-text) / 0.35);
|
||||
border: 1px solid hsl(var(--color-text) / 0.08);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.hero-aside-toggle::-webkit-details-marker {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.hero-aside-toggle::before {
|
||||
content: '▸';
|
||||
font-size: 0.65em;
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
.hero-aside[open] .hero-aside-toggle::before {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.hero-aside-toggle:hover {
|
||||
color: hsl(var(--color-text) / 0.55);
|
||||
border-color: hsl(var(--color-text) / 0.15);
|
||||
}
|
||||
|
||||
.hero-aside-body {
|
||||
margin-top: 1.25rem;
|
||||
animation: aside-reveal 0.3s ease;
|
||||
}
|
||||
|
||||
@keyframes aside-reveal {
|
||||
from { opacity: 0; transform: translateY(-6px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
/* ── Citations ── */
|
||||
|
||||
.hero-citations {
|
||||
margin: 0 0 1.25rem;
|
||||
padding: 0.6rem 0 0.6rem 1rem;
|
||||
border-left: 2px solid hsl(var(--color-accent) / 0.3);
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.hero-cite {
|
||||
font-family: var(--font-display);
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
font-size: 0.92rem;
|
||||
line-height: 1.55;
|
||||
color: hsl(var(--color-text) / 0.45);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.hero-cite + .hero-cite {
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
/* ── Approach + Axes ── */
|
||||
|
||||
.hero-approach {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.hero-approach-text {
|
||||
font-family: var(--font-sans);
|
||||
font-size: 0.82rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.03em;
|
||||
text-transform: uppercase;
|
||||
color: hsl(var(--color-text) / 0.35);
|
||||
margin: 0 0 0.6rem;
|
||||
}
|
||||
|
||||
.hero-axes {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 0.2rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.hero-axis {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 0.4rem;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* ── Active sentence — opacity fade ── */
|
||||
|
||||
.hero-active {
|
||||
opacity: 0;
|
||||
transition: opacity 1s ease;
|
||||
.hero-axis dt {
|
||||
font-family: var(--font-display);
|
||||
font-weight: 600;
|
||||
font-size: 0.9rem;
|
||||
color: hsl(var(--color-primary));
|
||||
}
|
||||
|
||||
.hero-active.is-visible {
|
||||
opacity: 1;
|
||||
.hero-axis dt::after {
|
||||
content: ' →';
|
||||
color: hsl(var(--color-text) / 0.2);
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
/* ── Locked sentences entrance ── */
|
||||
|
||||
.lock-enter-active {
|
||||
transition: opacity 0.8s ease-out, transform 0.8s ease-out;
|
||||
}
|
||||
|
||||
.lock-enter-from {
|
||||
opacity: 0;
|
||||
transform: translateY(8px);
|
||||
}
|
||||
|
||||
.lock-move {
|
||||
transition: transform 0.5s ease;
|
||||
.hero-axis dd {
|
||||
font-family: var(--font-sans);
|
||||
font-size: 0.88rem;
|
||||
color: hsl(var(--color-text) / 0.5);
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -16,8 +16,10 @@
|
||||
|
||||
<!-- Desktop navigation -->
|
||||
<nav class="hidden md:flex items-center gap-1" aria-label="Navigation principale">
|
||||
<!-- Autonomie : prefix + axis buttons -->
|
||||
<span class="nav-prefix">Autonomie :</span>
|
||||
<NuxtLink
|
||||
v-for="item in site?.navigation"
|
||||
v-for="item in axes"
|
||||
:key="item.to"
|
||||
:to="item.to"
|
||||
class="btn-ghost text-sm"
|
||||
@@ -25,6 +27,19 @@
|
||||
>
|
||||
{{ item.label }}
|
||||
</NuxtLink>
|
||||
|
||||
<!-- Separator + extra nav -->
|
||||
<span class="nav-sep" />
|
||||
<NuxtLink
|
||||
v-for="item in extra"
|
||||
:key="item.to"
|
||||
:to="item.to"
|
||||
class="btn-ghost btn-ghost--muted text-sm"
|
||||
active-class="!text-[hsl(var(--color-text))] bg-[hsl(var(--color-text)/0.06)]"
|
||||
>
|
||||
{{ item.label }}
|
||||
</NuxtLink>
|
||||
|
||||
<UiPaletteSelector />
|
||||
</nav>
|
||||
|
||||
@@ -39,13 +54,17 @@
|
||||
</div>
|
||||
|
||||
<!-- Mobile menu -->
|
||||
<LayoutNavMobile v-model:open="isMobileMenuOpen" :nav="site?.navigation ?? []" />
|
||||
<LayoutNavMobile v-model:open="isMobileMenuOpen" :nav="allNav" />
|
||||
</header>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const { data: site } = await useSiteContent()
|
||||
const isMobileMenuOpen = ref(false)
|
||||
|
||||
const axes = computed(() => (site.value as any)?.navigation?.axes ?? [])
|
||||
const extra = computed(() => (site.value as any)?.navigation?.extra ?? [])
|
||||
const allNav = computed(() => [...axes.value, ...extra.value])
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -65,9 +84,34 @@ const isMobileMenuOpen = ref(false)
|
||||
|
||||
.logo-text {
|
||||
font-family: var(--font-display);
|
||||
font-weight: 600;
|
||||
font-size: 1.15rem;
|
||||
letter-spacing: 0.02em;
|
||||
color: hsl(var(--color-primary));
|
||||
font-weight: 400;
|
||||
font-size: 1.25rem;
|
||||
letter-spacing: 0.04em;
|
||||
background-image: linear-gradient(to right, hsl(var(--color-primary)), hsl(var(--color-accent)));
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
|
||||
.nav-prefix {
|
||||
font-family: var(--font-display);
|
||||
font-size: 0.92rem;
|
||||
font-weight: 500;
|
||||
font-style: italic;
|
||||
letter-spacing: 0.03em;
|
||||
color: hsl(var(--color-text) / 0.4);
|
||||
margin-right: 0.125rem;
|
||||
}
|
||||
|
||||
.nav-sep {
|
||||
display: block;
|
||||
width: 1px;
|
||||
height: 1.25rem;
|
||||
background: hsl(var(--color-text) / 0.12);
|
||||
margin: 0 0.375rem;
|
||||
}
|
||||
|
||||
.btn-ghost--muted {
|
||||
color: hsl(var(--color-text) / 0.45);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,109 +0,0 @@
|
||||
export interface TypewriterSentence {
|
||||
text: string
|
||||
style?: 'title' | 'citation' | 'text'
|
||||
stays?: boolean
|
||||
separator?: boolean
|
||||
}
|
||||
|
||||
interface SequenceOptions {
|
||||
fadeMs?: number
|
||||
holdMs?: number
|
||||
gapMs?: number
|
||||
}
|
||||
|
||||
export function useTypewriter(sentences: TypewriterSentence[], options: SequenceOptions = {}) {
|
||||
const {
|
||||
fadeMs = 1000,
|
||||
holdMs = 2800,
|
||||
gapMs = 300,
|
||||
} = options
|
||||
|
||||
const currentText = ref('')
|
||||
const currentStyle = ref<string>('title')
|
||||
const isVisible = ref(false)
|
||||
const lockedSentences = ref<TypewriterSentence[]>([])
|
||||
const isComplete = ref(false)
|
||||
|
||||
let currentIdx = -1
|
||||
let timer: ReturnType<typeof setTimeout> | null = null
|
||||
|
||||
function clearTimer() {
|
||||
if (timer) {
|
||||
clearTimeout(timer)
|
||||
timer = null
|
||||
}
|
||||
}
|
||||
|
||||
function next() {
|
||||
currentIdx++
|
||||
if (currentIdx >= sentences.length) {
|
||||
currentText.value = ''
|
||||
isComplete.value = true
|
||||
return
|
||||
}
|
||||
|
||||
const sentence = sentences[currentIdx]
|
||||
|
||||
if (sentence.separator) {
|
||||
lockedSentences.value = [...lockedSentences.value, { text: '', separator: true }]
|
||||
}
|
||||
|
||||
// Set text while invisible
|
||||
currentText.value = sentence.text
|
||||
currentStyle.value = sentence.style || 'title'
|
||||
|
||||
// Fade in on next frame
|
||||
requestAnimationFrame(() => {
|
||||
isVisible.value = true
|
||||
})
|
||||
|
||||
// After fade-in + hold → fade out
|
||||
timer = setTimeout(() => {
|
||||
isVisible.value = false
|
||||
|
||||
// After fade-out completes → lock if stays, then next
|
||||
timer = setTimeout(() => {
|
||||
if (sentence.stays) {
|
||||
lockedSentences.value = [...lockedSentences.value, { ...sentence }]
|
||||
}
|
||||
timer = setTimeout(next, gapMs)
|
||||
}, fadeMs)
|
||||
}, fadeMs + holdMs)
|
||||
}
|
||||
|
||||
function start() {
|
||||
next()
|
||||
}
|
||||
|
||||
function skipToEnd() {
|
||||
clearTimer()
|
||||
isVisible.value = false
|
||||
currentText.value = ''
|
||||
|
||||
const locked: TypewriterSentence[] = []
|
||||
for (const sentence of sentences) {
|
||||
if (sentence.separator) {
|
||||
locked.push({ text: '', separator: true })
|
||||
}
|
||||
if (sentence.stays) {
|
||||
locked.push({ ...sentence })
|
||||
}
|
||||
}
|
||||
|
||||
lockedSentences.value = locked
|
||||
currentIdx = sentences.length - 1
|
||||
isComplete.value = true
|
||||
}
|
||||
|
||||
onUnmounted(clearTimer)
|
||||
|
||||
return {
|
||||
currentText: readonly(currentText),
|
||||
currentStyle: readonly(currentStyle),
|
||||
isVisible,
|
||||
lockedSentences: readonly(lockedSentences),
|
||||
isComplete: readonly(isComplete),
|
||||
start,
|
||||
skipToEnd,
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,27 @@
|
||||
<template>
|
||||
<div class="app-layout grid grid-cols-1 min-h-dvh">
|
||||
<LayoutTheHeader />
|
||||
<main>
|
||||
<main class="app-main">
|
||||
<slot />
|
||||
|
||||
<!-- 益 Yì (Increase, #42) — sceau hexagramme -->
|
||||
<svg class="app-seal" viewBox="0 0 130 100" fill="currentColor" aria-hidden="true">
|
||||
<!-- Line 6 (top) — yang -->
|
||||
<rect x="5" y="5" width="120" height="5" rx="1"/>
|
||||
<!-- Line 5 — yang -->
|
||||
<rect x="5" y="22" width="120" height="5" rx="1"/>
|
||||
<!-- Line 4 — yin -->
|
||||
<rect x="5" y="39" width="49" height="5" rx="1"/>
|
||||
<rect x="76" y="39" width="49" height="5" rx="1"/>
|
||||
<!-- Line 3 — yin -->
|
||||
<rect x="5" y="56" width="49" height="5" rx="1"/>
|
||||
<rect x="76" y="56" width="49" height="5" rx="1"/>
|
||||
<!-- Line 2 — yin -->
|
||||
<rect x="5" y="73" width="49" height="5" rx="1"/>
|
||||
<rect x="76" y="73" width="49" height="5" rx="1"/>
|
||||
<!-- Line 1 (bottom) — yang -->
|
||||
<rect x="5" y="90" width="120" height="5" rx="1"/>
|
||||
</svg>
|
||||
</main>
|
||||
<LayoutTheFooter />
|
||||
</div>
|
||||
@@ -12,4 +31,15 @@
|
||||
.app-layout {
|
||||
grid-template-rows: auto 1fr auto;
|
||||
}
|
||||
|
||||
/* === Seal — 益 Yì (Increase) === */
|
||||
.app-seal {
|
||||
display: block;
|
||||
width: 44px;
|
||||
margin: 2rem 1.5rem 1rem auto;
|
||||
color: hsl(var(--color-accent));
|
||||
opacity: 0.28;
|
||||
filter: drop-shadow(1px 1px 0.5px rgba(0,0,0,0.25))
|
||||
drop-shadow(-0.5px -0.5px 0.5px rgba(255,255,255,0.15));
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -74,6 +74,13 @@
|
||||
</svg>
|
||||
|
||||
<div class="container-content">
|
||||
<!-- Page de couverture du livre -->
|
||||
<HomeBookSection
|
||||
class="mb-16"
|
||||
@open-player="showBookPlayer = true"
|
||||
@open-pdf="showPdfReader = true"
|
||||
/>
|
||||
|
||||
<header class="mb-12 text-center">
|
||||
<p class="mb-2 font-mono text-sm tracking-widest text-primary uppercase">{{ content?.kicker }}</p>
|
||||
<h1 class="page-title font-display font-bold tracking-tight text-white">
|
||||
@@ -118,6 +125,9 @@
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<BookPlayer v-model="showBookPlayer" />
|
||||
<BookPdfReader v-model="showPdfReader" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -135,6 +145,9 @@ useHead({
|
||||
const { data: chapters } = await useAsyncData('book-toc', () =>
|
||||
queryCollection('book').order('order', 'ASC').all(),
|
||||
)
|
||||
|
||||
const showBookPlayer = ref(false)
|
||||
const showPdfReader = ref(false)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -117,10 +117,10 @@ definePageMeta({
|
||||
layout: 'default',
|
||||
})
|
||||
|
||||
const { data: content } = await usePageContent('autonomie')
|
||||
const { data: content } = await usePageContent('numerique')
|
||||
|
||||
useHead({
|
||||
title: content.value?.meta?.title ?? 'Autonomie',
|
||||
title: content.value?.meta?.title ?? 'Autonomie numérique',
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -20,9 +20,9 @@ export default defineNuxtConfig({
|
||||
safelist: [
|
||||
// Axis block icons (dynamic from YAML)
|
||||
'i-lucide-monitor', 'i-lucide-coins', 'i-lucide-landmark',
|
||||
'i-lucide-code-2', 'i-lucide-fingerprint', 'i-lucide-cloud',
|
||||
'i-lucide-circle-dollar-sign', 'i-lucide-heart-handshake', 'i-lucide-users',
|
||||
'i-lucide-scale', 'i-lucide-droplets', 'i-lucide-calendar-heart',
|
||||
'i-lucide-code-2', 'i-lucide-share-2', 'i-lucide-cloud',
|
||||
'i-lucide-scale', 'i-lucide-gavel', 'i-lucide-users',
|
||||
'i-lucide-droplets', 'i-lucide-calendar-heart',
|
||||
// Action icons
|
||||
'i-lucide-play', 'i-lucide-book-open', 'i-lucide-sparkles',
|
||||
// Decision page
|
||||
|
||||
@@ -10,4 +10,8 @@ export default defineEventHandler((event) => {
|
||||
const rest = path.slice(8) // remove '/ecouter'
|
||||
return sendRedirect(event, `/en-musique${rest || '/'}`, 301)
|
||||
}
|
||||
|
||||
if (path === '/autonomie' || path === '/autonomie/') {
|
||||
return sendRedirect(event, '/numerique', 301)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,37 +1,34 @@
|
||||
hero:
|
||||
typewriter:
|
||||
sentences:
|
||||
- text: "Construire une autonomie collective"
|
||||
style: title
|
||||
stays: false
|
||||
- text: "à l'échelle des bassins de vie."
|
||||
style: title
|
||||
stays: false
|
||||
- text: "Pousser les curseurs de l'autonomie numérique, économique et politique — et s'en donner les moyens"
|
||||
style: title
|
||||
stays: true
|
||||
- text: "Il s'agit d'émancipation"
|
||||
style: citation
|
||||
stays: false
|
||||
- text: "Les trois dimensions qui nous émancipent sont le numérique, l'économie et le politique"
|
||||
style: citation
|
||||
stays: false
|
||||
- text: "Ce sont les 3 axes de l'espace dans lequel nous naviguons"
|
||||
style: citation
|
||||
stays: true
|
||||
separator: true
|
||||
- text: "Dans chaque dimension, nous adressons ce qui est le plus en amont"
|
||||
style: text
|
||||
stays: false
|
||||
- text: "Pour le numérique, c'est le code source"
|
||||
style: text
|
||||
stays: false
|
||||
- text: "Pour l'économie, c'est la création monétaire"
|
||||
style: text
|
||||
stays: false
|
||||
- text: "Pour le politique, c'est la prise de décision — comment on choisit, comment on décide"
|
||||
style: text
|
||||
stays: true
|
||||
heading:
|
||||
- "Construire une autonomie collective."
|
||||
- "à l'échelle des bassins de vie"
|
||||
- "Pousser les curseurs"
|
||||
- "Autonomie numérique, économique, citoyenne."
|
||||
- "— s'en donner les moyens —"
|
||||
citations:
|
||||
- "Il s'agit d'émancipation."
|
||||
- "Les trois dimensions qui nous émancipent sont le numérique, l'économie et le politique."
|
||||
- "Elles peuvent nous asservir tout autant."
|
||||
- "Ce sont les 3 axes de l'espace dans lequel nous naviguons."
|
||||
approach: "Dans chaque dimension, nous adressons ce qui est le plus en amont"
|
||||
axes:
|
||||
- label: numérique
|
||||
value: le code source
|
||||
- label: économie
|
||||
value: la création monétaire
|
||||
- label: citoyenne
|
||||
value: la décision
|
||||
|
||||
book:
|
||||
kicker: Modèle économique
|
||||
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
|
||||
accompagné de chansons qui le racontent, un peu autrement.
|
||||
coverImage: /images/book-cover-spread.jpg
|
||||
coverAlt: Couverture — Une économie du don, enfin concevable
|
||||
cta:
|
||||
player: Présentation musicale
|
||||
pdf: Lecture du livre
|
||||
|
||||
axes:
|
||||
numerique:
|
||||
@@ -50,7 +47,7 @@ axes:
|
||||
description: Une toile de confiance décentralisée, sans autorité centrale. Chaque identité est certifiée par ses pairs.
|
||||
to: /gestation/authentification-wot
|
||||
gestation: true
|
||||
icon: fingerprint
|
||||
icon: share-2
|
||||
presentation:
|
||||
title: trustWallet
|
||||
text: Gestionnaire de confiances.
|
||||
@@ -69,21 +66,23 @@ axes:
|
||||
- label: Monnaie libre
|
||||
description: "La Ğ1 (June) : une monnaie co-créée par ses membres, sans dette ni intérêt. Le dividende universel comme base."
|
||||
href: https://monnaie-libre.fr
|
||||
icon: circle-dollar-sign
|
||||
icon: g1
|
||||
- label: Économie du don
|
||||
description: Un livre et des chansons pour une proposition de modèle économique fondé sur le don.
|
||||
to: /modele-eco
|
||||
icon: heart-handshake
|
||||
icon: scale
|
||||
actions:
|
||||
- id: open-player
|
||||
label: Présentation musicale
|
||||
icon: play
|
||||
highlight: true
|
||||
- id: open-pdf
|
||||
label: Lecture du livre
|
||||
icon: book-open
|
||||
- id: launch-gratewizard
|
||||
label: grateWizard
|
||||
icon: sparkles
|
||||
secondary: true
|
||||
- label: Productions collectives
|
||||
description: Une plateforme pour faciliter la création d'équipes et la réalisation de productions à l'échelle des bassins de vie.
|
||||
to: /gestation/productions-collectives
|
||||
@@ -96,7 +95,7 @@ axes:
|
||||
- label: Décision collective
|
||||
description: Se donner les moyens de la décision collective.
|
||||
to: /decision
|
||||
icon: scale
|
||||
icon: gavel
|
||||
- label: Tarifs de l'eau
|
||||
description: Application pour obtenir justice sociale et incitation dynamique à la réduction. Permet de confier la décision à la population des communes.
|
||||
to: /gestation/tarifs-eau
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
kicker: Pourquoi l'autonomie
|
||||
title: Autonomie
|
||||
description: Des passages du livre qui éclairent la démarche d'autonomie collective — le fil rouge du projet.
|
||||
kicker: Autonomie numérique
|
||||
title: Le code source
|
||||
description: Des passages du livre qui éclairent la démarche d'autonomie numérique — maîtriser le code source, c'est maîtriser l'outil.
|
||||
meta:
|
||||
title: Autonomie
|
||||
title: Autonomie numérique
|
||||
extracts:
|
||||
- chapter: Introduction
|
||||
chapterSlug: 01-introduction
|
||||
@@ -4,14 +4,16 @@ identity:
|
||||
racontent, autrement.
|
||||
url: https://librodrome.org
|
||||
navigation:
|
||||
- label: Numérique
|
||||
to: /#numerique
|
||||
- label: Économique
|
||||
to: /#economique
|
||||
- label: Citoyenne
|
||||
to: /#citoyenne
|
||||
- label: Événement
|
||||
to: /evenement
|
||||
axes:
|
||||
- label: numérique
|
||||
to: /numerique
|
||||
- label: économique
|
||||
to: /modele-eco
|
||||
- label: citoyenne
|
||||
to: /decision
|
||||
extra:
|
||||
- label: Événement
|
||||
to: /evenement
|
||||
footer:
|
||||
credits: © 2026 Le librodrome — Productions collectives
|
||||
links:
|
||||
|
||||
Reference in New Issue
Block a user