// LibrePortal site — interactions (mobile drawer, copy, scrollspy, reveal, app filter) (() => { // mobile drawer (same behaviour as the dashboard) const menuToggle = document.getElementById('mobile-menu-toggle'); const drawer = document.getElementById('mobile-drawer'); if (menuToggle && drawer) { menuToggle.addEventListener('click', () => { const open = drawer.classList.toggle('mobile-open'); document.body.style.overflow = open ? 'hidden' : ''; }); drawer.querySelectorAll('a').forEach((a) => a.addEventListener('click', () => { drawer.classList.remove('mobile-open'); document.body.style.overflow = ''; })); } // copy the install command const cmdEl = document.querySelector('[data-install-cmd]'); const CMD = cmdEl ? cmdEl.getAttribute('data-install-cmd') : ''; document.querySelectorAll('.copy').forEach((btn) => { btn.addEventListener('click', async () => { try { await navigator.clipboard.writeText(CMD); } catch { const t = document.createElement('textarea'); t.value = CMD; document.body.appendChild(t); t.select(); document.execCommand('copy'); t.remove(); } btn.classList.add('done'); const span = btn.querySelector('span'); const prev = span.textContent; span.textContent = 'Copied ✓'; setTimeout(() => { btn.classList.remove('done'); span.textContent = prev; }, 1700); }); }); // scrollspy → light up the active topbar pill const spies = [...document.querySelectorAll('.nav-item[data-spy]')]; if (spies.length) { const spyIO = new IntersectionObserver((entries) => { entries.forEach((e) => { if (e.isIntersecting) spies.forEach((s) => s.classList.toggle('nav-active', s.dataset.spy === e.target.id)); }); }, { rootMargin: '-45% 0px -50% 0px', threshold: 0 }); spies.map((s) => document.getElementById(s.dataset.spy)).filter(Boolean).forEach((s) => spyIO.observe(s)); } // reveal on scroll const io = new IntersectionObserver((entries) => { entries.forEach((e) => { if (e.isIntersecting) { e.target.classList.add('in'); io.unobserve(e.target); } }); }, { threshold: 0.16, rootMargin: '0px 0px -8% 0px' }); document.querySelectorAll('.reveal, .stagger').forEach((el) => io.observe(el)); // app category filter const filters = document.querySelector('.app-filters'); if (filters) { const cards = [...document.querySelectorAll('.app-card')]; const countEl = document.querySelector('.app-count'); const update = (cat) => { let shown = 0; cards.forEach((c) => { const match = cat === 'all' || (c.dataset.cats || '').split(' ').includes(cat); c.classList.toggle('hidden', !match); if (match) shown++; }); if (countEl) countEl.textContent = `${shown} app${shown === 1 ? '' : 's'}`; }; filters.addEventListener('click', (e) => { const chip = e.target.closest('.chip'); if (!chip) return; filters.querySelectorAll('.chip').forEach((c) => c.classList.toggle('active', c === chip)); update(chip.dataset.cat); }); } })();