<template>
  <Teleport to="body">
    <Transition name="immersive">
      <div
        v-if="isOpen"
        class="bp"
        :style="sceneVars"
        @keydown="handleKeydown"
        tabindex="0"
        ref="overlayRef"
      >
        <!-- Animated gradient background -->
        <div class="bp-bg" aria-hidden="true">
          <div class="bp-orb bp-orb--1" />
          <div class="bp-orb bp-orb--2" />
          <div class="bp-orb bp-orb--3" />
        </div>

        <!-- Central glow ring -->
        <div
          class="bp-glow"
          :class="{ 'bp-glow--active': playerStore.isPlaying }"
          aria-hidden="true"
        />

        <!-- ═══ READER ═══ -->
          <div class="bp-phase bp-reader">
            <!-- Top bar -->
            <div class="reader-bar">
              <button
                class="reader-bar-btn"
                @click="showSommaire = !showSommaire"
                aria-label="Sommaire"
              >
                <div class="i-lucide-list h-5 w-5" />
              </button>
              <div class="reader-bar-title">
                <span class="reader-bar-num">{{ trackIdx + 1 }}.</span>
                {{ currentTrack?.title ?? '' }}
              </div>
              <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>
            </div>

            <!-- Sommaire sidebar -->
            <Transition name="sommaire">
              <aside v-if="showSommaire" class="reader-sommaire" @click.self="showSommaire = false">
                <div class="sommaire-panel">
                  <h4 class="sommaire-title">{{ bpContent?.reader.sommaireTitle ?? 'Sommaire' }}</h4>
                  <button
                    v-for="(track, i) in tracks"
                    :key="track.id"
                    class="sommaire-item"
                    :class="{ 'sommaire-item--active': trackIdx === i }"
                    @click="goToTrack(i)"
                  >
                    <span class="sommaire-num">{{ i + 1 }}</span>
                    {{ track.title }}
                  </button>
                </div>
              </aside>
            </Transition>

            <!-- Content viewport -->
            <div
              class="reader-viewport"
              :class="{ 'reader-viewport--scroll': isScrollMode }"
              ref="viewportEl"
              @scroll="onViewportScroll"
            >
              <div
                class="reader-columns prose"
                :class="{ 'reader-columns--scroll': isScrollMode }"
                ref="contentEl"
                :style="contentStyle"
              >
                <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 }" />
            </div>

            <!-- Bottom navigation -->
            <div class="reader-nav">
              <button
                class="reader-nav-btn"
                :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 -->
              <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">{{ currentSong.title }}</span>
              </div>
              <div v-else class="reader-song" />

              <button
                class="reader-nav-btn"
                :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>
            </div>
          </div>

        <!-- Hint -->
        <p class="bp-hint">
          <template v-if="isScrollMode">
            <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>
            <span class="hidden md:inline">{{ bpContent?.reader.hints.desktop }}</span>
            <span class="md:hidden">{{ bpContent?.reader.hints.mobile }}</span>
          </template>
        </p>
      </div>
    </Transition>
  </Teleport>
</template>

<script setup lang="ts">
const { data: bpContent } = await usePageContent('book-player')

const COL_GAP = 80

const props = defineProps<{ modelValue: boolean }>()
const emit = defineEmits<{ 'update:modelValue': [value: boolean] }>()

const isOpen = computed({
  get: () => props.modelValue,
  set: (v) => emit('update:modelValue', v),
})

const overlayRef = ref<HTMLElement>()
const viewportEl = ref<HTMLElement>()
const contentEl = ref<HTMLElement>()

const trackIdx = ref(0)
const currentPage = ref(0)
const totalPages = ref(1)
const colWidth = ref(500)
const showSommaire = ref(false)
const isTurning = ref(false)

// ── Reading mode ──
const readingMode = ref<'paginated' | 'scroll'>('scroll')
const isScrollMode = computed(() => readingMode.value === 'scroll')
const scrollPercent = ref(0)

// When switching back to paginated, recalc pages
watch(readingMode, async (mode) => {
  if (mode === 'paginated') {
    await nextTick()
    await nextTick()
    setTimeout(recalcPages, 100)
  }
})

function onViewportScroll() {
  if (!isScrollMode.value || !viewportEl.value) return
  const el = viewportEl.value
  const max = el.scrollHeight - el.clientHeight
  scrollPercent.value = max > 0 ? Math.round((el.scrollTop / max) * 100) : 0
}

const { init: initBookData, getPlaylistOrder } = useBookData()
const audioPlayer = useAudioPlayer()
const playerStore = usePlayerStore()

