Sign in to Vivid Hearth Studio
This demonstration form keeps your learning space ready for future account features.
Create your learning profile
Explore the catalog first, then contact us if you need help choosing a path.
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}
${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|>