diff --git a/components/ui/BackToTop.tsx b/components/ui/BackToTop.tsx
new file mode 100644
index 0000000..ffbb249
--- /dev/null
+++ b/components/ui/BackToTop.tsx
@@ -0,0 +1,57 @@
+'use client';
+
+import React, { useState, useEffect } from 'react';
+import { ChevronUp } from 'lucide-react';
+
+/**
+ * BackToTop - Floating button to scroll back to top of page
+ * Follows Liquid Glass design system
+ */
+export function BackToTop() {
+ const [isVisible, setIsVisible] = useState(false);
+
+ useEffect(() => {
+ const toggleVisibility = () => {
+ // Show button after scrolling down 300px
+ if (window.scrollY > 300) {
+ setIsVisible(true);
+ } else {
+ setIsVisible(false);
+ }
+ };
+
+ window.addEventListener('scroll', toggleVisibility, { passive: true });
+
+ // Initial check in case page is already scrolled (e.g. on refresh)
+ toggleVisibility();
+
+ return () => window.removeEventListener('scroll', toggleVisibility);
+ }, []);
+
+ const scrollToTop = () => {
+ window.scrollTo({
+ top: 0,
+ behavior: 'smooth',
+ });
+ };
+
+ return (
+
+ );
+}
diff --git a/lib/hooks/useHomePage.ts b/lib/hooks/useHomePage.ts
index 7a639a3..dd9c558 100644
--- a/lib/hooks/useHomePage.ts
+++ b/lib/hooks/useHomePage.ts
@@ -12,6 +12,7 @@ export function useHomePage() {
const { loadFromCache, saveToCache } = useSearchCache();
const hasLoadedCache = useRef(false);
const hasSearchedWithSourcesRef = useRef(false);
+ const isInitialCacheLoad = useRef(false);
const [query, setQuery] = useState('');
const [hasSearched, setHasSearched] = useState(false);
@@ -55,7 +56,9 @@ export function useHomePage() {
// Re-sort results when sort preference changes
useEffect(() => {
- if (hasSearched && results.length > 0) {
+ // Skip re-sorting if this is a load from cache, to preserve the "remembered" position
+ // Only re-sort if the user explicitly changes the sortBy option later
+ if (hasSearched && results.length > 0 && !isInitialCacheLoad.current) {
applySorting(currentSortBy);
}
}, [currentSortBy, applySorting, hasSearched, results.length]);
@@ -95,6 +98,14 @@ export function useHomePage() {
const handleSearch = useCallback((searchQuery: string) => {
if (!searchQuery.trim()) return;
+
+ // Clear scroll position for this search query to ensure we start at the top on a fresh search
+ const scrollKey = `scroll-pos:/?q=${encodeURIComponent(searchQuery)}`;
+ sessionStorage.removeItem(scrollKey);
+
+ // Reset cache load flag for new search
+ isInitialCacheLoad.current = false;
+
setQuery(searchQuery);
setHasSearched(true);
executeSearch(searchQuery);
@@ -111,6 +122,7 @@ export function useHomePage() {
if (urlQuery) {
setQuery(urlQuery);
if (cached && cached.query === urlQuery && cached.results.length > 0) {
+ isInitialCacheLoad.current = true;
setHasSearched(true);
loadCachedResults(cached.results, cached.availableSources);
hasSearchedWithSourcesRef.current = true;
diff --git a/lib/hooks/useSearchCache.ts b/lib/hooks/useSearchCache.ts
index e6dfedb..fccafda 100644
--- a/lib/hooks/useSearchCache.ts
+++ b/lib/hooks/useSearchCache.ts
@@ -8,7 +8,7 @@ interface SearchCache {
}
const CACHE_KEY = 'kvideo_search_cache';
-const CACHE_DURATION = 10 * 60 * 1000; // 10 minutes
+const CACHE_DURATION = 24 * 60 * 60 * 1000; // 24 hours
const MAX_CACHED_RESULTS = 300;
diff --git a/lib/store/settings-store.ts b/lib/store/settings-store.ts
index 71227cc..2621945 100644
--- a/lib/store/settings-store.ts
+++ b/lib/store/settings-store.ts
@@ -46,6 +46,7 @@ export interface AppSettings {
episodeReverseOrder: boolean; // Persist episode list reverse state
fullscreenType: 'native' | 'window'; // Fullscreen mode preference
proxyMode: ProxyMode; // Proxy behavior: 'retry' | 'none' | 'always'
+ rememberScrollPosition: boolean; // Remember scroll position when navigating back or refreshing
}
import { exportSettings, importSettings, SEARCH_HISTORY_KEY, WATCH_HISTORY_KEY } from './settings-helpers';
@@ -119,6 +120,7 @@ function getDefaultAppSettings(): AppSettings {
episodeReverseOrder: false,
fullscreenType: 'native',
proxyMode: 'retry',
+ rememberScrollPosition: true,
};
}
@@ -196,6 +198,7 @@ export const settingsStore = {
episodeReverseOrder: parsed.episodeReverseOrder !== undefined ? parsed.episodeReverseOrder : false,
fullscreenType: parsed.fullscreenType === 'window' ? 'window' : 'native',
proxyMode: (parsed.proxyMode === 'retry' || parsed.proxyMode === 'none' || parsed.proxyMode === 'always') ? parsed.proxyMode : 'retry',
+ rememberScrollPosition: parsed.rememberScrollPosition !== undefined ? parsed.rememberScrollPosition : true,
};
} catch {
// Even if localStorage fails, we should return defaults + ENV subscriptions
diff --git a/package-lock.json b/package-lock.json
index 6dfe967..b77d712 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "kvideo",
- "version": "4.0.4",
+ "version": "4.0.5",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "kvideo",
- "version": "4.0.4",
+ "version": "4.0.5",
"dependencies": {
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0",
diff --git a/package.json b/package.json
index 9bc3693..fd6cfa8 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "kvideo",
- "version": "4.0.4",
+ "version": "4.0.5",
"private": true,
"scripts": {
"dev": "next dev",