Welcome back
This demonstration account panel is ready for a future secure authentication service.
Contact the team
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.
`;
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}
${venue.amenities.map(a => `- ${a}
`).join('')}
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();
})();