Refactoring complet : contenu livre, config unique, routes, admin et light mode

- Source unique : supprime app/data/librodrome.config.yml, renomme site/ en bookplayer.config.yml
- Morceaux : renommés avec slugs lisibles, fichiers audio renommés, inversion ch2↔ch3 corrigée
- Chapitres : 11 fichiers .md réécrits avec le vrai contenu du livre (synthèse fidèle du PDF)
- Routes : /lire → /modele-eco, /ecouter → /en-musique, redirections 301
- Admin chapitres : champs structurés (titre, description, temps lecture), compteur mots
- Éditeur markdown : mode split, plein écran, support Tab, meilleur rendu aperçu
- Admin morceaux : drag & drop, ajout/suppression, gestion playlist
- Light mode : palettes printemps/été plus saturées et contrastées, teintes primary
- Raccourcis clavier player : espace, flèches gauche/droite
- Paroles : toggle supprimé, toujours visibles et scrollables
- Nouvelles pages : autonomie, evenement

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Yvv
2026-02-26 20:20:52 +01:00
parent 4fce862df6
commit 2f438d9d7a
70 changed files with 2125 additions and 1385 deletions

View File

@@ -0,0 +1,81 @@
<template>
<div v-if="chapter">
<BookChapterHeader
:title="chapter.title"
:description="chapter.description"
:order="chapter.order"
:reading-time="chapter.readingTime"
:chapter-slug="slug"
/>
<BookChapterContent :content="chapter" />
<!-- Prev / Next navigation -->
<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}`"
class="btn-ghost gap-2"
>
<div class="i-lucide-arrow-left h-4 w-4" />
<span class="text-sm">{{ prevChapter.title }}</span>
</NuxtLink>
<div v-else />
<NuxtLink
v-if="nextChapter"
:to="`/modele-eco/${nextChapter.stem}`"
class="btn-ghost gap-2"
>
<span class="text-sm">{{ nextChapter.title }}</span>
<div class="i-lucide-arrow-right h-4 w-4" />
</NuxtLink>
<div v-else />
</nav>
</div>
</template>
<script setup lang="ts">
definePageMeta({
layout: 'reading',
})
const route = useRoute()
const slug = route.params.slug as string
// Initialize guided mode
useGuidedMode()
const { data: chapter } = await useAsyncData(`chapter-${slug}`, () =>
queryCollection('book').path(`/book/${slug}`).first(),
)
if (!chapter.value) {
throw createError({ statusCode: 404, statusMessage: 'Chapitre non trouvé' })
}
useHead({
title: chapter.value?.title,
})
// Get adjacent chapters for navigation
const { data: allChapters } = await useAsyncData('book-nav', () =>
queryCollection('book').order('order', 'ASC').all(),
)
const currentIndex = computed(() =>
allChapters.value?.findIndex(c => c.stem === slug) ?? -1,
)
const prevChapter = computed(() => {
const idx = currentIndex.value
if (idx <= 0 || !allChapters.value) return null
return allChapters.value[idx - 1]
})
const nextChapter = computed(() => {
const idx = currentIndex.value
if (!allChapters.value || idx >= allChapters.value.length - 1) return null
return allChapters.value[idx + 1]
})
</script>