From f41f8a9c224fdd4f8086d717459af4cc0f113d31 Mon Sep 17 00:00:00 2001 From: Zerebos Date: Sat, 23 May 2026 02:30:27 -0400 Subject: [PATCH] Finish phase 2 --- src/lib/core/ui/idle.ts | 49 ++++++++++++++ src/lib/core/ui/zoom.ts | 14 ++++ src/lib/state/app.svelte.ts | 1 + src/lib/ui/chrome/Sidebar.svelte | 35 +++++++--- src/routes/+layout.svelte | 110 ++++++++++++++++++++++++------- 5 files changed, 177 insertions(+), 32 deletions(-) create mode 100644 src/lib/core/ui/idle.ts diff --git a/src/lib/core/ui/idle.ts b/src/lib/core/ui/idle.ts new file mode 100644 index 0000000..394442a --- /dev/null +++ b/src/lib/core/ui/idle.ts @@ -0,0 +1,49 @@ +const IDLE_EVENTS = ['mousemove', 'mousedown', 'keydown', 'touchstart', 'wheel'] as const; + +export function mountIdleDetection( + getTimeoutMinutes: () => number | undefined, + onIdle: () => void, + onActive: () => void, +): () => void { + let timer: ReturnType | null = null; + let idle = false; + + const markActive = () => { + if (!idle) return; + idle = false; + onActive(); + }; + + const resetTimer = () => { + if (timer) clearTimeout(timer); + + const timeoutMinutes = getTimeoutMinutes() ?? 5; + const timeoutMs = Math.max(0, timeoutMinutes) * 60 * 1000; + + if (timeoutMs === 0) { + markActive(); + return; + } + + markActive(); + + timer = setTimeout(() => { + if (idle) return; + idle = true; + onIdle(); + }, timeoutMs); + }; + + IDLE_EVENTS.forEach((eventName) => { + window.addEventListener(eventName, resetTimer, { passive: true }); + }); + + resetTimer(); + + return () => { + if (timer) clearTimeout(timer); + IDLE_EVENTS.forEach((eventName) => { + window.removeEventListener(eventName, resetTimer); + }); + }; +} \ No newline at end of file diff --git a/src/lib/core/ui/zoom.ts b/src/lib/core/ui/zoom.ts index 09f520b..dbc8860 100644 --- a/src/lib/core/ui/zoom.ts +++ b/src/lib/core/ui/zoom.ts @@ -22,6 +22,20 @@ export function zoomDelta(e: KeyboardEvent, current: number): number | null { return null; } +export function mountZoomKey(getCurrent: () => number, onChange: (next: number) => void): () => void { + const handleKey = (event: KeyboardEvent) => { + const nextZoom = zoomDelta(event, getCurrent()); + if (nextZoom === null) return; + onChange(nextZoom); + }; + + window.addEventListener('keydown', handleKey); + + return () => { + window.removeEventListener('keydown', handleKey); + }; +} + export function clampZoom(z: number, min: number, max: number): number { return Math.round(Math.min(max, Math.max(min, z)) * 1000) / 1000; } diff --git a/src/lib/state/app.svelte.ts b/src/lib/state/app.svelte.ts index ec19fcd..0af799b 100644 --- a/src/lib/state/app.svelte.ts +++ b/src/lib/state/app.svelte.ts @@ -8,4 +8,5 @@ export const appState = $state({ authMode: 'NONE' as 'NONE' | 'BASIC_AUTH' | 'UI_LOGIN', platform: 'web' as 'web' | 'tauri' | 'capacitor', version: '', + idle: false, }) \ No newline at end of file diff --git a/src/lib/ui/chrome/Sidebar.svelte b/src/lib/ui/chrome/Sidebar.svelte index eee7a72..9f4716a 100644 --- a/src/lib/ui/chrome/Sidebar.svelte +++ b/src/lib/ui/chrome/Sidebar.svelte @@ -1,8 +1,7 @@ @@ -80,6 +91,7 @@ margin-bottom: var(--sp-4); border-radius: var(--radius-lg); transition: opacity var(--t-base), transform var(--t-base); + text-decoration: none; } .logo:hover { opacity: 0.8; transform: scale(0.96); } .logo:active { transform: scale(0.92); } @@ -140,6 +152,7 @@ border-radius: var(--radius-md); color: var(--text-faint); transition: color var(--t-base), background var(--t-base); + text-decoration: none; } .tab:hover { color: var(--text-muted); background: var(--bg-raised); } .tab:active { transform: scale(0.88); } @@ -167,7 +180,9 @@ border-radius: var(--radius-md); color: var(--text-faint); transition: color var(--t-base), background var(--t-base), transform var(--t-slow); + text-decoration: none; } .settings-btn:hover { color: var(--text-muted); background: var(--bg-raised); transform: rotate(30deg); } .settings-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; } + .settings-btn.active { color: var(--accent-fg); background: var(--accent-muted); transform: none; } diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 94b992c..28f776e 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -1,5 +1,11 @@ -{#if splashVisible} +{#if showSplash && splashVisible} window.location.reload()} /> {/if} -{#if showApp} -
-
- {#if isTauri} - import('@tauri-apps/api/window').then(m => m.getCurrentWindow().close())} /> - {/if} -
- -
- {@render children()} -
+{#if showShell} + {#if hideShellChrome} +
+ {@render children()} +
+ {:else} +
+
+ {#if isTauri} + import('@tauri-apps/api/window').then(m => m.getCurrentWindow().close())} /> + {/if} +
+ +
+ {@render children()} +
+
-
+ {/if} {/if} - +{#if showAuthGate} + +{/if} \ No newline at end of file