// ── Tracks: built from playlist order (songs), not chapters ──
const tracks = computed(() => {
  return playerStore.playlist.map(song => ({
    id: song.id,
    title: song.title,
    song,
  }))
})

const currentTrack = computed(() => tracks.value[trackIdx.value] ?? null)
const currentSong = computed(() => currentTrack.value?.song ?? null)

const currentLyrics = computed(() => {
  return currentSong.value?.lyrics?.trim() || ''
})

const currentLyricsHtml = computed(() => {
  if (!currentLyrics.value) return ''
  return currentLyrics.value
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .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 [h1, h2] = trackHues[trackIdx.value] ?? trackHues[0]
  return { '--scene-h1': h1, '--scene-h2': h2 } as Record<string, number>
})

// ── CSS columns pagination ──
const contentStyle = computed(() => {
  if (isScrollMode.value) return {}
  return {
    columnWidth: colWidth.value + 'px',
    columnGap: COL_GAP + 'px',
    transform: `translateX(-${currentPage.value * (colWidth.value + COL_GAP)}px)`,
  }
})

function recalcPages() {
  if (isScrollMode.value) return
  if (!contentEl.value || !viewportEl.value) return
  colWidth.value = viewportEl.value.offsetWidth
  const sw = contentEl.value.scrollWidth
  totalPages.value = Math.max(1, Math.round(sw / (colWidth.value + COL_GAP)))
}

let resizeObs: ResizeObserver | null = null

// Recalc when track changes
watch(trackIdx, async () => {
  currentPage.value = 0
  await nextTick()
  await nextTick()
  setTimeout(recalcPages, 100)
})

async function initReading() {
  trackIdx.value = 0
  currentPage.value = 0
  await nextTick()
  await nextTick()
  // Set up ResizeObserver
  if (viewportEl.value && !resizeObs) {
    resizeObs = new ResizeObserver(recalcPages)
    resizeObs.observe(viewportEl.value)
  }
  setTimeout(recalcPages, 200)
}

// ── Navigation by tracks (songs) ──
let _skipSongWatch = false

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 the song
  const song = tracks.value[idx]?.song
  if (song) {
    _skipSongWatch = true
    audioPlayer.loadAndPlay(song)
  }
}

function nextTrack() {
  if (trackIdx.value < tracks.value.length - 1) {
    goToTrack(trackIdx.value + 1)
  }
}

function prevTrack() {
  if (trackIdx.value > 0) {
    goToTrack(trackIdx.value - 1)
  }
}

function nextPage() {
  if (currentPage.value < totalPages.value - 1) {
    triggerTurn()
    currentPage.value++
  }
  else if (trackIdx.value < tracks.value.length - 1) {
    goToTrack(trackIdx.value + 1)
  }
}

function prevPage() {
  if (currentPage.value > 0) {
    triggerTurn()
    currentPage.value--
  }
  else if (trackIdx.value > 0) {
    goToTrack(trackIdx.value - 1)
  }
}

function triggerTurn() {
  isTurning.value = true
  setTimeout(() => { isTurning.value = false }, 700)
}

function close() {
  isOpen.value = false
}

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 (e.key === 'ArrowRight') { e.preventDefault(); nextTrack() }
    else if (e.key === 'ArrowLeft') { e.preventDefault(); prevTrack() }
  }
  else {
    if (e.key === 'ArrowRight') { e.preventDefault(); nextPage() }
    else if (e.key === 'ArrowLeft') { e.preventDefault(); prevPage() }
    else if (e.key === 'ArrowDown') { e.preventDefault(); nextTrack() }
    else if (e.key === 'ArrowUp') { e.preventDefault(); prevTrack() }
  }
}

// ── Touch / swipe ──
let touchStartX = 0

function onTouchStart(e: TouchEvent) {
  touchStartX = e.changedTouches[0]?.screenX ?? 0
}

function onTouchEnd(e: TouchEvent) {
  if (isScrollMode.value) return
  const diff = touchStartX - (e.changedTouches[0]?.screenX ?? 0)
  if (Math.abs(diff) > 50) {
    if (diff > 0) nextPage()
    else prevPage()
  }
}

