Live Channels

All Channels

// DOM Elements const grid = document.getElementById("channel-grid"); const alphaNavEl = document.getElementById("alpha-nav"); const searchToggle = document.getElementById("search-toggle"); const searchFilterBar = document.getElementById("search-filter-bar"); const searchInput = document.getElementById("search-input"); const searchClear = document.getElementById("search-clear"); const countryFilter = document.getElementById("country-filter"); const categoryFilter = document.getElementById("category-filter"); const resultsInfo = document.getElementById("results-info"); const resultsCount = document.getElementById("results-count"); const clearFilters = document.getElementById("clear-filters"); const clearFiltersBtn = document.getElementById("clear-filters"); const noResults = document.getElementById("no-results"); const alphaNavEl = document.getElementById("alpha-nav"); const recentlyWatchedGrid = document.getElementById("recently-watched-grid"); const favoritesGrid = document.getElementById("favorites-grid"); const recentlyWatchedSection = document.getElementById("recently-watched"); const favoritesSection = document.getElementById("favorites"); const noResultsEl = document.getElementById("no-results"); const resultsInfoEl = document.getElementById("results-info"); const resultsCountEl = document.getElementById("results-count"); const countryFilterEl = document.getElementById("country-filter"); const categoryFilterEl = document.getElementById("category-filter"); const recentlyWatchedSectionEl = document.getElementById("recently-watched"); const favoritesSectionEl = document.getElementById("favorites"); const allChannelsSection = document.getElementById("all-channels"); const searchToggle = document.getElementById("search-toggle"); const searchFilterBarEl = document.getElementById("search-filter-bar"); const searchInputEl = document.getElementById("search-input"); const searchClearBtn = document.getElementById("search-clear"); const clearFiltersEl = document.getElementById("clear-filters"); const recentlyWatchedGridEl = document.getElementById("recently-watched-grid"); const favoritesGridEl = document.getElementById("favorites-grid"); const noResultsEl2 = document.getElementById("no-results"); // State let allChannels = []; let filteredChannels = []; let currentFilters = { search: '', country: 'all', category: 'all' }; let searchDebounceTimer = null; let isSearchOpen = false; // Initialize function init() { // Load channels from data layer allChannels = window.ChannelData.getAll() || []; // Populate filters populateFilters(); // Render initial state renderRecentlyWatched(); renderFavorites(); renderAllChannels(); buildAlphaNav(); // Setup event listeners setupEventListeners(); // Load persisted data loadPersistedData(); } function populateFilters() { // Populate country filter const countries = window.ChannelData.countries || []; countryFilter.innerHTML = ''; countries.forEach(country => { const option = document.createElement('option'); option.value = country.name; option.textContent = `${country.name} (${country.count})`; countryFilter.appendChild(option); }); // Populate category filter const categories = window.ChannelData.categories || []; categoryFilter.innerHTML = ''; categories.forEach(cat => { const option = document.createElement('option'); option.value = cat.name; option.textContent = `${cat.name} (${cat.count})`; categoryFilter.appendChild(option); }); } function buildAlphaNav() { const letters = ["#"].concat("ABCDEFGHIJKLMNOPQRSTUVWXYZ".split("")); alphaNavEl.innerHTML = ''; letters.forEach(letter => { const btn = document.createElement("button"); btn.type = "button"; btn.textContent = letter; btn.setAttribute("data-letter", letter); btn.setAttribute("aria-label", "Jump to " + (letter === "#" ? "numbers/symbols" : letter)); btn.addEventListener("click", function () { const targetId = "country-" + (letter === "#" ? "other" : letter.toLowerCase()); const target = document.getElementById(targetId); if (target) { target.scrollIntoView({ behavior: "smooth", block: "start" }); } }); alphaNavEl.appendChild(btn); }); } function renderChannels(channels, container, options = {}) { if (!container) return; if (!channels.length) { container.innerHTML = '

No channels available.

'; return; } const { showCountry = true, showCategory = true, maxItems } = options; container.innerHTML = ''; const channelsToRender = maxItems ? channels.slice(0, maxItems) : channels; channelsToRender.forEach(c => { const card = document.createElement("a"); card.className = "channel-card"; card.href = "/channel/" + encodeURIComponent(c.slug) + "/"; card.setAttribute("aria-label", "Watch " + c.name); card.dataset.channelId = c.id; card.dataset.channelSlug = c.slug; // Add favorite button if not in favorites view const isFav = isFavorite(c.id); const favIcon = isFav ? '' : ''; card.innerHTML = '
' + ' LIVE' + '
' + c.name.charAt(0) + '
' + "
" + '
' + "

" + escapeHtml(c.name) + "

" + "

" + (showCountry ? escapeHtml(c.country) : '') + (showCountry && showCategory ? ' · ' : '') + (showCategory ? escapeHtml(c.category) : '') + "

