Ouverture directe du BookPlayer en lecture, corrections éditoriales
- Suppression des phases intro (livre 3D) et cover (page intermédiaire) du BookPlayer : le reader s'ouvre directement depuis la home - Corrections textuelles : about.md, app.config.ts, app.vue - Mise à jour de GrateWizard app Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -14,7 +14,7 @@ export default defineAppConfig({
|
||||
],
|
||||
},
|
||||
footer: {
|
||||
credits: '© 2026 Le Librodrome — Production collective',
|
||||
credits: '© 2026 Le Librodrome — Productions collectives',
|
||||
links: [
|
||||
{ label: 'Mentions légales', to: '/mentions-legales' },
|
||||
],
|
||||
|
||||
@@ -14,15 +14,12 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const { data: site } = await useSiteContent()
|
||||
|
||||
useHead({
|
||||
titleTemplate: (title) => {
|
||||
const siteName = site.value?.identity.name ?? 'Le Librodrome'
|
||||
return title ? `${title} — ${siteName}` : siteName
|
||||
return title ? `${title} — Le Librodrome` : 'Le librodrome'
|
||||
},
|
||||
meta: [
|
||||
{ name: 'description', content: site.value?.identity.description ?? '' },
|
||||
{ name: 'description', content: 'Une économie du don — enfin concevable. Un livre et 9 chansons, lecture guidée et écoute libre.' },
|
||||
],
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -28,40 +28,8 @@
|
||||
<div class="i-lucide-x h-5 w-5" />
|
||||
</button>
|
||||
|
||||
<!-- ═══ PHASE TRANSITIONS ═══ -->
|
||||
<Transition name="phase" mode="out-in">
|
||||
<!-- ─── INTRO: 3D spinning book ─── -->
|
||||
<div v-if="phase === 'intro'" key="intro" class="bp-phase bp-intro">
|
||||
<div class="spin-scene">
|
||||
<div class="spin-book" @animationend="onSpinEnd">
|
||||
<div class="spin-face spin-front">
|
||||
<img src="/images/book-cover-spread.jpg" alt="Couverture" />
|
||||
</div>
|
||||
<div class="spin-face spin-back">
|
||||
<img src="/images/book-cover-spread.jpg" alt="" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ─── COVER: title + CTA ─── -->
|
||||
<div v-else-if="phase === 'cover'" key="cover" class="bp-phase bp-cover">
|
||||
<div class="cover-frame">
|
||||
<img src="/images/book-cover-spread.jpg" :alt="bpContent?.cover.coverAlt ?? 'Couverture'" class="cover-img" />
|
||||
</div>
|
||||
<h1 class="cover-title text-gradient">{{ bpContent?.cover.title }}</h1>
|
||||
<p class="cover-sub">{{ bpContent?.cover.subtitle }}</p>
|
||||
<p class="cover-desc">
|
||||
{{ bpContent?.cover.description }}
|
||||
</p>
|
||||
<button class="cover-cta" @click="startReading">
|
||||
{{ bpContent?.cover.cta }}
|
||||
<div class="i-lucide-arrow-right ml-2 h-5 w-5 inline-block align-middle" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- ─── READING: paginated book reader ─── -->
|
||||
<div v-else key="reading" class="bp-phase bp-reader">
|
||||
<!-- ═══ READER ═══ -->
|
||||
<div class="bp-phase bp-reader">
|
||||
<!-- Top bar -->
|
||||
<div class="reader-bar">
|
||||
<button
|
||||
@@ -143,17 +111,11 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
|
||||
<!-- Hint -->
|
||||
<p class="bp-hint">
|
||||
<template v-if="phase === 'reading'">
|
||||
<span class="hidden md:inline">{{ bpContent?.reader.hints.desktop }}</span>
|
||||
<span class="md:hidden">{{ bpContent?.reader.hints.mobile }}</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span class="hidden md:inline">{{ bpContent?.reader.hints.default }}</span>
|
||||
</template>
|
||||
<span class="hidden md:inline">{{ bpContent?.reader.hints.desktop }}</span>
|
||||
<span class="md:hidden">{{ bpContent?.reader.hints.mobile }}</span>
|
||||
</p>
|
||||
</div>
|
||||
</Transition>
|
||||
@@ -177,8 +139,6 @@ const overlayRef = ref<HTMLElement>()
|
||||
const viewportEl = ref<HTMLElement>()
|
||||
const contentEl = ref<HTMLElement>()
|
||||
|
||||
// ── Phase state ──
|
||||
const phase = ref<'intro' | 'cover' | 'reading'>('intro')
|
||||
const chapterIdx = ref(0)
|
||||
const currentPage = ref(0)
|
||||
const totalPages = ref(1)
|
||||
@@ -243,13 +203,12 @@ const chapterHues: [number, number][] = [
|
||||
]
|
||||
|
||||
const sceneVars = computed(() => {
|
||||
const idx = phase.value === 'reading' ? chapterIdx.value + 1 : 0
|
||||
const idx = chapterIdx.value + 1
|
||||
const [h1, h2] = chapterHues[idx] ?? chapterHues[0]
|
||||
return { '--scene-h1': h1, '--scene-h2': h2 } as Record<string, number>
|
||||
})
|
||||
|
||||
const chapterSong = computed(() => {
|
||||
if (phase.value !== 'reading') return null
|
||||
return getPrimarySong(chapters[chapterIdx.value].slug)
|
||||
})
|
||||
|
||||
@@ -278,16 +237,10 @@ watch(activeChapter, async () => {
|
||||
setTimeout(recalcPages, 100)
|
||||
})
|
||||
|
||||
// ── Phase transitions ──
|
||||
function onSpinEnd() {
|
||||
phase.value = 'cover'
|
||||
}
|
||||
|
||||
async function startReading() {
|
||||
async function initReading() {
|
||||
await loadContent()
|
||||
chapterIdx.value = 0
|
||||
currentPage.value = 0
|
||||
phase.value = 'reading'
|
||||
await nextTick()
|
||||
await nextTick()
|
||||
// Set up ResizeObserver
|
||||
@@ -353,16 +306,11 @@ function close() {
|
||||
}
|
||||
|
||||
function handleKeydown(e: KeyboardEvent) {
|
||||
if (phase.value === 'reading') {
|
||||
if (e.key === 'ArrowRight') { e.preventDefault(); nextPage() }
|
||||
else if (e.key === 'ArrowLeft') { e.preventDefault(); prevPage() }
|
||||
else if (e.key === 'ArrowDown') { e.preventDefault(); if (chapterIdx.value < chapters.length - 1) goToChapter(chapterIdx.value + 1) }
|
||||
else if (e.key === 'ArrowUp') { e.preventDefault(); if (chapterIdx.value > 0) goToChapter(chapterIdx.value - 1) }
|
||||
else if (e.key === 'Escape') close()
|
||||
}
|
||||
else if (e.key === 'Escape') {
|
||||
close()
|
||||
}
|
||||
if (e.key === 'ArrowRight') { e.preventDefault(); nextPage() }
|
||||
else if (e.key === 'ArrowLeft') { e.preventDefault(); prevPage() }
|
||||
else if (e.key === 'ArrowDown') { e.preventDefault(); if (chapterIdx.value < chapters.length - 1) goToChapter(chapterIdx.value + 1) }
|
||||
else if (e.key === 'ArrowUp') { e.preventDefault(); if (chapterIdx.value > 0) goToChapter(chapterIdx.value - 1) }
|
||||
else if (e.key === 'Escape') close()
|
||||
}
|
||||
|
||||
// ── Touch / swipe ──
|
||||
@@ -383,9 +331,6 @@ function onTouchEnd(e: TouchEvent) {
|
||||
// ── Lifecycle ──
|
||||
watch(isOpen, async (open) => {
|
||||
if (open) {
|
||||
phase.value = 'intro'
|
||||
chapterIdx.value = 0
|
||||
currentPage.value = 0
|
||||
showSommaire.value = false
|
||||
contentLoaded.value = false
|
||||
await initBookData()
|
||||
@@ -398,6 +343,8 @@ watch(isOpen, async (open) => {
|
||||
if (playlist.length) playerStore.setPlaylist(playlist)
|
||||
const first = getSongs().find(s => s.id === 'chanson-01')
|
||||
if (first) audioPlayer.loadAndPlay(first)
|
||||
// Start reading directly
|
||||
await initReading()
|
||||
}
|
||||
else {
|
||||
overlayRef.value?.removeEventListener('touchstart', onTouchStart)
|
||||
@@ -567,135 +514,6 @@ onUnmounted(() => {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* Phase transitions */
|
||||
.phase-enter-active { animation: phase-in 0.5s cubic-bezier(0.16, 1, 0.3, 1) both; }
|
||||
.phase-leave-active { animation: phase-out 0.3s cubic-bezier(0.7, 0, 0.84, 0) both; }
|
||||
@keyframes phase-in {
|
||||
from { opacity: 0; transform: scale(0.97); filter: blur(4px); }
|
||||
to { opacity: 1; transform: scale(1); filter: blur(0); }
|
||||
}
|
||||
@keyframes phase-out {
|
||||
from { opacity: 1; transform: scale(1); filter: blur(0); }
|
||||
to { opacity: 0; transform: scale(0.97); filter: blur(4px); }
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════
|
||||
INTRO: 3D SPINNING BOOK
|
||||
═══════════════════════════════════════ */
|
||||
.bp-intro {
|
||||
justify-content: center;
|
||||
}
|
||||
.spin-scene {
|
||||
perspective: 1200px;
|
||||
}
|
||||
.spin-book {
|
||||
position: relative;
|
||||
width: min(220px, 45vw);
|
||||
aspect-ratio: 3 / 4;
|
||||
transform-style: preserve-3d;
|
||||
animation: book-spin 2.8s cubic-bezier(0.16, 1, 0.3, 1) forwards;
|
||||
}
|
||||
.spin-face {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
backface-visibility: hidden;
|
||||
border-radius: 0.5rem;
|
||||
overflow: hidden;
|
||||
border: 1px solid hsl(20 8% 18%);
|
||||
box-shadow: 0 20px 60px hsl(0 0% 0% / 0.5);
|
||||
}
|
||||
.spin-front img {
|
||||
width: 200%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
.spin-back {
|
||||
transform: rotateY(180deg);
|
||||
}
|
||||
.spin-back img {
|
||||
width: 200%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
@keyframes book-spin {
|
||||
0% { transform: rotateY(0deg) scale(0.65); opacity: 0; }
|
||||
8% { opacity: 1; }
|
||||
45% { transform: rotateY(180deg) scale(0.9); }
|
||||
75% { transform: rotateY(320deg) scale(1); }
|
||||
90% { transform: rotateY(352deg) scale(1); }
|
||||
100% { transform: rotateY(360deg) scale(1); }
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════
|
||||
COVER
|
||||
═══════════════════════════════════════ */
|
||||
.bp-cover {
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
}
|
||||
.cover-frame {
|
||||
width: min(200px, 42vw);
|
||||
aspect-ratio: 3 / 4;
|
||||
border-radius: 0.625rem;
|
||||
overflow: hidden;
|
||||
border: 1px solid hsl(20 8% 18%);
|
||||
box-shadow:
|
||||
0 25px 60px hsl(0 0% 0% / 0.5),
|
||||
0 0 40px hsl(var(--scene-h1) 60% 40% / 0.1);
|
||||
margin-bottom: 2rem;
|
||||
animation: cover-float 7s ease-in-out infinite;
|
||||
}
|
||||
.cover-img {
|
||||
width: 200%; height: 100%;
|
||||
object-fit: cover;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
@keyframes cover-float {
|
||||
0%, 100% { transform: translateY(0) rotate(-0.5deg); }
|
||||
50% { transform: translateY(-10px) rotate(0.5deg); }
|
||||
}
|
||||
.cover-title {
|
||||
font-family: var(--font-display, 'Syne', sans-serif);
|
||||
font-size: clamp(1.75rem, 5vw, 2.75rem);
|
||||
font-weight: 800;
|
||||
line-height: 1.1;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
.cover-sub {
|
||||
font-family: var(--font-display, 'Syne', sans-serif);
|
||||
font-size: clamp(1rem, 3vw, 1.4rem);
|
||||
color: hsl(20 8% 55%);
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
.cover-desc {
|
||||
font-size: 0.9rem;
|
||||
color: hsl(20 8% 45%);
|
||||
max-width: 26rem;
|
||||
line-height: 1.65;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
.cover-cta {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 0.75rem 2rem;
|
||||
border-radius: 9999px;
|
||||
background: hsl(var(--scene-h1) 70% 45%);
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
font-size: 0.95rem;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: all 0.35s cubic-bezier(0.16, 1, 0.3, 1);
|
||||
box-shadow: 0 0 24px hsl(var(--scene-h1) 70% 45% / 0.3);
|
||||
}
|
||||
.cover-cta:hover {
|
||||
background: hsl(var(--scene-h1) 70% 52%);
|
||||
box-shadow: 0 0 36px hsl(var(--scene-h1) 70% 50% / 0.45);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════
|
||||
READER
|
||||
═══════════════════════════════════════ */
|
||||
|
||||
Reference in New Issue
Block a user