// ── Lifecycle ──
watch(isOpen, async (open) => {
  if (open) {
    showSommaire.value = false
    await initBookData()
    await nextTick()
    overlayRef.value?.focus()
    overlayRef.value?.addEventListener('touchstart', onTouchStart, { passive: true })
    overlayRef.value?.addEventListener('touchend', onTouchEnd, { passive: true })
    // Load playlist & play first song
    const playlist = getPlaylistOrder()
    if (playlist.length) playerStore.setPlaylist(playlist)
    if (playlist.length) {
      _skipSongWatch = true
      audioPlayer.loadAndPlay(playlist[0])
    }
    await initReading()
  }
  else {
    overlayRef.value?.removeEventListener('touchstart', onTouchStart)
    overlayRef.value?.removeEventListener('touchend', onTouchEnd)
    if (resizeObs) { resizeObs.disconnect(); resizeObs = null }
  }
})

// ── 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 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
    }
  }
})

watch(isOpen, (open) => {
  if (import.meta.client) document.body.style.overflow = open ? 'hidden' : ''
})

onUnmounted(() => {
  if (import.meta.client) document.body.style.overflow = ''
  if (resizeObs) resizeObs.disconnect()
})
</script>

<style scoped>
/* Animatable hue properties */
@property --scene-h1 {
  syntax: '<number>';
  inherits: true;
  initial-value: 12;
}
@property --scene-h2 {
  syntax: '<number>';
  inherits: true;
  initial-value: 36;
}

/* ═══════════════════════════════════════
   BASE OVERLAY
   ═══════════════════════════════════════ */
.bp {
  position: fixed;
  inset: 0;
  z-index: 60;
  background: hsl(var(--color-bg));
  display: flex;
  flex-direction: column;
  align-items: center;
  outline: none;
  overflow: hidden;
  padding-bottom: 1rem;
  transition: --scene-h1 1.6s ease, --scene-h2 1.6s ease;
}

/* ─── GRADIENT BACKGROUND ─── */
.bp-bg {
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
}
.bp-orb {
  position: absolute;
  border-radius: 50%;
  filter: blur(100px);
  will-change: transform;
}
.bp-orb--1 {
  width: 55vmax; height: 55vmax;
  top: -20%; right: -15%;
  background: hsl(var(--scene-h1) 65% 40% / 0.18);
  animation: orb-1 22s ease-in-out infinite;
}
.bp-orb--2 {
  width: 45vmax; height: 45vmax;
  bottom: -25%; left: -12%;
  background: hsl(var(--scene-h2) 55% 35% / 0.15);
  animation: orb-2 28s ease-in-out infinite;
}
.bp-orb--3 {
  width: 35vmax; height: 35vmax;
  top: 35%; left: 25%;
  background: hsl(calc((var(--scene-h1) + var(--scene-h2)) / 2) 45% 30% / 0.1);
  animation: orb-3 18s ease-in-out infinite;
}
@keyframes orb-1 {
  0%, 100% { transform: translate(0, 0) scale(1); }
  33% { transform: translate(-4%, 7%) scale(1.06); }
  66% { transform: translate(3%, -4%) scale(0.94); }
}
@keyframes orb-2 {
  0%, 100% { transform: translate(0, 0) scale(1); }
  33% { transform: translate(7%, -3%) scale(0.95); }
  66% { transform: translate(-5%, 5%) scale(1.06); }
}
@keyframes orb-3 {
  0%, 100% { transform: translate(0, 0) scale(1); }
  50% { transform: translate(-8%, -6%) scale(1.12); }
}

/* ─── GLOW RING ─── */
.bp-glow {
  position: absolute;
  top: 50%; left: 50%;
  width: min(420px, 65vmin); height: min(420px, 65vmin);
  border-radius: 50%;
  pointer-events: none;
  transform: translate(-50%, -50%);
  box-shadow:
    0 0 80px 25px hsl(var(--scene-h1) 65% 42% / 0.06),
    0 0 160px 50px hsl(var(--scene-h2) 55% 38% / 0.04);
  animation: glow-breathe 5s ease-in-out infinite;
}
.bp-glow--active {
  box-shadow:
    0 0 100px 35px hsl(var(--scene-h1) 70% 45% / 0.14),
    0 0 200px 70px hsl(var(--scene-h2) 60% 40% / 0.08);
  animation: glow-active 2.5s ease-in-out infinite;
}
@keyframes glow-breathe {
  0%, 100% { opacity: 0.5; } 50% { opacity: 1; }
}
@keyframes glow-active {
  0%, 100% { opacity: 0.7; transform: translate(-50%, -50%) scale(1); }
  50% { opacity: 1; transform: translate(-50%, -50%) scale(1.04); }
}

/* ─── HINT ─── */
.bp-hint {
  position: absolute;
  bottom: 0.5rem;
  left: 50%;
  transform: translateX(-50%);
  z-index: 10;
  font-size: 0.68rem;
  color: hsl(var(--color-text-muted));
  white-space: nowrap;
}

