VenueHearth

The room finder

Find the right room for the right sound.

Explore practical venue notes and atmosphere-led spaces for concerts, showcases, listening sessions, and community performances.

View Fallows
`; const footerHTML = ` `; headerContainer.innerHTML = headerHTML; footerContainer.innerHTML = footerHTML; // header/footer logic reuse const header = headerContainer; const menuToggle = header.querySelector('[data-menu-toggle]'); const navMenu = header.querySelector('[data-menu]'); const themeToggle = header.querySelector('[data-theme-toggle]'); const authButtons = header.querySelectorAll('[data-auth]'); const authModal = header.querySelector('[data-auth-modal]'); const closeModal = authModal.querySelector('[data-close-modal]'); const authTitle = authModal.querySelector('[data-auth-title]'); let currentTheme = localStorage.getItem('venuehearth-theme') || 'light'; function applyTheme(theme) { if (theme === 'dark') { document.documentElement.classList.add('dark'); document.documentElement.style.setProperty('--bg-main', '#352319'); } else { document.documentElement.classList.remove('dark'); document.documentElement.style.setProperty('--bg-main', '#f4ecdf'); } localStorage.setItem('venuehearth-theme', theme); } applyTheme(currentTheme); themeToggle.addEventListener('click', () => { currentTheme = currentTheme === 'light' ? 'dark' : 'light'; applyTheme(currentTheme); }); menuToggle.addEventListener('click', () => { navMenu.classList.toggle('hidden'); navMenu.classList.toggle('flex'); navMenu.classList.toggle('flex-col'); navMenu.classList.toggle('absolute'); navMenu.classList.toggle('top-full'); navMenu.classList.toggle('left-0'); navMenu.classList.toggle('w-full'); navMenu.classList.toggle('bg-[#f4ecdf]'); navMenu.classList.toggle('border-b'); navMenu.classList.toggle('border-[#cdb99f]'); navMenu.classList.toggle('px-6'); navMenu.classList.toggle('py-4'); navMenu.classList.toggle('gap-3'); }); authButtons.forEach(btn => { btn.addEventListener('click', () => { const mode = btn.getAttribute('data-auth'); authTitle.textContent = mode === 'login' ? 'Welcome back' : 'Create your account'; authModal.classList.remove('hidden'); authModal.classList.add('flex'); }); }); closeModal.addEventListener('click', () => { authModal.classList.remove('flex'); authModal.classList.add('hidden'); }); authModal.addEventListener('click', (e) => { if (e.target === authModal) { authModal.classList.remove('flex'); authModal.classList.add('hidden'); } }); // cookie banner const footer = footerContainer; const banner = footer.querySelector('[data-cookie-banner]'); const acceptBtn = footer.querySelector('[data-cookie-accept]'); const consentKey = 'venuehearth-cookies-accepted'; if (!localStorage.getItem(consentKey)) { banner.classList.remove('hidden'); banner.classList.add('flex'); } acceptBtn.addEventListener('click', () => { localStorage.setItem(consentKey, 'true'); banner.classList.remove('flex'); banner.classList.add('hidden'); }); // catalog logic const searchInput = document.getElementById('search'); const typeSelect = document.getElementById('type'); const citySelect = document.getElementById('city'); const venueGrid = document.getElementById('venue-grid'); const pagination = document.getElementById('pagination'); const status = document.getElementById('catalog-status'); const detailModal = document.getElementById('detail-modal'); const closeDetail = document.getElementById('close-detail'); const detailContent = document.getElementById('detail-content'); let venues = []; let filtered = []; let currentPage = 1; const perPage = 6; async function loadData() { try { const res = await fetch('./catalog.json'); venues = await res.json(); } catch (e) { venues = [ {id:"v1",name:"The Timber Hall",city:"Portland",type:"Theater",capacity:280,hourlyRate:185,description:"Warm wooden acoustics perfect for intimate sets.",amenities:["Sound System","Green Room","Lighting"],availability:"Available",image:"./images/venue_timber_hall_portland_theater.jpg"}, {id:"v2",name:"The Loft at Harbor",city:"San Francisco",type:"Warehouse",capacity:450,hourlyRate:260,description:"Industrial concrete with river views for electronic nights.",amenities:["Stage","Bar Area","Restrooms"],availability:"Available",image:"./images/venue_loft_harbor_sanfrancisco_warehouse.jpg"}, {id:"v3",name:"Evergreen Chapel",city:"Seattle",type:"Church",capacity:120,hourlyRate:145,description:"Sunlit vaulted ceilings and vintage pipe organs.",amenities:["Organ","Seating","Parking"],availability:"Limited",image:"./images/venue_evergreen_chapel_seattle_church.jpg"} ]; } populateFilters(); applyFilters(); } function populateFilters() { const types = [...new Set(venues.map(v => v.type))]; const cities = [...new Set(venues.map(v => v.city))]; types.forEach(t => { const opt = document.createElement('option'); opt.value = t; opt.textContent = t; typeSelect.appendChild(opt); }); cities.forEach(c => { const opt = document.createElement('option'); opt.value = c; opt.textContent = c; citySelect.appendChild(opt); }); } function applyFilters() { const term = searchInput.value.toLowerCase(); const tFilter = typeSelect.value; const cFilter = citySelect.value; filtered = venues.filter(v => (v.name.toLowerCase().includes(term) || v.city.toLowerCase().includes(term)) && (!tFilter || v.type === tFilter) && (!cFilter || v.city === cFilter) ); currentPage = 1; render(); } function render() { venueGrid.innerHTML = ''; const start = (currentPage - 1) * perPage; const items = filtered.slice(start, start + perPage); status.textContent = `${filtered.length} venues found`; if (items.length === 0) { venueGrid.innerHTML = '

No venues match your criteria.

'; } items.forEach(v => { const card = document.createElement('div'); card.className = 'border border-[#cdb99f] bg-[#f4ecdf] rounded-3xl p-6 flex flex-col'; card.innerHTML = `
${v.city} · ${v.type}

