@@ -449,4 +571,11 @@
.err-btn:hover { border-color:var(--border-strong); color:var(--text-secondary); }
.err-btn--primary { border-color:var(--accent-dim); color:var(--accent-fg); background:var(--accent-muted); }
.err-btn--primary:hover { border-color:var(--accent); color:var(--accent-bright); }
+
+ .dev-overlay { position:absolute; top:12px; left:12px; z-index:10; background:rgba(0,0,0,0.72); border:1px solid rgba(255,255,255,0.10); border-radius:6px; padding:8px 10px; pointer-events:none; backdrop-filter:blur(6px); }
+ .dev-title { display:block; font-family:var(--font-ui); font-size:9px; letter-spacing:0.14em; text-transform:uppercase; color:var(--accent); margin-bottom:6px; }
+ .dev-grid { display:grid; grid-template-columns:auto auto; column-gap:12px; row-gap:2px; }
+ .dev-k { font-family:var(--font-ui); font-size:10px; color:var(--text-faint); white-space:nowrap; }
+ .dev-v { font-family:var(--font-ui); font-size:10px; color:var(--text-secondary); text-align:right; white-space:nowrap; }
+ .dev-warn { color:#f87171; }
\ No newline at end of file
diff --git a/src/lib/components/reader/Reader.svelte b/src/lib/components/reader/Reader.svelte
index 326654f..c697ea8 100644
--- a/src/lib/components/reader/Reader.svelte
+++ b/src/lib/components/reader/Reader.svelte
@@ -2,7 +2,7 @@
import { onMount, untrack, tick } from "svelte";
import { readerState, PAGE_STYLES } from "$lib/state/reader.svelte";
import { settingsState, updateSettings } from "$lib/state/settings.svelte";
- import { app } from "$lib/state/app.svelte";
+ import { app, appState } from "$lib/state/app.svelte";
import { DEFAULT_KEYBINDS } from "$lib/core/keybinds/defaultBinds";
import { fetchPages, resolveUrl, preloadImage, measureAspect, buildPageGroups } from "$lib/components/reader/lib/pageLoader";
import { setupScrollTracking, appendNextChapter } from "$lib/components/reader/lib/scrollHandler";
@@ -13,6 +13,8 @@
import { loadChapter, scheduleResumeDismiss } from "$lib/components/reader/lib/chapterLoader";
import { historyState } from "$lib/state/history.svelte";
import { getAdapter } from "$lib/request-manager";
+ import { setReading, clearReading } from "$lib/core/discord";
+ import { revokeBlobUrl } from "$lib/core/cache/imageCache";
import type { ReaderSettings } from "$lib/state/reader.svelte";
import ReaderControls from "$lib/components/reader/ReaderControls.svelte";
import PageView from "$lib/components/reader/PageView.svelte";
@@ -216,10 +218,20 @@
? () => goForward(style, adjacent, lastPage, maybeMarkCurrentRead, startAtLast)
: () => goBack(style, adjacent, startAtLast));
+ // clear Discord presence and free page blob textures before closing
+ function handleCloseReader() {
+ clearReading().catch(() => {});
+ for (const url of readerState.pageUrls) revokeBlobUrl(url);
+ for (const strip of readerState.stripChapters) {
+ for (const url of strip.urls) revokeBlobUrl(url);
+ }
+ readerState.closeReader();
+ }
+
const onKey = createReaderKeyHandler({
goNext: () => goNext(),
goPrev: () => goPrev(),
- closeReader: () => readerState.closeReader(),
+ closeReader: () => handleCloseReader(),
goToPage: (p) => jumpToPage(p, style, lastPage, containerEl),
lastPage: () => lastPage,
adjustZoom: (d) => { captureZoomAnchor(containerEl, style, zoomAnchor); applySettings({ readerZoom: clampZoom(zoom + d) }); restoreZoomAnchor(containerEl, zoomAnchor); },
@@ -313,6 +325,16 @@
}
});
+ // Separate from chapter load: also re-fires when idle splash dismisses so presence is restored.
+ $effect(() => {
+ const ch = readerState.activeChapter;
+ const manga = readerState.activeManga;
+ const idle = appState.idleSplash;
+ if (ch && manga && !idle) {
+ untrack(() => setReading(manga, ch).catch(() => {}));
+ }
+ });
+
$effect(() => {
const page = readerState.pageNumber;
const chId = style === "longstrip"
diff --git a/src/lib/components/reader/lib/chapterLoader.ts b/src/lib/components/reader/lib/chapterLoader.ts
index 16c6f62..5634eff 100644
--- a/src/lib/components/reader/lib/chapterLoader.ts
+++ b/src/lib/components/reader/lib/chapterLoader.ts
@@ -1,13 +1,15 @@
-import { readerState } from "$lib/state/reader.svelte";
-import { fetchPages } from "./pageLoader";
-import { cancelQueuedFetches } from "$lib/core/cache/imageCache";
-import { clearResolvedUrlCache } from "$lib/core/cache/pageCache";
+import { readerState } from "$lib/state/reader.svelte";
+import { fetchPages } from "./pageLoader";
+import { cancelQueuedFetches, revokeBlobUrl } from "$lib/core/cache/imageCache";
+import { clearResolvedUrlCache, clearPageCache } from "$lib/core/cache/pageCache";
export function scheduleResumeDismiss() {
setTimeout(() => { readerState.resumeFading = true; }, 1500);
setTimeout(() => { readerState.resumeVisible = false; readerState.resumeFading = false; }, 2500);
}
+let prefetchedChapterId: number | null = null;
+
export async function loadChapter(
id: number,
useBlob: boolean,
@@ -21,7 +23,19 @@ export async function loadChapter(
abortCtrl.current = ctrl;
cancelQueuedFetches();
- if (useBlob) clearResolvedUrlCache();
+ if (useBlob) {
+ clearResolvedUrlCache();
+ for (const url of readerState.pageUrls) revokeBlobUrl(url);
+ for (const strip of readerState.stripChapters) {
+ for (const url of strip.urls) revokeBlobUrl(url);
+ }
+ if (prefetchedChapterId !== null && prefetchedChapterId !== id) {
+ const prefetchedUrls = await fetchPages(prefetchedChapterId, false).catch(() => [] as string[]);
+ for (const url of prefetchedUrls) revokeBlobUrl(url);
+ clearPageCache(prefetchedChapterId);
+ }
+ prefetchedChapterId = null;
+ }
startAtLastPage.current = false;
markedRead.clear();
@@ -44,10 +58,13 @@ export async function loadChapter(
else if (resumeTo > 1) readerState.pageNumber = Math.min(resumeTo, urls.length || resumeTo);
readerState.pageReady = true;
readerState.loading = false;
- if (adjacent.next) fetchPages(adjacent.next.id, useBlob, ctrl.signal).catch(() => {});
+ if (adjacent.next) {
+ prefetchedChapterId = adjacent.next.id;
+ fetchPages(adjacent.next.id, useBlob, ctrl.signal).catch(() => {});
+ }
} catch (e: unknown) {
if (ctrl.signal.aborted) return;
readerState.error = e instanceof Error ? e.message : String(e);
readerState.loading = false;
}
-}
+}
\ No newline at end of file
diff --git a/src/lib/components/series/SeriesHeader.svelte b/src/lib/components/series/SeriesHeader.svelte
index 1c9f474..ee1b8ce 100644
--- a/src/lib/components/series/SeriesHeader.svelte
+++ b/src/lib/components/series/SeriesHeader.svelte
@@ -93,7 +93,7 @@
diff --git a/src/lib/components/settings/Settings.css b/src/lib/components/settings/Settings.css
index b1ac984..1f6e904 100644
--- a/src/lib/components/settings/Settings.css
+++ b/src/lib/components/settings/Settings.css
@@ -10,9 +10,7 @@
/* ── Backdrop & Modal Shell ───────────────────────────────────────── */
.s-backdrop {
position: fixed; inset: 0;
- background: rgba(0,0,0,0.6);
- backdrop-filter: blur(8px);
- -webkit-backdrop-filter: blur(8px);
+
z-index: var(--z-settings);
display: flex; align-items: center; justify-content: center;
animation: s-fade-in 0.14s ease both;
@@ -29,10 +27,7 @@
overflow: visible;
position: relative;
animation: s-scale-in 0.2s cubic-bezier(0.16,1,0.3,1) both;
- box-shadow:
- 0 0 0 1px rgba(255,255,255,0.04) inset,
- 0 24px 80px rgba(0,0,0,0.7),
- 0 8px 24px rgba(0,0,0,0.4);
+ box-shadow: 0 0 0 1px var(--border-dim), 0 24px 64px rgba(0,0,0,0.6);
}
@@ -46,7 +41,7 @@
display: flex;
flex-direction: column;
gap: 1px;
- overflow-y: auto;
+ overflow-y: hidden;
border-radius: var(--radius-2xl) 0 0 var(--radius-2xl);
}
diff --git a/src/lib/components/settings/Settings.svelte b/src/lib/components/settings/Settings.svelte
index 8e618a7..edc1851 100644
--- a/src/lib/components/settings/Settings.svelte
+++ b/src/lib/components/settings/Settings.svelte
@@ -19,6 +19,7 @@
import ContentSettings from './sections/ContentSettings.svelte'
import AboutSettings from './sections/AboutSettings.svelte'
import DevtoolsSettings from './sections/DevToolsSettings.svelte'
+ import ModalBlur from '$lib/components/shared/ui/ModalBlur.svelte'
interface Props { onclose?: () => void; onOpenThemeEditor?: (id?: string | null) => void }
let { onclose, onOpenThemeEditor }: Props = $props()
@@ -111,6 +112,7 @@
})
+
{ if (e.target === e.currentTarget) close() }}
onkeydown={(e) => { if (e.key === 'Escape') { e.stopPropagation(); close() } }}>
diff --git a/src/lib/components/settings/sections/DevToolsSettings.svelte b/src/lib/components/settings/sections/DevToolsSettings.svelte
index 260b50a..b13b4c8 100644
--- a/src/lib/components/settings/sections/DevToolsSettings.svelte
+++ b/src/lib/components/settings/sections/DevToolsSettings.svelte
@@ -102,9 +102,10 @@
}
function triggerSplash() {
+ if (appState.devSplash) return
splashTriggered = true
setTimeout(() => splashTriggered = false, 200)
- appState.idleSplash = true
+ appState.devSplash = true
}
async function testWindowsHello() {
diff --git a/src/lib/components/shared/manga/MangaPreview.svelte b/src/lib/components/shared/manga/MangaPreview.svelte
index 7687228..6ca25f2 100644
--- a/src/lib/components/shared/manga/MangaPreview.svelte
+++ b/src/lib/components/shared/manga/MangaPreview.svelte
@@ -20,6 +20,7 @@
} from "$lib/state/series.svelte";
import { app } from "$lib/state/app.svelte";
import type { Manga, Chapter, Category } from "$lib/types";
+ import ModalBlur from '$lib/components/shared/ui/ModalBlur.svelte'
let manga: Manga | null = $state(null);
@@ -353,6 +354,7 @@
{#if seriesState.previewManga}
+
.backdrop {
position: fixed; inset: 0;
- background: rgba(0,0,0,0.72);
z-index: var(--z-settings);
display: flex; align-items: center; justify-content: center;
- backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px);
animation: fadeIn 0.12s ease both;
}
.modal {
diff --git a/src/lib/components/shared/ui/ModalBlur.svelte b/src/lib/components/shared/ui/ModalBlur.svelte
new file mode 100644
index 0000000..eca8d46
--- /dev/null
+++ b/src/lib/components/shared/ui/ModalBlur.svelte
@@ -0,0 +1,40 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/src/lib/components/tracking/TrackingPanel.svelte b/src/lib/components/tracking/TrackingPanel.svelte
index 41365c4..8662a31 100644
--- a/src/lib/components/tracking/TrackingPanel.svelte
+++ b/src/lib/components/tracking/TrackingPanel.svelte
@@ -10,6 +10,7 @@
import { markManyRead } from "$lib/request-manager/chapters";
import type { Tracker, TrackRecord, TrackSearch } from "$lib/types";
import type { Chapter } from "$lib/types";
+ import ModalBlur from '$lib/components/shared/ui/ModalBlur.svelte'
let { mangaId, mangaTitle, onClose }: {
mangaId: number;
@@ -250,6 +251,7 @@
}
}} />
+
{ if (e.target === e.currentTarget) onClose(); }}>
@@ -497,6 +499,7 @@
{#if confirmUnbindId !== null}
{@const rec = records.find(r => r.id === confirmUnbindId)}
{@const trk = rec ? trackerFor(rec.trackerId) : null}
+
confirmUnbindId = null}
onkeydown={(e) => { if (e.key === "Escape") confirmUnbindId = null; }}>
@@ -515,10 +518,9 @@