Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cf3a3ad910 | ||
|
|
8660c675b6 |
@@ -170,6 +170,21 @@ class AudioStore {
|
||||
return this.status === 'ready' || this.status === 'generating' || this.status === 'loading';
|
||||
}
|
||||
|
||||
/**
|
||||
* When true the persistent mini-bar in +layout.svelte is hidden.
|
||||
* Set by the chapter reader page when playerStyle is 'float' or 'minimal'
|
||||
* so the in-page player is the sole control surface.
|
||||
* Cleared when leaving the chapter page (page destroy / onDestroy effect).
|
||||
*/
|
||||
suppressMiniBar = $state(false);
|
||||
|
||||
/**
|
||||
* Position of the draggable float overlay (bottom-right anchor offsets).
|
||||
* Stored here (module singleton) so the position survives chapter navigation.
|
||||
* x > 0 = moved left; y > 0 = moved up.
|
||||
*/
|
||||
floatPos = $state({ x: 0, y: 0 });
|
||||
|
||||
/** True when the currently loaded track matches slug+chapter */
|
||||
isCurrentChapter(slug: string, chapter: number): boolean {
|
||||
return this.slug === slug && this.chapter === chapter;
|
||||
|
||||
@@ -945,19 +945,18 @@
|
||||
}
|
||||
|
||||
// ── Float player drag state ──────────────────────────────────────────────
|
||||
/** Position of the floating overlay (bottom-right anchor by default). */
|
||||
let floatPos = $state({ x: 0, y: 0 });
|
||||
// floatPos lives on audioStore (singleton) so position survives chapter navigation.
|
||||
let floatDragging = $state(false);
|
||||
let floatDragStart = $state({ mx: 0, my: 0, ox: 0, oy: 0 });
|
||||
|
||||
function onFloatPointerDown(e: PointerEvent) {
|
||||
floatDragging = true;
|
||||
floatDragStart = { mx: e.clientX, my: e.clientY, ox: floatPos.x, oy: floatPos.y };
|
||||
floatDragStart = { mx: e.clientX, my: e.clientY, ox: audioStore.floatPos.x, oy: audioStore.floatPos.y };
|
||||
(e.currentTarget as HTMLElement).setPointerCapture(e.pointerId);
|
||||
}
|
||||
function onFloatPointerMove(e: PointerEvent) {
|
||||
if (!floatDragging) return;
|
||||
floatPos = {
|
||||
audioStore.floatPos = {
|
||||
x: floatDragStart.ox + (e.clientX - floatDragStart.mx),
|
||||
y: floatDragStart.oy + (e.clientY - floatDragStart.my)
|
||||
};
|
||||
@@ -1459,8 +1458,8 @@
|
||||
<div
|
||||
class="fixed z-[55] select-none"
|
||||
style="
|
||||
bottom: calc(4.5rem + {-floatPos.y}px);
|
||||
right: calc(1rem + {-floatPos.x}px);
|
||||
bottom: calc(1rem + {-audioStore.floatPos.y}px);
|
||||
right: calc(1rem + {-audioStore.floatPos.x}px);
|
||||
touch-action: none;
|
||||
"
|
||||
>
|
||||
|
||||
@@ -512,7 +512,7 @@
|
||||
style="display:none"
|
||||
></audio>
|
||||
|
||||
<div class="min-h-screen flex flex-col" class:pb-24={audioStore.active}>
|
||||
<div class="min-h-screen flex flex-col" class:pb-24={audioStore.active && !audioStore.suppressMiniBar}>
|
||||
<!-- Navigation progress bar — shown while SSR is running for any page transition -->
|
||||
{#if navigating}
|
||||
<div class="fixed top-0 left-0 right-0 z-[100] h-1 bg-(--color-surface-2)">
|
||||
@@ -745,14 +745,24 @@
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<!-- Click-outside overlay for dropdowns -->
|
||||
{#if langMenuOpen || userMenuOpen}
|
||||
<div
|
||||
class="fixed inset-0 z-40"
|
||||
onpointerdown={() => { langMenuOpen = false; userMenuOpen = false; }}
|
||||
aria-hidden="true"
|
||||
></div>
|
||||
{/if}
|
||||
<!-- Click-outside overlay for dropdowns -->
|
||||
{#if langMenuOpen || userMenuOpen}
|
||||
<div
|
||||
class="fixed inset-0 z-40"
|
||||
onpointerdown={() => { langMenuOpen = false; userMenuOpen = false; }}
|
||||
aria-hidden="true"
|
||||
></div>
|
||||
{/if}
|
||||
|
||||
<!-- Backdrop for mobile hamburger menu -->
|
||||
{#if menuOpen}
|
||||
<div
|
||||
class="fixed inset-0 z-40 sm:hidden"
|
||||
style="background: rgba(0,0,0,0.6); backdrop-filter: blur(4px);"
|
||||
onpointerdown={() => { menuOpen = false; }}
|
||||
aria-hidden="true"
|
||||
></div>
|
||||
{/if}
|
||||
{:else}
|
||||
<div class="ml-auto">
|
||||
<a
|
||||
@@ -959,7 +969,7 @@
|
||||
</div>
|
||||
|
||||
<!-- ── Persistent mini-player bar ─────────────────────────────────────────── -->
|
||||
{#if audioStore.active}
|
||||
{#if audioStore.active && !audioStore.suppressMiniBar}
|
||||
<div class="fixed bottom-0 left-0 right-0 z-50 bg-(--color-surface) border-t border-(--color-border) shadow-2xl">
|
||||
|
||||
<!-- Generation progress bar (sits at very top of the bar) -->
|
||||
|
||||
@@ -100,6 +100,33 @@
|
||||
document.documentElement.style.setProperty('--reading-max-width', READ_WIDTHS[layout.readWidth]);
|
||||
});
|
||||
|
||||
// ── Suppress mini-bar for float / minimal player styles ──────────────────────
|
||||
// The in-page player is the sole control surface for these styles; the layout
|
||||
// mini-bar would be a duplicate. Clear on page destroy so the mini-bar returns
|
||||
// on other pages (library, catalogue, etc.) where audio may still be playing.
|
||||
$effect(() => {
|
||||
audioStore.suppressMiniBar = layout.playerStyle === 'float' || layout.playerStyle === 'minimal';
|
||||
return () => { audioStore.suppressMiniBar = false; };
|
||||
});
|
||||
|
||||
// ── Persist float overlay position across reloads ─────────────────────────────
|
||||
const FLOAT_POS_KEY = 'reader_float_pos_v1';
|
||||
if (browser) {
|
||||
try {
|
||||
const saved = localStorage.getItem(FLOAT_POS_KEY);
|
||||
if (saved) {
|
||||
const p = JSON.parse(saved) as { x: number; y: number };
|
||||
if (typeof p.x === 'number' && typeof p.y === 'number') audioStore.floatPos = p;
|
||||
}
|
||||
} catch { /* ignore */ }
|
||||
}
|
||||
$effect(() => {
|
||||
const pos = audioStore.floatPos;
|
||||
if (browser && (pos.x !== 0 || pos.y !== 0)) {
|
||||
try { localStorage.setItem(FLOAT_POS_KEY, JSON.stringify(pos)); } catch { /* ignore */ }
|
||||
}
|
||||
});
|
||||
|
||||
// ── Scroll progress bar ──────────────────────────────────────────────────────
|
||||
let scrollProgress = $state(0);
|
||||
|
||||
@@ -797,7 +824,7 @@
|
||||
{#if layout.readMode === 'scroll' && !layout.focusMode}
|
||||
{@const atTop = scrollProgress <= 0.01}
|
||||
{@const atBottom = scrollProgress >= 0.99}
|
||||
<div class="fixed right-4 {audioStore.active ? 'bottom-[5.5rem]' : 'bottom-8'} z-40 flex flex-col gap-2 transition-all">
|
||||
<div class="fixed right-4 {audioStore.active && !audioStore.suppressMiniBar ? 'bottom-[5.5rem]' : 'bottom-8'} z-40 flex flex-col gap-2 transition-all">
|
||||
<!-- Up button / Prev chapter -->
|
||||
{#if atTop && data.prev}
|
||||
<a
|
||||
@@ -856,7 +883,7 @@
|
||||
|
||||
<!-- ── Focus mode floating nav ───────────────────────────────────────────── -->
|
||||
{#if layout.focusMode}
|
||||
<div class="fixed {audioStore.active ? 'bottom-[4.5rem]' : 'bottom-6'} left-1/2 -translate-x-1/2 z-50 max-w-[calc(100vw-2rem)]">
|
||||
<div class="fixed {audioStore.active && !audioStore.suppressMiniBar ? 'bottom-[4.5rem]' : 'bottom-6'} left-1/2 -translate-x-1/2 z-50 max-w-[calc(100vw-2rem)]">
|
||||
<div class="flex items-center divide-x divide-(--color-border) rounded-full bg-(--color-surface-2)/95 backdrop-blur border border-(--color-border) shadow-lg text-xs text-(--color-muted) overflow-hidden">
|
||||
<!-- Prev chapter -->
|
||||
{#if data.prev}
|
||||
|
||||
Reference in New Issue
Block a user