diff --git a/src/lib/components/chrome/SplashScreen.svelte b/src/lib/components/chrome/SplashScreen.svelte index 9a4c06c..05e5415 100644 --- a/src/lib/components/chrome/SplashScreen.svelte +++ b/src/lib/components/chrome/SplashScreen.svelte @@ -1,9 +1,46 @@ + + @@ -357,6 +465,20 @@ {/if} {/if} + {#if isDev && mode === 'idle' && devMetrics && showDevOverlay} +
+ canvas · idle splash +
+ live 1}>{devLiveCount} + total mounts {devMetrics.totalMounts} + stamps {devMetrics.stampCount} + resizes {devMetrics.resizeCount} + uptime {fmtUptime(uptimeSecs)} + last resize {fmtAgo(devMetrics.lastResizeAt)} +
+
+ {/if} + {#if mode === 'idle'}
@@ -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 @@ }) +