" + "
" + '
' + '' + 'Watch Live →' + '
'; container.appendChild(card); }); // Add click handlers for favorite buttons container.querySelectorAll('.fav-btn').forEach(btn => { btn.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); toggleFavorite(btn.dataset.channelId); }); }); } function renderAllChannels() { filteredChannels = filterChannels(allChannels, currentFilters); renderChannels(filteredChannels, grid); // Build country sections with alpha navigation buildCountrySections(); buildAlphaNav(); // Update results info updateResultsInfo(); } function buildCountrySections() { // Group filtered channels by country const byCountry = {}; filteredChannels.forEach(c => { if (!byCountry[c.country]) byCountry[c.country] = []; byCountry[c.country].push(c); }); // Sort countries alphabetically const sortedCountries = Object.keys(byCountry).sort(); // Clear grid and rebuild with country sections grid.innerHTML = ''; // Track section IDs for scroll spy const sectionIds = []; sortedCountries.forEach(country => { const countryChannels = byCountry[country]; const firstChar = country.charAt(0).toUpperCase(); const letterGroup = /^[A-Z]$/.test(firstChar) ? firstChar : "#"; const sectionId = "country-" + (letterGroup === "#" ? "other" : letterGroup.toLowerCase()); const section = document.createElement("div"); section.className = "country-section"; section.id = sectionId; const header = document.createElement("div"); header.className = "country-header"; header.innerHTML = '

' + escapeHtml(country) + '

' + '' + countryChannels.length + ' channel' + (countryChannels.length > 1 ? 's' : '') + ''; section.appendChild(header); const countryGrid = document.createElement("div"); countryGrid.className = "channel-grid"; countryChannels.forEach(c => { const card = document.createElement("a"); card.className = "channel-card"; card.href = "/channel/" + encodeURIComponent(c.slug) + "/"; card.setAttribute("aria-label", "Watch " + c.name); card.dataset.channelId = c.id; card.dataset.channelSlug = c.slug; const isFav = isFavorite(c.id); const favIcon = isFav ? '' : ''; card.innerHTML = '
' + ' LIVE' + '
' + c.name.charAt(0) + '
' + "
" + '
' + "

" + escapeHtml(c.name) + "

" + "

" + escapeHtml(c.country) + ' · ' + escapeHtml(c.category) + "