/* ═══════════════════════════════════════
   PHASE CONTAINER
   ═══════════════════════════════════════ */
.bp-phase {
  position: relative;
  z-index: 10;
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 100%;
  flex: 1;
  min-height: 0;
}

/* ═══════════════════════════════════════
   READER
   ═══════════════════════════════════════ */
.bp-reader {
  width: 100%;
  max-width: 52rem;
  padding: 0 1rem;
  gap: 0;
  min-height: 0;
}

/* ─── Top bar ─── */
.reader-bar {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 0;
  width: 100%;
}
.reader-bar-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 2.25rem; height: 2.25rem;
  border-radius: 0.5rem;
  background: transparent;
  color: hsl(var(--color-text-muted));
  border: none;
  cursor: pointer;
  transition: all 0.3s;
  flex-shrink: 0;
}
.reader-bar-btn:hover {
  color: white;
  background: hsl(0 0% 100% / 0.06);
}
.reader-bar-close {
  color: hsl(0 0% 100% / 0.7);
  background: hsl(0 0% 100% / 0.08);
}
.reader-bar-close:hover {
  color: white;
  background: hsl(0 70% 55% / 0.3);
}
.reader-bar-title {
  flex: 1;
  font-family: var(--font-display, 'Syne', sans-serif);
  font-size: clamp(0.85rem, 2.5vw, 1.1rem);
  font-weight: 600;
  color: white;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.reader-bar-num {
  color: hsl(var(--scene-h1) 65% 55%);
  margin-right: 0.375rem;
}
.reader-bar-pages {
  font-family: var(--font-mono, monospace);
  font-size: 0.75rem;
  color: hsl(var(--color-text-muted));
  flex-shrink: 0;
}

/* ─── Sommaire ─── */
.reader-sommaire {
  position: absolute;
  inset: 0;
  z-index: 72;
  background: hsl(var(--color-bg) / 0.6);
  backdrop-filter: blur(4px);
  display: flex;
}
.sommaire-panel {
  width: min(300px, 80vw);
  height: 100%;
  background: hsl(var(--color-surface) / 0.95);
  backdrop-filter: blur(16px);
  border-right: 1px solid hsl(var(--color-surface-light));
  padding: 1.5rem;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}
.sommaire-title {
  font-family: var(--font-display, 'Syne', sans-serif);
  font-size: 0.85rem;
  font-weight: 700;
  color: hsl(var(--color-text-muted));
  text-transform: uppercase;
  letter-spacing: 0.1em;
  margin-bottom: 0.75rem;
}
.sommaire-item {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.625rem 0.75rem;
  border-radius: 0.5rem;
  font-size: 0.85rem;
  color: hsl(var(--color-text-muted));
  background: transparent;
  border: none;
  cursor: pointer;
  text-align: left;
  transition: all 0.25s;
  width: 100%;
}
.sommaire-item:hover {
  background: hsl(var(--color-surface));
  color: white;
}
.sommaire-item--active {
  background: hsl(var(--scene-h1) 50% 40% / 0.15);
  color: white;
}
.sommaire-num {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 1.5rem; height: 1.5rem;
  border-radius: 50%;
  font-size: 0.65rem;
  font-weight: 700;
  font-family: var(--font-mono, monospace);
  color: hsl(var(--color-text-muted));
  background: hsl(var(--color-surface));
  flex-shrink: 0;
}
.sommaire-item--active .sommaire-num {
  background: hsl(var(--scene-h1) 65% 42%);
  color: white;
}

/* Sommaire slide transition */
.sommaire-enter-active { animation: sommaire-in 0.35s cubic-bezier(0.16, 1, 0.3, 1) both; }
.sommaire-leave-active { animation: sommaire-in 0.25s cubic-bezier(0.7, 0, 0.84, 0) reverse both; }
@keyframes sommaire-in {
  from { opacity: 0; }
  to { opacity: 1; }
}
.sommaire-enter-active .sommaire-panel { animation: panel-slide 0.35s cubic-bezier(0.16, 1, 0.3, 1) both; }
.sommaire-leave-active .sommaire-panel { animation: panel-slide 0.25s cubic-bezier(0.7, 0, 0.84, 0) reverse both; }
@keyframes panel-slide {
  from { transform: translateX(-100%); }
  to { transform: translateX(0); }
}

/* ─── Content viewport ─── */
.reader-viewport {
  position: relative;
  flex: 1;
  width: 100%;
  overflow: hidden auto;
  border-radius: 0.75rem;
  background: hsl(var(--color-bg) / 0.4);
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
}

.reader-columns {
  height: 100%;
  column-fill: auto;
  padding: 2rem 2.5rem;
  transition: transform 0.65s cubic-bezier(0.16, 1, 0.3, 1);
  max-width: none;
}

/* ─── Scroll mode overrides ─── */
.reader-viewport--scroll {
  overflow: hidden auto;
  min-height: 0;
}
.reader-columns--scroll {
  height: auto;
  column-fill: unset;
  column-width: unset !important;
  transition: none;
}

/* Lyrics: preserve line breaks from \n in text nodes */
.reader-columns :deep(p) {
  white-space: pre-line;
}

/* Tighten prose for column context */
.reader-columns :deep(h1) {
  font-size: clamp(1.5rem, 3.5vw, 2rem);
  margin-top: 0;
  padding-bottom: 0.5rem;
}
.reader-columns :deep(h2) {
  font-size: clamp(1.25rem, 3vw, 1.625rem);
  margin-top: 2rem;
}
.reader-columns :deep(h3) {
  font-size: clamp(1.065rem, 2.5vw, 1.25rem);
  margin-top: 1.75rem;
}

/* Prevent awkward column breaks */
.reader-columns :deep(h2),
.reader-columns :deep(h3) {
  break-after: avoid;
}
.reader-columns :deep(blockquote),
.reader-columns :deep(ul),
.reader-columns :deep(ol) {
  break-inside: avoid;
}
/* 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;
}

/* Page-turn shadow overlay */
.reader-shadow {
  position: absolute;
  top: 0;
  right: 0;
  width: 60px;
  height: 100%;
  pointer-events: none;
  opacity: 0;
  background: linear-gradient(to left, hsl(var(--color-bg) / 0.4), transparent);
  transition: opacity 0.15s;
  border-radius: 0 0.75rem 0.75rem 0;
}
.reader-shadow.visible {
  opacity: 1;
}

/* ─── Bottom nav ─── */
.reader-nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  padding: 0.25rem 0;
  gap: 1rem;
}
.reader-nav-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 2.5rem; height: 2.5rem;
  border-radius: 50%;
  background: transparent;
  color: hsl(var(--color-text-muted));
  border: none;
  cursor: pointer;
  transition: all 0.3s;
  flex-shrink: 0;
}
.reader-nav-btn:hover {
  background: hsl(0 0% 100% / 0.06);
  color: white;
}
.reader-nav-btn--hidden {
  opacity: 0;
  pointer-events: none;
}

