Compare commits

...

10 Commits

Author SHA1 Message Date
Yvv
e6c91fea7d Replie les paroles par défaut dans le player persistant
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Toggle discret pour les afficher au besoin (usage autonome).
Gain de place dans le panel déployé.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 14:11:32 +01:00
Yvv
25bfc07b59 Fix double-fire player, navigation par morceaux, admin labels morceaux
- BookPlayer : navigation par playlist (9 morceaux) au lieu de 11 chapitres
- stopPropagation clavier → plus de saut 1→3→5
- Sommaire aligné avec titres des morceaux
- Bouton back aligné avec clavier (toujours morceau précédent)
- Admin chapitres : tags morceaux cliquables avec étoile primary
- Admin liste chapitres : badges morceaux associés
- Éditeur markdown en vue split par défaut

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 14:08:58 +01:00
Yvv
8803087e77 Fix player : plus de saut de morceaux, mode scroll par défaut, supprime toggle paginé, media → sources
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 13:10:12 +01:00
Yvv
14d3a7b3e3 Logo § restauré, couleur unie partout, palettes printemps/été plus chaudes, rectifs admin
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 12:42:44 +01:00
Yvv
d8439cba0f 9 morceaux : Relativité supprimé, Créer une économie = #8, Coder la liberté = #9
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 02:21:25 +01:00
Yvv
0308785de9 Supprime media/ du repo, gardé en local via .gitignore
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 02:14:59 +01:00
Yvv
52c0af4c83 10 morceaux corrigés : titres, ordre, IDs et fichiers audio renommés
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 01:59:07 +01:00
Yvv
8d9feed760 Préfixe ED-## sur les fichiers audio et config à jour
Renomme les 9 morceaux avec préfixe album ED (Économie du Don) + numéro
d'ordre conforme aux sources dans media/musiques/. Met à jour les chemins
dans bookplayer.config.yml.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 22:10:42 +01:00
Yvv
922afa2763 BookPlayer affiche les paroles du morceau, plus le contenu chapitre
Le BookPlayer chargeait les .md via Nuxt Content — qui contenaient avant
les paroles par erreur. Maintenant que les .md ont le vrai contenu du
livre, le BookPlayer doit afficher les lyrics depuis bookplayer.config.yml.

Supprime queryCollection('book') du BookPlayer, remplace ContentRenderer
par un rendu HTML des paroles avec tags stylisés.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 21:06:11 +01:00
Yvv
dd1d8baf4f Fix 404 chapitres : stem Nuxt Content inclut le dossier parent
Le `chapter.stem` de Nuxt Content renvoie `book/01-introduction` et non
`01-introduction`. Extraction du slug final via `.split('/').pop()` dans
les liens et la navigation prev/next.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 20:46:19 +01:00
37 changed files with 603 additions and 577 deletions

3
.gitignore vendored
View File

@@ -18,6 +18,9 @@ logs
.fleet .fleet
.idea .idea
# Sources originales (PDF, JPG — pas servies par l'appli)
sources/
# Local env files # Local env files
.env .env
.env.* .env.*

View File

