Nivora Signal

Your personal shelf

Reports worth following.

This browser-only shelf keeps the stories you want nearby without creating an account. All changes are saved locally.

`; const footerHTML = ` `; document.querySelector('header').innerHTML = headerHTML; document.querySelector('footer').innerHTML = footerHTML; // Tailwind script function initTailwind() { document.documentElement.style.setProperty('--accent', '#A8B69A'); } // Mobile nav function initMobileNav() { const toggle = document.getElementById('mobileNavToggle'); const nav = document.getElementById('mobileNav'); if (toggle && nav) { toggle.addEventListener('click', () => nav.classList.toggle('hidden')); } } // Theme toggle function initTheme() { const btn = document.getElementById('themeToggle'); const root = document.documentElement; const saved = localStorage.getItem('nivoraTheme'); if (saved === 'dark') { root.classList.add('dark'); if (btn) btn.textContent = '☾'; } if (btn) { btn.addEventListener('click', () => { root.classList.toggle('dark'); const isDark = root.classList.contains('dark'); localStorage.setItem('nivoraTheme', isDark ? 'dark' : 'light'); btn.textContent = isDark ? '☾' : '☼'; }); } } // Auth modal function initAuthModal() { const loginBtn = document.getElementById('loginTrigger'); const registerBtn = document.getElementById('registerTrigger'); const modal = document.getElementById('authModal'); const closeBtn = document.getElementById('closeAuthModal'); const modalClose = document.getElementById('modalCloseBtn'); function openModal() { if (modal) modal.hidden = false; } function closeModal() { if (modal) modal.hidden = true; } if (loginBtn) loginBtn.addEventListener('click', openModal); if (registerBtn) registerBtn.addEventListener('click', openModal); if (closeBtn) closeBtn.addEventListener('click', closeModal); if (modalClose) modalClose.addEventListener('click', closeModal); if (modal) { modal.addEventListener('click', (e) => { if (e.target === modal) closeModal(); }); } } // Cookie banner function initCookies() { const banner = document.getElementById('cookieBanner'); const accept = document.getElementById('acceptCookies'); const dismiss = document.getElementById('dismissCookies'); const key = 'nivoraCookies'; function hide() { if (banner) banner.hidden = true; } function show() { if (banner) banner.hidden = false; } if (!localStorage.getItem(key)) show(); if (accept) accept.addEventListener('click', () => { localStorage.setItem(key, 'accepted'); hide(); }); if (dismiss) dismiss.addEventListener('click', () => { localStorage.setItem(key, 'dismissed'); hide(); }); } // Fallows logic let catalogData = []; async function loadCatalog() { try { const res = await fetch('./catalog.json'); catalogData = await res.json(); } catch (e) { catalogData = []; } } function getFavorites() { try { return JSON.parse(localStorage.getItem('nivoraFavorites') || '[]'); } catch { return []; } } function saveFavorites(ids) { localStorage.setItem('nivoraFavorites', JSON.stringify(ids)); } function removeFromFavorites(id) { let ids = getFavorites(); ids = ids.filter(x => x !== id); saveFavorites(ids); renderTracked(); } function renderTracked() { const container = document.getElementById('trackedReports'); const empty = document.getElementById('emptyState'); if (!container || !empty) return; const favIds = getFavorites(); const tracked = catalogData.filter(item => favIds.includes(item.id)); container.innerHTML = ''; empty.classList.add('hidden'); if (tracked.length === 0) { empty.classList.remove('hidden'); return; } tracked.forEach(item => { const card = document.createElement('div'); card.className = 'group rounded-3xl border border-[#DCCFC1] bg-[#FFFDF9] p-8 transition hover:border-[#B95F45]'; card.innerHTML = `
${item.category || 'REPORT'}

${item.title}

${item.description}

${item.duration || '12 min'} • ${item.date || 'Latest edition'}
`; const removeBtn = card.querySelector('.remove-btn'); removeBtn.addEventListener('click', () => removeFromFavorites(item.id)); container.appendChild(card); }); } async function initFallows() { await loadCatalog(); renderTracked(); } function initAll() { initTailwind(); initMobileNav(); initTheme(); initAuthModal(); initCookies(); initFallows(); } window.onload = initAll; })();