fix: chapter list modal full-screen clip + add clear close button
All checks were successful
Release / Test backend (push) Successful in 45s
Release / Check ui (push) Successful in 2m2s
Release / Docker / caddy (push) Successful in 52s
Release / Docker / backend (push) Successful in 3m31s
Release / Docker / runner (push) Successful in 3m33s
Release / Upload source maps (push) Successful in 1m37s
Release / Docker / ui (push) Successful in 5m16s
Release / Gitea Release (push) Successful in 44s
All checks were successful
Release / Test backend (push) Successful in 45s
Release / Check ui (push) Successful in 2m2s
Release / Docker / caddy (push) Successful in 52s
Release / Docker / backend (push) Successful in 3m31s
Release / Docker / runner (push) Successful in 3m33s
Release / Upload source maps (push) Successful in 1m37s
Release / Docker / ui (push) Successful in 5m16s
Release / Gitea Release (push) Successful in 44s
Root cause: the chapter picker overlay (fixed inset-0) was rendered as a
descendant of the AudioPlayer's wrapping div, which is itself inside a
'rounded-b-lg overflow-hidden' container on the chapter page. Chrome clips
position:fixed descendants when a parent has overflow:hidden + border-radius,
so the overlay was constrained to the parent's bounding box instead of
covering the full viewport.
Fix: moved the {#if showChapterPanel} block from inside the standard player
div to a top-level sibling at the end of the component template. Svelte
components support multiple root nodes, so the overlay is now a sibling of
all player containers — no ancestor overflow-hidden can clip it.
Also replaced the subtle chevron-down close button with a clear X (×) button
in the top-right of the header, making it obvious how to dismiss the panel.
This commit is contained in:
@@ -1166,78 +1166,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<!-- ── Chapter picker overlay ────────────────────────────────────────── -->
|
|
||||||
{#if showChapterPanel && audioStore.chapters.length > 0}
|
|
||||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
||||||
<div
|
|
||||||
class="fixed inset-0 z-[60] flex flex-col"
|
|
||||||
style="background: var(--color-surface);"
|
|
||||||
>
|
|
||||||
<!-- Header -->
|
|
||||||
<div class="flex items-center gap-3 px-4 py-3 border-b border-(--color-border) shrink-0">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onclick={() => { showChapterPanel = false; chapterSearch = ''; }}
|
|
||||||
class="p-2 rounded-full text-(--color-muted) hover:text-(--color-text) hover:bg-(--color-surface-2) transition-colors"
|
|
||||||
aria-label="Close chapter picker"
|
|
||||||
>
|
|
||||||
<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"/>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
<span class="text-xs font-semibold text-(--color-muted) uppercase tracking-wider flex-1">Chapters</span>
|
|
||||||
</div>
|
|
||||||
<!-- Search -->
|
|
||||||
<div class="px-4 py-3 shrink-0 border-b border-(--color-border)">
|
|
||||||
<div class="relative">
|
|
||||||
<svg class="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-(--color-muted)" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/>
|
|
||||||
</svg>
|
|
||||||
<input
|
|
||||||
type="search"
|
|
||||||
placeholder="Search chapters…"
|
|
||||||
bind:value={chapterSearch}
|
|
||||||
class="w-full pl-9 pr-4 py-2 text-sm bg-(--color-surface-2) border border-(--color-border) rounded-lg text-(--color-text) placeholder:text-(--color-muted) focus:outline-none focus:border-(--color-brand) transition-colors"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- Chapter list -->
|
|
||||||
<div class="flex-1 overflow-y-auto">
|
|
||||||
{#each filteredChapters as ch (ch.number)}
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onclick={() => playChapter(ch.number)}
|
|
||||||
class={cn(
|
|
||||||
'w-full flex items-center gap-3 px-4 py-3 border-b border-(--color-border)/40 transition-colors text-left',
|
|
||||||
ch.number === chapter ? 'bg-(--color-brand)/8' : 'hover:bg-(--color-surface-2)'
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<!-- Chapter number badge (mirrors voice radio indicator) -->
|
|
||||||
<span class={cn(
|
|
||||||
'w-8 h-8 shrink-0 rounded-full border-2 flex items-center justify-center tabular-nums text-xs font-semibold transition-colors',
|
|
||||||
ch.number === chapter
|
|
||||||
? 'border-(--color-brand) bg-(--color-brand) text-(--color-surface)'
|
|
||||||
: 'border-(--color-border) text-(--color-muted)'
|
|
||||||
)}>{ch.number}</span>
|
|
||||||
<!-- Title -->
|
|
||||||
<span class={cn(
|
|
||||||
'flex-1 text-sm truncate',
|
|
||||||
ch.number === chapter ? 'font-semibold text-(--color-brand)' : 'text-(--color-text)'
|
|
||||||
)}>{ch.title || `Chapter ${ch.number}`}</span>
|
|
||||||
<!-- Now-playing indicator -->
|
|
||||||
{#if ch.number === chapter}
|
|
||||||
<svg class="w-4 h-4 shrink-0 text-(--color-brand)" fill="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path d="M8 5v14l11-7z"/>
|
|
||||||
</svg>
|
|
||||||
{/if}
|
|
||||||
</button>
|
|
||||||
{/each}
|
|
||||||
{#if filteredChapters.length === 0}
|
|
||||||
<p class="px-4 py-8 text-sm text-(--color-muted) text-center">No chapters match "{chapterSearch}"</p>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if audioStore.isCurrentChapter(slug, chapter)}
|
{#if audioStore.isCurrentChapter(slug, chapter)}
|
||||||
<!-- ── This chapter is the active one ── -->
|
<!-- ── This chapter is the active one ── -->
|
||||||
@@ -1318,3 +1247,79 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
<!-- ── Chapter picker overlay ─────────────────────────────────────────────────
|
||||||
|
Rendered as a top-level sibling (outside all player containers) so that
|
||||||
|
the fixed inset-0 positioning is never clipped by overflow-hidden or
|
||||||
|
border-radius on any ancestor wrapping the AudioPlayer component. -->
|
||||||
|
{#if showChapterPanel && audioStore.chapters.length > 0}
|
||||||
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||||
|
<div
|
||||||
|
class="fixed inset-0 z-[60] flex flex-col"
|
||||||
|
style="background: var(--color-surface);"
|
||||||
|
>
|
||||||
|
<!-- Header -->
|
||||||
|
<div class="flex items-center gap-3 px-4 py-3 border-b border-(--color-border) shrink-0">
|
||||||
|
<span class="text-sm font-semibold text-(--color-text) flex-1">Chapters</span>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onclick={() => { showChapterPanel = false; chapterSearch = ''; }}
|
||||||
|
class="w-9 h-9 flex items-center justify-center rounded-full text-(--color-muted) hover:text-(--color-text) hover:bg-(--color-surface-2) transition-colors"
|
||||||
|
aria-label="Close chapter picker"
|
||||||
|
>
|
||||||
|
<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="M6 18L18 6M6 6l12 12"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<!-- Search -->
|
||||||
|
<div class="px-4 py-3 shrink-0 border-b border-(--color-border)">
|
||||||
|
<div class="relative">
|
||||||
|
<svg class="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-(--color-muted)" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/>
|
||||||
|
</svg>
|
||||||
|
<input
|
||||||
|
type="search"
|
||||||
|
placeholder="Search chapters…"
|
||||||
|
bind:value={chapterSearch}
|
||||||
|
class="w-full pl-9 pr-4 py-2 text-sm bg-(--color-surface-2) border border-(--color-border) rounded-lg text-(--color-text) placeholder:text-(--color-muted) focus:outline-none focus:border-(--color-brand) transition-colors"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Chapter list -->
|
||||||
|
<div class="flex-1 overflow-y-auto">
|
||||||
|
{#each filteredChapters as ch (ch.number)}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onclick={() => playChapter(ch.number)}
|
||||||
|
class={cn(
|
||||||
|
'w-full flex items-center gap-3 px-4 py-3 border-b border-(--color-border)/40 transition-colors text-left',
|
||||||
|
ch.number === chapter ? 'bg-(--color-brand)/8' : 'hover:bg-(--color-surface-2)'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<!-- Chapter number badge -->
|
||||||
|
<span class={cn(
|
||||||
|
'w-8 h-8 shrink-0 rounded-full border-2 flex items-center justify-center tabular-nums text-xs font-semibold transition-colors',
|
||||||
|
ch.number === chapter
|
||||||
|
? 'border-(--color-brand) bg-(--color-brand) text-(--color-surface)'
|
||||||
|
: 'border-(--color-border) text-(--color-muted)'
|
||||||
|
)}>{ch.number}</span>
|
||||||
|
<!-- Title -->
|
||||||
|
<span class={cn(
|
||||||
|
'flex-1 text-sm truncate',
|
||||||
|
ch.number === chapter ? 'font-semibold text-(--color-brand)' : 'text-(--color-text)'
|
||||||
|
)}>{ch.title || `Chapter ${ch.number}`}</span>
|
||||||
|
<!-- Now-playing indicator -->
|
||||||
|
{#if ch.number === chapter}
|
||||||
|
<svg class="w-4 h-4 shrink-0 text-(--color-brand)" fill="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path d="M8 5v14l11-7z"/>
|
||||||
|
</svg>
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
|
{/each}
|
||||||
|
{#if filteredChapters.length === 0}
|
||||||
|
<p class="px-4 py-8 text-sm text-(--color-muted) text-center">No chapters match "{chapterSearch}"</p>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|||||||
Reference in New Issue
Block a user