Compare commits
10 Commits
2f438d9d7a
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e6c91fea7d | ||
|
|
25bfc07b59 | ||
|
|
8803087e77 | ||
|
|
14d3a7b3e3 | ||
|
|
d8439cba0f | ||
|
|
0308785de9 | ||
|
|
52c0af4c83 | ||
|
|
8d9feed760 | ||
|
|
922afa2763 | ||
|
|
dd1d8baf4f |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -18,6 +18,9 @@ logs
|
||||
.fleet
|
||||
.idea
|
||||
|
||||
# Sources originales (PDF, JPG — pas servies par l'appli)
|
||||
sources/
|
||||
|
||||
# Local env files
|
||||
.env
|
||||
.env.*
|
||||
|
||||
@@ -146,14 +146,7 @@ a {
|
||||
.palette-light .prose { 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 */
|
||||
.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;
|
||||
}
|
||||
/* text-gradient — solid primary color everywhere */
|
||||
|
||||
/* card surfaces — subtle shadow for depth */
|
||||
.palette-light .card-surface {
|
||||
|
||||
@@ -65,7 +65,7 @@ const emit = defineEmits<{
|
||||
'update:modelValue': [value: string]
|
||||
}>()
|
||||
|
||||
const tab = ref<'edit' | 'preview' | 'split'>('edit')
|
||||
const tab = ref<'edit' | 'preview' | 'split'>('split')
|
||||
const fullscreen = ref(false)
|
||||
const textareaRef = ref<HTMLTextAreaElement>()
|
||||
|
||||
|
||||
@@ -34,22 +34,11 @@
|
||||
>
|
||||
<div class="i-lucide-list h-5 w-5" />
|
||||
</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">
|
||||
<span class="reader-bar-num">{{ chapterIdx + 1 }}.</span>
|
||||
{{ chapters[chapterIdx].title }}
|
||||
<span class="reader-bar-num">{{ trackIdx + 1 }}.</span>
|
||||
{{ currentTrack?.title ?? '' }}
|
||||
</div>
|
||||
<span class="reader-bar-pages">
|
||||
<template v-if="isScrollMode">{{ scrollPercent }}%</template>
|
||||
<template v-else>{{ currentPage + 1 }}<span class="op-40">/</span>{{ totalPages }}</template>
|
||||
</span>
|
||||
<span class="reader-bar-pages">{{ scrollPercent }}%</span>
|
||||
<button class="reader-bar-btn reader-bar-close" @click="close" aria-label="Fermer">
|
||||
<div class="i-lucide-x h-5 w-5" />
|
||||
</button>
|
||||
@@ -61,14 +50,14 @@
|
||||
<div class="sommaire-panel">
|
||||
<h4 class="sommaire-title">{{ bpContent?.reader.sommaireTitle ?? 'Sommaire' }}</h4>
|
||||
<button
|
||||
v-for="(ch, i) in chapters"
|
||||
:key="i"
|
||||
v-for="(track, i) in tracks"
|
||||
:key="track.id"
|
||||
class="sommaire-item"
|
||||
:class="{ 'sommaire-item--active': chapterIdx === i }"
|
||||
@click="goToChapter(i)"
|
||||
:class="{ 'sommaire-item--active': trackIdx === i }"
|
||||
@click="goToTrack(i)"
|
||||
>
|
||||
<span class="sommaire-num">{{ i + 1 }}</span>
|
||||
{{ ch.title }}
|
||||
{{ track.title }}
|
||||
</button>
|
||||
</div>
|
||||
</aside>
|
||||
@@ -87,7 +76,13 @@
|
||||
ref="contentEl"
|
||||
: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>
|
||||
<!-- Page turn shadow overlay (paginated only) -->
|
||||
<div v-if="!isScrollMode" class="reader-shadow" :class="{ visible: isTurning }" />
|
||||
@@ -97,28 +92,28 @@
|
||||
<div class="reader-nav">
|
||||
<button
|
||||
class="reader-nav-btn"
|
||||
:class="{ 'reader-nav-btn--hidden': isScrollMode ? chapterIdx <= 0 : (currentPage <= 0 && chapterIdx <= 0) }"
|
||||
@click="isScrollMode ? prevChapter() : prevPage()"
|
||||
:aria-label="isScrollMode ? 'Chapitre précédent' : 'Page précédente'"
|
||||
:class="{ 'reader-nav-btn--hidden': isScrollMode ? trackIdx <= 0 : (currentPage <= 0 && trackIdx <= 0) }"
|
||||
@click="isScrollMode ? prevTrack() : prevPage()"
|
||||
:aria-label="isScrollMode ? 'Morceau précédent' : 'Page précédente'"
|
||||
>
|
||||
<div class="i-lucide-chevron-left h-5 w-5" />
|
||||
</button>
|
||||
|
||||
<!-- Song disc (if chapter has a song) -->
|
||||
<div v-if="chapterSong" class="reader-song">
|
||||
<!-- Song disc -->
|
||||
<div v-if="currentSong" class="reader-song">
|
||||
<div class="reader-disc" :class="{ spinning: playerStore.isPlaying }">
|
||||
<img src="/images/book-cover-spread.jpg" alt="" class="reader-disc-img" />
|
||||
<div class="reader-disc-hole" />
|
||||
</div>
|
||||
<span class="reader-song-name">{{ chapterSong.title }}</span>
|
||||
<span class="reader-song-name">{{ currentSong.title }}</span>
|
||||
</div>
|
||||
<div v-else class="reader-song" />
|
||||
|
||||
<button
|
||||
class="reader-nav-btn"
|
||||
:class="{ 'reader-nav-btn--hidden': isScrollMode ? chapterIdx >= chapters.length - 1 : (currentPage >= totalPages - 1 && chapterIdx >= chapters.length - 1) }"
|
||||
@click="isScrollMode ? nextChapter() : nextPage()"
|
||||
:aria-label="isScrollMode ? 'Chapitre suivant' : 'Page suivante'"
|
||||
:class="{ 'reader-nav-btn--hidden': isScrollMode ? trackIdx >= tracks.length - 1 : (currentPage >= totalPages - 1 && trackIdx >= tracks.length - 1) }"
|
||||
@click="isScrollMode ? nextTrack() : nextPage()"
|
||||
:aria-label="isScrollMode ? 'Morceau suivant' : 'Page suivante'"
|
||||
>
|
||||
<div class="i-lucide-chevron-right h-5 w-5" />
|
||||
</button>
|
||||
@@ -128,7 +123,7 @@
|
||||
<!-- Hint -->
|
||||
<p class="bp-hint">
|
||||
<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>
|
||||
</template>
|
||||
<template v-else>
|
||||
@@ -158,7 +153,7 @@ const overlayRef = ref<HTMLElement>()
|
||||
const viewportEl = ref<HTMLElement>()
|
||||
const contentEl = ref<HTMLElement>()
|
||||
|
||||
const chapterIdx = ref(0)
|
||||
const trackIdx = ref(0)
|
||||
const currentPage = ref(0)
|
||||
const totalPages = ref(1)
|
||||
const colWidth = ref(500)
|
||||
@@ -166,14 +161,10 @@ const showSommaire = ref(false)
|
||||
const isTurning = ref(false)
|
||||
|
||||
// ── Reading mode ──
|
||||
const readingMode = ref<'paginated' | 'scroll'>('paginated')
|
||||
const readingMode = ref<'paginated' | 'scroll'>('scroll')
|
||||
const isScrollMode = computed(() => readingMode.value === 'scroll')
|
||||
const scrollPercent = ref(0)
|
||||
|
||||
function toggleReadingMode() {
|
||||
readingMode.value = readingMode.value === 'paginated' ? 'scroll' : 'paginated'
|
||||
}
|
||||
|
||||
// When switching back to paginated, recalc pages
|
||||
watch(readingMode, async (mode) => {
|
||||
if (mode === 'paginated') {
|
||||
@@ -190,72 +181,57 @@ function onViewportScroll() {
|
||||
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 playerStore = usePlayerStore()
|
||||
|
||||
// ── Content from Nuxt Content ──
|
||||
const chaptersContent = ref<any[]>([])
|
||||
const contentLoaded = ref(false)
|
||||
|
||||
async function loadContent() {
|
||||
if (contentLoaded.value) return
|
||||
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
|
||||
// ── Tracks: built from playlist order (songs), not chapters ──
|
||||
const tracks = computed(() => {
|
||||
return playerStore.playlist.map(song => ({
|
||||
id: song.id,
|
||||
title: song.title,
|
||||
song,
|
||||
}))
|
||||
})
|
||||
|
||||
// ── Chapter metadata ──
|
||||
const chapters = [
|
||||
{ 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' },
|
||||
]
|
||||
const currentTrack = computed(() => tracks.value[trackIdx.value] ?? null)
|
||||
const currentSong = computed(() => currentTrack.value?.song ?? null)
|
||||
|
||||
// ── Per-chapter color hues ──
|
||||
const chapterHues: [number, number][] = [
|
||||
[12, 36], // cover (intro + cover phases)
|
||||
[15, 35], // 1
|
||||
[350, 15], // 2
|
||||
[36, 50], // 3
|
||||
[170, 200], // 4
|
||||
[220, 250], // 5
|
||||
[270, 300], // 6
|
||||
[320, 345], // 7
|
||||
[150, 170], // 8
|
||||
[190, 220], // 9
|
||||
[40, 20], // 10
|
||||
[210, 240], // 11
|
||||
const currentLyrics = computed(() => {
|
||||
return currentSong.value?.lyrics?.trim() || ''
|
||||
})
|
||||
|
||||
const currentLyricsHtml = computed(() => {
|
||||
if (!currentLyrics.value) return ''
|
||||
return currentLyrics.value
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/\[([^\]]+)\]/g, '<span class="lyrics-tag">[$1]</span>')
|
||||
.replace(/\n\n/g, '</p><p>')
|
||||
.replace(/\n/g, '<br>')
|
||||
.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 idx = chapterIdx.value + 1
|
||||
const [h1, h2] = chapterHues[idx] ?? chapterHues[0]
|
||||
const [h1, h2] = trackHues[trackIdx.value] ?? trackHues[0]
|
||||
return { '--scene-h1': h1, '--scene-h2': h2 } as Record<string, number>
|
||||
})
|
||||
|
||||
const chapterSong = computed(() => {
|
||||
return getPrimarySong(chapters[chapterIdx.value].slug)
|
||||
})
|
||||
|
||||
// ── CSS columns pagination ──
|
||||
const contentStyle = computed(() => {
|
||||
if (isScrollMode.value) return {}
|
||||
@@ -276,18 +252,16 @@ function recalcPages() {
|
||||
|
||||
let resizeObs: ResizeObserver | null = null
|
||||
|
||||
// Recalc when chapter content changes
|
||||
watch(activeChapter, async () => {
|
||||
// Recalc when track changes
|
||||
watch(trackIdx, async () => {
|
||||
currentPage.value = 0
|
||||
// Wait for ContentRenderer to update DOM
|
||||
await nextTick()
|
||||
await nextTick()
|
||||
setTimeout(recalcPages, 100)
|
||||
})
|
||||
|
||||
async function initReading() {
|
||||
await loadContent()
|
||||
chapterIdx.value = 0
|
||||
trackIdx.value = 0
|
||||
currentPage.value = 0
|
||||
await nextTick()
|
||||
await nextTick()
|
||||
@@ -299,34 +273,35 @@ async function initReading() {
|
||||
setTimeout(recalcPages, 200)
|
||||
}
|
||||
|
||||
// ── Navigation ──
|
||||
// ── Navigation by tracks (songs) ──
|
||||
let _skipSongWatch = false
|
||||
|
||||
function goToChapter(idx: number) {
|
||||
chapterIdx.value = idx
|
||||
function goToTrack(idx: number) {
|
||||
if (idx < 0 || idx >= tracks.value.length) return
|
||||
trackIdx.value = idx
|
||||
currentPage.value = 0
|
||||
showSommaire.value = false
|
||||
// Scroll to top in scroll mode
|
||||
if (isScrollMode.value && viewportEl.value) {
|
||||
viewportEl.value.scrollTop = 0
|
||||
}
|
||||
// Play chapter song (skip watcher to avoid loop)
|
||||
const song = getPrimarySong(chapters[idx].slug)
|
||||
// Play the song
|
||||
const song = tracks.value[idx]?.song
|
||||
if (song) {
|
||||
_skipSongWatch = true
|
||||
audioPlayer.loadAndPlay(song)
|
||||
}
|
||||
}
|
||||
|
||||
function nextChapter() {
|
||||
if (chapterIdx.value < chapters.length - 1) {
|
||||
goToChapter(chapterIdx.value + 1)
|
||||
function nextTrack() {
|
||||
if (trackIdx.value < tracks.value.length - 1) {
|
||||
goToTrack(trackIdx.value + 1)
|
||||
}
|
||||
}
|
||||
|
||||
function prevChapter() {
|
||||
if (chapterIdx.value > 0) {
|
||||
goToChapter(chapterIdx.value - 1)
|
||||
function prevTrack() {
|
||||
if (trackIdx.value > 0) {
|
||||
goToTrack(trackIdx.value - 1)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -335,9 +310,8 @@ function nextPage() {
|
||||
triggerTurn()
|
||||
currentPage.value++
|
||||
}
|
||||
else if (chapterIdx.value < chapters.length - 1) {
|
||||
// Next chapter
|
||||
goToChapter(chapterIdx.value + 1)
|
||||
else if (trackIdx.value < tracks.value.length - 1) {
|
||||
goToTrack(trackIdx.value + 1)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -346,25 +320,8 @@ function prevPage() {
|
||||
triggerTurn()
|
||||
currentPage.value--
|
||||
}
|
||||
else if (chapterIdx.value > 0) {
|
||||
// Previous chapter, go to last page
|
||||
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 })
|
||||
else if (trackIdx.value > 0) {
|
||||
goToTrack(trackIdx.value - 1)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -378,19 +335,26 @@ function close() {
|
||||
}
|
||||
|
||||
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) {
|
||||
// Scroll mode: left/right = chapters, up/down = natural scroll (no preventDefault)
|
||||
if (e.key === 'ArrowRight') { e.preventDefault(); nextChapter() }
|
||||
else if (e.key === 'ArrowLeft') { e.preventDefault(); prevChapter() }
|
||||
else if (e.key === 'Escape') close()
|
||||
if (e.key === 'ArrowRight') { e.preventDefault(); nextTrack() }
|
||||
else if (e.key === 'ArrowLeft') { e.preventDefault(); prevTrack() }
|
||||
}
|
||||
else {
|
||||
// Paginated mode: left/right = pages, up/down = chapters
|
||||
if (e.key === 'ArrowRight') { e.preventDefault(); nextPage() }
|
||||
else if (e.key === 'ArrowLeft') { e.preventDefault(); prevPage() }
|
||||
else if (e.key === 'ArrowDown') { e.preventDefault(); nextChapter() }
|
||||
else if (e.key === 'ArrowUp') { e.preventDefault(); prevChapter() }
|
||||
else if (e.key === 'Escape') close()
|
||||
else if (e.key === 'ArrowDown') { e.preventDefault(); nextTrack() }
|
||||
else if (e.key === 'ArrowUp') { e.preventDefault(); prevTrack() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -402,7 +366,6 @@ function onTouchStart(e: TouchEvent) {
|
||||
}
|
||||
|
||||
function onTouchEnd(e: TouchEvent) {
|
||||
// Disable page-swipe in scroll mode (vertical scroll is native)
|
||||
if (isScrollMode.value) return
|
||||
const diff = touchStartX - (e.changedTouches[0]?.screenX ?? 0)
|
||||
if (Math.abs(diff) > 50) {
|
||||
@@ -415,7 +378,6 @@ function onTouchEnd(e: TouchEvent) {
|
||||
watch(isOpen, async (open) => {
|
||||
if (open) {
|
||||
showSommaire.value = false
|
||||
contentLoaded.value = false
|
||||
await initBookData()
|
||||
await nextTick()
|
||||
overlayRef.value?.focus()
|
||||
@@ -424,12 +386,10 @@ watch(isOpen, async (open) => {
|
||||
// Load playlist & play first song
|
||||
const playlist = getPlaylistOrder()
|
||||
if (playlist.length) playerStore.setPlaylist(playlist)
|
||||
const first = getSongs().find(s => s.id === 'ce-livre-est-une-facon')
|
||||
if (first) {
|
||||
if (playlist.length) {
|
||||
_skipSongWatch = true
|
||||
audioPlayer.loadAndPlay(first)
|
||||
audioPlayer.loadAndPlay(playlist[0])
|
||||
}
|
||||
// Start reading directly
|
||||
await initReading()
|
||||
}
|
||||
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) => {
|
||||
if (!song || !isOpen.value) return
|
||||
if (_skipSongWatch) {
|
||||
_skipSongWatch = false
|
||||
return
|
||||
}
|
||||
const slug = getChapterForSong(song.id)
|
||||
if (!slug) return
|
||||
const idx = chapters.findIndex(ch => ch.slug === slug)
|
||||
if (idx !== -1 && idx !== chapterIdx.value) {
|
||||
chapterIdx.value = idx
|
||||
const idx = tracks.value.findIndex(t => t.id === song.id)
|
||||
if (idx !== -1 && idx !== trackIdx.value) {
|
||||
trackIdx.value = idx
|
||||
currentPage.value = 0
|
||||
showSommaire.value = false
|
||||
if (isScrollMode.value && viewportEl.value) {
|
||||
viewportEl.value.scrollTop = 0
|
||||
}
|
||||
@@ -809,7 +768,33 @@ onUnmounted(() => {
|
||||
.reader-columns :deep(ol) {
|
||||
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) {
|
||||
break-inside: auto;
|
||||
overflow-y: auto;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<ul class="flex flex-col gap-1">
|
||||
<li v-for="chapter in chapters" :key="chapter.path">
|
||||
<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"
|
||||
active-class="bg-primary/10 text-primary font-medium"
|
||||
>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<div class="container-content flex h-[var(--header-height)] items-center justify-between px-4">
|
||||
<!-- Logo -->
|
||||
<NuxtLink to="/" class="logo-link flex items-center gap-2.5">
|
||||
<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="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"/>
|
||||
@@ -56,7 +56,7 @@ const isMobileMenuOpen = ref(false)
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.logo-section {
|
||||
.logo-icon {
|
||||
width: 1.6rem;
|
||||
height: 2rem;
|
||||
color: hsl(var(--color-primary));
|
||||
@@ -65,12 +65,9 @@ const isMobileMenuOpen = ref(false)
|
||||
|
||||
.logo-text {
|
||||
font-family: var(--font-display);
|
||||
font-weight: 300;
|
||||
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;
|
||||
font-weight: 600;
|
||||
font-size: 1.15rem;
|
||||
letter-spacing: 0.02em;
|
||||
color: hsl(var(--color-primary));
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -51,10 +51,18 @@
|
||||
>
|
||||
</div>
|
||||
|
||||
<!-- Lyrics (if available) -->
|
||||
<div v-if="store.currentSong.lyrics" class="panel-lyrics">
|
||||
<!-- Lyrics (collapsed by default, available for standalone use) -->
|
||||
<div v-if="store.currentSong.lyrics && showLyrics" class="panel-lyrics">
|
||||
<pre class="panel-lyrics-text">{{ store.currentSong.lyrics }}</pre>
|
||||
</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 -->
|
||||
<div class="panel-playlist">
|
||||
@@ -128,6 +136,7 @@ useKeyboardShortcuts()
|
||||
|
||||
const widgetRef = ref<HTMLElement>()
|
||||
const isExpanded = ref(false)
|
||||
const showLyrics = ref(false)
|
||||
let previousVolume = 0.8
|
||||
|
||||
const circumference = 2 * Math.PI * 16
|
||||
@@ -401,6 +410,26 @@ onClickOutside(widgetRef, () => {
|
||||
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 ─── */
|
||||
.panel-playlist {
|
||||
max-height: 200px;
|
||||
|
||||
@@ -124,13 +124,7 @@ export function useAudioPlayer() {
|
||||
function playPrev() {
|
||||
const song = store.prevSong()
|
||||
if (song) {
|
||||
if (song === store.currentSong && store.currentTime <= 3) {
|
||||
// prevSong already reset time
|
||||
seek(0)
|
||||
}
|
||||
else {
|
||||
loadAndPlay(song)
|
||||
}
|
||||
loadAndPlay(song)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -34,6 +34,36 @@
|
||||
</div>
|
||||
</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>
|
||||
<AdminMarkdownEditor v-model="body" :rows="35" />
|
||||
</AdminFormSection>
|
||||
@@ -51,6 +81,7 @@ const route = useRoute()
|
||||
const slug = computed(() => route.params.slug as string)
|
||||
|
||||
const { data: chapter } = await useFetch(() => `/api/admin/chapters/${slug.value}`)
|
||||
const { data: bookConfig } = await useFetch<any>('/api/content/config')
|
||||
|
||||
const title = ref('')
|
||||
const description = ref('')
|
||||
@@ -65,7 +96,6 @@ const wordCount = computed(() => {
|
||||
|
||||
watch(chapter, (val) => {
|
||||
if (val) {
|
||||
// Parse frontmatter fields
|
||||
const fm = val.frontmatter ?? ''
|
||||
title.value = extractFmField(fm, 'title')
|
||||
description.value = extractFmField(fm, 'description')
|
||||
@@ -79,6 +109,52 @@ function extractFmField(fm: string, field: string): string {
|
||||
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 saved = ref(false)
|
||||
|
||||
@@ -86,6 +162,7 @@ async function save() {
|
||||
saving.value = true
|
||||
saved.value = false
|
||||
try {
|
||||
// 1. Sauvegarder le contenu du chapitre
|
||||
const order = chapter.value?.frontmatter?.match(/order:\s*(\d+)/)?.[1] ?? '1'
|
||||
const frontmatter = [
|
||||
`title: "${title.value}"`,
|
||||
@@ -98,6 +175,27 @@ async function save() {
|
||||
method: 'PUT',
|
||||
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
|
||||
setTimeout(() => { saved.value = false }, 2000)
|
||||
}
|
||||
@@ -129,4 +227,70 @@ async function save() {
|
||||
outline: none;
|
||||
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>
|
||||
|
||||
@@ -20,12 +20,21 @@
|
||||
<div class="i-lucide-grip-vertical h-4 w-4" />
|
||||
</div>
|
||||
<span class="chapter-order">{{ String(i + 1).padStart(2, '0') }}</span>
|
||||
<NuxtLink
|
||||
:to="`/admin/book/${chapter.slug}`"
|
||||
class="chapter-title"
|
||||
>
|
||||
{{ chapter.title }}
|
||||
</NuxtLink>
|
||||
<div class="chapter-info">
|
||||
<NuxtLink
|
||||
:to="`/admin/book/${chapter.slug}`"
|
||||
class="chapter-title"
|
||||
>
|
||||
{{ 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
|
||||
class="delete-btn"
|
||||
@click="removeChapter(chapter.slug)"
|
||||
@@ -64,6 +73,19 @@ definePageMeta({
|
||||
})
|
||||
|
||||
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 saved = ref(false)
|
||||
const newTitle = ref('')
|
||||
@@ -176,8 +198,13 @@ async function removeChapter(slug: string) {
|
||||
width: 1.75rem;
|
||||
}
|
||||
|
||||
.chapter-title {
|
||||
.chapter-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.chapter-title {
|
||||
display: block;
|
||||
color: white;
|
||||
font-weight: 500;
|
||||
text-decoration: none;
|
||||
@@ -187,6 +214,22 @@ async function removeChapter(slug: string) {
|
||||
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 {
|
||||
flex-shrink: 0;
|
||||
padding: 0.375rem;
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<nav class="mt-16 flex items-center justify-between border-t border-white/8 pt-8">
|
||||
<NuxtLink
|
||||
v-if="prevChapter"
|
||||
:to="`/modele-eco/${prevChapter.stem}`"
|
||||
:to="`/modele-eco/${prevChapter.stem?.split('/').pop()}`"
|
||||
class="btn-ghost gap-2"
|
||||
>
|
||||
<div class="i-lucide-arrow-left h-4 w-4" />
|
||||
@@ -24,7 +24,7 @@
|
||||
|
||||
<NuxtLink
|
||||
v-if="nextChapter"
|
||||
:to="`/modele-eco/${nextChapter.stem}`"
|
||||
:to="`/modele-eco/${nextChapter.stem?.split('/').pop()}`"
|
||||
class="btn-ghost gap-2"
|
||||
>
|
||||
<span class="text-sm">{{ nextChapter.title }}</span>
|
||||
@@ -64,7 +64,7 @@ const { data: allChapters } = await useAsyncData('book-nav', () =>
|
||||
)
|
||||
|
||||
const currentIndex = computed(() =>
|
||||
allChapters.value?.findIndex(c => c.stem === slug) ?? -1,
|
||||
allChapters.value?.findIndex(c => c.stem?.split('/').pop() === slug) ?? -1,
|
||||
)
|
||||
|
||||
const prevChapter = computed(() => {
|
||||
|
||||
@@ -91,7 +91,7 @@
|
||||
:key="chapter.path"
|
||||
>
|
||||
<NuxtLink
|
||||
:to="`/modele-eco/${chapter.stem}`"
|
||||
:to="`/modele-eco/${chapter.stem?.split('/').pop()}`"
|
||||
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">
|
||||
@@ -109,7 +109,7 @@
|
||||
<span class="i-lucide-clock inline-block h-3 w-3 mr-1 align-middle" />
|
||||
{{ chapter.readingTime }}
|
||||
</span>
|
||||
<SongBadges :chapter-slug="chapter.stem!" />
|
||||
<SongBadges :chapter-slug="chapter.stem?.split('/').pop() ?? ''" />
|
||||
</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" />
|
||||
|
||||
@@ -46,29 +46,29 @@ const palettes: Record<PaletteName, PaletteColors> = {
|
||||
|
||||
// ══════ LIGHT THEMES ══════
|
||||
|
||||
// Printemps : vert vif, rose cerisier punchy, lumière fraîche
|
||||
// Printemps : vert soutenu, magenta chaud, lumière vivante
|
||||
printemps: {
|
||||
primary: '152 65% 36%', // vert émeraude vif
|
||||
accent: '340 78% 52%', // rose cerisier punchy
|
||||
surface: '130 18% 90%', // rosée du matin
|
||||
bg: '110 20% 94%', // clarté verte
|
||||
surfaceLight: '125 14% 84%', // feuille vive
|
||||
text: '155 30% 10%', // vert profond saturé
|
||||
textMuted: '145 14% 38%', // mousse riche
|
||||
primary: '152 80% 24%', // vert émeraude sombre
|
||||
accent: '338 88% 45%', // magenta profond
|
||||
surface: '145 25% 85%', // prairie franche
|
||||
bg: '140 28% 90%', // vert lumineux franc
|
||||
surfaceLight: '148 22% 77%', // feuillage vif
|
||||
text: '155 50% 6%', // encre noire-verte
|
||||
textMuted: '150 22% 28%', // sous-bois dense
|
||||
isLight: true,
|
||||
label: 'Printemps',
|
||||
icon: 'i-lucide-flower-2',
|
||||
},
|
||||
|
||||
// Été : orange solaire, turquoise pop, lumineux chaleureux
|
||||
// Été : orange brûlant, corail profond, chaleur méditerranéenne
|
||||
ete: {
|
||||
primary: '22 92% 48%', // soleil éclatant
|
||||
accent: '175 72% 38%', // turquoise pop
|
||||
surface: '38 35% 88%', // sable doré
|
||||
bg: '40 38% 93%', // lumière dorée chaude
|
||||
surfaceLight: '35 28% 82%', // dune chaude
|
||||
text: '28 35% 8%', // brun intense
|
||||
textMuted: '28 16% 35%', // ombre chaude
|
||||
primary: '18 90% 44%', // terre cuite brûlante
|
||||
accent: '355 78% 50%', // corail ardent
|
||||
surface: '32 40% 85%', // ocre clair
|
||||
bg: '35 42% 90%', // chaleur dorée
|
||||
surfaceLight: '30 32% 78%', // argile chaude
|
||||
text: '20 45% 8%', // brun profond
|
||||
textMuted: '22 22% 30%', // ombre terracotta
|
||||
isLight: true,
|
||||
label: 'Été',
|
||||
icon: 'i-lucide-sun',
|
||||
|
||||
@@ -119,20 +119,13 @@ export const usePlayerStore = defineStore('player', () => {
|
||||
function prevSong(): Song | 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
|
||||
if (prevIdx < 0) {
|
||||
if (repeatMode.value === 'all') {
|
||||
prevIdx = playlist.value.length - 1
|
||||
}
|
||||
else {
|
||||
currentTime.value = 0
|
||||
return currentSong.value
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,19 +1,17 @@
|
||||
book:
|
||||
title: "Une économie du don — enfin concevable"
|
||||
author: "Yvv"
|
||||
description: "Un livre et 9 chansons pour explorer ensemble les fondements d'une économie fondée sur le don."
|
||||
coverImage: "/images/book-cover.jpg"
|
||||
license: "CC-BY-NC"
|
||||
isbn: "979-1-042-45206-3"
|
||||
|
||||
title: Une économie du don — enfin concevable
|
||||
author: Yvv
|
||||
description: Un livre et 9 chansons pour explorer ensemble les fondements d'une économie fondée sur le don.
|
||||
coverImage: /images/book-cover.jpg
|
||||
license: CC-BY-NC
|
||||
isbn: 979-1-042-45206-3
|
||||
songs:
|
||||
- id: ce-livre-est-une-facon
|
||||
title: "Ce livre est une façon"
|
||||
title: Ce livre est une façon
|
||||
artist: Yvv
|
||||
file: /audio/ce-livre-est-une-facon.mp3
|
||||
file: /audio/ED-01-ce-livre-est-une-facon.mp3
|
||||
duration: 718
|
||||
lyrics: |
|
||||
[Intro]
|
||||
Ce livre est un essai.
|
||||
Une proposition.
|
||||
Une intention.
|
||||
@@ -21,18 +19,16 @@ songs:
|
||||
(...)
|
||||
Une façon.
|
||||
|
||||
[bridge]
|
||||
Deux mille vingt'-quatre j'écris tout l'été, ...
|
||||
Deux mille vingt'-cinq je mûris toute l'année, ...
|
||||
Deux mille vingt'-siss la sortie... mmmmhh,
|
||||
Le temps pass
|
||||
2024, j'écris tout l'été, ...
|
||||
2025, je mûris toute l'année, ...
|
||||
2026, la sortie... mmmmhh,
|
||||
Le temps passe
|
||||
Est-ce une menace ?
|
||||
|
||||
[Verse 1]
|
||||
Pour éviter tout quiproquo :
|
||||
Pour mieux zaimer le propos
|
||||
Pour mieux aimer le propos
|
||||
Ce n'est pas une théorie.
|
||||
Pas d'u-niversalité.
|
||||
Pas d'universalité.
|
||||
Loin s'en faut.
|
||||
|
||||
Une expérimentation.
|
||||
@@ -42,24 +38,21 @@ songs:
|
||||
Mais si ça reste à ton échelle... c'est symbolique.
|
||||
Viser mon bassin de vie ? ça se complique.
|
||||
|
||||
[Chorus]
|
||||
Ce livre n'est pas un guiDe,
|
||||
davantage un guit'.
|
||||
Ce n'est pas un Kit.
|
||||
[Refrain]
|
||||
Ce livre n'est pas un guide,
|
||||
davantage un git.
|
||||
Ce n'est pas un kit.
|
||||
Ça ne dit pas quoi faire lundi.
|
||||
|
||||
[Bridge]
|
||||
Tu veux une baguette magique ?
|
||||
Supprimer la pression, l'oppression ? - Ce s'rait pas con...
|
||||
Si je ne résous pas mes problèmes d'aujourd'hui... à quoi bon ?
|
||||
Naviguer dans le ciel des idées...
|
||||
C'est fini !
|
||||
Si je ne résous pas mes problèmes d'aujourd'hui... A quoi bon ?
|
||||
Naviguer dans le ciel des idées... C'est fini !
|
||||
|
||||
[Verse 2]
|
||||
Créer une économie ?
|
||||
On en a déjà une. Tu veux décrocher la lune ?
|
||||
Elle couvre mes besoins vitaux. De facto.
|
||||
Alors pourquoi ?
|
||||
Alors pourquoi ? Pourquoi ?
|
||||
|
||||
Ben. Pour l'autonomie.
|
||||
...C'est tout - sauf un repli.
|
||||
@@ -70,55 +63,49 @@ songs:
|
||||
"restez des enfants !"...
|
||||
mmh, suspect et sans avenir.
|
||||
|
||||
[Bridge]
|
||||
Deux mille vingt-six. L'année des défis.
|
||||
Ne plus subir les agendas. Créer les nôtr'.
|
||||
On manque de repères ?... Entre autres, ...
|
||||
2026, l'année des défis.
|
||||
Ne plus subir les agendas. Créer les nôtres.
|
||||
On manque de repères ? Entre autres, ...
|
||||
Faut les trouver,...
|
||||
En produisant, les inventer.
|
||||
|
||||
[Chorus]
|
||||
Ce livre n'est pas un guiDe,
|
||||
davantage un guit'.
|
||||
Ce n'est pas un Kit.
|
||||
[Refrain]
|
||||
Ce livre n'est pas un guide,
|
||||
davantage un git.
|
||||
Ce n'est pas un kit.
|
||||
Ça ne dit pas quoi faire lundi.
|
||||
|
||||
[Bridge 2]
|
||||
C'est un os à ronger.
|
||||
Une cartographie. Quelques boussoles.
|
||||
Dans une jungle à défricher.
|
||||
Ce n'est pas encore l'heure...
|
||||
Du prêtà-porter.
|
||||
Du prêt-à-porter.
|
||||
A nous de tailler.
|
||||
|
||||
[outro]
|
||||
On tourne une page pour voir ?
|
||||
Décline le rôle de la bonne poire...
|
||||
Je n'ai pas que l'espoir
|
||||
J'ai un pouvoir
|
||||
Tu as bien mieux que l'espoir,
|
||||
Nous - zavons un grand pouvoir.
|
||||
Nous avons un grand pouvoir.
|
||||
|
||||
(whisper :) hey, on tourne une page ?
|
||||
tags: [introduction, livre, don]
|
||||
|
||||
- id: un-don-qui-se-mesure
|
||||
title: "Un don qui se mesure"
|
||||
Hey, on tourne une page ?
|
||||
tags:
|
||||
- introduction
|
||||
- livre
|
||||
- don
|
||||
- id: de-quel-don-nous-parlons
|
||||
title: De quel don nous parlons ?
|
||||
artist: Yvv
|
||||
file: /audio/un-don-qui-se-mesure.mp3
|
||||
file: /audio/ED-02-de-quel-don-nous-parlons.mp3
|
||||
duration: 589
|
||||
lyrics: |
|
||||
[Intro]
|
||||
(Piano Rhodes : accords jazzy)
|
||||
(Basse : Ligne ronde et enveloppante)
|
||||
|
||||
lyrics: |-
|
||||
Une économie du don ?
|
||||
Mais de quel don nous parlons ?
|
||||
Ce mariage fait peur. Il claque.
|
||||
Un oxymore, on l'apprend juste après l'bac.
|
||||
Une contradiction pour l'esprit.
|
||||
|
||||
[Verse 1]
|
||||
On évacue tout de suite le spirituel.
|
||||
Désolé pour le karma, désolé pour le ciel.
|
||||
Je ne parle pas du "centuple divin".
|
||||
@@ -128,17 +115,16 @@ songs:
|
||||
Qui sert de base, qui sert de fondation,
|
||||
À une autre forme de construction.
|
||||
|
||||
[Chorus]
|
||||
[Refrain]
|
||||
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.
|
||||
(Females: Donner... Recevoir... Rendre...)
|
||||
(Donner... Recevoir... Rendre...)
|
||||
|
||||
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.
|
||||
|
||||
[Verse 2]
|
||||
J'entends les rêves de brûler la monnaie.
|
||||
"Le troc", "la gratuité", "Mocica", le grand projet.
|
||||
La monnaie serait le vice, la corruption mentale.
|
||||
@@ -149,58 +135,32 @@ songs:
|
||||
Mais si la monnaie est libre ? Elle permet les équilibres.
|
||||
Si elle devient notre outil ? Elle change le récit.
|
||||
|
||||
[Bridge]
|
||||
(Music strips down. Just Bass and Snare rimshots)
|
||||
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]
|
||||
Alors voici une définition du don :
|
||||
C'est simplement une transmission
|
||||
|
||||
FIN à remettre.
|
||||
tags:
|
||||
- don
|
||||
- mesure
|
||||
- valeur
|
||||
- id: les-asymetries
|
||||
title: "Les asymétries"
|
||||
title: Les asymétries
|
||||
artist: Yvv
|
||||
file: /audio/les-asymetries.mp3
|
||||
file: /audio/ED-03-les-asymetries.mp3
|
||||
duration: 727
|
||||
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.
|
||||
On est entre nous On se rassure.
|
||||
On discute assidus...hey, on assure !
|
||||
(suspension)
|
||||
(...)
|
||||
Nous sommes trop convaincus.
|
||||
Cela nous endort. Ce que je crois peut-être à tord ... tue.
|
||||
(Females: Pseudo-isolés... Pseudo-isolés...)
|
||||
Cela nous endort.
|
||||
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.
|
||||
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.
|
||||
Des abandons moribons, ...
|
||||
Des abandons moribonds, ...
|
||||
Ha bon ?
|
||||
|
||||
Malgré quelques notifs, et les esprits attentifs
|
||||
@@ -208,32 +168,25 @@ songs:
|
||||
J'ai vu aussi bien sûr, quelques trous du cul
|
||||
Mais ! Est-ce là une bonne raison ?
|
||||
|
||||
[Bridge 1]
|
||||
(Rhythm becomes jagged, syncopated stops)
|
||||
Rien n'est symétrique, ce n'est pas magique.
|
||||
(Females: ) Rien.
|
||||
Rien.
|
||||
Une pomme aujourd'hui, n'est pas la même demain.
|
||||
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 à genoux sur un toit...ho ! balaise
|
||||
(Females:) Est-ce le même geste, ou une ascèse ? mmmhh.
|
||||
(Silence - 1 second)
|
||||
Est-ce le même geste, ou une ascèse ? mmmhh.
|
||||
|
||||
[Chorus]
|
||||
(Music swells slightly, singing questions)
|
||||
[Refrain]
|
||||
Alors que faire ? On désespère, on baisse les bras ?
|
||||
on légifère ? On écrit des lois ?
|
||||
On réglemente, on décide de l'issue ?
|
||||
(suspension)
|
||||
(...)
|
||||
Mieux vaut peut-être un protocole avec quelques bémol.
|
||||
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,
|
||||
la lassitude, l'envenime-ment.
|
||||
la lassitude, l'envenimement.
|
||||
Il suffit d'une mesure.
|
||||
Sans morsure,
|
||||
qui fait bonne figure,
|
||||
@@ -241,46 +194,39 @@ songs:
|
||||
pour tout le monde.
|
||||
Qui mesure la pénibilité ? - Pourquoi pas.
|
||||
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 ?
|
||||
|
||||
[Bridge 2]
|
||||
Une communauté ne peut pas tout écrire.
|
||||
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,
|
||||
Une belle et grande morale ? - c'est bancal :
|
||||
Il est utile de prévenir, se souvenir, ...
|
||||
C'est le pire.
|
||||
|
||||
[Chorus]
|
||||
(Music swells slightly)
|
||||
[Refrain]
|
||||
Alors que faire ? On désespère, on baisse les bras ?
|
||||
on légifère ? On écrit des lois ?
|
||||
On réglemente, on décide de l'issue ?
|
||||
(suspension)
|
||||
(...)
|
||||
Mieux vaut peut-être un protocole, avec quelques bémol.
|
||||
Une façon de traiter, d'éviter de juger, préfigurer.
|
||||
(suspension) Ne pas tranchez les sorts, à leur insu.
|
||||
(suspension) Ne tranchez pas mon sort, à mon insu.
|
||||
Ne pas trancher les sorts, à leur 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.
|
||||
(Females:) Rien.
|
||||
Rien.
|
||||
Pour se rétablir, retomber sur nos pieds
|
||||
Il existe un outil qui s'appelle, ... "la monnaie".
|
||||
|
||||
[Verse 3]
|
||||
C'est elle en permanence qui résout le "schmilblick".
|
||||
Même sans qu'on y pense, faut avouer, c'est pratique
|
||||
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.
|
||||
Ne t'y méprends pas, son pouvoir est immense.
|
||||
|
||||
[Outro]
|
||||
(Piano flowing arpeggios, fading)
|
||||
Résoudre le problème des asymétries.
|
||||
Réduire le besoin de légiférer.
|
||||
Pour une simple coloc... ou pour un monde entier.
|
||||
@@ -288,24 +234,18 @@ songs:
|
||||
Ne les néglige pas.
|
||||
Tu peux les mesurer,
|
||||
et choisir ... ta monnaie.
|
||||
[...]
|
||||
Choisir ta monnaie
|
||||
tags: [asymétrie, communauté, philosophie]
|
||||
|
||||
Choisir ta monnaie
|
||||
tags:
|
||||
- asymétrie
|
||||
- communauté
|
||||
- philosophie
|
||||
- id: inverser-les-flux
|
||||
title: "Inverser les flux"
|
||||
title: Inverser les flux
|
||||
artist: Yvv
|
||||
file: /audio/inverser-les-flux.mp3
|
||||
file: /audio/ED-04-inverser-les-flux.mp3
|
||||
duration: 610
|
||||
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,
|
||||
son économie,
|
||||
Tu passes de l'une à l'autre
|
||||
@@ -313,86 +253,79 @@ songs:
|
||||
|
||||
Ici, tu peux l'appeler la monnaie du don.
|
||||
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.
|
||||
|
||||
[Chorus]
|
||||
(Groove s'intensifie, la batterie claque)
|
||||
[Refrain]
|
||||
Le D.U, c'est une mesure.
|
||||
(Choir: La mesure...)
|
||||
(La mesure...)
|
||||
Pour ne plus obliger. Pour ne plus devoir.
|
||||
Je donne à mon économie, j'alimente le réservoir.
|
||||
[break]
|
||||
(...)
|
||||
Si je t'aime, j'y mets mon affect.
|
||||
Si je ne t'aime pas, du moins je te respecte.
|
||||
Je compte sur les autres, sur mon économie,
|
||||
Pour y trouver ma pleine mesure.
|
||||
La solidarité organique, pas de paniK
|
||||
La solidarité organique, pas de panique,
|
||||
Elle franchit les limites.
|
||||
(Choir: Organique...)
|
||||
|
||||
[Verse 1]
|
||||
"Je ne veux rien en retour", c'est ce que tu dis.
|
||||
Mais tu m'obliges.
|
||||
Tu crées une dette non dite,
|
||||
De quoi rester perplexe.
|
||||
La solidarité mécanique est complexe
|
||||
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.
|
||||
Je choisis mes mots, c'est un glissement.
|
||||
(...)
|
||||
Je ne vends plus.
|
||||
(Choir: Non...)
|
||||
Je donne. (...) C'est mon offre.
|
||||
Non !
|
||||
Je donne. C'est mon offre.
|
||||
Le mot retrouve du sens, pleinement.
|
||||
(...)
|
||||
Je n'achète plus.
|
||||
(Choir: Non...)
|
||||
Non !
|
||||
Je reçois.
|
||||
(...)
|
||||
Je reçois la valeur. Je l'évalue. Avec le D.U.
|
||||
|
||||
La "dépossession monétaire" n'a plus lieu d'être.
|
||||
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]
|
||||
(Male) Je ne paye plus.
|
||||
(Female) Je mesure. J'estime...
|
||||
(Male) Je négocie détendu. (c'est un virage)
|
||||
(Female) J'ajuste ma balance intime...
|
||||
(Male) Je donne du poids.
|
||||
(Female) De la masse.
|
||||
(Both) Ou une température.
|
||||
Je rentre du marché, nouveau vocabulaire :
|
||||
"Hey - j'ai reçu une semaine de cour-ss."
|
||||
"Wow, t'as mis gratitude max à la source ?"
|
||||
|
||||
Je ne paye plus.
|
||||
Je mesure. J'estime...
|
||||
Je négocie détendu. (c'est un virage)
|
||||
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.
|
||||
[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.
|
||||
|
||||
[Chorus - Ensemble]
|
||||
(Groove s'intensifie, la batterie claque)
|
||||
[Refrain]
|
||||
Le D.U, c'est une mesure.
|
||||
(Choir: La mesure...)
|
||||
(La mesure...)
|
||||
Pour ne plus obliger. Pour ne plus devoir.
|
||||
Je donne à mon économie, j'alimente le réservoir.
|
||||
[break]
|
||||
|
||||
Si je t'aime, j'y mets mon affect.
|
||||
Si je ne t'aime pas, du moins je te respecte.
|
||||
Je compte sur les autres, sur mon économie,
|
||||
Pour y trouver ma pleine mesure.
|
||||
La solidarité organique, pas de panique
|
||||
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 ?
|
||||
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.
|
||||
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 entres dans la danse.
|
||||
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.
|
||||
(...)
|
||||
Au delà d'un simple théorème
|
||||
C'est le cadeau de la T.R.M.
|
||||
|
||||
[Outro]
|
||||
(Female ad-libs: Équilibre... Invariant...)
|
||||
[Rhodes solo, jazzy and improvised]
|
||||
[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]
|
||||
|
||||
tags:
|
||||
- flux
|
||||
- économie
|
||||
- production
|
||||
- id: ainsi-soit-il
|
||||
title: "Ainsi soit-il"
|
||||
title: Ainsi soit-il !
|
||||
artist: Yvv
|
||||
file: /audio/ainsi-soit-il.mp3
|
||||
file: /audio/ED-05-ainsi-soit-il.mp3
|
||||
duration: 545
|
||||
lyrics: |
|
||||
Ainsi soit-il
|
||||
|
||||
[Intro]
|
||||
[Vinyl Crackle Sound]
|
||||
[Minimalist Piano Loop]
|
||||
lyrics: |+
|
||||
Fiat...
|
||||
Fiat Lux… Fiat Euro.
|
||||
Fiat Lux...
|
||||
Fiat Euro.
|
||||
|
||||
[Verse 1]
|
||||
[Spoken Word, Calm and Clear]
|
||||
Fiat. Ce n'est pas une marque.
|
||||
Fiat. Ce n'est pas qu'une marque.
|
||||
C'est du latin.
|
||||
Ç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 Euro : Que la dette soit.
|
||||
Un monopole déclaré.
|
||||
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]
|
||||
Un monopole institué.
|
||||
|
||||
tags:
|
||||
- fiat
|
||||
- monnaie-dette
|
||||
- DU
|
||||
- id: la-croissance-une-option
|
||||
title: "La croissance, une option ?"
|
||||
title: La croissance, une option ?
|
||||
artist: Yvv
|
||||
file: /audio/la-croissance-une-option.mp3
|
||||
file: /audio/ED-06-la-croissance-une-option.mp3
|
||||
duration: 510
|
||||
lyrics: ""
|
||||
tags: [croissance, monnaie, questionnement]
|
||||
|
||||
- id: monnaie-libre-essence
|
||||
title: "Monnaie libre essence"
|
||||
tags:
|
||||
- croissance
|
||||
- monnaie
|
||||
- questionnement
|
||||
- id: monnaie-libre-une-essence
|
||||
title: Monnaie libre, une essence ?
|
||||
artist: Yvv
|
||||
file: /audio/monnaie-libre-essence.mp3
|
||||
file: /audio/ED-07-monnaie-libre-essence.mp3
|
||||
duration: 475
|
||||
lyrics: ""
|
||||
tags: [monnaie libre, TRM, June]
|
||||
|
||||
- id: des-cercles-qui-se-croisent
|
||||
title: "Des cercles qui se croisent"
|
||||
tags:
|
||||
- monnaie libre
|
||||
- TRM
|
||||
- June
|
||||
- id: creer-une-economie
|
||||
title: Créer une économie
|
||||
artist: Yvv
|
||||
file: /audio/des-cercles-qui-se-croisent.mp3
|
||||
file: /audio/ED-08-creer-une-économie.mp3
|
||||
duration: 496
|
||||
lyrics: |
|
||||
Hymne à la monnaie libre
|
||||
|
||||
[Verse]
|
||||
Des cercles qui se croisent
|
||||
Sans les regards qui toisent
|
||||
Des poings qui se détendent
|
||||
@@ -519,7 +402,7 @@ songs:
|
||||
Un souffle tout petit
|
||||
Pas de chaînes pour les pensées
|
||||
|
||||
[Chorus]
|
||||
[Refrain]
|
||||
Construire des vies
|
||||
Nos cœurs qui grossissent
|
||||
Couvrir nos besoins
|
||||
@@ -535,7 +418,6 @@ songs:
|
||||
Si tu en as la fibre
|
||||
C'est l'hymne à la monnaie libre
|
||||
|
||||
[Verse 2]
|
||||
Les jours se lèvent sur des rêves partagés
|
||||
Quelques champs pour les possibles
|
||||
Des ponts à imaginer
|
||||
@@ -544,7 +426,6 @@ songs:
|
||||
Juste l'humanité
|
||||
Est-elle si pénible ?
|
||||
|
||||
[Bridge]
|
||||
Ni maîtres ni esclaves
|
||||
Juste un écho
|
||||
Des esprits qui dansent
|
||||
@@ -552,7 +433,7 @@ songs:
|
||||
Un chant nouveau
|
||||
Surmontent les entraves
|
||||
|
||||
[Chorus]
|
||||
[Refrain]
|
||||
Construire des vies
|
||||
Nos cœurs qui grossissent
|
||||
Couvrir nos besoins
|
||||
@@ -564,48 +445,46 @@ songs:
|
||||
En arrêtant de nuire
|
||||
Sans place pour la honte
|
||||
Sans laissés-pour-compte
|
||||
tags: [échange, réseau, cercles]
|
||||
|
||||
tags:
|
||||
- économie
|
||||
- hymne
|
||||
- monnaie libre
|
||||
- id: coder-la-liberte
|
||||
title: "Coder la liberté"
|
||||
title: Coder la liberté
|
||||
artist: Yvv
|
||||
file: /audio/coder-la-liberte.mp3
|
||||
file: /audio/ED-09-coder-la-liberte.mp3
|
||||
duration: 376
|
||||
lyrics: |
|
||||
Coder un rêve
|
||||
|
||||
[Verse]
|
||||
Dans l'ombre des géants big tek
|
||||
Des lignes poussent discrètes
|
||||
Tourné vers la grande ourse
|
||||
Je coule open source
|
||||
Je code open source
|
||||
|
||||
[Prechorus]
|
||||
Balance ton Jiz Onne
|
||||
Balance ton Json
|
||||
mon shell résonne
|
||||
Coup de dés Py - thon Runtime Upgrade, ninja blayde
|
||||
Ça bilt, Docker compose. Devant l'écran je pose. Ca biilt.
|
||||
shhhhhhh... it com paille ...llss,
|
||||
tapis dans une typo sans serif, je check les certif
|
||||
Coup de dés Python, runtime upgrade, ninja blade
|
||||
Ça build, Docker compose. Devant l'écran je pose. Ca build.
|
||||
shhhhhhh... it compils,
|
||||
Tapis dans une typo sans serif, je check les certifs
|
||||
merde ça lag,
|
||||
mate mes log,
|
||||
j'ai la langue qui bog.
|
||||
j'ai la langue qui bogue.
|
||||
Mon café est tout froid
|
||||
Je ne perds pas la foi.
|
||||
|
||||
[Chorus]
|
||||
Codeurs de rêv
|
||||
Rime pour les dèvs
|
||||
dans les réseaux, où que j'aille
|
||||
vous êtes mes sudo,
|
||||
mes samouraï
|
||||
[Refrain]
|
||||
Codeurs de rêve
|
||||
Une rime pour les dèvs
|
||||
Dans les réseaux, où que j'aille
|
||||
Vous êtes mes sudo,
|
||||
Mes samouraï
|
||||
Hackers, Admin,
|
||||
Dèvop, développ
|
||||
Vous décentralisez
|
||||
Vous open sourcez
|
||||
Un monde moins obscur
|
||||
Duniterre bien sûr.
|
||||
Notre toil fiduciaire
|
||||
Duniter bien sûr.
|
||||
Notre toile fiduciaire
|
||||
Nous pouvez être fiers
|
||||
|
||||
[Verse 2]
|
||||
@@ -617,10 +496,9 @@ songs:
|
||||
La tuyauterie
|
||||
Manipule des bounty
|
||||
Du fuel pour les applis
|
||||
tous vos dons ... les mettent à l'abri
|
||||
Tous vos dons ... les mettent à l'abri
|
||||
Merci
|
||||
|
||||
[Bridge]
|
||||
Crash à minuit
|
||||
Je reste éveillé
|
||||
Le bug fatal
|
||||
@@ -630,118 +508,65 @@ songs:
|
||||
Le café est-il prêt ?
|
||||
C'est bientôt aujourd'hui
|
||||
|
||||
[Chorus]
|
||||
Codeurs de rêv
|
||||
Rime pour les dèvs
|
||||
dans les réseaux, où que j'aille
|
||||
vous êtes mes sudo,
|
||||
mes samouraï
|
||||
[Refrain]
|
||||
Codeurs de rêve
|
||||
Une rime pour les dèvs
|
||||
Dans les réseaux, où que j'aille
|
||||
Vous êtes mes sudo,
|
||||
Mes samouraï
|
||||
Hackers, Admin,
|
||||
Dèvop, développ
|
||||
Vous décentralisez
|
||||
Vous open sourcez
|
||||
Un monde moins obscur
|
||||
Duniterre bien sûr.
|
||||
Notre toil fiduciaire
|
||||
Duniter bien sûr.
|
||||
Notre toile fiduciaire
|
||||
Nous en sommes fiers
|
||||
tags: [logiciel libre, code, liberté]
|
||||
|
||||
tags:
|
||||
- logiciel libre
|
||||
- code
|
||||
- liberté
|
||||
chapterSongs:
|
||||
# Chapitre 1 — Introduction
|
||||
- chapterSlug: 01-introduction
|
||||
songId: ce-livre-est-une-facon
|
||||
primary: true
|
||||
- chapterSlug: 01-introduction
|
||||
songId: un-don-qui-se-mesure
|
||||
primary: false
|
||||
|
||||
# Chapitre 2 — De quel don parlons-nous ?
|
||||
- chapterSlug: 02-don
|
||||
songId: un-don-qui-se-mesure
|
||||
songId: de-quel-don-nous-parlons
|
||||
primary: true
|
||||
- chapterSlug: 02-don
|
||||
songId: ce-livre-est-une-facon
|
||||
primary: false
|
||||
|
||||
# Chapitre 3 — La mesure du don
|
||||
- chapterSlug: 03-mesure
|
||||
songId: les-asymetries
|
||||
primary: true
|
||||
- chapterSlug: 03-mesure
|
||||
songId: un-don-qui-se-mesure
|
||||
primary: false
|
||||
|
||||
# Chapitre 4 — Raison d'être d'une monnaie
|
||||
- chapterSlug: 04-monnaie
|
||||
songId: la-croissance-une-option
|
||||
primary: true
|
||||
- chapterSlug: 04-monnaie
|
||||
songId: monnaie-libre-essence
|
||||
primary: false
|
||||
|
||||
# Chapitre 5 — La TRM
|
||||
- chapterSlug: 05-trm
|
||||
songId: monnaie-libre-essence
|
||||
songId: inverser-les-flux
|
||||
primary: true
|
||||
- chapterSlug: 05-trm
|
||||
songId: ainsi-soit-il
|
||||
primary: true
|
||||
- chapterSlug: 06-economie
|
||||
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
|
||||
- chapterSlug: 07-echange
|
||||
songId: inverser-les-flux
|
||||
primary: false
|
||||
|
||||
# Chapitre 8 — Relation institutionnelle
|
||||
songId: monnaie-libre-une-essence
|
||||
primary: true
|
||||
- chapterSlug: 08-institution
|
||||
songId: ainsi-soit-il
|
||||
primary: false
|
||||
- chapterSlug: 08-institution
|
||||
songId: des-cercles-qui-se-croisent
|
||||
primary: false
|
||||
|
||||
# Chapitre 9 — Autres greffes
|
||||
songId: creer-une-economie
|
||||
primary: true
|
||||
- chapterSlug: 09-greffes
|
||||
songId: inverser-les-flux
|
||||
primary: false
|
||||
- chapterSlug: 09-greffes
|
||||
songId: des-cercles-qui-se-croisent
|
||||
primary: false
|
||||
|
||||
# Chapitre 10 — Et maintenant ?
|
||||
- chapterSlug: 10-maintenant
|
||||
songId: ainsi-soit-il
|
||||
songId: coder-la-liberte
|
||||
primary: true
|
||||
- chapterSlug: 10-maintenant
|
||||
songId: coder-la-liberte
|
||||
primary: false
|
||||
|
||||
# Chapitre 11 — Annexes
|
||||
primary: true
|
||||
- chapterSlug: 11-annexes
|
||||
songId: coder-la-liberte
|
||||
primary: true
|
||||
- chapterSlug: 11-annexes
|
||||
songId: monnaie-libre-essence
|
||||
primary: false
|
||||
|
||||
defaultPlaylistOrder:
|
||||
- ce-livre-est-une-facon
|
||||
- un-don-qui-se-mesure
|
||||
- de-quel-don-nous-parlons
|
||||
- les-asymetries
|
||||
- inverser-les-flux
|
||||
- ainsi-soit-il
|
||||
- la-croissance-une-option
|
||||
- monnaie-libre-essence
|
||||
- des-cercles-qui-se-croisent
|
||||
- monnaie-libre-une-essence
|
||||
- creer-une-economie
|
||||
- coder-la-liberte
|
||||
|
||||
@@ -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-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',
|
||||
'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)]',
|
||||
'section-padding': 'px-4 py-16 md:px-8 lg:px-16 lg:py-24',
|
||||
'container-content': 'mx-auto max-w-7xl w-full',
|
||||
|
||||
Reference in New Issue
Block a user