${esc(x.category)} · ${esc(x.level)}
${esc(x.title)}
${esc(x.description)}
$${x.price.toFixed(2)} · ${esc(x.duration)}
The course shelf
Every course is written for deliberate practice, with transparent scope and a clear reason for each exercise.
${esc(x.category)} · ${esc(x.level)}
${esc(x.description)}
$${x.price.toFixed(2)} · ${esc(x.duration)}
No course matches those filters. Try a broader search.
`; const pag = document.getElementById('pagination'); pag.innerHTML = Array.from({length: pages}, (_, i) => `${i+1}`).join(''); } async function init() { try { const res = await fetch('./catalog.json'); products = await res.json(); } catch (e) { products = []; } populateCategories(); render(); document.getElementById('search').addEventListener('input', () => { page = 1; render(); }); document.getElementById('category').addEventListener('change', () => { page = 1; render(); }); document.addEventListener('click', e => { const a = e.target.closest('[data-detail],[data-track],[data-cart],[data-page]'); if (!a) return; e.preventDefault(); const id = a.dataset.detail || a.dataset.track || a.dataset.cart; if (a.dataset.detail) { const x = products.find(p => p.id === id); document.getElementById('detail-content').innerHTML = `${esc(x.category)}
${esc(x.details)}
$${x.price.toFixed(2)} · ${esc(x.duration)}
`; document.getElementById('detail-modal').classList.replace('hidden', 'flex'); } else if (a.dataset.track) { let t = tracked(); const n = t.includes(id) ? t.filter(v => v !== id) : [...t, id]; localStorage.setItem('stillwoodTracked', JSON.stringify(n)); render(); } else if (a.dataset.cart) { let b = cart(); b[id] = (b[id] || 0) + 1; localStorage.setItem('stillwoodCart', JSON.stringify(b)); window.location.href = './cart.html'; } else if (a.dataset.page) { page = +a.dataset.page; render(); } }); document.getElementById('close-detail').addEventListener('click', e => { e.preventDefault(); document.getElementById('detail-modal').classList.replace('flex', 'hidden'); }); document.getElementById('detail-modal').addEventListener('click', e => { if (e.target.id === 'detail-modal') document.getElementById('detail-modal').classList.replace('flex', 'hidden'); }); const menuToggle = document.getElementById('menu-toggle'); const mainNav = document.getElementById('main-nav'); if (menuToggle && mainNav) { menuToggle.addEventListener('click', () => { mainNav.classList.toggle('hidden'); mainNav.classList.toggle('flex'); mainNav.classList.toggle('flex-col'); mainNav.classList.toggle('absolute'); mainNav.classList.toggle('top-20'); mainNav.classList.toggle('left-6'); mainNav.classList.toggle('right-6'); mainNav.classList.toggle('bg-[#F7F2E8]'); mainNav.classList.toggle('border'); mainNav.classList.toggle('border-[#D8CDBB]'); mainNav.classList.toggle('p-6'); mainNav.classList.toggle('rounded-2xl'); mainNav.classList.toggle('shadow-xl'); mainNav.classList.toggle('dark:bg-[#24352D]'); mainNav.classList.toggle('dark:border-[#3A4A42]'); }); } const cookieBanner = document.getElementById('cookie-banner'); function hideCookieBanner() { if (cookieBanner) cookieBanner.style.display = 'none'; } if (localStorage.getItem('stillwoodCookies') === 'accepted') hideCookieBanner(); const cookieCloseLink = document.querySelector('[data-cookie-close]'); if (cookieCloseLink) { cookieCloseLink.addEventListener('click', function(e) { e.preventDefault(); localStorage.setItem('stillwoodCookies', 'accepted'); hideCookieBanner(); }); } const themeToggleLinks = document.querySelectorAll('[data-theme-toggle]'); themeToggleLinks.forEach(link => { link.addEventListener('click', function(e) { e.preventDefault(); const html = document.documentElement; if (html.classList.contains('dark')) { html.classList.remove('dark'); localStorage.setItem('stillwoodTheme', 'light'); } else { html.classList.add('dark'); localStorage.setItem('stillwoodTheme', 'dark'); } }); }); if (localStorage.getItem('stillwoodTheme') === 'dark') { document.documentElement.classList.add('dark'); } } init();