const rssUrl = window.__RSS_URL__; const els = { feedTitle: document.getElementById('feedTitle'), headlineText: document.getElementById('headlineText'), headlineLink: document.getElementById('headlineLink'), clock: document.getElementById('clock'), articleTime: document.getElementById('articleTime'), progressFill: document.getElementById('progressFill'), bg: document.getElementById('bg'), }; let items = []; let idx = 0; let timer = null; let progressTimer = null; let paused = false; const SLIDE_MS = 6000; function fmtClock(d) { const pad = (n) => String(n).padStart(2, '0'); return `${pad(d.getDate())}-${pad(d.getMonth() + 1)}-${d.getFullYear()} ${pad(d.getHours())}:${pad(d.getMinutes())}`; } function tickClock() { els.clock.textContent = fmtClock(new Date()); } function resetProgress() { if (progressTimer) { cancelAnimationFrame(progressTimer); progressTimer = null; } const start = performance.now(); els.progressFill.style.width = '0%'; const loop = (t) => { if (paused) return; const p = Math.min(1, (t - start) / SLIDE_MS); els.progressFill.style.width = `${(p * 100).toFixed(2)}%`; if (p < 1) progressTimer = requestAnimationFrame(loop); }; progressTimer = requestAnimationFrame(loop); } function setBackground(item) { if (!els.bg) return; const url = item?.enclosure_url; if (url) { // Include the same gradient fallbacks as the CSS, but insert the image layer. // NOTE: No dark overlay on top of the image (requested). els.bg.style.backgroundImage = [ `url("${url}")`, 'radial-gradient(1200px 600px at 70% 20%, rgba(100, 150, 255, 0.35), transparent 55%)', 'radial-gradient(900px 500px at 20% 70%, rgba(20, 255, 190, 0.16), transparent 55%)', 'linear-gradient(130deg, var(--bg1), var(--bg2))', ].join(', '); } else { // Reset to stylesheet default. els.bg.style.backgroundImage = ''; } } function setHeadline(i, animate = true) { if (!items.length) return; const item = items[i]; setBackground(item); const node = els.headlineText; if (animate) { node.classList.remove('slide-in'); node.classList.add('slide-out'); setTimeout(() => { node.textContent = item.title || '(untitled)'; els.headlineLink.href = item.link || '#'; if (els.articleTime) els.articleTime.textContent = item.published || '—'; node.classList.remove('slide-out'); node.classList.add('slide-in'); }, 280); } else { node.textContent = item.title || '(untitled)'; els.headlineLink.href = item.link || '#'; node.classList.add('slide-in'); } resetProgress(); } function next() { if (!items.length) return; idx = (idx + 1) % items.length; setHeadline(idx); } function prev() { if (!items.length) return; idx = (idx - 1 + items.length) % items.length; setHeadline(idx); } function startAuto() { stopAuto(); timer = setInterval(() => { if (!paused) next(); }, SLIDE_MS); } function stopAuto() { if (timer) clearInterval(timer); timer = null; } function togglePause() { paused = !paused; if (!paused) resetProgress(); } async function loadFeed() { els.feedTitle.textContent = 'Loading…'; els.headlineText.textContent = 'Loading headlines…'; const apiUrl = `/api/feed?url=${encodeURIComponent(rssUrl)}`; const r = await fetch(apiUrl, { cache: 'no-store' }); const data = await r.json(); if (!r.ok) throw new Error(data?.error || 'Failed to load feed'); els.feedTitle.textContent = data.title || 'News'; items = (data.items || []).filter((x) => (x.title || '').trim().length > 0).slice(0, 5); if (!items.length) { els.headlineText.textContent = 'No items found.'; return; } idx = 0; setHeadline(idx, false); startAuto(); } function wireControls() { // Removed manual navigation controls. This is now a passive slideshow. } (async function init() { tickClock(); setInterval(tickClock, 1000); wireControls(); try { await loadFeed(); } catch (e) { els.feedTitle.textContent = 'Error'; els.headlineText.textContent = e?.message || 'Failed to load.'; } })();