" + "
" + '
' + '' + 'Watch Live →' + '
'; countryGrid.appendChild(card); }); section.appendChild(countryGrid); grid.appendChild(section); // Track for scroll spy sectionIds.push({ id: sectionId, letter: firstChar }); }); // Add click handlers for favorite buttons grid.querySelectorAll('.fav-btn').forEach(btn => { btn.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); toggleFavorite(btn.dataset.channelId); }); }); // Scroll spy for alpha nav setupScrollSpy(sectionIds); } function setupScrollSpy(sectionIds) { const alphaNavEl = document.getElementById("alpha-nav"); const alphaButtons = alphaNavEl.querySelectorAll("button[data-letter]"); const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { const letter = entry.target.dataset.letterGroup; if (!letter) return; const btn = alphaNavEl.querySelector('button[data-letter="' + letter + '"]'); if (btn) { if (entry.isIntersecting) { btn.classList.add("active"); } else { btn.classList.remove("active"); } } }); }, { rootMargin: "-80px 0px -60% 0px", threshold: 0.1 }); sectionIds.forEach(s => { const el = document.getElementById(s.id); if (el) { el.dataset.letterGroup = s.letter; observer.observe(el); } }); } function renderRecentlyWatched() { const recent = getRecentlyWatched(); if (recent.length === 0) { recentlyWatchedSectionEl.hidden = true; return; } recentlyWatchedSectionEl.hidden = false; renderChannels(recent, recentlyWatchedGridEl, { maxItems: 10, showCountry: true, showCategory: true }); } function renderFavorites() { const favs = getFavorites(); if (favs.length === 0) { favoritesSectionEl.hidden = true; return; } favoritesSectionEl.hidden = false; renderChannels(favs, favoritesGridEl, { showCountry: true, showCategory: true }); } function updateResultsInfo() { const count = filteredChannels.length; resultsCountEl.textContent = count; resultsInfoEl.hidden = (currentFilters.search === '' && currentFilters.country === 'all' && currentFilters.category === 'all'); noResultsEl.hidden = count > 0; allChannelsSection.hidden = count === 0 && (currentFilters.search || currentFilters.country !== 'all' || currentFilters.category !== 'all'); // Show clear filters button if any filter is active const hasFilters = currentFilters.search || currentFilters.country !== 'all' || currentFilters.category !== 'all'; clearFiltersBtn.hidden = !hasFilters; } function applyFilters() { filteredChannels = window.ChannelData.filterChannels(allChannels, currentFilters); renderAllChannels(); } function setupEventListeners() { // Search toggle searchToggle.addEventListener('click', () => { isSearchOpen = !isSearchOpen; searchFilterBarEl.hidden = !isSearchOpen; searchToggle.setAttribute('aria-expanded', isSearchOpen); if (isSearchOpen) { searchInputEl.focus(); } }); // Search input with debounce searchInputEl.addEventListener('input', (e) => { clearTimeout(searchDebounceTimer); searchDebounceTimer = setTimeout(() => { currentFilters.search = e.target.value.trim(); searchClearBtn.hidden = !currentFilters.search; applyFilters(); }, 150); }); searchClearBtn.addEventListener('click', () => { searchInputEl.value = ''; currentFilters.search = ''; searchClearBtn.hidden = true; applyFilters(); }); // Country filter countryFilterEl.addEventListener('change', (e) => { currentFilters.country = e.target.value; applyFilters(); }); // Category filter categoryFilterEl.addEventListener('change', (e) => { currentFilters.category = e.target.value; applyFilters(); }); // Clear filters clearFiltersBtn.addEventListener('click', () => { currentFilters = { search: '', country: 'all', category: 'all' }; searchInputEl.value = ''; countryFilterEl.value = 'all'; categoryFilterEl.value = 'all'; searchClearBtn.hidden = true; applyFilters(); }); // Favorite button clicks (delegated) document.addEventListener('click', (e) => { const favBtn = e.target.closest('.fav-btn'); if (favBtn) { e.preventDefault(); e.stopPropagation(); toggleFavorite(favBtn.dataset.channelId); } }); // Keyboard shortcuts document.addEventListener('keydown', (e) => { // Ctrl/Cmd + K for search if ((e.ctrlKey || e.metaKey) && e.key === 'k') { e.preventDefault(); if (!isSearchOpen) { isSearchOpen = true; searchFilterBarEl.hidden = false; searchToggle.setAttribute('aria-expanded', true); searchInputEl.focus(); } } // Escape to close search if (e.key === 'Escape') { if (isSearchOpen) { isSearchOpen = false; searchFilterBarEl.hidden = true; searchToggle.setAttribute('aria-expanded', false); searchInputEl.blur(); } } }); // Channel card click for recently watched document.addEventListener('click', (e) => { const card = e.target.closest('.channel-card'); if (card && card.dataset.channelSlug) { addToRecentlyWatched(card.dataset.channelId); } }); } // Persistence functions function getFavorites() { try { const stored = localStorage.getItem('tv-favorites'); return stored ? JSON.parse(stored) : []; } catch { return []; } } function isFavorite(channelId) { const favs = getFavorites(); return favs.some(f => f.id === channelId); } function toggleFavorite(channelId) { const channel = allChannels.find(c => c.id === channelId); if (!channel) return; let favs = getFavorites(); const index = favs.findIndex(f => f.id === channelId); if (index >= 0) { favs.splice(index, 1); } else { favs.unshift({ id: channelId, name: channel.name, slug: channel.slug, addedAt: Date.now() }); } localStorage.setItem('tv-favorites', JSON.stringify(favs)); // Re-render affected views renderAllChannels(); renderFavorites(); renderRecentlyWatched(); } function getRecentlyWatched() { try { const stored = localStorage.getItem('tv-recent'); return stored ? JSON.parse(stored) : []; } catch { return []; } } function addToRecentlyWatched(channelId) { const channel = allChannels.find(c => c.id === channelId); if (!channel) return; let recent = getRecentlyWatched(); recent = recent.filter(r => r.id !== channelId); recent.unshift({ id: channelId, name: channel.name, slug: channel.slug, watchedAt: Date.now() }); recent = recent.slice(0, 20); localStorage.setItem('tv-recent', JSON.stringify(recent)); renderRecentlyWatched(); } function loadPersistedData() { // Load last channel try { const lastChannel = localStorage.getItem('tv-last-channel'); if (lastChannel) { // Could auto-play or show suggestion } } catch {} } // Utility functions function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } // Handle channel navigation (for keyboard zapping) let currentChannelIndex = -1; function getChannelIndex(slug) { return allChannels.findIndex(c => c.slug === slug); } // Keyboard navigation for zapping document.addEventListener('keydown', (e) => { // Don't intercept if typing in search if (e.target.tagName === 'INPUT' || e.target.tagName === 'SELECT') return; if (e.key === 'ArrowUp' || e.key === 'ArrowDown') { e.preventDefault(); if (e.key === 'ArrowUp') { currentChannelIndex = Math.max(0, currentChannelIndex - 1); } else { currentChannelIndex = Math.min(allChannels.length - 1, currentChannelIndex + 1); } const channel = allChannels[currentChannelIndex]; if (channel) { window.location.href = '/channel/' + encodeURIComponent(channel.slug) + '/'; } } // Number keys for quick channel selection (1-9) if (e.key >= '1' && e.key <= '9') { const index = parseInt(e.key) - 1; if (index < filteredChannels.length) { window.location.href = '/channel/' + encodeURIComponent(filteredChannels[index].slug) + '/'; } } }); // Initialize when DOM is ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); }