Files
librodrome/app/pages/en-musique/index.vue
Yvv 2f438d9d7a 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>
2026-02-26 20:20:52 +01:00

277 lines
10 KiB
Vue

<template>
<div class="relative overflow-hidden section-padding">
<!-- Shadok danseur: character dancing with music notes -->
<svg class="shadok-danseur" viewBox="0 0 240 300" fill="none" aria-hidden="true">
<!-- Body (dynamic pose, leaning) -->
<ellipse cx="120" cy="155" rx="38" ry="46" fill="currentColor" opacity="0.85"/>
<!-- Head (tilted with joy) -->
<ellipse cx="125" cy="92" rx="24" ry="23" fill="currentColor" opacity="0.8"/>
<!-- Eyes (happy, squinted) -->
<path d="M114 88 Q118 84 122 88" stroke="currentColor" stroke-width="2" stroke-linecap="round" fill="none" opacity="0.4"/>
<path d="M130 88 Q134 84 138 88" stroke="currentColor" stroke-width="2" stroke-linecap="round" fill="none" opacity="0.4"/>
<!-- Big smile -->
<path d="M116 100 Q125 108 134 100" stroke="currentColor" stroke-width="2" stroke-linecap="round" fill="none" opacity="0.35"/>
<!-- Arms thrown up (dancing) -->
<line x1="85" y1="140" x2="50" y2="100" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" opacity="0.6"/>
<line x1="155" y1="140" x2="190" y2="105" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" opacity="0.6"/>
<!-- Hands -->
<circle cx="50" cy="98" r="4" fill="currentColor" opacity="0.4"/>
<circle cx="190" cy="103" r="4" fill="currentColor" opacity="0.4"/>
<!-- Legs (one kicked up) -->
<line x1="105" y1="198" x2="80" y2="255" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" opacity="0.6"/>
<line x1="135" y1="198" x2="170" y2="240" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" opacity="0.6"/>
<!-- Feet -->
<path d="M80 255 L68 258 M80 255 L75 261" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" opacity="0.5"/>
<path d="M170 240 L180 238 M170 240 L175 246" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" opacity="0.5"/>
<!-- Music notes floating around -->
<text x="42" y="82" fill="currentColor" opacity="0.3" font-size="18">&#9834;</text>
<text x="195" y="88" fill="currentColor" opacity="0.25" font-size="16">&#9835;</text>
<text x="60" y="65" fill="currentColor" opacity="0.2" font-size="14">&#9833;</text>
<text x="180" y="72" fill="currentColor" opacity="0.2" font-size="20">&#9834;</text>
</svg>
<!-- Shadok DJ: character with headphones behind a turntable -->
<svg class="shadok-dj" viewBox="0 0 260 300" fill="none" aria-hidden="true">
<!-- Body -->
<ellipse cx="130" cy="155" rx="42" ry="50" fill="currentColor" opacity="0.85"/>
<!-- Head -->
<circle cx="130" cy="88" r="26" fill="currentColor" opacity="0.8"/>
<!-- Headphones band -->
<path d="M104 78 Q130 55 156 78" stroke="currentColor" stroke-width="4" stroke-linecap="round" fill="none" opacity="0.6"/>
<!-- Headphone ear pads -->
<ellipse cx="102" cy="85" rx="8" ry="12" fill="currentColor" opacity="0.5"/>
<ellipse cx="158" cy="85" rx="8" ry="12" fill="currentColor" opacity="0.5"/>
<!-- Eyes (cool, half-lidded) -->
<ellipse cx="120" cy="85" rx="5" ry="3" fill="currentColor" opacity="0.25"/>
<ellipse cx="140" cy="85" rx="5" ry="3" fill="currentColor" opacity="0.25"/>
<circle cx="121" cy="86" r="1.8" fill="currentColor" opacity="0.5"/>
<circle cx="141" cy="86" r="1.8" fill="currentColor" opacity="0.5"/>
<!-- Mouth (grin) -->
<path d="M122 98 Q130 104 138 98" stroke="currentColor" stroke-width="2" stroke-linecap="round" fill="none" opacity="0.35"/>
<!-- Arms reaching to turntable -->
<line x1="90" y1="150" x2="55" y2="195" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" opacity="0.6"/>
<line x1="170" y1="150" x2="205" y2="195" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" opacity="0.6"/>
<!-- Turntable body -->
<rect x="30" y="200" width="200" height="18" rx="4" fill="currentColor" opacity="0.4"/>
<!-- Turntable platter -->
<ellipse cx="130" cy="200" rx="55" ry="15" fill="currentColor" opacity="0.25"/>
<ellipse cx="130" cy="200" rx="55" ry="15" stroke="currentColor" stroke-width="1.5" fill="none" opacity="0.35"/>
<!-- Record center -->
<circle cx="130" cy="200" r="5" fill="currentColor" opacity="0.4"/>
<!-- Tone arm -->
<line x1="195" y1="188" x2="150" y2="195" stroke="currentColor" stroke-width="2" stroke-linecap="round" opacity="0.5"/>
<circle cx="195" cy="188" r="3" fill="currentColor" opacity="0.35"/>
<!-- Legs -->
<line x1="115" y1="202" x2="105" y2="260" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" opacity="0.6"/>
<line x1="145" y1="202" x2="155" y2="260" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" opacity="0.6"/>
</svg>
<div class="container-content">
<!-- Hero section with book cover -->
<div class="mb-12 grid items-center gap-8 md:grid-cols-2">
<div class="book-cover-wrapper">
<div class="book-cover-3d">
<img
:src="homeContent?.book.coverImage"
:alt="homeContent?.book.coverAlt"
class="book-cover-img"
/>
</div>
</div>
<div class="text-center md:text-left">
<p class="mb-2 font-mono text-sm tracking-widest text-accent uppercase">{{ content?.kicker }}</p>
<h1 class="page-title font-display font-bold tracking-tight text-white">
{{ content?.title }}
</h1>
<p class="mt-4 text-white/60">
{{ content?.description }}
</p>
<div class="mt-6 flex flex-col gap-3 sm:flex-row sm:gap-4 justify-center md:justify-start">
<UiBaseButton @click="showBookPlayer = true">
<div class="i-lucide-play mr-2 h-5 w-5" />
Présentation musicale
</UiBaseButton>
<UiBaseButton variant="accent" @click="showPdfReader = true">
<div class="i-lucide-book-open mr-2 h-5 w-5" />
Lire le livre
</UiBaseButton>
</div>
</div>
</div>
<!-- Search + view toggle -->
<div class="mb-6 flex items-center justify-between gap-4">
<div class="relative flex-1 max-w-md">
<div class="i-lucide-search absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-white/30" />
<input
v-model="search"
type="text"
:placeholder="content?.searchPlaceholder"
class="w-full rounded-lg bg-surface border border-white/8 py-2 pl-10 pr-4 text-sm text-white placeholder:text-white/30 focus:border-primary/50 focus:outline-none"
>
</div>
<div class="flex items-center gap-1 rounded-lg bg-surface p-1">
<button
class="rounded p-1.5 transition-colors"
:class="viewMode === 'list' ? 'bg-white/10 text-white' : 'text-white/40'"
@click="viewMode = 'list'"
>
<div class="i-lucide-list h-4 w-4" />
</button>
<button
class="rounded p-1.5 transition-colors"
:class="viewMode === 'grid' ? 'bg-white/10 text-white' : 'text-white/40'"
@click="viewMode = 'grid'"
>
<div class="i-lucide-grid-3x3 h-4 w-4" />
</button>
</div>
</div>
<!-- Song list -->
<div v-if="viewMode === 'list'" class="flex flex-col gap-2">
<SongItem
v-for="song in filteredSongs"
:key="song.id"
:song="song"
/>
</div>
<!-- Song grid -->
<div v-else class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
<SongItem
v-for="song in filteredSongs"
:key="song.id"
:song="song"
/>
</div>
<p v-if="filteredSongs.length === 0" class="text-center text-white/40 py-12">
{{ content?.noResults }}
</p>
</div>
<BookPlayer v-model="showBookPlayer" />
<BookPdfReader v-model="showPdfReader" />
</div>
</template>
<script setup lang="ts">
definePageMeta({
layout: 'default',
})
const { data: content } = await usePageContent('en-musique')
const { data: homeContent } = await usePageContent('home')
useHead({
title: content.value?.meta?.title ?? 'En musique',
})
const store = usePlayerStore()
const bookData = useBookData()
const { loadFullPlaylist } = usePlaylist()
await bookData.init()
// Switch to free mode
store.setMode('free')
await loadFullPlaylist()
const search = ref('')
const viewMode = ref<'list' | 'grid'>('list')
const showBookPlayer = ref(false)
const showPdfReader = ref(false)
const filteredSongs = computed(() => {
const songs = bookData.getSongs()
if (!search.value.trim()) return songs
const q = search.value.toLowerCase()
return songs.filter(
s => s.title.toLowerCase().includes(q)
|| s.artist.toLowerCase().includes(q)
|| s.tags.some(t => t.toLowerCase().includes(q)),
)
})
</script>
<style scoped>
.page-title {
font-size: clamp(2rem, 5vw, 2.75rem);
}
.book-cover-wrapper {
perspective: 800px;
display: flex;
justify-content: center;
}
.book-cover-3d {
aspect-ratio: 3 / 4;
border-radius: 0.75rem;
overflow: hidden;
border: 1px solid hsl(var(--color-text) / 0.1);
box-shadow:
0 12px 40px hsl(var(--color-text) / 0.15),
0 0 0 1px hsl(var(--color-text) / 0.08);
transition: transform 0.5s cubic-bezier(0.645, 0.045, 0.355, 1),
box-shadow 0.5s ease;
max-width: 280px;
}
.book-cover-3d:hover {
transform: rotateY(-8deg) rotateX(3deg) scale(1.02);
box-shadow:
12px 16px 48px hsl(var(--color-text) / 0.2),
0 0 0 1px hsl(var(--color-primary) / 0.2);
}
.book-cover-img {
width: 200%;
height: 100%;
object-fit: cover;
transform: translateX(-50%);
}
.shadok-danseur {
position: absolute;
left: 2%;
top: 3%;
width: clamp(100px, 14vw, 200px);
opacity: 0.28;
pointer-events: none;
color: hsl(var(--color-primary));
animation: shadok-float-danseur 7s ease-in-out infinite;
}
.shadok-dj {
position: absolute;
right: 2%;
top: 3%;
width: clamp(120px, 16vw, 230px);
opacity: 0.3;
pointer-events: none;
color: hsl(var(--color-accent));
animation: shadok-float-dj 8s ease-in-out infinite;
}
@keyframes shadok-float-danseur {
0%, 100% { transform: translateY(0) rotate(0deg); }
25% { transform: translateY(-8px) rotate(2deg); }
75% { transform: translateY(-4px) rotate(-2deg); }
}
@keyframes shadok-float-dj {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-10px); }
}
@media (max-width: 768px) {
.shadok-danseur { display: none; }
.shadok-dj { display: none; }
}
</style>