/* Song disc in nav */
.reader-song {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  min-width: 0;
}
.reader-disc {
  position: relative;
  width: 2.25rem; height: 2.25rem;
  border-radius: 50%;
  overflow: hidden;
  flex-shrink: 0;
  border: 2px solid hsl(var(--color-surface-light));
  box-shadow: 0 0 10px hsl(var(--scene-h1) 50% 40% / 0.15);
}
.reader-disc.spinning {
  animation: spin-slow 4s linear infinite;
}
.reader-disc-img {
  width: 200%; height: 100%;
  object-fit: cover;
  transform: translateX(-50%);
}
.reader-disc-hole {
  position: absolute;
  top: 50%; left: 50%;
  transform: translate(-50%, -50%);
  width: 0.5rem; height: 0.5rem;
  border-radius: 50%;
  background: hsl(var(--color-bg));
  border: 1.5px solid hsl(var(--color-surface-light));
}
.reader-song-name {
  font-size: 0.75rem;
  color: hsl(var(--color-text-muted));
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ═══════════════════════════════════════
   OVERLAY TRANSITIONS
   ═══════════════════════════════════════ */
.immersive-enter-active {
  animation: bp-enter 0.55s cubic-bezier(0.16, 1, 0.3, 1) both;
}
.immersive-leave-active {
  animation: bp-enter 0.35s cubic-bezier(0.7, 0, 0.84, 0) reverse both;
}
@keyframes bp-enter {
  from { opacity: 0; transform: scale(1.02); }
  to { opacity: 1; transform: scale(1); }
}

/* ─── MOBILE ─── */
@media (max-width: 768px) {
  .bp-reader { padding: 0 0.5rem; }
  .reader-columns { padding: 1.25rem 1rem; }
  .bp-orb { filter: blur(80px); opacity: 0.7; }
  .cover-frame { width: min(160px, 40vw); margin-bottom: 1.5rem; }
}
</style>
