// FolxPlay clone — vanilla JS SPA // Each channel plays its JW Player promo (HLS from cdn.jwplayer.com). // No tracking, no AdSense, no external API. const JW_HLS = id => `https://cdn.jwplayer.com/manifests/${id}.m3u8`; const JW_POSTER = id => `https://cdn.jwplayer.com/v2/media/${id}/poster.jpg?width=1280`; const CHANNELS = [ { key: 'one', name: 'One Music TV', color: '#a84d7e', logo: '/logos/one_mt_neg_b.png', alt: 'logo of One', jw: 'CBM2fSnV', live: 'https://oneapp.b-cdn.net/master.m3u8' , bg: '/backgrounds/ch_one.jpg' }, { key: 'zwei', name: 'Zwei Music TV', color: '#f36700', logo: '/logos/zwei_neg_b.png', alt: 'logo of Zwei', jw: 'LZVUn3Qy', live: 'https://zweiapp.b-cdn.net/master.m3u8' , bg: '/backgrounds/ch_zwei.jpg' }, { key: 'mt', name: 'Folx Music TV', color: '#C43651', logo: '/logos/mt_neg_b.png', alt: 'logo of Folx Music',jw: 'UYU6dx3Q', live: 'https://folxapp.b-cdn.net/master.m3u8' , bg: '/backgrounds/ch_mt.jpg' }, { key: 'adria', name: 'Adria Music TV', color: '#30a6d1', logo: '/logos/adria_neg_b.png', alt: 'logo of Adria', jw: 'lI4SC7Do', live: 'https://adriaapp.b-cdn.net/master.m3u8' , bg: '/backgrounds/ch_adria.jpg' }, { key: 'folx-slo', name: 'Folx Slovenija', color: '#a8b539', logo: '/logos/folx_slo_neg_b.png', alt: 'logo of Folx Slovenija', jw: 'aoCWwwSg', live: 'https://folxsloapp.b-cdn.net/master.m3u8' , bg: '/backgrounds/ch_folx-slo.jpg' }, { key: 'one-adria', name: 'One Adria Music TV', color: '#a84d7e', logo: '/logos/one_neg_b.png', alt: 'logo of One Adria', jw: 'IGOSgmvW', live: 'https://oneadriaapp.b-cdn.net/master.m3u8' , bg: '/backgrounds/ch_one-adria.jpg' }, ]; // Coloured dot per channel (colour taken from the channel's brand colour). const DOT = (color) => ``; // One flowing About paragraph, each station introduced with its brand-colour dot. const ABOUT_INTRO = `FOLX PLAY brings together the channels of the FOLX NETWORK — a family of music television stations, each with its own sound and its own audience. ${DOT('#C43651')} FOLX MUSIC TELEVISION is dedicated to folk music, with more than 90% of its programming produced in-house by FOLX NETWORK. ${DOT('#a84d7e')} ONE MUSIC TELEVISION covers contemporary German-language and international pop and rock, enriched with beloved classics of the 80s, 90s and 00s, while ${DOT('#f36700')} ZWEI MUSIC TELEVISION mixes today's pop hits with familiar favourites and adds Latin American sounds with a touch of reggaeton for daily variety. ${DOT('#30a6d1')} ADRIA MUSIC TELEVISION speaks to everyone in Germany, Austria and Switzerland with Adriatic roots — from Slovenia, Croatia, Bosnia and Herzegovina, Serbia, Macedonia and Montenegro — bringing traditional Dalmatian music, Klapa singing and the biggest pop and rock hits of the region. ${DOT('#a8b539')} FOLX SLOVENIJA MUSIC TELEVISION carries European folk music with exclusive, largely in-house programming, and ${DOT('#a84d7e')} ONE ADRIA MUSIC TELEVISION rounds out the family with fresh new music from the Adriatic alongside international pop, rock and global hits of the 80s, 90s and 00s. Together, these stations make FOLX PLAY a single home for the full spectrum of the FOLX NETWORK sound.`; const PLAY_SVG = ``; // pick a random background on each load const bgs = ['bg_01','bg_02','bg_03','bg_04','bg_05','bg_06','bg_07']; const BG_URL = `/backgrounds/${bgs[Math.floor(Math.random() * bgs.length)]}.jpg`; // ---------- helpers ---------- function el(html) { const t = document.createElement('template'); t.innerHTML = html.trim(); return t.content.firstElementChild; } function escapeHtml(s) { return String(s ?? '').replace(/[&<>"']/g, c => ({ '&':'&','<':'<','>':'>','"':'"',"'":''' }[c])); } async function api() { return null; /* no backend */ } // ---------- shell ---------- function renderShell() { document.getElementById('root').innerHTML = `
`; // intercept for client-side navigation document.body.addEventListener('click', (e) => { const a = e.target.closest('a[data-link]'); if (!a) return; if (e.metaKey || e.ctrlKey || e.shiftKey || e.button !== 0) return; e.preventDefault(); navigate(a.getAttribute('href')); }); window.addEventListener('popstate', () => render(location.pathname)); } function navigate(path) { if (location.pathname !== path) history.pushState({}, '', path); render(path); } // ---------- pages ---------- const pages = { '/': renderHome, '/artists': renderArtists, '/folxnetwork': renderAbout, '/policies': renderPolicies, '/log': renderLog, }; function render(path) { const handler = pages[path] || renderNotFound; // mark active link document.querySelectorAll('.nav .links a').forEach(a => { a.classList.toggle('active', a.getAttribute('data-route') === path); }); handler(); } // ===== HOME ===== let shakaInstance = null; let currentChannel = null; let playToken = 0; // invalidates async work when channel changes let liveRetryTimer = null; // background timer that re-checks the live stream let promoFallbackActive = false; function renderHome() { const main = document.getElementById('main'); main.innerHTML = `
`; showSelectChannelBox(); // channel click handlers (sidebar) main.querySelectorAll('[data-channel]').forEach(a => { a.addEventListener('click', (e) => { e.preventDefault(); const key = a.getAttribute('data-channel'); playChannel(key); }); }); } function showSelectChannelBox() { document.getElementById('player-area').innerHTML = `
FolxPlay Live TV
Select channel on right
Or ${PLAY_SVG} random channel
`; document.getElementById('random-btn')?.addEventListener('click', () => { const ch = CHANNELS[Math.floor(Math.random() * CHANNELS.length)]; playChannel(ch.key); }); } function channelOverlayBg(color) { // Exactly the channel colour (same value the sidebar button uses), flat fill. return color; } function showVideoPlayer(ch) { const color = ch ? ch.color : '#000'; const logo = ch ? ch.logo : ''; const bgImg = ch && ch.bg ? `background-image:url('${ch.bg}');background-size:cover;background-position:center;` : ''; document.getElementById('player-area').innerHTML = `
`; } // Hide the channel-logo loading overlay once real playback starts. // Instant hide (no opacity fade) so the player never shows through a // half-transparent overlay mid-transition. function hideLoadingOverlay() { const el = document.getElementById('video-loading'); if (el) el.style.display = 'none'; } // Show it again (e.g. when switching channel / falling back). function showLoadingOverlay(ch) { const el = document.getElementById('video-loading'); if (!el) return; if (ch) { el.style.setProperty('--ch', ch.color); if (ch.bg) { el.classList.add('channelLoading--img'); el.style.backgroundImage = `url('${ch.bg}')`; el.style.backgroundSize = 'cover'; el.style.backgroundPosition = 'center'; } else { el.classList.remove('channelLoading--img'); el.style.backgroundImage = ''; } } el.style.display = 'flex'; el.style.opacity = '1'; } // Load an HLS url into the