From 9b56cd4cb4a89f8642d883ae5e9805a4469d969a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 15 Feb 2026 03:15:39 +0000 Subject: [PATCH 1/5] Initial plan From e455cffe9f31a7c3cbde752fa903ce053a7c2863 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 15 Feb 2026 03:18:26 +0000 Subject: [PATCH 2/5] Add search/filter and configurable scroll speed features Co-authored-by: thehack904 <35552907+thehack904@users.noreply.github.com> --- static/css/base.css | 75 +++++++++++++++++++++++++++++++ static/js/auto-scroll.js | 92 +++++++++++++++++++++++++++++++++++++-- static/js/guide-filter.js | 80 ++++++++++++++++++++++++++++++++++ templates/_header.html | 16 +++++++ templates/guide.html | 16 +++++++ 5 files changed, 276 insertions(+), 3 deletions(-) create mode 100644 static/js/guide-filter.js diff --git a/static/css/base.css b/static/css/base.css index c0884d9..66d65f4 100644 --- a/static/css/base.css +++ b/static/css/base.css @@ -526,3 +526,78 @@ body.light{--arrow-collapsed:"▶";--arrow-expanded:"▼";} list-style: none; height: 0; } + +/* Search Container */ +.search-container { + display: flex; + align-items: center; + gap: 8px; + padding: 10px 15px; + background: var(--bg-color, #1a1a1a); + border-bottom: 1px solid var(--border-color, #333); + position: sticky; + top: 0; + z-index: 100; +} + +#channelSearch { + flex: 1; + padding: 8px 12px; + border: 1px solid var(--border-color, #333); + border-radius: 4px; + background: var(--input-bg, #2a2a2a); + color: var(--text-color, #fff); + font-size: 14px; +} + +#channelSearch:focus { + outline: none; + border-color: var(--accent-color, #4a9eff); +} + +#clearSearch { + padding: 6px 10px; + background: var(--button-bg, #333); + border: 1px solid var(--border-color, #444); + border-radius: 4px; + color: var(--text-color, #fff); + cursor: pointer; + font-size: 16px; + line-height: 1; +} + +#clearSearch:hover { + background: var(--button-hover-bg, #444); +} + +#searchResultCount { + font-size: 12px; + color: var(--text-muted, #999); + white-space: nowrap; +} + +.guide-row.hidden-by-search { + display: none !important; +} + +/* Scroll Speed Control */ +.scroll-speed-control { + display: flex; + align-items: center; + gap: 8px; + padding: 8px 12px; +} + +.scroll-speed-control label { + font-size: 13px; + color: var(--text-color, #fff); +} + +#scrollSpeed, #mobileScrollSpeed { + padding: 4px 8px; + border: 1px solid var(--border-color, #333); + border-radius: 4px; + background: var(--input-bg, #2a2a2a); + color: var(--text-color, #fff); + font-size: 13px; +} diff --git a/static/js/auto-scroll.js b/static/js/auto-scroll.js index 2d9f9a1..8bff237 100644 --- a/static/js/auto-scroll.js +++ b/static/js/auto-scroll.js @@ -10,6 +10,22 @@ function prefEnabled() { return localStorage.getItem(PREF_KEY) !== 'false'; } function setPref(v) { localStorage.setItem(PREF_KEY, v ? 'true' : 'false'); } + // Add speed mapping for configurable scroll speeds + const SCROLL_SPEEDS = { + slow: 1200, + medium: 650, + fast: 350 + }; + + function getScrollDuration() { + try { + const savedSpeed = localStorage.getItem('autoScrollSpeed') || 'medium'; + return SCROLL_SPEEDS[savedSpeed] || SCROLL_SPEEDS.medium; + } catch (e) { + return SCROLL_SPEEDS.medium; + } + } + const SELECTOR_PRIORITY = ['#guideOuter', '.guide-outer', '.grid-col']; const scrollSpeed = 1.2; // px per frame (visual) const idleDelay = 15000; // ms initial inactivity/start delay (15s) @@ -69,19 +85,23 @@ try { return 'scrollBehavior' in document.documentElement.style; } catch (e) { return false; } } - function smoothScrollTo(el, targetTop, duration = 650) { + function smoothScrollTo(el, targetTop, duration = null) { if (!el) return Promise.resolve(); + + // Use custom duration or get from settings + const scrollDuration = duration !== null ? duration : getScrollDuration(); + if (supportsNativeSmoothScroll()) { try { el.scrollTo({ top: targetTop, behavior: 'smooth' }); - return new Promise(resolve => setTimeout(resolve, duration)); + return new Promise(resolve => setTimeout(resolve, scrollDuration)); } catch (e) {} } return new Promise(resolve => { const start = el.scrollTop; const change = targetTop - start; const startTime = performance.now(); - const dur = Math.max(1, duration); + const dur = Math.max(1, scrollDuration); const ease = t => (t < 0.5) ? (2 * t * t) : (-1 + (4 - 2 * t) * t); function step(now) { const elapsed = now - startTime; @@ -446,3 +466,69 @@ else init(); })(); + +// Initialize speed selector +(function initSpeedControl() { + function syncSpeedSelectors() { + const speedSelect = document.getElementById('scrollSpeed'); + const mobileSpeedSelect = document.getElementById('mobileScrollSpeed'); + + // Load saved speed + try { + const saved = localStorage.getItem('autoScrollSpeed') || 'medium'; + if (speedSelect) speedSelect.value = saved; + if (mobileSpeedSelect) mobileSpeedSelect.value = saved; + } catch (e) {} + } + + function handleSpeedChange(e) { + try { + const newSpeed = e.target.value; + localStorage.setItem('autoScrollSpeed', newSpeed); + + // Sync both selectors + const speedSelect = document.getElementById('scrollSpeed'); + const mobileSpeedSelect = document.getElementById('mobileScrollSpeed'); + if (speedSelect) speedSelect.value = newSpeed; + if (mobileSpeedSelect) mobileSpeedSelect.value = newSpeed; + + if (typeof console !== 'undefined' && console.debug) { + console.debug('[auto-scroll] Scroll speed changed to:', newSpeed); + } + } catch (err) { + if (typeof console !== 'undefined' && console.debug) { + console.debug('[auto-scroll] Failed to save scroll speed:', err); + } + } + } + + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', () => { + syncSpeedSelectors(); + + const speedSelect = document.getElementById('scrollSpeed'); + const mobileSpeedSelect = document.getElementById('mobileScrollSpeed'); + + if (speedSelect) { + speedSelect.addEventListener('change', handleSpeedChange); + } + + if (mobileSpeedSelect) { + mobileSpeedSelect.addEventListener('change', handleSpeedChange); + } + }); + } else { + syncSpeedSelectors(); + + const speedSelect = document.getElementById('scrollSpeed'); + const mobileSpeedSelect = document.getElementById('mobileScrollSpeed'); + + if (speedSelect) { + speedSelect.addEventListener('change', handleSpeedChange); + } + + if (mobileSpeedSelect) { + mobileSpeedSelect.addEventListener('change', handleSpeedChange); + } + } +})(); diff --git a/static/js/guide-filter.js b/static/js/guide-filter.js new file mode 100644 index 0000000..ba341e5 --- /dev/null +++ b/static/js/guide-filter.js @@ -0,0 +1,80 @@ +(function() { + 'use strict'; + + const searchInput = document.getElementById('channelSearch'); + const clearBtn = document.getElementById('clearSearch'); + const resultCount = document.getElementById('searchResultCount'); + + if (!searchInput) return; + + function filterGuide() { + const query = searchInput.value.toLowerCase().trim(); + const rows = Array.from(document.querySelectorAll('.guide-row')); + + // Don't filter the time header row + const channelRows = rows.filter(row => { + return row.querySelector('.chan-col') && + !row.classList.contains('hide-in-grid'); + }); + + if (!query) { + // Show all rows + channelRows.forEach(row => row.classList.remove('hidden-by-search')); + resultCount.textContent = ''; + return; + } + + let visibleCount = 0; + + channelRows.forEach(row => { + const chanName = row.querySelector('.chan-name'); + const programs = Array.from(row.querySelectorAll('.programme, .program')); + + let matchFound = false; + + // Check channel name + if (chanName && chanName.textContent.toLowerCase().includes(query)) { + matchFound = true; + } + + // Check program titles + if (!matchFound) { + for (const prog of programs) { + const title = prog.dataset.title || prog.textContent; + if (title && title.toLowerCase().includes(query)) { + matchFound = true; + break; + } + } + } + + if (matchFound) { + row.classList.remove('hidden-by-search'); + visibleCount++; + } else { + row.classList.add('hidden-by-search'); + } + }); + + resultCount.textContent = `${visibleCount} of ${channelRows.length} channels`; + } + + function clearSearch() { + searchInput.value = ''; + filterGuide(); + searchInput.focus(); + } + + // Event listeners + searchInput.addEventListener('input', filterGuide); + searchInput.addEventListener('keyup', (e) => { + if (e.key === 'Escape') clearSearch(); + }); + + if (clearBtn) { + clearBtn.addEventListener('click', clearSearch); + } + + // Initial state + filterGuide(); +})(); diff --git a/templates/_header.html b/templates/_header.html index 9ecd347..66892b8 100644 --- a/templates/_header.html +++ b/templates/_header.html @@ -29,6 +29,14 @@