${v.name}

${v.description}

Capacity: ${v.capacity} · $${v.hourlyRate}/hr
`; venueGrid.appendChild(card); }); renderPagination(); attachCardListeners(); } function renderPagination() { pagination.innerHTML = ''; const totalPages = Math.ceil(filtered.length / perPage); for (let i = 1; i <= totalPages; i++) { const btn = document.createElement('button'); btn.textContent = i; btn.className = `px-4 py-2 border border-[#cdb99f] rounded-full text-sm ${i === currentPage ? 'bg-[#5a3825] text-[#fffaf2]' : ''}`; btn.onclick = () => { currentPage = i; render(); }; pagination.appendChild(btn); } } function attachCardListeners() { venueGrid.querySelectorAll('button').forEach(btn => { btn.addEventListener('click', () => { const action = btn.getAttribute('data-action'); const id = btn.getAttribute('data-id'); const venue = venues.find(v => v.id === id); if (action === 'details') showDetail(venue); if (action === 'fallow') saveFallow(id); if (action === 'cart') addToCart(id); }); }); } function showDetail(venue) { detailContent.innerHTML = `
${venue.city} — ${venue.type}

${venue.name}

${venue.description}
Capacity: ${venue.capacity}
Hourly Rate: $${venue.hourlyRate}
Availability: ${venue.availability}
Book this venue`; detailModal.classList.remove('hidden'); detailModal.classList.add('grid'); } closeDetail.addEventListener('click', () => { detailModal.classList.remove('grid'); detailModal.classList.add('hidden'); }); document.addEventListener('keydown', e => { if (e.key === 'Escape' && !detailModal.classList.contains('hidden')) { detailModal.classList.remove('grid'); detailModal.classList.add('hidden'); } }); function saveFallow(id) { let falls = JSON.parse(localStorage.getItem('venuehearth-fallows') || '[]'); if (!falls.includes(id)) falls.push(id); localStorage.setItem('venuehearth-fallows', JSON.stringify(falls)); alert('Saved to Fallows'); } function addToCart(id) { let cart = JSON.parse(localStorage.getItem('venuehearth-cart') || '[]'); const existing = cart.find(i => i.id === id); if (existing) existing.qty++; else cart.push({id, qty:1}); localStorage.setItem('venuehearth-cart', JSON.stringify(cart)); alert('Added to cart'); } searchInput.addEventListener('input', applyFilters); typeSelect.addEventListener('change', applyFilters); citySelect.addEventListener('change', applyFilters); loadData(); })();