feat: portrait cover card + blurred bg in ListeningMode
Some checks failed
Release / Check ui (push) Has been cancelled
Release / Docker / backend (push) Has been cancelled
Release / Docker / runner (push) Has been cancelled
Release / Upload source maps (push) Has been cancelled
Release / Docker / ui (push) Has been cancelled
Release / Docker / caddy (push) Has been cancelled
Release / Gitea Release (push) Has been cancelled
Release / Test backend (push) Has been cancelled

Replace the full-bleed landscape hero with the Apple Music / Spotify
layout pattern:
- Full-screen blurred+darkened cover as atmospheric background layer
- Centered portrait 2/3 cover card (38svh tall, rounded-2xl, shadow-2xl)
- Track info (chapter label, title, book name) moved below cover card
- Radial vignette overlay for depth
- No dead empty space between art and controls
- Header bar and controls area lifted to z-index 2 above the bg layers
This commit is contained in:
root
2026-04-06 17:22:31 +05:00
parent 1cdc7275f8
commit bb61a4654a

View File

@@ -262,90 +262,101 @@
ontouchend={onTouchEnd} ontouchend={onTouchEnd}
> >
<!-- ── Full-bleed cover hero (top ~50% of screen) ────────────────────── --> <!-- ── Blurred background (full-screen atmospheric layer) ───────────── -->
<div class="relative w-full shrink-0" style="height: 52svh; min-height: 220px; max-height: 380px;"> {#if audioStore.cover}
{#if audioStore.cover} <img
<!-- Full-bleed cover image --> src={audioStore.cover}
<img alt=""
src={audioStore.cover}
alt=""
class="absolute inset-0 w-full h-full object-cover"
/>
{:else}
<!-- Fallback when no cover -->
<div class="absolute inset-0 flex items-center justify-center bg-(--color-surface-2)">
<svg class="w-20 h-20 text-(--color-muted)/30" fill="currentColor" viewBox="0 0 24 24">
<path d="M18 2H6c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-2 14H8v-2h8v2zm0-4H8v-2h8v2zm0-4H8V6h8v2z"/>
</svg>
</div>
{/if}
<!-- Top gradient: surface → transparent (for header legibility) -->
<div
class="absolute inset-x-0 top-0 h-28 pointer-events-none"
style="background: linear-gradient(to bottom, var(--color-surface) 0%, transparent 100%);"
aria-hidden="true" aria-hidden="true"
></div> class="absolute inset-0 w-full h-full object-cover pointer-events-none select-none"
<!-- Bottom gradient: transparent → surface (seamless blend into controls) --> style="filter: blur(40px) brightness(0.25) saturate(1.4); transform: scale(1.15); z-index: 0;"
<div />
class="absolute inset-x-0 bottom-0 h-40 pointer-events-none" {:else}
style="background: linear-gradient(to top, var(--color-surface) 0%, transparent 100%);" <div class="absolute inset-0 pointer-events-none" style="background: var(--color-surface-2); z-index: 0;"></div>
aria-hidden="true" {/if}
></div> <!-- Subtle vignette overlay for depth -->
<div
class="absolute inset-0 pointer-events-none"
style="background: radial-gradient(ellipse at center, transparent 40%, rgba(0,0,0,0.55) 100%); z-index: 1;"
aria-hidden="true"
></div>
<!-- Header bar (sits over the top gradient) --> <!-- ── Header bar ─────────────────────────────────────────────────────── -->
<div class="relative z-10 flex items-center justify-between px-4 pt-3 pb-2"> <div class="relative flex items-center justify-between px-4 pt-3 pb-2 shrink-0" style="z-index: 2;">
<button <button
type="button" type="button"
onclick={onclose} onclick={onclose}
class="p-2 rounded-full text-(--color-text)/70 hover:text-(--color-text) hover:bg-black/20 transition-colors" class="p-2 rounded-full text-(--color-text)/70 hover:text-(--color-text) hover:bg-white/10 transition-colors"
aria-label="Close listening mode" aria-label="Close listening mode"
> >
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
</svg> </svg>
</button> </button>
<span class="text-xs font-semibold text-(--color-text)/60 uppercase tracking-wider">Now Playing</span> <span class="text-xs font-semibold text-(--color-text)/60 uppercase tracking-wider">Now Playing</span>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<!-- Chapters button --> <!-- Chapters button -->
{#if audioStore.chapters.length > 0} {#if audioStore.chapters.length > 0}
<button
type="button"
onclick={() => { showChapterModal = !showChapterModal; showVoiceModal = false; voiceSearch = ''; }}
class={cn(
'flex items-center gap-1.5 px-3 py-1.5 rounded-full text-xs font-medium border transition-colors',
showChapterModal
? 'border-(--color-brand) bg-(--color-brand)/15 text-(--color-brand)'
: 'border-white/20 bg-black/25 text-(--color-text)/70 hover:text-(--color-text) backdrop-blur-sm'
)}
aria-label="Browse chapters"
>
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 10h16M4 14h10"/>
</svg>
Chapters
</button>
{/if}
<!-- Voice selector button -->
<button <button
type="button" type="button"
onclick={() => { showVoiceModal = !showVoiceModal; showChapterModal = false; }} onclick={() => { showChapterModal = !showChapterModal; showVoiceModal = false; voiceSearch = ''; }}
class={cn( class={cn(
'flex items-center gap-1.5 px-3 py-1.5 rounded-full text-xs font-medium border transition-colors', 'flex items-center gap-1.5 px-3 py-1.5 rounded-full text-xs font-medium border transition-colors',
showVoiceModal showChapterModal
? 'border-(--color-brand) bg-(--color-brand)/15 text-(--color-brand)' ? 'border-(--color-brand) bg-(--color-brand)/15 text-(--color-brand)'
: 'border-white/20 bg-black/25 text-(--color-text)/70 hover:text-(--color-text) backdrop-blur-sm' : 'border-white/20 bg-black/25 text-(--color-text)/70 hover:text-(--color-text) backdrop-blur-sm'
)} )}
aria-label="Browse chapters"
> >
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11a7 7 0 01-7 7m0 0a7 7 0 01-7-7m7 7v4m0 0H8m4 0h4m-4-8a3 3 0 01-3-3V5a3 3 0 116 0v6a3 3 0 01-3 3z"/> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 10h16M4 14h10"/>
</svg> </svg>
<span class="max-w-[80px] truncate">{voiceLabel(audioStore.voice)}</span> Chapters
</button> </button>
</div> {/if}
<!-- Voice selector button -->
<button
type="button"
onclick={() => { showVoiceModal = !showVoiceModal; showChapterModal = false; }}
class={cn(
'flex items-center gap-1.5 px-3 py-1.5 rounded-full text-xs font-medium border transition-colors',
showVoiceModal
? 'border-(--color-brand) bg-(--color-brand)/15 text-(--color-brand)'
: 'border-white/20 bg-black/25 text-(--color-text)/70 hover:text-(--color-text) backdrop-blur-sm'
)}
>
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11a7 7 0 01-7 7m0 0a7 7 0 01-7-7m7 7v4m0 0H8m4 0h4m-4-8a3 3 0 01-3-3V5a3 3 0 116 0v6a3 3 0 01-3 3z"/>
</svg>
<span class="max-w-[80px] truncate">{voiceLabel(audioStore.voice)}</span>
</button>
</div>
</div>
<!-- ── Portrait cover card + track info ───────────────────────────────── -->
<div class="relative flex flex-col items-center gap-4 px-8 pt-2 pb-4 shrink-0" style="z-index: 2;">
<!-- Cover card -->
<div
class="rounded-2xl overflow-hidden shadow-2xl"
style="height: 38svh; min-height: 180px; max-height: 320px; aspect-ratio: 2/3;"
>
{#if audioStore.cover}
<img
src={audioStore.cover}
alt=""
class="w-full h-full object-cover"
/>
{:else}
<div class="w-full h-full bg-(--color-surface-2) flex items-center justify-center">
<svg class="w-16 h-16 text-(--color-muted)/30" fill="currentColor" viewBox="0 0 24 24">
<path d="M18 2H6c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-2 14H8v-2h8v2zm0-4H8v-2h8v2zm0-4H8V6h8v2z"/>
</svg>
</div>
{/if}
</div> </div>
<!-- Track info (sits over the bottom gradient) --> <!-- Track info -->
<div class="absolute inset-x-0 bottom-0 z-10 px-6 pb-3 text-center"> <div class="text-center w-full">
{#if audioStore.chapter > 0} {#if audioStore.chapter > 0}
<p class="text-[10px] font-bold uppercase tracking-widest text-(--color-brand) mb-0.5"> <p class="text-[10px] font-bold uppercase tracking-widest text-(--color-brand) mb-0.5">
Chapter {audioStore.chapter} Chapter {audioStore.chapter}
@@ -499,7 +510,7 @@
{/if} {/if}
<!-- ── Controls area (bottom half) ───────────────────────────────────── --> <!-- ── Controls area (bottom half) ───────────────────────────────────── -->
<div class="flex-1 flex flex-col justify-end px-6 pb-6 gap-0 shrink-0 overflow-hidden"> <div class="flex-1 flex flex-col justify-end px-6 pb-6 gap-0 shrink-0 overflow-hidden" style="z-index: 2; position: relative;">
<!-- Seek bar --> <!-- Seek bar -->
<div class="shrink-0 mb-1"> <div class="shrink-0 mb-1">