- Add data-nav-entry-col-from-above so moving down from the season
selector always focuses the first episode instead of the tile
visually closest to the selected season poster; moving back up
still returns to the current season.
- Require recent real mouse movement before treating mouseover as
mouse intent: scroll/re-render under a stationary cursor no longer
activates mouse mode, so sideways season browsing no longer loses
focus to the episode tile under the parked pointer.
Skip ensure-visible and synced-row animations on focus restore, and
snapshot/restore panel scrollTop plus synced-row scrollLeft around
detail navigation so the browse page comes back pixel-identical.
- useMediaWebSocket: shallowRef mediaIndex/tasks/loading/error/connected
to eliminate Proxy overhead on the entire library data structure.
- useMediaWebSocket: replace per-task setTimeout leak with single 3s
sweep interval for completed task cleanup.
- App.vue: remove {deep:true} watcher on mediaIndex; shallow ref change
is sufficient to trigger search worker sync.
- useGamepadNavigation: rAF only when gamepads are connected; idle
fallback to 500ms setTimeout to stop permanent 60fps CPU drain.
- useKeyboardNavigation: deduplicate synced scroll rAF requests to
prevent overlapping animation frames.
- MediaDetail/SeriesFullView/CollageHero: pause, clear src, and load()
video elements on unmount and before ref replacement to release
decoder/memory resources.
- MediaDetail/SeriesFullView: clear all volume fade intervals on
unmount to stop interval timer leaks.
Replace the fragile per-row tail/maxVirtual state with a clean global-
metrics approach:
- Measure cardWidth, stride, paddingLeft, viewportWidth, rightDeadzone
once from the first synced row; use the same values for all rows.
- Per-row max scroll computed purely from math:
max(0, paddingLeft + (N-1)*stride - maxVisibleLeft)
where maxVisibleLeft = viewportWidth - cardWidth - rightDeadzone.
- Global virtual offset (syncedRowsCurrentOffset/targetOffset) is shared
across all rows; each row clamps independently in applySyncedRowScroll.
- Tail is a global constant (= rightDeadzone) applied to all rows.
- Fix scrolling-back bug: desiredOffset was stuck at currentOffset when
moving left; now always computed from anchor column position.
- Avoid full animation sweep on view entry: init current/target offset
from existing DOM scrollLeft on first use.
Also removes per-row --sync-row-max-virtual CSS var and simplifies
.media-row padding to use only --sync-row-tail.
- Tail is now computed and applied per-row instead of globally.
- Right-side-only tail padding so item positions don't shift when tail grows.
- Removed anchor-row clamping from effectiveAnchorOffset and targetOffset;
each row independently clamps the shared virtual offset.
- Rows that fit entirely on screen (maxOffset == 0) keep left alignment.
- Short rows retain their scrolled position when focus moves to longer rows.
Fixes erratic scrolling and zig-zag when navigating across rows of different
lengths in movie/series/search categories.