diff --git a/src/lib/components/chrome/SplashScreen.svelte b/src/lib/components/chrome/SplashScreen.svelte
index 31bd9be..fad48ec 100644
--- a/src/lib/components/chrome/SplashScreen.svelte
+++ b/src/lib/components/chrome/SplashScreen.svelte
@@ -1,9 +1,46 @@
+
+
@@ -373,6 +464,20 @@
{/if}
{/if}
+ {#if isDev && mode === 'idle' && devMetrics}
+
@@ -465,4 +570,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/settings/sections/DevToolsSettings.svelte b/src/lib/components/settings/sections/DevToolsSettings.svelte
index 260b50a..9733bbe 100644
--- a/src/lib/components/settings/sections/DevToolsSettings.svelte
+++ b/src/lib/components/settings/sections/DevToolsSettings.svelte
@@ -102,6 +102,7 @@
}
function triggerSplash() {
+ if (appState.idleSplash) return
splashTriggered = true
setTimeout(() => splashTriggered = false, 200)
appState.idleSplash = true
diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte
index 6163767..3db14b5 100644
--- a/src/routes/+layout.svelte
+++ b/src/routes/+layout.svelte
@@ -7,7 +7,7 @@
import { settingsState, loadSettingsIntoState, updateSettings } from '$lib/state/settings.svelte'
import { applyTheme, mountSystemThemeSync } from '$lib/core/theme'
import { platformService } from '$lib/platform-service'
- import { initRpc, setIdle, destroyRpc } from '$lib/core/discord'
+ import * as discord from '$lib/core/discord'
import SplashScreen from '$lib/components/chrome/SplashScreen.svelte'
import AuthGate from '$lib/components/chrome/AuthGate.svelte'
import Sidebar from '$lib/components/chrome/Sidebar.svelte'
@@ -24,7 +24,6 @@
const POLL_MS = 1500
let pollTimer: ReturnType | null = null
- let idleTimer: ReturnType | null = null
let polling = false
async function pollLoop() {
@@ -35,13 +34,12 @@
const isTauri = typeof window !== 'undefined' && '__TAURI_INTERNALS__' in window
- let _splashDismissed = $state(false)
- let bypassed = $state(false)
- let themeEditorOpen = $state(false)
- let themeEditorId = $state(null)
+ let splashDismissed = $state(false)
+ let themeEditorOpen = $state(false)
+ let themeEditorId = $state(null)
const splashVisible = $derived(
- !_splashDismissed ||
+ !splashDismissed ||
appState.status === 'booting' ||
appState.status === 'locked' ||
appState.status === 'error' ||
@@ -49,17 +47,16 @@
)
const ringFull = $derived(appState.status === 'ready')
+ const showApp = $derived(!splashVisible)
- const showApp = $derived(
- !splashVisible && (
- appState.status === 'ready' ||
- bypassed
- )
- )
-
- function onSplashReady() { _splashDismissed = true }
- function onSplashUnlock() { appState.status = 'ready'; _splashDismissed = true }
- function onSplashBypass() { bypassed = true; _splashDismissed = true }
+ function onSplashReady() { splashDismissed = true }
+ function onSplashUnlock() { appState.status = 'ready'; splashDismissed = true }
+ function onSplashBypass() {
+ import('$lib/state/boot.svelte').then(({ bypassBoot }) => {
+ bypassBoot(appState.authMode ?? 'NONE', appState.authUser ?? '', appState.authPass ?? '')
+ })
+ splashDismissed = true
+ }
const isReaderRoute = $derived($page.url.pathname.startsWith('/reader'))
const readerContainerized = $derived(settingsState.settings.readerContainerized ?? false)
@@ -108,24 +105,19 @@
isTauri && settingsState.settings.autoStartServer ? 2000 : 100,
)
- let discordInitialized = false
if (settingsState.settings.discordRpc) {
- await initRpc()
- await setIdle()
- discordInitialized = true
+ await discord.initRpc()
+ await discord.setIdle()
}
polling = true
pollLoop()
- resetIdleTimer()
-
return () => {
polling = false
if (pollTimer !== null) { clearTimeout(pollTimer); pollTimer = null }
- if (idleTimer !== null) { clearTimeout(idleTimer); idleTimer = null }
- if (discordInitialized) destroyRpc().catch(() => {})
- platformService.destroy().catch(() => {})
+ discord.destroyRpc()
+ platformService.destroy()
}
})
@@ -147,49 +139,22 @@
})
$effect(() => {
- if (appState.status === 'booting') _splashDismissed = false
+ if (appState.status === 'booting') splashDismissed = false
})
- $effect(() => {
- if (appState.status === 'ready') resetIdleTimer()
- })
+ let idleSplashLocked = false
- $effect(() => {
- if (appState.idleSplash && settingsState.settings.discordRpc) setIdle().catch(() => {})
- })
-
- $effect(() => {
- if (appState.status !== 'ready') return
- // capture phase so events from any component — including modals — reset the timer
- const onActivity = () => resetIdleTimer()
- document.addEventListener('mousemove', onActivity, true)
- document.addEventListener('keydown', onActivity, true)
- document.addEventListener('touchstart', onActivity, true)
- // passive:true tells the browser it can render scroll frames without waiting for the handler
- document.addEventListener('touchmove', onActivity, { capture: true, passive: true })
- document.addEventListener('wheel', onActivity, { capture: true, passive: true })
- document.addEventListener('click', onActivity, true)
- return () => {
- document.removeEventListener('mousemove', onActivity, true)
- document.removeEventListener('keydown', onActivity, true)
- document.removeEventListener('touchstart', onActivity, true)
- document.removeEventListener('touchmove', onActivity, { capture: true })
- document.removeEventListener('wheel', onActivity, { capture: true })
- document.removeEventListener('click', onActivity, true)
- }
- })
-
- function resetIdleTimer() {
- if (idleTimer) { clearTimeout(idleTimer); idleTimer = null }
- // 0 means "Never" — skip the timer entirely
- const mins = settingsState.settings.idleTimeoutMin ?? 5
- if (mins === 0) return
- idleTimer = setTimeout(() => {
- if (appState.status === 'ready') appState.idleSplash = true
- }, mins * 60_000)
+ function showIdleSplash() {
+ if (idleSplashLocked || appState.idleSplash) return
+ appState.idleSplash = true
}
- function onIdleDismiss() { appState.idleSplash = false; resetIdleTimer() }
+ function onIdleDismiss() {
+ if (idleSplashLocked) return
+ idleSplashLocked = true
+ appState.idleSplash = false
+ setTimeout(() => { idleSplashLocked = false }, 400)
+ }
function onSplashRetry() {
import('$lib/state/boot.svelte').then(({ retryBoot }) => {
@@ -219,7 +184,7 @@
{/if}
{#if appState.idleSplash}
-
+
{/if}
{#if showApp}