156 lines
4.0 KiB
JavaScript
156 lines
4.0 KiB
JavaScript
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.';
|
|
}
|
|
})();
|