From 5aa4055207e0df04f81474dde840b170d95fa05c Mon Sep 17 00:00:00 2001 From: gca Date: Sun, 5 Apr 2026 22:04:05 -0500 Subject: [PATCH 1/4] fix: use window-level listeners for progress bar scrubbing on mobile Once a scrub starts, attach pointermove/pointerup listeners to window instead of the bar element. This way the finger can drift vertically off the bar during a drag and it still tracks the horizontal X position. --- src/lib/components/ProgressBar.svelte | 40 +++++++++++++++++---------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/src/lib/components/ProgressBar.svelte b/src/lib/components/ProgressBar.svelte index 67af893..42c50dd 100644 --- a/src/lib/components/ProgressBar.svelte +++ b/src/lib/components/ProgressBar.svelte @@ -36,38 +36,50 @@ return ratio * duration; } + // Track pointer type so we know whether to clear hover on end + let activePointerType: string | null = null; + function handlePointerDown(e: PointerEvent) { e.preventDefault(); e.stopPropagation(); scrubbing = true; - barEl?.setPointerCapture(e.pointerId); + activePointerType = e.pointerType; const t = getTimeFromX(e.clientX); scrubTime = t; onscrubstart?.(); onseek(t); + // Listen on window so the finger can drift anywhere and we still track X + window.addEventListener('pointermove', handleWindowPointerMove); + window.addEventListener('pointerup', handleWindowPointerUp); + window.addEventListener('pointercancel', handleWindowPointerUp); } - function handlePointerMove(e: PointerEvent) { - if (scrubbing) { - const t = getTimeFromX(e.clientX); - scrubTime = t; - // Video is paused during scrub, so seek directly on every move — no throttle needed - onseek(t); - } else if (e.pointerType === 'mouse') { - hoverProgress = getTimeFromX(e.clientX); - } + function handleWindowPointerMove(e: PointerEvent) { + const t = getTimeFromX(e.clientX); + scrubTime = t; + onseek(t); } - function handlePointerUp(e: PointerEvent) { + function handleWindowPointerUp(_e: PointerEvent) { if (!scrubbing) return; + window.removeEventListener('pointermove', handleWindowPointerMove); + window.removeEventListener('pointerup', handleWindowPointerUp); + window.removeEventListener('pointercancel', handleWindowPointerUp); if (scrubTime !== null) onseek(scrubTime); scrubbing = false; scrubTime = null; - barEl?.releasePointerCapture(e.pointerId); - if (e.pointerType !== 'mouse') hoverProgress = null; + if (activePointerType !== 'mouse') hoverProgress = null; + activePointerType = null; onscrubend?.(); } + function handlePointerMove(e: PointerEvent) { + // Only used for mouse hover preview when not scrubbing + if (!scrubbing && e.pointerType === 'mouse') { + hoverProgress = getTimeFromX(e.clientX); + } + } + function handlePointerLeave() { if (!scrubbing) hoverProgress = null; } @@ -81,8 +93,6 @@ bind:this={barEl} onpointerdown={handlePointerDown} onpointermove={handlePointerMove} - onpointerup={handlePointerUp} - onpointercancel={handlePointerUp} onpointerleave={handlePointerLeave} tabindex="0" role="slider" From ad44241cfc5647b67b1075d72fa58ab1e7d5f837 Mon Sep 17 00:00:00 2001 From: gca Date: Sun, 5 Apr 2026 22:04:13 -0500 Subject: [PATCH 2/4] feat(nav): add unwatched clip count badge to home tab --- src/routes/(app)/+layout.svelte | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/routes/(app)/+layout.svelte b/src/routes/(app)/+layout.svelte index cf15b9c..3e53173 100644 --- a/src/routes/(app)/+layout.svelte +++ b/src/routes/(app)/+layout.svelte @@ -5,7 +5,12 @@ import { addVideoModalOpen } from '$lib/stores/addVideoModal'; import { queueSheetOpen } from '$lib/stores/queueSheet'; import { homeTapSignal } from '$lib/stores/homeTap'; - import { unreadCount, startPolling, stopPolling } from '$lib/stores/notifications'; + import { + unreadCount, + unwatchedCount, + startPolling, + stopPolling + } from '$lib/stores/notifications'; import { queueCount } from '$lib/stores/queue'; import { globalMuted } from '$lib/stores/mute'; import { initAudioContext } from '$lib/audio/normalizer'; @@ -179,12 +184,22 @@