@@ -146,14 +146,7 @@ a {
.palette-light .prose { color: hsl(var(--color-text)); } .palette-light .prose { color: hsl(var(--color-text)); }
.palette-light .prose :where(h1,h2,h3,h4,h5,h6) { color: hsl(var(--color-text)); } .palette-light .prose :where(h1,h2,h3,h4,h5,h6) { color: hsl(var(--color-text)); }
/* text-gradient in light mode — vivid gradient */ /* text-gradient — solid primary color everywhere */
.palette-light .text-gradient {
background-image: linear-gradient(135deg, hsl(var(--color-primary)), hsl(var(--color-accent)));
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
color: transparent !important;
}
/* card surfaces — subtle shadow for depth */ /* card surfaces — subtle shadow for depth */
.palette-light .card-surface { .palette-light .card-surface {

View File

@@ -65,7 +65,7 @@ const emit = defineEmits<{
'update:modelValue': [value: string] 'update:modelValue': [value: string]
}>() }>()
const tab = ref<'edit' | 'preview' | 'split'>('edit') const tab = ref<'edit' | 'preview' | 'split'>('split')
const fullscreen = ref(false) const fullscreen = ref(false)
const textareaRef = ref<HTMLTextAreaElement>() const textareaRef = ref<HTMLTextAreaElement>()

View File

@@ -34,22 +34,11 @@
> >
<div class="i-lucide-list h-5 w-5" /> <div class="i-lucide-list h-5 w-5" />
</button> </button>
<button
class="reader-bar-btn"
@click="toggleReadingMode"
:aria-label="isScrollMode ? 'Mode paginé' : 'Mode défilement'"
:title="isScrollMode ? 'Mode paginé' : 'Mode défilement'"
>
<div :class="isScrollMode ? 'i-lucide-book-open' : 'i-lucide-scroll-text'" class="h-5 w-5" />
</button>
<div class="reader-bar-title"> <div class="reader-bar-title">
<span class="reader-bar-num">{{ chapterIdx + 1 }}.</span> <span class="reader-bar-num">{{ trackIdx + 1 }}.</span>
{{ chapters[chapterIdx].title }} {{ currentTrack?.title ?? '' }}
</div> </div>
<span class="reader-bar-pages"> <span class="reader-bar-pages">{{ scrollPercent }}%</span>
<template v-if="isScrollMode">{{ scrollPercent }}%</template>
<template v-else>{{ currentPage + 1 }}<span class="op-40">/</span>{{ totalPages }}</template>
</span>
<button class="reader-bar-btn reader-bar-close" @click="close" aria-label="Fermer"> <button class="reader-bar-btn reader-bar-close" @click="close" aria-label="Fermer">
<div class="i-lucide-x h-5 w-5" /> <div class="i-lucide-x h-5 w-5" />
</button> </button>
@@ -61,14 +50,14 @@
<div class="sommaire-panel"> <div class="sommaire-panel">
<h4 class="sommaire-title">{{ bpContent?.reader.sommaireTitle ?? 'Sommaire' }}</h4> <h4 class="sommaire-title">{{ bpContent?.reader.sommaireTitle ?? 'Sommaire' }}</h4>
<button <button
v-for="(ch, i) in chapters" v-for="(track, i) in tracks"
:key="i" :key="track.id"
class="sommaire-item" class="sommaire-item"
:class="{ 'sommaire-item--active': chapterIdx === i }" :class="{ 'sommaire-item--active': trackIdx === i }"
@click="goToChapter(i)" @click="goToTrack(i)"
> >
<span class="sommaire-num">{{ i + 1 }}</span> <span class="sommaire-num">{{ i + 1 }}</span>
{{ ch.title }} {{ track.title }}
</button> </button>
</div> </div>
</aside> </aside>
@@ -87,7 +76,13 @@
ref="contentEl" ref="contentEl"
:style="contentStyle" :style="contentStyle"
> >
<ContentRenderer v-if="activeChapter" :value="activeChapter" /> <div v-if="currentLyrics" class="lyrics-content" v-html="currentLyricsHtml" />
<div v-else-if="currentSong" class="lyrics-empty">
<p class="op-40 italic">Paroles à venir pour « {{ currentSong.title }} »</p>
</div>
<div v-else class="lyrics-empty">
<p class="op-40 italic">Aucun morceau sélectionné</p>
</div>
</div> </div>
<!-- Page turn shadow overlay (paginated only) --> <!-- Page turn shadow overlay (paginated only) -->
<div v-if="!isScrollMode" class="reader-shadow" :class="{ visible: isTurning }" /> <div v-if="!isScrollMode" class="reader-shadow" :class="{ visible: isTurning }" />
@@ -97,28 +92,28 @@
<div class="reader-nav"> <div class="reader-nav">
<button <button
class="reader-nav-btn" class="reader-nav-btn"
:class="{ 'reader-nav-btn--hidden': isScrollMode ? chapterIdx <= 0 : (currentPage <= 0 && chapterIdx <= 0) }" :class="{ 'reader-nav-btn--hidden': isScrollMode ? trackIdx <= 0 : (currentPage <= 0 && trackIdx <= 0) }"
@click="isScrollMode ? prevChapter() : prevPage()" @click="isScrollMode ? prevTrack() : prevPage()"
:aria-label="isScrollMode ? 'Chapitre précédent' : 'Page précédente'" :aria-label="isScrollMode ? 'Morceau précédent' : 'Page précédente'"
> >
<div class="i-lucide-chevron-left h-5 w-5" /> <div class="i-lucide-chevron-left h-5 w-5" />
</button> </button>
<!-- Song disc (if chapter has a song) --> <!-- Song disc -->
<div v-if="chapterSong" 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/book-cover-spread.jpg" alt="" class="reader-disc-img" />
<div class="reader-disc-hole" /> <div class="reader-disc-hole" />
</div> </div>
<span class="reader-song-name">{{ chapterSong.title }}</span> <span class="reader-song-name">{{ currentSong.title }}</span>
</div> </div>
<div v-else class="reader-song" /> <div v-else class="reader-song" />
<button <button
class="reader-nav-btn" class="reader-nav-btn"
:class="{ 'reader-nav-btn--hidden': isScrollMode ? chapterIdx >= chapters.length - 1 : (currentPage >= totalPages - 1 && chapterIdx >= chapters.length - 1) }" :class="{ 'reader-nav-btn--hidden': isScrollMode ? trackIdx >= tracks.length - 1 : (currentPage >= totalPages - 1 && trackIdx >= tracks.length - 1) }"
@click="isScrollMode ? nextChapter() : nextPage()" @click="isScrollMode ? nextTrack() : nextPage()"
:aria-label="isScrollMode ? 'Chapitre suivant' : 'Page suivante'" :aria-label="isScrollMode ? 'Morceau suivant' : 'Page suivante'"
> >
<div class="i-lucide-chevron-right h-5 w-5" /> <div class="i-lucide-chevron-right h-5 w-5" />
</button> </button>
@@ -128,7 +123,7 @@
<!-- Hint --> <!-- Hint -->
<p class="bp-hint"> <p class="bp-hint">
<template v-if="isScrollMode"> <template v-if="isScrollMode">
<span class="hidden md:inline">{{ bpContent?.reader.hints.desktopScroll ?? '← → chapitres · Défilement libre · Esc fermer' }}</span> <span class="hidden md:inline">{{ bpContent?.reader.hints.desktopScroll ?? '← → morceaux · Défilement libre · Esc fermer' }}</span>
<span class="md:hidden">{{ bpContent?.reader.hints.mobileScroll ?? 'Défilez pour lire' }}</span> <span class="md:hidden">{{ bpContent?.reader.hints.mobileScroll ?? 'Défilez pour lire' }}</span>
</template> </template>
<template v-else> <template v-else>
@@ -158,7 +153,7 @@ const overlayRef = ref<HTMLElement>()
const viewportEl = ref<HTMLElement>() const viewportEl = ref<HTMLElement>()
const contentEl = ref<HTMLElement>() const contentEl = ref<HTMLElement>()
const chapterIdx = ref(0) const trackIdx = ref(0)
const currentPage = ref(0) const currentPage = ref(0)
const totalPages = ref(1) const totalPages = ref(1)
const colWidth = ref(500) const colWidth = ref(500)
@@ -166,14 +161,10 @@ const showSommaire = ref(false)
const isTurning = ref(false) const isTurning = ref(false)
// ── Reading mode ── // ── Reading mode ──
const readingMode = ref<'paginated' | 'scroll'>('paginated') const readingMode = ref<'paginated' | 'scroll'>('scroll')
const isScrollMode = computed(() => readingMode.value === 'scroll') const isScrollMode = computed(() => readingMode.value === 'scroll')
const scrollPercent = ref(0) const scrollPercent = ref(0)
function toggleReadingMode() {
readingMode.value = readingMode.value === 'paginated' ? 'scroll' : 'paginated'
}
// When switching back to paginated, recalc pages // When switching back to paginated, recalc pages
watch(readingMode, async (mode) => { watch(readingMode, async (mode) => {
if (mode === 'paginated') { if (mode === 'paginated') {
@@ -190,72 +181,57 @@ function onViewportScroll() {
scrollPercent.value = max > 0 ? Math.round((el.scrollTop / max) * 100) : 0 scrollPercent.value = max > 0 ? Math.round((el.scrollTop / max) * 100) : 0
} }
const { init: initBookData, getSongs, getPrimarySong, getChapterForSong, getPlaylistOrder } = useBookData() const { init: initBookData, getPlaylistOrder } = useBookData()
const audioPlayer = useAudioPlayer() const audioPlayer = useAudioPlayer()
const playerStore = usePlayerStore() const playerStore = usePlayerStore()
// ── Content from Nuxt Content ── // ── Tracks: built from playlist order (songs), not chapters ──
const chaptersContent = ref<any[]>([]) const tracks = computed(() => {
const contentLoaded = ref(false) return playerStore.playlist.map(song => ({
id: song.id,
async function loadContent() { title: song.title,
if (contentLoaded.value) return song,
try { }))
const data = await queryCollection('book').order('order', 'ASC').all()
chaptersContent.value = data
contentLoaded.value = true
}
catch (err) {
console.error('Failed to load book content:', err)
}
}
const activeChapter = computed(() => {
if (chapterIdx.value < 0 || !chaptersContent.value.length) return null
return chaptersContent.value[chapterIdx.value] ?? null
}) })
// ── Chapter metadata ── const currentTrack = computed(() => tracks.value[trackIdx.value] ?? null)
const chapters = [ const currentSong = computed(() => currentTrack.value?.song ?? null)
{ slug: '01-introduction', title: 'Introduction' },
{ slug: '02-don', title: 'De quel don parlons-nous ?' },
{ slug: '03-mesure', title: 'La mesure du don' },
{ slug: '04-monnaie', title: 'Raison d\'être d\'une monnaie' },
{ slug: '05-trm', title: 'La TRM' },
{ slug: '06-economie', title: 'Créer une économie ?' },
{ slug: '07-echange', title: 'Échanger' },
{ slug: '08-institution', title: 'Relation institutionnelle' },
{ slug: '09-greffes', title: 'Autres greffes' },
{ slug: '10-maintenant', title: 'Et maintenant ?… action ?' },
{ slug: '11-annexes', title: 'Chapitres annexes' },
]
// ── Per-chapter color hues ── const currentLyrics = computed(() => {
const chapterHues: [number, number][] = [ return currentSong.value?.lyrics?.trim() || ''
[12, 36], // cover (intro + cover phases) })
[15, 35], // 1
[350, 15], // 2 const currentLyricsHtml = computed(() => {
[36, 50], // 3 if (!currentLyrics.value) return ''
[170, 200], // 4 return currentLyrics.value
[220, 250], // 5 .replace(/&/g, '&amp;')
[270, 300], // 6 .replace(/</g, '&lt;')
[320, 345], // 7 .replace(/>/g, '&gt;')
[150, 170], // 8 .replace(/\[([^\]]+)\]/g, '<span class="lyrics-tag">[$1]</span>')
[190, 220], // 9 .replace(/\n\n/g, '</p><p>')
[40, 20], // 10 .replace(/\n/g, '<br>')
[210, 240], // 11 .replace(/^/, '<p>')
.replace(/$/, '</p>')
})
// ── Per-track color hues (9 tracks) ──
const trackHues: [number, number][] = [
[15, 35], // 1 Ce livre est une façon
[350, 15], // 2 De quel don nous parlons
[36, 50], // 3 Les asymétries
[170, 200], // 4 Inverser les flux
[220, 250], // 5 Ainsi soit-il
[270, 300], // 6 La croissance
[320, 345], // 7 Monnaie libre
[150, 170], // 8 Créer une économie
[190, 220], // 9 Coder la liberté
] ]
const sceneVars = computed(() => { const sceneVars = computed(() => {
const idx = chapterIdx.value + 1 const [h1, h2] = trackHues[trackIdx.value] ?? trackHues[0]
const [h1, h2] = chapterHues[idx] ?? chapterHues[0]
return { '--scene-h1': h1, '--scene-h2': h2 } as Record<string, number> return { '--scene-h1': h1, '--scene-h2': h2 } as Record<string, number>
}) })
const chapterSong = computed(() => {
return getPrimarySong(chapters[chapterIdx.value].slug)
})
// ── CSS columns pagination ── // ── CSS columns pagination ──
const contentStyle = computed(() => { const contentStyle = computed(() => {
if (isScrollMode.value) return {} if (isScrollMode.value) return {}
@@ -276,18 +252,16 @@ function recalcPages() {
let resizeObs: ResizeObserver | null = null let resizeObs: ResizeObserver | null = null
// Recalc when chapter content changes // Recalc when track changes
watch(activeChapter, async () => { watch(trackIdx, async () => {
currentPage.value = 0 currentPage.value = 0
// Wait for ContentRenderer to update DOM
await nextTick() await nextTick()
await nextTick() await nextTick()
setTimeout(recalcPages, 100) setTimeout(recalcPages, 100)
}) })
async function initReading() { async function initReading() {
await loadContent() trackIdx.value = 0
chapterIdx.value = 0
currentPage.value = 0 currentPage.value = 0
await nextTick() await nextTick()
await nextTick() await nextTick()
@@ -299,34 +273,35 @@ async function initReading() {
setTimeout(recalcPages, 200) setTimeout(recalcPages, 200)
} }
// ── Navigation ── // ── Navigation by tracks (songs) ──
let _skipSongWatch = false let _skipSongWatch = false
function goToChapter(idx: number) { function goToTrack(idx: number) {
chapterIdx.value = idx if (idx < 0 || idx >= tracks.value.length) return
trackIdx.value = idx
currentPage.value = 0 currentPage.value = 0
showSommaire.value = false showSommaire.value = false
// Scroll to top in scroll mode // Scroll to top in scroll mode
if (isScrollMode.value && viewportEl.value) { if (isScrollMode.value && viewportEl.value) {
viewportEl.value.scrollTop = 0 viewportEl.value.scrollTop = 0
} }
// Play chapter song (skip watcher to avoid loop) // Play the song
const song = getPrimarySong(chapters[idx].slug) const song = tracks.value[idx]?.song
if (song) { if (song) {
_skipSongWatch = true _skipSongWatch = true
audioPlayer.loadAndPlay(song) audioPlayer.loadAndPlay(song)
} }
} }
function nextChapter() { function nextTrack() {
if (chapterIdx.value < chapters.length - 1) { if (trackIdx.value < tracks.value.length - 1) {
goToChapter(chapterIdx.value + 1) goToTrack(trackIdx.value + 1)
} }
} }
function prevChapter() { function prevTrack() {
if (chapterIdx.value > 0) { if (trackIdx.value > 0) {
goToChapter(chapterIdx.value - 1) goToTrack(trackIdx.value - 1)
} }
} }
@@ -335,9 +310,8 @@ function nextPage() {
triggerTurn() triggerTurn()
currentPage.value++ currentPage.value++
} }
else if (chapterIdx.value < chapters.length - 1) { else if (trackIdx.value < tracks.value.length - 1) {
// Next chapter goToTrack(trackIdx.value + 1)
goToChapter(chapterIdx.value + 1)
} }
} }
@@ -346,25 +320,8 @@ function prevPage() {
triggerTurn() triggerTurn()
currentPage.value-- currentPage.value--
} }
else if (chapterIdx.value > 0) { else if (trackIdx.value > 0) {
// Previous chapter, go to last page goToTrack(trackIdx.value - 1)
chapterIdx.value--
currentPage.value = 0
showSommaire.value = false
const song = getPrimarySong(chapters[chapterIdx.value].slug)
if (song) {
_skipSongWatch = true
audioPlayer.loadAndPlay(song)
}
// After content loads, go to last page
watch(activeChapter, async () => {
await nextTick()
await nextTick()
setTimeout(() => {
recalcPages()
currentPage.value = Math.max(0, totalPages.value - 1)
}, 150)
}, { once: true })
} }
} }
@@ -378,19 +335,26 @@ function close() {
} }
function handleKeydown(e: KeyboardEvent) { function handleKeydown(e: KeyboardEvent) {
// CRITICAL: stop propagation so useKeyboardShortcuts doesn't also fire
e.stopPropagation()
if (e.key === 'Escape') { close(); return }
if (e.key === ' ') {
e.preventDefault()
audioPlayer.togglePlayPause()
return
}
if (isScrollMode.value) { if (isScrollMode.value) {
// Scroll mode: left/right = chapters, up/down = natural scroll (no preventDefault) if (e.key === 'ArrowRight') { e.preventDefault(); nextTrack() }
if (e.key === 'ArrowRight') { e.preventDefault(); nextChapter() } else if (e.key === 'ArrowLeft') { e.preventDefault(); prevTrack() }
else if (e.key === 'ArrowLeft') { e.preventDefault(); prevChapter() }
else if (e.key === 'Escape') close()
} }
else { else {
// Paginated mode: left/right = pages, up/down = chapters
if (e.key === 'ArrowRight') { e.preventDefault(); nextPage() } if (e.key === 'ArrowRight') { e.preventDefault(); nextPage() }
else if (e.key === 'ArrowLeft') { e.preventDefault(); prevPage() } else if (e.key === 'ArrowLeft') { e.preventDefault(); prevPage() }
else if (e.key === 'ArrowDown') { e.preventDefault(); nextChapter() } else if (e.key === 'ArrowDown') { e.preventDefault(); nextTrack() }
else if (e.key === 'ArrowUp') { e.preventDefault(); prevChapter() } else if (e.key === 'ArrowUp') { e.preventDefault(); prevTrack() }
else if (e.key === 'Escape') close()
} }
} }
@@ -402,7 +366,6 @@ function onTouchStart(e: TouchEvent) {
} }
function onTouchEnd(e: TouchEvent) { function onTouchEnd(e: TouchEvent) {
// Disable page-swipe in scroll mode (vertical scroll is native)
if (isScrollMode.value) return if (isScrollMode.value) return
const diff = touchStartX - (e.changedTouches[0]?.screenX ?? 0) const diff = touchStartX - (e.changedTouches[0]?.screenX ?? 0)
if (Math.abs(diff) > 50) { if (Math.abs(diff) > 50) {
@@ -415,7 +378,6 @@ function onTouchEnd(e: TouchEvent) {
watch(isOpen, async (open) => { watch(isOpen, async (open) => {
if (open) { if (open) {
showSommaire.value = false showSommaire.value = false
contentLoaded.value = false
await initBookData() await initBookData()
await nextTick() await nextTick()
overlayRef.value?.focus() overlayRef.value?.focus()
@@ -424,12 +386,10 @@ watch(isOpen, async (open) => {
// Load playlist & play first song // Load playlist & play first song
const playlist = getPlaylistOrder() const playlist = getPlaylistOrder()
if (playlist.length) playerStore.setPlaylist(playlist) if (playlist.length) playerStore.setPlaylist(playlist)
const first = getSongs().find(s => s.id === 'ce-livre-est-une-facon') if (playlist.length) {
if (first) {
_skipSongWatch = true _skipSongWatch = true
audioPlayer.loadAndPlay(first) audioPlayer.loadAndPlay(playlist[0])
} }
// Start reading directly
await initReading() await initReading()
} }
else { else {
@@ -439,19 +399,18 @@ watch(isOpen, async (open) => {
} }
}) })
// ── Sync: when song changes in player, navigate to matching chapter ── // ── Sync: when song changes externally (persistent player controls), update trackIdx ──
watch(() => playerStore.currentSong, (song) => { watch(() => playerStore.currentSong, (song) => {
if (!song || !isOpen.value) return if (!song || !isOpen.value) return
if (_skipSongWatch) { if (_skipSongWatch) {
_skipSongWatch = false _skipSongWatch = false
return return
} }
const slug = getChapterForSong(song.id) const idx = tracks.value.findIndex(t => t.id === song.id)
if (!slug) return if (idx !== -1 && idx !== trackIdx.value) {
const idx = chapters.findIndex(ch => ch.slug === slug) trackIdx.value = idx
if (idx !== -1 && idx !== chapterIdx.value) {
chapterIdx.value = idx
currentPage.value = 0 currentPage.value = 0
showSommaire.value = false
if (isScrollMode.value && viewportEl.value) { if (isScrollMode.value && viewportEl.value) {
viewportEl.value.scrollTop = 0 viewportEl.value.scrollTop = 0
} }
@@ -809,7 +768,33 @@ onUnmounted(() => {
.reader-columns :deep(ol) { .reader-columns :deep(ol) {
break-inside: avoid; break-inside: avoid;
} }
/* p with pre-line (lyrics) can be taller than a column — allow break */ /* Lyrics content */
.lyrics-content {
white-space: pre-line;
line-height: 1.9;
font-size: clamp(0.9rem, 2vw, 1.05rem);
}
.lyrics-content :deep(.lyrics-tag) {
display: block;
margin-top: 1.5rem;
margin-bottom: 0.5rem;
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
opacity: 0.35;
}
.lyrics-content :deep(p) {
break-inside: auto;
overflow-y: auto;
}
.lyrics-empty {
display: flex;
align-items: center;
justify-content: center;
min-height: 200px;
}
.reader-columns :deep(p) { .reader-columns :deep(p) {
break-inside: auto; break-inside: auto;
overflow-y: auto; overflow-y: auto;

View File

@@ -6,7 +6,7 @@
<ul class="flex flex-col gap-1"> <ul class="flex flex-col gap-1">
<li v-for="chapter in chapters" :key="chapter.path"> <li v-for="chapter in chapters" :key="chapter.path">
<NuxtLink <NuxtLink
:to="`/modele-eco/${chapter.stem}`" :to="`/modele-eco/${chapter.stem?.split('/').pop()}`"
class="flex items-center gap-3 rounded-lg px-3 py-2 text-sm transition-colors hover:bg-white/5" class="flex items-center gap-3 rounded-lg px-3 py-2 text-sm transition-colors hover:bg-white/5"
active-class="bg-primary/10 text-primary font-medium" active-class="bg-primary/10 text-primary font-medium"
> >

View File

@@ -3,7 +3,7 @@
<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-section" viewBox="0 0 64 80" fill="none" aria-hidden="true"> <svg class="logo-icon" viewBox="0 0 64 80" 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"/> <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"/>
<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"/> <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"/>
<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"/> <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"/>
@@ -56,7 +56,7 @@ const isMobileMenuOpen = ref(false)
opacity: 0.8; opacity: 0.8;
} }
.logo-section { .logo-icon {
width: 1.6rem; width: 1.6rem;
height: 2rem; height: 2rem;
color: hsl(var(--color-primary)); color: hsl(var(--color-primary));
@@ -65,12 +65,9 @@ const isMobileMenuOpen = ref(false)
.logo-text { .logo-text {
font-family: var(--font-display); font-family: var(--font-display);
font-weight: 300; font-weight: 600;
font-size: 1.25rem; font-size: 1.15rem;
letter-spacing: 0.04em; letter-spacing: 0.02em;
background-image: linear-gradient(to right, hsl(var(--color-primary)), hsl(var(--color-accent))); color: hsl(var(--color-primary));
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
} }
</style> </style>

View File

@@ -51,10 +51,18 @@
> >
</div> </div>
<!-- Lyrics (if available) --> <!-- Lyrics (collapsed by default, available for standalone use) -->
<div v-if="store.currentSong.lyrics" class="panel-lyrics"> <div v-if="store.currentSong.lyrics && showLyrics" class="panel-lyrics">
<pre class="panel-lyrics-text">{{ store.currentSong.lyrics }}</pre> <pre class="panel-lyrics-text">{{ store.currentSong.lyrics }}</pre>
</div> </div>
<button
v-if="store.currentSong.lyrics"
class="panel-lyrics-toggle"
@click="showLyrics = !showLyrics"
>
<div :class="showLyrics ? 'i-lucide-chevron-up' : 'i-lucide-text'" class="h-3 w-3" />
{{ showLyrics ? 'Masquer les paroles' : 'Paroles' }}
</button>
<!-- Playlist --> <!-- Playlist -->
<div class="panel-playlist"> <div class="panel-playlist">
@@ -128,6 +136,7 @@ useKeyboardShortcuts()
const widgetRef = ref<HTMLElement>() const widgetRef = ref<HTMLElement>()
const isExpanded = ref(false) const isExpanded = ref(false)
const showLyrics = ref(false)
let previousVolume = 0.8 let previousVolume = 0.8
const circumference = 2 * Math.PI * 16 const circumference = 2 * Math.PI * 16
@@ -401,6 +410,26 @@ onClickOutside(widgetRef, () => {
margin: 0; margin: 0;
} }
/* ─── Lyrics toggle ─── */
.panel-lyrics-toggle {
display: flex;
align-items: center;
justify-content: center;
gap: 0.375rem;
width: 100%;
padding: 0.375rem;
border: none;
border-top: 1px solid hsl(0 0% 100% / 0.04);
background: none;
color: hsl(0 0% 100% / 0.25);
font-size: 0.65rem;
cursor: pointer;
transition: color 0.15s;
}
.panel-lyrics-toggle:hover {
color: hsl(0 0% 100% / 0.5);
}
/* ─── Playlist ─── */ /* ─── Playlist ─── */
.panel-playlist { .panel-playlist {
max-height: 200px; max-height: 200px;

View File

@@ -124,13 +124,7 @@ export function useAudioPlayer() {
function playPrev() { function playPrev() {
const song = store.prevSong() const song = store.prevSong()
if (song) { if (song) {
if (song === store.currentSong && store.currentTime <= 3) { loadAndPlay(song)
// prevSong already reset time
seek(0)
}
else {
loadAndPlay(song)
}
} }
} }

View File

@@ -34,6 +34,36 @@
</div> </div>
</AdminFormSection> </AdminFormSection>
<AdminFormSection title="Morceaux associés">
<p class="text-xs text-white/40 mb-3">
Cliquez pour associer/dissocier. Cliquez sur l'étoile pour définir le morceau principal.
</p>
<div class="flex flex-wrap gap-2">
<div
v-for="song in allSongs"
:key="song.id"
class="song-tag"
:class="{
'song-tag--active': isLinked(song.id),
'song-tag--primary': isPrimary(song.id),
}"
>
<button
v-if="isLinked(song.id)"
class="song-star"
:class="{ 'song-star--active': isPrimary(song.id) }"
@click="setPrimary(song.id)"
aria-label="Définir comme principal"
>
<div class="i-lucide-star h-3 w-3" />
</button>
<button class="song-tag-label" @click="toggleSong(song.id)">
{{ song.title }}
</button>
</div>
</div>
</AdminFormSection>
<AdminFormSection title="Contenu" open> <AdminFormSection title="Contenu" open>
<AdminMarkdownEditor v-model="body" :rows="35" /> <AdminMarkdownEditor v-model="body" :rows="35" />
</AdminFormSection> </AdminFormSection>
@@ -51,6 +81,7 @@ const route = useRoute()
const slug = computed(() => route.params.slug as string) const slug = computed(() => route.params.slug as string)
const { data: chapter } = await useFetch(() => `/api/admin/chapters/${slug.value}`) const { data: chapter } = await useFetch(() => `/api/admin/chapters/${slug.value}`)
const { data: bookConfig } = await useFetch<any>('/api/content/config')
const title = ref('') const title = ref('')
const description = ref('') const description = ref('')
@@ -65,7 +96,6 @@ const wordCount = computed(() => {
watch(chapter, (val) => { watch(chapter, (val) => {
if (val) { if (val) {
// Parse frontmatter fields
const fm = val.frontmatter ?? '' const fm = val.frontmatter ?? ''
title.value = extractFmField(fm, 'title') title.value = extractFmField(fm, 'title')
description.value = extractFmField(fm, 'description') description.value = extractFmField(fm, 'description')
@@ -79,6 +109,52 @@ function extractFmField(fm: string, field: string): string {
return match ? match[1].trim() : '' return match ? match[1].trim() : ''
} }
// ── Morceaux associés ──
const allSongs = computed(() => bookConfig.value?.songs ?? [])
const linkedSongIds = ref<Set<string>>(new Set())
const primarySongId = ref<string | null>(null)
watch(bookConfig, (val) => {
if (!val) return
const links = (val.chapterSongs ?? []).filter(
(cs: any) => cs.chapterSlug === slug.value,
)
linkedSongIds.value = new Set(links.map((l: any) => l.songId))
const primary = links.find((l: any) => l.primary)
primarySongId.value = primary?.songId ?? null
}, { immediate: true })
function isLinked(songId: string) {
return linkedSongIds.value.has(songId)
}
function isPrimary(songId: string) {
return primarySongId.value === songId
}
function toggleSong(songId: string) {
const next = new Set(linkedSongIds.value)
if (next.has(songId)) {
next.delete(songId)
if (primarySongId.value === songId) primarySongId.value = null
}
else {
next.add(songId)
if (!primarySongId.value) primarySongId.value = songId
}
linkedSongIds.value = next
}
function setPrimary(songId: string) {
if (!linkedSongIds.value.has(songId)) {
const next = new Set(linkedSongIds.value)
next.add(songId)
linkedSongIds.value = next
}
primarySongId.value = songId
}
// ── Save ──
const saving = ref(false) const saving = ref(false)
const saved = ref(false) const saved = ref(false)
@@ -86,6 +162,7 @@ async function save() {
saving.value = true saving.value = true
saved.value = false saved.value = false
try { try {
// 1. Sauvegarder le contenu du chapitre
const order = chapter.value?.frontmatter?.match(/order:\s*(\d+)/)?.[1] ?? '1' const order = chapter.value?.frontmatter?.match(/order:\s*(\d+)/)?.[1] ?? '1'
const frontmatter = [ const frontmatter = [
`title: "${title.value}"`, `title: "${title.value}"`,
@@ -98,6 +175,27 @@ async function save() {
method: 'PUT', method: 'PUT',
body: { frontmatter, body: body.value }, body: { frontmatter, body: body.value },
}) })
// 2. Sauvegarder les liaisons morceaux dans la config
if (bookConfig.value) {
const otherLinks = (bookConfig.value.chapterSongs ?? []).filter(
(cs: any) => cs.chapterSlug !== slug.value,
)
const newLinks = [...linkedSongIds.value].map(songId => ({
chapterSlug: slug.value,
songId,
primary: songId === primarySongId.value,
}))
const updatedConfig = {
...bookConfig.value,
chapterSongs: [...otherLinks, ...newLinks],
}
await $fetch('/api/admin/content/config', {
method: 'PUT',
body: updatedConfig,
})
}
saved.value = true saved.value = true
setTimeout(() => { saved.value = false }, 2000) setTimeout(() => { saved.value = false }, 2000)
} }
@@ -129,4 +227,70 @@ async function save() {
outline: none; outline: none;
border-color: hsl(12 76% 48% / 0.5); border-color: hsl(12 76% 48% / 0.5);
} }
/* ── Song tags ── */
.song-tag {
display: inline-flex;
align-items: center;
border-radius: 9999px;
border: 1px solid hsl(20 8% 22%);
transition: all 0.15s;
overflow: hidden;
}
.song-tag:hover {
border-color: hsl(12 76% 48% / 0.4);
}
.song-tag--active {
border-color: hsl(12 76% 48% / 0.6);
background: hsl(12 76% 48% / 0.08);
}
.song-tag--primary {
border-color: hsl(45 90% 55%);
background: hsl(45 90% 55% / 0.08);
}
.song-tag-label {
padding: 0.375rem 0.75rem;
background: none;
border: none;
color: hsl(20 8% 50%);
font-size: 0.8rem;
cursor: pointer;
transition: color 0.15s;
}
.song-tag--active .song-tag-label {
color: hsl(12 76% 68%);
}
.song-tag--primary .song-tag-label {
color: hsl(45 90% 65%);
}
.song-tag-label:hover {
color: white;
}
.song-star {
display: flex;
align-items: center;
justify-content: center;
padding: 0.375rem 0 0.375rem 0.625rem;
background: none;
border: none;
color: hsl(20 8% 30%);
cursor: pointer;
transition: color 0.15s;
}
.song-star:hover {
color: hsl(45 90% 55%);
}
.song-star--active {
color: hsl(45 90% 55%);
}
</style> </style>

View File

@@ -20,12 +20,21 @@
<div class="i-lucide-grip-vertical h-4 w-4" /> <div class="i-lucide-grip-vertical h-4 w-4" />
</div> </div>
<span class="chapter-order">{{ String(i + 1).padStart(2, '0') }}</span> <span class="chapter-order">{{ String(i + 1).padStart(2, '0') }}</span>
<NuxtLink <div class="chapter-info">
:to="`/admin/book/${chapter.slug}`" <NuxtLink
class="chapter-title" :to="`/admin/book/${chapter.slug}`"
> class="chapter-title"
{{ chapter.title }} >
</NuxtLink> {{ chapter.title }}
</NuxtLink>
<div v-if="getChapterSongNames(chapter.slug).length" class="chapter-songs">
<span
v-for="name in getChapterSongNames(chapter.slug)"
:key="name"
class="song-badge"
>{{ name }}</span>
</div>
</div>
<button <button
class="delete-btn" class="delete-btn"
@click="removeChapter(chapter.slug)" @click="removeChapter(chapter.slug)"
@@ -64,6 +73,19 @@ definePageMeta({
}) })
const { data: chapters, refresh } = await useFetch<any[]>('/api/admin/chapters') const { data: chapters, refresh } = await useFetch<any[]>('/api/admin/chapters')
const { data: bookConfig } = await useFetch<any>('/api/content/config')
function getChapterSongNames(chapterSlug: string): string[] {
if (!bookConfig.value) return []
const links = (bookConfig.value.chapterSongs ?? []).filter(
(cs: any) => cs.chapterSlug === chapterSlug,
)
return links.map((link: any) => {
const song = bookConfig.value.songs.find((s: any) => s.id === link.songId)
return song?.title ?? link.songId
})
}
const saving = ref(false) const saving = ref(false)
const saved = ref(false) const saved = ref(false)
const newTitle = ref('') const newTitle = ref('')
@@ -176,8 +198,13 @@ async function removeChapter(slug: string) {
width: 1.75rem; width: 1.75rem;
} }
.chapter-title { .chapter-info {
flex: 1; flex: 1;
min-width: 0;
}
.chapter-title {
display: block;
color: white; color: white;
font-weight: 500; font-weight: 500;
text-decoration: none; text-decoration: none;
@@ -187,6 +214,22 @@ async function removeChapter(slug: string) {
color: hsl(12 76% 68%); color: hsl(12 76% 68%);
} }
.chapter-songs {
display: flex;
flex-wrap: wrap;
gap: 0.25rem;
margin-top: 0.25rem;
}
.song-badge {
font-size: 0.65rem;
padding: 0.1rem 0.5rem;
border-radius: 9999px;
background: hsl(12 76% 48% / 0.1);
color: hsl(12 76% 60%);
border: 1px solid hsl(12 76% 48% / 0.2);
}
.delete-btn { .delete-btn {
flex-shrink: 0; flex-shrink: 0;
padding: 0.375rem; padding: 0.375rem;

View File

@@ -14,7 +14,7 @@
<nav class="mt-16 flex items-center justify-between border-t border-white/8 pt-8"> <nav class="mt-16 flex items-center justify-between border-t border-white/8 pt-8">
<NuxtLink <NuxtLink
v-if="prevChapter" v-if="prevChapter"
:to="`/modele-eco/${prevChapter.stem}`" :to="`/modele-eco/${prevChapter.stem?.split('/').pop()}`"
class="btn-ghost gap-2" class="btn-ghost gap-2"
> >
<div class="i-lucide-arrow-left h-4 w-4" /> <div class="i-lucide-arrow-left h-4 w-4" />
@@ -24,7 +24,7 @@
<NuxtLink <NuxtLink
v-if="nextChapter" v-if="nextChapter"
:to="`/modele-eco/${nextChapter.stem}`" :to="`/modele-eco/${nextChapter.stem?.split('/').pop()}`"
class="btn-ghost gap-2" class="btn-ghost gap-2"
> >
<span class="text-sm">{{ nextChapter.title }}</span> <span class="text-sm">{{ nextChapter.title }}</span>
@@ -64,7 +64,7 @@ const { data: allChapters } = await useAsyncData('book-nav', () =>
) )
const currentIndex = computed(() => const currentIndex = computed(() =>
allChapters.value?.findIndex(c => c.stem === slug) ?? -1, allChapters.value?.findIndex(c => c.stem?.split('/').pop() === slug) ?? -1,
) )
const prevChapter = computed(() => { const prevChapter = computed(() => {

View File

@@ -91,7 +91,7 @@
:key="chapter.path" :key="chapter.path"
> >
<NuxtLink <NuxtLink
:to="`/modele-eco/${chapter.stem}`" :to="`/modele-eco/${chapter.stem?.split('/').pop()}`"
class="card-surface flex items-start gap-4 group" class="card-surface flex items-start gap-4 group"
> >
<span class="font-mono text-2xl font-bold text-primary/30 leading-none mt-1 w-10 text-right flex-shrink-0"> <span class="font-mono text-2xl font-bold text-primary/30 leading-none mt-1 w-10 text-right flex-shrink-0">
@@ -109,7 +109,7 @@
<span class="i-lucide-clock inline-block h-3 w-3 mr-1 align-middle" /> <span class="i-lucide-clock inline-block h-3 w-3 mr-1 align-middle" />
{{ chapter.readingTime }} {{ chapter.readingTime }}
</span> </span>
<SongBadges :chapter-slug="chapter.stem!" /> <SongBadges :chapter-slug="chapter.stem?.split('/').pop() ?? ''" />
</div> </div>
</div> </div>
<div class="i-lucide-chevron-right h-5 w-5 text-white/20 group-hover:text-primary/60 transition-colors flex-shrink-0 mt-2" /> <div class="i-lucide-chevron-right h-5 w-5 text-white/20 group-hover:text-primary/60 transition-colors flex-shrink-0 mt-2" />

View File

@@ -46,29 +46,29 @@ const palettes: Record<PaletteName, PaletteColors> = {
// ══════ LIGHT THEMES ══════ // ══════ LIGHT THEMES ══════
// Printemps : vert vif, rose cerisier punchy, lumière fraîche // Printemps : vert soutenu, magenta chaud, lumière vivante
printemps: { printemps: {
primary: '152 65% 36%', // vert émeraude vif primary: '152 80% 24%', // vert émeraude sombre
accent: '340 78% 52%', // rose cerisier punchy accent: '338 88% 45%', // magenta profond
surface: '130 18% 90%', // rosée du matin surface: '145 25% 85%', // prairie franche
bg: '110 20% 94%', // clarté verte bg: '140 28% 90%', // vert lumineux franc
surfaceLight: '125 14% 84%', // feuille vive surfaceLight: '148 22% 77%', // feuillage vif
text: '155 30% 10%', // vert profond saturé text: '155 50% 6%', // encre noire-verte
textMuted: '145 14% 38%', // mousse riche textMuted: '150 22% 28%', // sous-bois dense
isLight: true, isLight: true,
label: 'Printemps', label: 'Printemps',
icon: 'i-lucide-flower-2', icon: 'i-lucide-flower-2',
}, },
// Été : orange solaire, turquoise pop, lumineux chaleureux // Été : orange brûlant, corail profond, chaleur méditerranéenne
ete: { ete: {
primary: '22 92% 48%', // soleil éclatant primary: '18 90% 44%', // terre cuite brûlante
accent: '175 72% 38%', // turquoise pop accent: '355 78% 50%', // corail ardent
surface: '38 35% 88%', // sable doré surface: '32 40% 85%', // ocre clair
bg: '40 38% 93%', // lumière dorée chaude bg: '35 42% 90%', // chaleur dorée
surfaceLight: '35 28% 82%', // dune chaude surfaceLight: '30 32% 78%', // argile chaude
text: '28 35% 8%', // brun intense text: '20 45% 8%', // brun profond
textMuted: '28 16% 35%', // ombre chaude textMuted: '22 22% 30%', // ombre terracotta
isLight: true, isLight: true,
label: 'Été', label: 'Été',
icon: 'i-lucide-sun', icon: 'i-lucide-sun',

View File

@@ -119,20 +119,13 @@ export const usePlayerStore = defineStore('player', () => {
function prevSong(): Song | null { function prevSong(): Song | null {
if (playlist.value.length === 0) return null if (playlist.value.length === 0) return null
// If more than 3 seconds in, restart current song
if (currentTime.value > 3) {
currentTime.value = 0
return currentSong.value
}
let prevIdx = currentIndex.value - 1 let prevIdx = currentIndex.value - 1
if (prevIdx < 0) { if (prevIdx < 0) {
if (repeatMode.value === 'all') { if (repeatMode.value === 'all') {
prevIdx = playlist.value.length - 1 prevIdx = playlist.value.length - 1
} }
else { else {
currentTime.value = 0 return null
return currentSong.value
} }
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,19 +1,17 @@
book: book:
title: "Une économie du don — enfin concevable" title: Une économie du don — enfin concevable
author: "Yvv" author: Yvv
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/book-cover.jpg
license: "CC-BY-NC" license: CC-BY-NC
isbn: "979-1-042-45206-3" isbn: 979-1-042-45206-3
songs: songs:
- id: ce-livre-est-une-facon - id: ce-livre-est-une-facon
title: "Ce livre est une façon" title: Ce livre est une façon
artist: Yvv artist: Yvv
file: /audio/ce-livre-est-une-facon.mp3 file: /audio/ED-01-ce-livre-est-une-facon.mp3
duration: 718 duration: 718
lyrics: | lyrics: |
[Intro]
Ce livre est un essai. Ce livre est un essai.
Une proposition. Une proposition.
Une intention. Une intention.
@@ -21,18 +19,16 @@ songs:
(...) (...)
Une façon. Une façon.
[bridge] 2024, j'écris tout l'été, ...
Deux mille vingt'-quatre j'écris tout l'été, ... 2025, je mûris toute l'année, ...
Deux mille vingt'-cinq je mûris toute l'année, ... 2026, la sortie... mmmmhh,
Deux mille vingt'-siss la sortie... mmmmhh, Le temps passe
Le temps pass
Est-ce une menace ? Est-ce une menace ?
[Verse 1]
Pour éviter tout quiproquo : Pour éviter tout quiproquo :
Pour mieux zaimer le propos Pour mieux aimer le propos
Ce n'est pas une théorie. Ce n'est pas une théorie.
Pas d'u-niversalité. Pas d'universalité.
Loin s'en faut. Loin s'en faut.
Une expérimentation. Une expérimentation.
@@ -42,24 +38,21 @@ songs:
Mais si ça reste à ton échelle... c'est symbolique. Mais si ça reste à ton échelle... c'est symbolique.
Viser mon bassin de vie ? ça se complique. Viser mon bassin de vie ? ça se complique.
[Chorus] [Refrain]
Ce livre n'est pas un guiDe, Ce livre n'est pas un guide,
davantage un guit'. davantage un git.
Ce n'est pas un Kit. Ce n'est pas un kit.
Ça ne dit pas quoi faire lundi. Ça ne dit pas quoi faire lundi.
[Bridge]
Tu veux une baguette magique ? Tu veux une baguette magique ?
Supprimer la pression, l'oppression ? - Ce s'rait pas con... Supprimer la pression, l'oppression ? - Ce s'rait pas con...
Si je ne résous pas mes problèmes d'aujourd'hui... à quoi bon ? Si je ne résous pas mes problèmes d'aujourd'hui... A quoi bon ?
Naviguer dans le ciel des idées... Naviguer dans le ciel des idées... C'est fini !
C'est fini !
[Verse 2]
Créer une économie ? Créer une économie ?
On en a déjà une. Tu veux décrocher la lune ? On en a déjà une. Tu veux décrocher la lune ?
Elle couvre mes besoins vitaux. De facto. Elle couvre mes besoins vitaux. De facto.
Alors pourquoi ? Alors pourquoi ? Pourquoi ?
Ben. Pour l'autonomie. Ben. Pour l'autonomie.
...C'est tout - sauf un repli. ...C'est tout - sauf un repli.
@@ -70,55 +63,49 @@ songs:
"restez des enfants !"... "restez des enfants !"...
mmh, suspect et sans avenir. mmh, suspect et sans avenir.
[Bridge] 2026, l'année des défis.
Deux mille vingt-six. L'année des défis. Ne plus subir les agendas. Créer les nôtres.
Ne plus subir les agendas. Créer les nôtr'. On manque de repères ? Entre autres, ...
On manque de repères ?... Entre autres, ...
Faut les trouver,... Faut les trouver,...
En produisant, les inventer. En produisant, les inventer.
[Chorus] [Refrain]
Ce livre n'est pas un guiDe, Ce livre n'est pas un guide,
davantage un guit'. davantage un git.
Ce n'est pas un Kit. Ce n'est pas un kit.
Ça ne dit pas quoi faire lundi. Ça ne dit pas quoi faire lundi.
[Bridge 2]
C'est un os à ronger. C'est un os à ronger.
Une cartographie. Quelques boussoles. Une cartographie. Quelques boussoles.
Dans une jungle à défricher. Dans une jungle à défricher.
Ce n'est pas encore l'heure... Ce n'est pas encore l'heure...
Du prêtà-porter. Du prêt-à-porter.
A nous de tailler. A nous de tailler.
[outro]
On tourne une page pour voir ? On tourne une page pour voir ?
Décline le rôle de la bonne poire... Décline le rôle de la bonne poire...
Je n'ai pas que l'espoir Je n'ai pas que l'espoir
J'ai un pouvoir J'ai un pouvoir
Tu as bien mieux que l'espoir, Tu as bien mieux que l'espoir,
Nous - zavons un grand pouvoir. Nous avons un grand pouvoir.
(whisper :) hey, on tourne une page ? Hey, on tourne une page ?
tags: [introduction, livre, don] tags:
- introduction
- id: un-don-qui-se-mesure - livre
title: "Un don qui se mesure" - don
- id: de-quel-don-nous-parlons
title: De quel don nous parlons ?
artist: Yvv artist: Yvv
file: /audio/un-don-qui-se-mesure.mp3 file: /audio/ED-02-de-quel-don-nous-parlons.mp3
duration: 589 duration: 589
lyrics: | lyrics: |-
[Intro]
(Piano Rhodes : accords jazzy)
(Basse : Ligne ronde et enveloppante)
Une économie du don ? Une économie du don ?
Mais de quel don nous parlons ? Mais de quel don nous parlons ?
Ce mariage fait peur. Il claque. Ce mariage fait peur. Il claque.
Un oxymore, on l'apprend juste après l'bac. Un oxymore, on l'apprend juste après l'bac.
Une contradiction pour l'esprit. Une contradiction pour l'esprit.
[Verse 1]
On évacue tout de suite le spirituel. On évacue tout de suite le spirituel.
Désolé pour le karma, désolé pour le ciel. Désolé pour le karma, désolé pour le ciel.
Je ne parle pas du "centuple divin". Je ne parle pas du "centuple divin".
@@ -128,17 +115,16 @@ songs:
Qui sert de base, qui sert de fondation, Qui sert de base, qui sert de fondation,
À une autre forme de construction. À une autre forme de construction.
[Chorus] [Refrain]
Ce n'est pas l'image d'Épinal, le don gratuit, le don idéal. Ce n'est pas l'image d'Épinal, le don gratuit, le don idéal.
Marcel Mauss nous l'a dit, dans son essai radical. Marcel Mauss nous l'a dit, dans son essai radical.
Ce n'est pas un cadeau, c'est un cycle vital. Ce n'est pas un cadeau, c'est un cycle vital.
(Females: Donner... Recevoir... Rendre...) (Donner... Recevoir... Rendre...)
C'est un pacte, une tension, parfois même un combat. C'est un pacte, une tension, parfois même un combat.
Si tu ne redonnes pas, si tu enfreins le protocole... Si tu ne redonnes pas, ou désoblige un protocole...
Ça ne pardonne pas. Ça ne pardonne pas.
[Verse 2]
J'entends les rêves de brûler la monnaie. J'entends les rêves de brûler la monnaie.
"Le troc", "la gratuité", "Mocica", le grand projet. "Le troc", "la gratuité", "Mocica", le grand projet.
La monnaie serait le vice, la corruption mentale. La monnaie serait le vice, la corruption mentale.
@@ -149,58 +135,32 @@ songs:
Mais si la monnaie est libre ? Elle permet les équilibres. Mais si la monnaie est libre ? Elle permet les équilibres.
Si elle devient notre outil ? Elle change le récit. Si elle devient notre outil ? Elle change le récit.
[Bridge] Alors voici une définition du don :
(Music strips down. Just Bass and Snare rimshots) C'est simplement une transmission
Le don qui se mesure, donne la mesure.
C'est quand tu donnes ton temps, ton énergie, ta sueur,
Que tu crées ton propre étalon de valeur.
Puis silence. Le geste pose un nouveau décor.
Le don — c'est pas une perte.
Le don — c'est le début d'un accord.
[Chorus 2]
Ce n'est pas l'image d'Épinal, le don gratuit, le don idéal.
Marcel Mauss nous l'a dit, dans son essai radical.
Ce n'est pas un cadeau, c'est un cycle vital.
Le don qui se mesure, donne la mesure.
Le don qui se mesure, donne la mesure.
[Outro]
De quel don nous parlons ?
...Celui qui construit.
Celui qui nous relie.
Celui qui t'investit,
Dans l'économie de ta vie.
tags: [don, mesure, valeur]
FIN à remettre.
tags:
- don
- mesure
- valeur
- id: les-asymetries - id: les-asymetries
title: "Les asymétries" title: Les asymétries
artist: Yvv artist: Yvv
file: /audio/les-asymetries.mp3 file: /audio/ED-03-les-asymetries.mp3
duration: 727 duration: 727
lyrics: | lyrics: |
Les asymétries
[Intro]
(Cello and Bowed Bass: low, scraping texture)
(Piano: Single discordant note repeated)
(Male: Voix très posée, grave, proche)
Entre soi... Connivence fait loi. Entre soi... Connivence fait loi.
On est entre nous On se rassure. On est entre nous On se rassure.
On discute assidus...hey, on assure ! On discute assidus...hey, on assure !
(suspension) (...)
Nous sommes trop convaincus. Nous sommes trop convaincus.
Cela nous endort. Ce que je crois peut-être à tord ... tue. Cela nous endort.
(Females: Pseudo-isolés... Pseudo-isolés...) Ce que je crois peut-être à tord ... tue.
[Verse 1]
(Drums enter: intricate brushwork, soft but fast)
J'ai vu des collectifs, plus ou moins dissruptifs. J'ai vu des collectifs, plus ou moins dissruptifs.
Parfois prônant le don, par exemple Solariss. Parfois prônant le don, par exemple Solaris.
J'y ai vu de l'usure, le sentiment d'abus. J'y ai vu de l'usure, le sentiment d'abus.
Des abandons moribons, ... Des abandons moribonds, ...
Ha bon ? Ha bon ?
Malgré quelques notifs, et les esprits attentifs Malgré quelques notifs, et les esprits attentifs
@@ -208,32 +168,25 @@ songs:
J'ai vu aussi bien sûr, quelques trous du cul J'ai vu aussi bien sûr, quelques trous du cul
Mais ! Est-ce là une bonne raison ? Mais ! Est-ce là une bonne raison ?
[Bridge 1]
(Rhythm becomes jagged, syncopated stops)
Rien n'est symétrique, ce n'est pas magique. Rien n'est symétrique, ce n'est pas magique.
(Females: ) Rien. Rien.
Une pomme aujourd'hui, n'est pas la même demain. Une pomme aujourd'hui, n'est pas la même demain.
Et s'il y a une cagette, ce n'est pas cinq palettes. Et s'il y a une cagette, ce n'est pas cinq palettes.
(Male: Tone hardens)
Six heures assis à parler bien à l'aise... Six heures assis à parler bien à l'aise...
Six heures à genoux sur un toit...ho ! balaise Six heures à genoux sur un toit...ho ! balaise
(Females:) Est-ce le même geste, ou une ascèse ? mmmhh. Est-ce le même geste, ou une ascèse ? mmmhh.
(Silence - 1 second)
[Chorus] [Refrain]
(Music swells slightly, singing questions)
Alors que faire ? On désespère, on baisse les bras ? Alors que faire ? On désespère, on baisse les bras ?
on légifère ? On écrit des lois ? on légifère ? On écrit des lois ?
On réglemente, on décide de l'issue ? On réglemente, on décide de l'issue ?
(suspension) (...)
Mieux vaut peut-être un protocole avec quelques bémol. Mieux vaut peut-être un protocole avec quelques bémol.
Une façon de traiter, d'éviter de juger, préfigurer. Une façon de traiter, d'éviter de juger, préfigurer.
(suspension) Ne pas tranchez les sorts, à leur insu. (...) Ne pas trancher les sorts, à leur insu.
[Verse 2]
(Bass is now plucked, heavy groove)
Pour limiter le ressentiment, Pour limiter le ressentiment,
la lassitude, l'envenime-ment. la lassitude, l'envenimement.
Il suffit d'une mesure. Il suffit d'une mesure.
Sans morsure, Sans morsure,
qui fait bonne figure, qui fait bonne figure,
@@ -241,46 +194,39 @@ songs:
pour tout le monde. pour tout le monde.
Qui mesure la pénibilité ? - Pourquoi pas. Qui mesure la pénibilité ? - Pourquoi pas.
Qui célèbre le mérite ? - Dans l'ombre du monde ; Qui célèbre le mérite ? - Dans l'ombre du monde ;
Pas celui des héritages ? - Ce s'rait possible ça ? Pas celui des héritages ! - Ce s'rait possible ça ?
Qui réellement, récompense et compense ? sans dépense ni dispense ? Qui réellement, récompense et compense ? sans dépense ni dispense ?
[Bridge 2]
Une communauté ne peut pas tout écrire. Une communauté ne peut pas tout écrire.
La loi, ne peut pas tout régler. La loi, ne peut pas tout régler.
même si c'est toi qui la fait même si c'est toi qui la fais
Prescrire un comportement ? - c'est tentant, Prescrire un comportement ? - c'est tentant,
Une belle et grande morale ? - c'est bancal : Une belle et grande morale ? - c'est bancal :
Il est utile de prévenir, se souvenir, ... Il est utile de prévenir, se souvenir, ...
C'est le pire. C'est le pire.
[Chorus] [Refrain]
(Music swells slightly)
Alors que faire ? On désespère, on baisse les bras ? Alors que faire ? On désespère, on baisse les bras ?
on légifère ? On écrit des lois ? on légifère ? On écrit des lois ?
On réglemente, on décide de l'issue ? On réglemente, on décide de l'issue ?
(suspension) (...)
Mieux vaut peut-être un protocole, avec quelques bémol. Mieux vaut peut-être un protocole, avec quelques bémol.
Une façon de traiter, d'éviter de juger, préfigurer. Une façon de traiter, d'éviter de juger, préfigurer.
(suspension) Ne pas tranchez les sorts, à leur insu. Ne pas trancher les sorts, à leur insu.
(suspension) Ne tranchez pas mon sort, à mon insu. Ne tranchez pas mon sort, à mon insu.
[Bridge 1]
(Rhythm becomes jagged, syncopated stops)
Rien n'est symétrique. Ce n'est pas magique. Rien n'est symétrique. Ce n'est pas magique.
(Females:) Rien. Rien.
Pour se rétablir, retomber sur nos pieds Pour se rétablir, retomber sur nos pieds
Il existe un outil qui s'appelle, ... "la monnaie". Il existe un outil qui s'appelle, ... "la monnaie".
[Verse 3]
C'est elle en permanence qui résout le "schmilblick". C'est elle en permanence qui résout le "schmilblick".
Même sans qu'on y pense, faut avouer, c'est pratique Même sans qu'on y pense, faut avouer, c'est pratique
C'est elle qui compense, récompense ou dispense C'est elle qui compense, récompense ou dispense
Mais attention, délit de pleine flagrance ! (drum, suspension) Mais attention, délit de pleine flagrance !
Chaque monnaie programme sa propre engence. Chaque monnaie programme sa propre engence.
Ne t'y méprends pas, son pouvoir est immense. Ne t'y méprends pas, son pouvoir est immense.
[Outro]
(Piano flowing arpeggios, fading)
Résoudre le problème des asymétries. Résoudre le problème des asymétries.
Réduire le besoin de légiférer. Réduire le besoin de légiférer.
Pour une simple coloc... ou pour un monde entier. Pour une simple coloc... ou pour un monde entier.
@@ -288,24 +234,18 @@ songs:
Ne les néglige pas. Ne les néglige pas.
Tu peux les mesurer, Tu peux les mesurer,
et choisir ... ta monnaie. et choisir ... ta monnaie.
[...]
Choisir ta monnaie
tags: [asymétrie, communauté, philosophie]
Choisir ta monnaie
tags:
- asymétrie
- communauté
- philosophie
- id: inverser-les-flux - id: inverser-les-flux
title: "Inverser les flux" title: Inverser les flux
artist: Yvv artist: Yvv
file: /audio/inverser-les-flux.mp3 file: /audio/ED-04-inverser-les-flux.mp3
duration: 610 duration: 610
lyrics: | lyrics: |
Inverser les flux
[Intro]
[Guitar vibraphone : Accords riches et harmoniques]
[Basse : Une ligne très ronde qui glisse]
[Cut Brass swell]
[Articulte, french]
Tu choisis ... ta monnaie, Tu choisis ... ta monnaie,
son économie, son économie,
Tu passes de l'une à l'autre Tu passes de l'une à l'autre
@@ -313,86 +253,79 @@ songs:
Ici, tu peux l'appeler la monnaie du don. Ici, tu peux l'appeler la monnaie du don.
Tu peux aussi ne plus l'appeler monnaie du tout. Tu peux aussi ne plus l'appeler monnaie du tout.
Changer de lunettes, (mais pas les rose) Changer de lunettes, (pas les roses)
c'est un ruban-mètre, posé sur la planète. c'est un ruban-mètre, posé sur la planète.
[Chorus] [Refrain]
(Groove s'intensifie, la batterie claque)
Le D.U, c'est une mesure. Le D.U, c'est une mesure.
(Choir: La mesure...) (La mesure...)
Pour ne plus obliger. Pour ne plus devoir. Pour ne plus obliger. Pour ne plus devoir.
Je donne à mon économie, j'alimente le réservoir. Je donne à mon économie, j'alimente le réservoir.
[break] (...)
Si je t'aime, j'y mets mon affect. Si je t'aime, j'y mets mon affect.
Si je ne t'aime pas, du moins je te respecte. Si je ne t'aime pas, du moins je te respecte.
Je compte sur les autres, sur mon économie, Je compte sur les autres, sur mon économie,
Pour y trouver ma pleine mesure. Pour y trouver ma pleine mesure.
La solidarité organique, pas de paniK La solidarité organique, pas de panique,
Elle franchit les limites. Elle franchit les limites.
(Choir: Organique...)
[Verse 1]
"Je ne veux rien en retour", c'est ce que tu dis. "Je ne veux rien en retour", c'est ce que tu dis.
Mais tu m'obliges. Mais tu m'obliges.
Tu crées une dette non dite, Tu crées une dette non dite,
De quoi rester perplexe. De quoi rester perplexe.
La solidarité mécanique est complexe La solidarité mécanique est complexe
Elle a ses limites. Elle a ses limites.
(Whisper: C'est horrible pour les mites) (C'est horrible pour les mites)
[Le rythme devient plus sec, plus percussif. Flow rapide]
Alors j'opère un retournement. Alors j'opère un retournement.
Je choisis mes mots, c'est un glissement. Je choisis mes mots, c'est un glissement.
(...) (...)
Je ne vends plus. Je ne vends plus.
(Choir: Non...) Non !
Je donne. (...) C'est mon offre. Je donne. C'est mon offre.
Le mot retrouve du sens, pleinement. Le mot retrouve du sens, pleinement.
(...) (...)
Je n'achète plus. Je n'achète plus.
(Choir: Non...) Non !
Je reçois. Je reçois.
(...) (...)
Je reçois la valeur. Je l'évalue. Avec le D.U. Je reçois la valeur. Je l'évalue. Avec le D.U.
La "dépossession monétaire" n'a plus lieu d'être. La "dépossession monétaire" n'a plus lieu d'être.
Ce n'est plus mortifère, ni délétère. Ce n'est plus mortifère, ni délétère.
(...)
Je rentre du marché, nouveau vocabulaire :
[Male]- "Hey - j'ai reçu une semaine de cour-ss."
[Female]- "Wow, t'as mis gratitude max à la source ?"
[Bridge - Duet Call & Response] Je rentre du marché, nouveau vocabulaire :
(Male) Je ne paye plus. "Hey - j'ai reçu une semaine de cour-ss."
(Female) Je mesure. J'estime... "Wow, t'as mis gratitude max à la source ?"
(Male) Je négocie détendu. (c'est un virage)
(Female) J'ajuste ma balance intime... Je ne paye plus.
(Male) Je donne du poids. Je mesure. J'estime...
(Female) De la masse. Je négocie détendu. (c'est un virage)
(Both) Ou une température. J'ajuste ma balance intime...
Je donne du poids.
De la masse.
Ou une température.
L'économie, c'est de l'énergie, de la chaleur c'est sûr. L'économie, c'est de l'énergie, de la chaleur c'est sûr.
[break] Je grave ma gratitude dans la chaîne.
Je grave ma gratitude dans la chaîne.
C'est une trans-action. Au sens noble du terme. C'est une trans-action. Au sens noble du terme.
[Chorus - Ensemble] [Refrain]
(Groove s'intensifie, la batterie claque)
Le D.U, c'est une mesure. Le D.U, c'est une mesure.
(Choir: La mesure...) (La mesure...)
Pour ne plus obliger. Pour ne plus devoir. Pour ne plus obliger. Pour ne plus devoir.
Je donne à mon économie, j'alimente le réservoir. Je donne à mon économie, j'alimente le réservoir.
[break]
Si je t'aime, j'y mets mon affect. Si je t'aime, j'y mets mon affect.
Si je ne t'aime pas, du moins je te respecte. Si je ne t'aime pas, du moins je te respecte.
Je compte sur les autres, sur mon économie, Je compte sur les autres, sur mon économie,
Pour y trouver ma pleine mesure. Pour y trouver ma pleine mesure.
La solidarité organique, pas de panique La solidarité organique, pas de panique
Elle franchit les limites. Elle franchit les limites.
(Choir: Organique...)
[Verse 3 - Male Lead]
[Musique s'épure, Basse et Claquements de doigts. Question tone]
Dis,... et quand c'est la course ? Si c'est une buvette ? Dis,... et quand c'est la course ? Si c'est une buvette ?
Pas le temps des discours, philosopher sur la canette ? Pas le temps des discours, philosopher sur la canette ?
(Female : Faut qu'ça dépote !) (Faut qu'ça dépote !)
(...) (...)
Alors le don est en amont. Alors le don est en amont.
L'équipe offre le choix, le lieu, le son. L'équipe offre le choix, le lieu, le son.
@@ -400,115 +333,65 @@ songs:
Tu prends ou pas, tu vois si c'est bon, Tu prends ou pas, tu vois si c'est bon,
tu entres dans la danse. tu entres dans la danse.
Tu peux gratifier plus, si le cœur t'en dit. Tu peux gratifier plus, si le cœur t'en dit.
Plaider un co-eff. relatif aussi... Plaider un coeff. relatif aussi...
Mais la mesure est là, autour d'un bel invariant, on sait où on va. Mais la mesure est là, autour d'un bel invariant, on sait où on va.
(...) (...)
Au delà d'un simple théorème Au delà d'un simple théorème
C'est le cadeau de la T.R.M. C'est le cadeau de la T.R.M.
tags:
[Outro] - flux
(Female ad-libs: Équilibre... Invariant...) - économie
[Rhodes solo, jazzy and improvised] - production
[Male spoken sexy]
On frotte nos échelles.
On construit avec les autres.
(Fade out on the warm bass line)
[Female sexy]
Construction culturelle.
[break smooth]
J'évalue mon degré de gratitude
Pour que ça devienne une habitude.
tags: [flux, économie, production]
- id: ainsi-soit-il - id: ainsi-soit-il
title: "Ainsi soit-il" title: Ainsi soit-il !
artist: Yvv artist: Yvv
file: /audio/ainsi-soit-il.mp3 file: /audio/ED-05-ainsi-soit-il.mp3
duration: 545 duration: 545
lyrics: | lyrics: |+
Ainsi soit-il
[Intro]
[Vinyl Crackle Sound]
[Minimalist Piano Loop]
Fiat... Fiat...
Fiat Lux… Fiat Euro. Fiat Lux...
Fiat Euro.
[Verse 1] Fiat. Ce n'est pas qu'une marque.
[Spoken Word, Calm and Clear]
Fiat. Ce n'est pas une marque.
C'est du latin. C'est du latin.
Ça veut dire : "Que cela soit". Ça veut dire : "Que cela soit".
Une parole magique. Performative. Une parole qui opére. Performative. Normative.
Fiat Lux : Que la lumière soit. Fiat Lux : Que la lumière soit.
Fiat Euro : Que la dette soit. Fiat Euro : Que la dette soit.
Un monopole déclaré. Un monopole institué.
Une clé de voûte qui tient tout l'édifice.
Si la clé casse... tout s'écroule.
[Chorus]
[Melodic Hook, Softly Sung]
Mais la monnaie n'est pas la richesse.
C'est juste le mètre... pas le tissu.
C'est le baromètre... pas le climat.
Ne confondons pas la carte et le territoire.
[Verse 2]
[Rhythmic Spoken, Slightly Faster]
Message aux pionniers :
Faire tourner la monnaie en rond, ce n'est pas créer.
Se faire des virements autour d'une table...
C'est juste du vent.
L'équation de Fischer est claire.
Si tu multiplies zéro production par mille transactions...
Ça fait toujours zéro.
L'économie, c'est "passer la seconde".
C'est produire. Transformer.
Le reste ? C'est de la comptabilité.
[Bridge]
[Bass Line Drops]
[Pause]
Notre monnaie-dette a un code génétique.
Elle programme le manque. Elle programme la course.
Mais le DU...
Le DU change le code source.
[Outro]
[Fading Music]
[Whispered]
Même accès pour tous.
Même pouvoir de création.
Ce n'est plus "Que la dette soit".
C'est "Que l'équilibre soit".
[Silence]
tags: [action, engagement, avenir]
tags:
- fiat
- monnaie-dette
- DU
- id: la-croissance-une-option - id: la-croissance-une-option
title: "La croissance, une option ?" title: La croissance, une option ?
artist: Yvv artist: Yvv
file: /audio/la-croissance-une-option.mp3 file: /audio/ED-06-la-croissance-une-option.mp3
duration: 510 duration: 510
lyrics: "" lyrics: ""
tags: [croissance, monnaie, questionnement] tags:
- croissance
- id: monnaie-libre-essence - monnaie
title: "Monnaie libre essence" - questionnement
- id: monnaie-libre-une-essence
title: Monnaie libre, une essence ?
artist: Yvv artist: Yvv
file: /audio/monnaie-libre-essence.mp3 file: /audio/ED-07-monnaie-libre-essence.mp3
duration: 475 duration: 475
lyrics: "" lyrics: ""
tags: [monnaie libre, TRM, June] tags:
- monnaie libre
- id: des-cercles-qui-se-croisent - TRM
title: "Des cercles qui se croisent" - June
- id: creer-une-economie
title: Créer une économie
artist: Yvv artist: Yvv
file: /audio/des-cercles-qui-se-croisent.mp3 file: /audio/ED-08-creer-une-économie.mp3
duration: 496 duration: 496
lyrics: | lyrics: |
Hymne à la monnaie libre Hymne à la monnaie libre
[Verse]
Des cercles qui se croisent Des cercles qui se croisent
Sans les regards qui toisent Sans les regards qui toisent
Des poings qui se détendent Des poings qui se détendent
@@ -519,7 +402,7 @@ songs:
Un souffle tout petit Un souffle tout petit
Pas de chaînes pour les pensées Pas de chaînes pour les pensées
[Chorus] [Refrain]
Construire des vies Construire des vies
Nos cœurs qui grossissent Nos cœurs qui grossissent
Couvrir nos besoins Couvrir nos besoins
@@ -535,7 +418,6 @@ songs:
Si tu en as la fibre Si tu en as la fibre
C'est l'hymne à la monnaie libre C'est l'hymne à la monnaie libre
[Verse 2]
Les jours se lèvent sur des rêves partagés Les jours se lèvent sur des rêves partagés
Quelques champs pour les possibles Quelques champs pour les possibles
Des ponts à imaginer Des ponts à imaginer
@@ -544,7 +426,6 @@ songs:
Juste l'humanité Juste l'humanité
Est-elle si pénible ? Est-elle si pénible ?
[Bridge]
Ni maîtres ni esclaves Ni maîtres ni esclaves
Juste un écho Juste un écho
Des esprits qui dansent Des esprits qui dansent
@@ -552,7 +433,7 @@ songs:
Un chant nouveau Un chant nouveau
Surmontent les entraves Surmontent les entraves
[Chorus] [Refrain]
Construire des vies Construire des vies
Nos cœurs qui grossissent Nos cœurs qui grossissent
Couvrir nos besoins Couvrir nos besoins
@@ -564,48 +445,46 @@ songs:
En arrêtant de nuire En arrêtant de nuire
Sans place pour la honte Sans place pour la honte
Sans laissés-pour-compte Sans laissés-pour-compte
tags: [échange, réseau, cercles] tags:
- économie
- hymne
- monnaie libre
- id: coder-la-liberte - id: coder-la-liberte
title: "Coder la liberté" title: Coder la liberté
artist: Yvv artist: Yvv
file: /audio/coder-la-liberte.mp3 file: /audio/ED-09-coder-la-liberte.mp3
duration: 376 duration: 376
lyrics: | lyrics: |
Coder un rêve
[Verse]
Dans l'ombre des géants big tek Dans l'ombre des géants big tek
Des lignes poussent discrètes Des lignes poussent discrètes
Tourné vers la grande ourse Tourné vers la grande ourse
Je coule open source Je code open source
[Prechorus] Balance ton Json
Balance ton Jiz Onne
mon shell résonne mon shell résonne
Coup de dés Py - thon Runtime Upgrade, ninja blayde Coup de dés Python, runtime upgrade, ninja blade
Ça bilt, Docker compose. Devant l'écran je pose. Ca biilt. Ça build, Docker compose. Devant l'écran je pose. Ca build.
shhhhhhh... it com paille ...llss, shhhhhhh... it compils,
tapis dans une typo sans serif, je check les certif Tapis dans une typo sans serif, je check les certifs
merde ça lag, merde ça lag,
mate mes log, mate mes log,
j'ai la langue qui bog. j'ai la langue qui bogue.
Mon café est tout froid Mon café est tout froid
Je ne perds pas la foi. Je ne perds pas la foi.
[Chorus] [Refrain]
Codeurs de rêv Codeurs de rêve
Rime pour les dèvs Une rime pour les dèvs
dans les réseaux, où que j'aille Dans les réseaux, où que j'aille
vous êtes mes sudo, Vous êtes mes sudo,
mes samouraï Mes samouraï
Hackers, Admin, Hackers, Admin,
Dèvop, développ Dèvop, développ
Vous décentralisez Vous décentralisez
Vous open sourcez Vous open sourcez
Un monde moins obscur Un monde moins obscur
Duniterre bien sûr. Duniter bien sûr.
Notre toil fiduciaire Notre toile fiduciaire
Nous pouvez être fiers Nous pouvez être fiers
[Verse 2] [Verse 2]
@@ -617,10 +496,9 @@ songs:
La tuyauterie La tuyauterie
Manipule des bounty Manipule des bounty
Du fuel pour les applis Du fuel pour les applis
tous vos dons ... les mettent à l'abri Tous vos dons ... les mettent à l'abri
Merci Merci
[Bridge]
Crash à minuit Crash à minuit
Je reste éveillé Je reste éveillé
Le bug fatal Le bug fatal
@@ -630,118 +508,65 @@ songs:
Le café est-il prêt ? Le café est-il prêt ?
C'est bientôt aujourd'hui C'est bientôt aujourd'hui
[Chorus] [Refrain]
Codeurs de rêv Codeurs de rêve
Rime pour les dèvs Une rime pour les dèvs
dans les réseaux, où que j'aille Dans les réseaux, où que j'aille
vous êtes mes sudo, Vous êtes mes sudo,
mes samouraï Mes samouraï
Hackers, Admin, Hackers, Admin,
Dèvop, développ Dèvop, développ
Vous décentralisez Vous décentralisez
Vous open sourcez Vous open sourcez
Un monde moins obscur Un monde moins obscur
Duniterre bien sûr. Duniter bien sûr.
Notre toil fiduciaire Notre toile fiduciaire
Nous en sommes fiers Nous en sommes fiers
tags: [logiciel libre, code, liberté] tags:
- logiciel libre
- code
- liberté
chapterSongs: chapterSongs:
# Chapitre 1 — Introduction
- chapterSlug: 01-introduction - chapterSlug: 01-introduction
songId: ce-livre-est-une-facon songId: ce-livre-est-une-facon
primary: true primary: true
- chapterSlug: 01-introduction
songId: un-don-qui-se-mesure
primary: false
# Chapitre 2 — De quel don parlons-nous ?
- chapterSlug: 02-don - chapterSlug: 02-don
songId: un-don-qui-se-mesure songId: de-quel-don-nous-parlons
primary: true primary: true
- chapterSlug: 02-don
songId: ce-livre-est-une-facon
primary: false
# Chapitre 3 — La mesure du don
- chapterSlug: 03-mesure - chapterSlug: 03-mesure
songId: les-asymetries songId: les-asymetries
primary: true primary: true
- chapterSlug: 03-mesure
songId: un-don-qui-se-mesure
primary: false
# Chapitre 4 — Raison d'être d'une monnaie
- chapterSlug: 04-monnaie - chapterSlug: 04-monnaie
songId: la-croissance-une-option songId: inverser-les-flux
primary: true
- chapterSlug: 04-monnaie
songId: monnaie-libre-essence
primary: false
# Chapitre 5 — La TRM
- chapterSlug: 05-trm
songId: monnaie-libre-essence
primary: true primary: true
- chapterSlug: 05-trm - chapterSlug: 05-trm
songId: ainsi-soit-il
primary: true
- chapterSlug: 06-economie
songId: la-croissance-une-option songId: la-croissance-une-option
primary: false
# Chapitre 6 — Créer une économie ?
- chapterSlug: 06-economie
songId: inverser-les-flux
primary: true
- chapterSlug: 06-economie
songId: monnaie-libre-essence
primary: false
# Chapitre 7 — Échanger
- chapterSlug: 07-echange
songId: des-cercles-qui-se-croisent
primary: true primary: true
- chapterSlug: 07-echange - chapterSlug: 07-echange
songId: inverser-les-flux songId: monnaie-libre-une-essence
primary: false primary: true
# Chapitre 8 — Relation institutionnelle
- chapterSlug: 08-institution - chapterSlug: 08-institution
songId: ainsi-soit-il songId: creer-une-economie
primary: false primary: true
- chapterSlug: 08-institution
songId: des-cercles-qui-se-croisent
primary: false
# Chapitre 9 — Autres greffes
- chapterSlug: 09-greffes - chapterSlug: 09-greffes
songId: inverser-les-flux songId: coder-la-liberte
primary: false
- chapterSlug: 09-greffes
songId: des-cercles-qui-se-croisent
primary: false
# Chapitre 10 — Et maintenant ?
- chapterSlug: 10-maintenant
songId: ainsi-soit-il
primary: true primary: true
- chapterSlug: 10-maintenant - chapterSlug: 10-maintenant
songId: coder-la-liberte songId: coder-la-liberte
primary: false primary: true
# Chapitre 11 — Annexes
- chapterSlug: 11-annexes - chapterSlug: 11-annexes
songId: coder-la-liberte songId: coder-la-liberte
primary: true primary: true
- chapterSlug: 11-annexes
songId: monnaie-libre-essence
primary: false
defaultPlaylistOrder: defaultPlaylistOrder:
- ce-livre-est-une-facon - ce-livre-est-une-facon
- un-don-qui-se-mesure - de-quel-don-nous-parlons
- les-asymetries - les-asymetries
- inverser-les-flux - inverser-les-flux
- ainsi-soit-il - ainsi-soit-il
- la-croissance-une-option - la-croissance-une-option
- monnaie-libre-essence - monnaie-libre-une-essence
- des-cercles-qui-se-croisent - creer-une-economie
- coder-la-liberte - coder-la-liberte

View File

@@ -77,7 +77,7 @@ export default defineConfig({
'btn-accent': 'inline-flex items-center justify-center px-6 py-3 rounded-lg bg-accent text-surface-bg font-display font-semibold tracking-wide border-none transition-all duration-200 hover:bg-accent-600 hover:scale-105 active:scale-95', 'btn-accent': 'inline-flex items-center justify-center px-6 py-3 rounded-lg bg-accent text-surface-bg font-display font-semibold tracking-wide border-none transition-all duration-200 hover:bg-accent-600 hover:scale-105 active:scale-95',
'btn-ghost': 'inline-flex items-center justify-center px-4 py-2 rounded-lg border-none text-[hsl(var(--color-text)/0.7)] font-sans transition-all duration-200 hover:bg-[hsl(var(--color-text)/0.1)] hover:text-[hsl(var(--color-text))]', 'btn-ghost': 'inline-flex items-center justify-center px-4 py-2 rounded-lg border-none text-[hsl(var(--color-text)/0.7)] font-sans transition-all duration-200 hover:bg-[hsl(var(--color-text)/0.1)] hover:text-[hsl(var(--color-text))]',
'card-surface': 'rounded-xl bg-surface border border-white/8 p-6 transition-all duration-300 hover:border-primary/30 hover:shadow-lg hover:shadow-primary/5', 'card-surface': 'rounded-xl bg-surface border border-white/8 p-6 transition-all duration-300 hover:border-primary/30 hover:shadow-lg hover:shadow-primary/5',
'text-gradient': 'bg-gradient-to-r from-primary-300 to-accent bg-clip-text text-transparent', 'text-gradient': 'text-primary',
'text-muted': 'text-[hsl(var(--color-text)/0.6)]', 'text-muted': 'text-[hsl(var(--color-text)/0.6)]',
'section-padding': 'px-4 py-16 md:px-8 lg:px-16 lg:py-24', 'section-padding': 'px-4 py-16 md:px-8 lg:px-16 lg:py-24',
'container-content': 'mx-auto max-w-7xl w-full', 'container-content': 'mx-auto max-w-7xl w-full',