Vivid Hearth Studio

The learning shelf

Courses for a more intentional AI practice.

Choose a focused workshop or build a complete path through visual research, prompt craft, image direction, and responsible delivery.

`; const footerHTML = ``; document.querySelector('header').innerHTML = headerHTML; document.querySelector('footer').innerHTML = footerHTML; } function initHeaderFeatures() { // Mobile menu const mobileBtn = document.querySelector('[data-mobile-menu]'); const mobilePanel = document.querySelector('[data-mobile-panel]'); if (mobileBtn && mobilePanel) { mobileBtn.addEventListener('click', () => { mobilePanel.classList.toggle('hidden'); }); } // Theme toggle const themeBtns = document.querySelectorAll('[data-theme-toggle]'); const savedTheme = localStorage.getItem('vh-theme'); if (savedTheme === 'dark') { document.body.classList.add('bg-[#26332D]', 'text-[#F7F1E8]'); document.body.classList.remove('bg-[#F7F1E8]', 'text-[#26332D]'); } themeBtns.forEach(btn => { btn.addEventListener('click', () => { if (document.body.classList.contains('bg-[#26332D]')) { document.body.classList.remove('bg-[#26332D]', 'text-[#F7F1E8]'); document.body.classList.add('bg-[#F7F1E8]', 'text-[#26332D]'); localStorage.setItem('vh-theme', 'light'); } else { document.body.classList.add('bg-[#26332D]', 'text-[#F7F1E8]'); document.body.classList.remove('bg-[#F7F1E8]', 'text-[#26332D]'); localStorage.setItem('vh-theme', 'dark'); } }); }); // Login/Register modals const loginModal = document.querySelector('[data-login-modal]'); const registerModal = document.querySelector('[data-register-modal]'); const openLogin = document.querySelector('[data-open-login]'); const openRegister = document.querySelector('[data-open-register]'); if (openLogin && loginModal) { openLogin.addEventListener('click', () => { loginModal.classList.remove('hidden'); loginModal.classList.add('flex'); }); } if (openRegister && registerModal) { openRegister.addEventListener('click', () => { registerModal.classList.remove('hidden'); registerModal.classList.add('flex'); }); } // Close modals document.querySelectorAll('[data-close-modal]').forEach(btn => { btn.addEventListener('click', () => { if (loginModal) { loginModal.classList.add('hidden'); loginModal.classList.remove('flex'); } if (registerModal) { registerModal.classList.add('hidden'); registerModal.classList.remove('flex'); } }); }); // Cookie banner const cookieBanner = document.querySelector('[data-cookie-banner]'); const cookieClose = document.querySelector('[data-cookie-close]'); if (cookieBanner && cookieClose) { if (!localStorage.getItem('vh-cookie-consent')) { cookieBanner.style.display = 'flex'; } cookieClose.addEventListener('click', () => { localStorage.setItem('vh-cookie-consent', 'true'); cookieBanner.style.display = 'none'; }); } } let allProducts = []; let currentPage = 1; const perPage = 6; async function fetchCatalog() { try { const res = await fetch('./catalog.json'); allProducts = await res.json(); } catch (e) { allProducts = []; } } function getFavorites() { return JSON.parse(localStorage.getItem('vh-favorites') || '[]'); } function toggleFavorite(id) { let favs = getFavorites(); if (favs.includes(id)) { favs = favs.filter(f => f !== id); } else { favs.push(id); } localStorage.setItem('vh-favorites', JSON.stringify(favs)); return favs; } function getCart() { return JSON.parse(localStorage.getItem('vh-cart') || '{}'); } function addToCart(id) { let cart = getCart(); cart[id] = (cart[id] || 0) + 1; localStorage.setItem('vh-cart', JSON.stringify(cart)); showCartToast(); } function showCartToast() { let toast = document.createElement('div'); toast.className = 'fixed bottom-8 right-8 bg-[#26332D] text-white px-6 py-3 rounded-full text-sm shadow-lg'; toast.textContent = 'Added to cart'; document.body.appendChild(toast); setTimeout(() => toast.remove(), 1500); } function filterProducts(searchTerm, category) { let filtered = allProducts; if (searchTerm) { const term = searchTerm.toLowerCase(); filtered = filtered.filter(p => p.title.toLowerCase().includes(term) || p.description.toLowerCase().includes(term) ); } if (category && category !== 'all') { filtered = filtered.filter(p => p.category === category); } return filtered; } function populateCategories() { const select = document.querySelector('[data-category]'); const categories = [...new Set(allProducts.map(p => p.category))]; categories.forEach(cat => { const opt = document.createElement('option'); opt.value = cat; opt.textContent = cat; select.appendChild(opt); }); } function renderGrid(filtered, page) { const grid = document.querySelector('[data-product-grid]'); const status = document.querySelector('[data-status]'); grid.innerHTML = ''; const start = (page - 1) * perPage; const items = filtered.slice(start, start + perPage); if (items.length === 0) { status.textContent = 'No courses found matching your filters.'; return; } status.textContent = `${filtered.length} courses`; const favs = getFavorites(); items.forEach(product => { const isFav = favs.includes(product.id); const card = document.createElement('div'); card.className = `product-card border border-[#DCCFC0] bg-[#FFFDF9] rounded-3xl p-6 flex flex-col`; card.innerHTML = `
${product.level}

${product.title}

${product.category} • ${product.duration}

${product.description}

$${product.price}
`; grid.appendChild(card); }); // Attach listeners grid.querySelectorAll('[data-fav]').forEach(btn => { btn.addEventListener('click', () => { toggleFavorite(btn.dataset.fav); renderGrid(filtered, currentPage); }); }); grid.querySelectorAll('[data-cart]').forEach(btn => { btn.addEventListener('click', () => { addToCart(btn.dataset.cart); btn.textContent = 'Added'; setTimeout(() => btn.textContent = 'Add', 1200); }); }); grid.querySelectorAll('[data-details]').forEach(btn => { btn.addEventListener('click', () => { showDetailModal(btn.dataset.details); }); }); } function renderPagination(filtered) { const pager = document.querySelector('[data-pagination]'); pager.innerHTML = ''; const pages = Math.ceil(filtered.length / perPage); if (pages <= 1) return; for (let i = 1; i <= pages; i++) { const a = document.createElement('a'); a.href = '#'; a.textContent = i; a.className = `rounded-full border px-4 py-2 text-sm font-semibold transition ${i === currentPage ? 'bg-[#B85C4A] text-white border-[#B85C4A]' : 'border-[#DCCFC0] hover:bg-[#F1E2D5]'}`; a.addEventListener('click', (e) => { e.preventDefault(); currentPage = i; const searchEl = document.querySelector('[data-search]'); const catEl = document.querySelector('[data-category]'); const filteredNow = filterProducts(searchEl.value, catEl.value); renderGrid(filteredNow, currentPage); renderPagination(filteredNow); }); pager.appendChild(a); } } function showDetailModal(id) { const product = allProducts.find(p => p.id === id); if (!product) return; const modal = document.querySelector('[data-detail-modal]'); const content = modal.querySelector('[data-detail-content]'); let outcomesHTML = product.outcomes.map(o => `
  • ${o}
  • `).join(''); content.innerHTML = `
    ${product.level}

    ${product.title}

    ${product.description}
    What you achieve
    ${product.duration}
    Course length
    $${product.price}
    `; modal.classList.remove('hidden'); modal.classList.add('flex'); const closeBtn = content.querySelector('[data-close-detail]'); closeBtn.onclick = () => { modal.classList.remove('flex'); modal.classList.add('hidden'); }; content.querySelector('[data-add-from-modal]').onclick = () => { addToCart(product.id); modal.classList.remove('flex'); modal.classList.add('hidden'); }; modal.onclick = (e) => { if (e.target === modal) { modal.classList.remove('flex'); modal.classList.add('hidden'); } }; } function initCatalog() { const search = document.querySelector('[data-search]'); const category = document.querySelector('[data-category]'); function applyFilters() { currentPage = 1; const filtered = filterProducts(search.value, category.value); renderGrid(filtered, currentPage); renderPagination(filtered); } search.addEventListener('input', applyFilters); category.addEventListener('change', applyFilters); // Initial render const initial = filterProducts('', 'all'); renderGrid(initial, currentPage); renderPagination(initial); } async function init() { injectHeaderFooter(); initHeaderFeatures(); await fetchCatalog(); populateCategories(); initCatalog(); // Show cookie banner if needed setTimeout(() => { const banner = document.querySelector('[data-cookie-banner]'); if (banner && !<|eos|>