Catálogo
'; document.getElementById('theme-fallback')?.addEventListener('click', toggleTheme); });
fetch('./footer.html').then(r=>r.text()).then(f=>{ document.querySelector('footer').innerHTML = f; bindFooter(); }).catch(()=>{ document.querySelector('footer').innerHTML = ''; });
}
function toggleTheme(){
document.documentElement.classList.toggle('dark');
localStorage.setItem('theme', document.documentElement.classList.contains('dark')?'dark':'light');
}
function bindHeader(){
document.querySelectorAll('[data-modal-target]').forEach(b=> b.onclick = ()=> document.getElementById(b.dataset.modalTarget).showModal());
document.getElementById('theme-toggle')?.addEventListener('click', toggleTheme);
}
function bindFooter(){
document.getElementById('cookies-accept')?.addEventListener('click', ()=> {
localStorage.setItem('cookiesAccepted','1');
document.getElementById('cookies-banner')?.classList.add('hidden');
});
}
(function initTheme(){ if(localStorage.getItem('theme')==='dark') document.documentElement.classList.add('dark'); })();
injectHF();
function getFallows(){ return JSON.parse(localStorage.getItem('fallows')||'[]'); }
function setFallows(v){ localStorage.setItem('fallows', JSON.stringify(v)); }
function getCart(){ return JSON.parse(localStorage.getItem('cart')||'[]'); }
function setCart(v){ localStorage.setItem('cart', JSON.stringify(v)); }
function renderSkeleton(n=8){
const g = el('grid'); g.innerHTML='';
for(let i=0;i
`;
g.appendChild(s);
}
const p = el('pagination'); p.innerHTML='';
}
function syncURL(){
const params = new URLSearchParams();
const qv = el('q').value.trim(); if(qv) params.set('q', qv);
const cv = el('category').value; if(cv) params.set('category', cv);
const lv = el('location').value; if(lv) params.set('location', lv);
const pv = el('priceMax').value; if(pv) params.set('priceMax', pv);
const dv = el('durationMin').value; if(dv) params.set('durationMin', dv);
if(state.page>1) params.set('page', String(state.page));
const qs = params.toString();
history.replaceState(null, '', qs ? ('?'+qs) : location.pathname + location.hash);
}
function restoreFiltersFromURL(){
const p = new URLSearchParams(location.search);
if(p.has('q')) el('q').value = p.get('q') || '';
if(p.has('category')) el('category').value = p.get('category') || '';
if(p.has('location')) el('location').value = p.get('location') || '';
if(p.has('priceMax')) el('priceMax').value = p.get('priceMax') || '';
if(p.has('durationMin')) el('durationMin').value = p.get('durationMin') || '';
if(p.has('page')) { const pg = parseInt(p.get('page')||'1',10); if(!isNaN(pg)&&pg>0) state.page=pg; }
}
async function loadCatalog(){
try{
state.loading = true;
renderSkeleton(state.perPage);
const res = await fetch('./catalog.json', {cache:'no-store'});
if(!res.ok) throw new Error('Network');
const data = await res.json();
state.data = Array.isArray(data)?data:[];
// categories
state.categories.clear();
state.data.forEach(p=> state.categories.add(p.category));
const catSel = el('category');
const currentCat = catSel.value;
catSel.innerHTML = '';
[...state.categories].sort().forEach(c=>{
const o = document.createElement('option'); o.value=c; o.textContent=c; catSel.appendChild(o);
});
if(currentCat) catSel.value = currentCat;
restoreFiltersFromURL();
applyFilters(false);
renderLdItemList();
// open from hash
if(location.hash && location.hash.length>1){
const slug = location.hash.slice(1);
const item = state.data.find(p=> p.slug === slug);
if(item) openDetails(item.id);
}
}catch(e){
const g = el('grid');
g.innerHTML = 'No se pudo cargar el catálogo. Intenta nuevamente.
';
}finally{
state.loading = false;
}
}
function applyFilters(updateUrl = true){
const q = el('q').value.trim().toLowerCase();
const cat = el('category').value;
const loc = el('location').value;
const pmax = parseInt(el('priceMax').value || '0', 10);
const dmin = parseInt(el('durationMin').value || '0', 10);
state.filtered = state.data.filter(p=>{
if(cat && p.category !== cat) return false;
if(loc && p.location !== loc) return false;
if(pmax && p.price > pmax) return false;
if(dmin && p.durationMinutes < dmin) return false;
if(q){
const blob = (p.title + ' ' + p.category + ' ' + p.tags.join(' ') + ' ' + p.description).toLowerCase();
if(!blob.includes(q)) return false;
}
return true;
});
if(state.page > Math.ceil(state.filtered.length/state.perPage)) state.page = 1;
el('results-count').textContent = state.filtered.length;
el('page-indicator').textContent = state.page;
renderGrid();
renderPagination();
if(updateUrl) syncURL();
}
function renderGrid(){
const start = (state.page-1)*state.perPage;
const visible = state.filtered.slice(start, start+state.perPage);
const favs = new Set(getFallows());
const g = el('grid'); g.innerHTML='';
if(visible.length === 0){
g.innerHTML = 'No hay resultados para los filtros aplicados.
';
return;
}
visible.forEach(p=>{
const card = document.createElement('article');
card.id = p.slug || p.id;
const disable = !p.availability;
const availability = p.availability ? 'Disponible' : 'Agotado';
card.className = 'overflow-hidden rounded-2xl border border-slate-200 dark:border-slate-800 c-raise bg-white dark:bg-slate-900';
card.innerHTML = `
${p.title}
${availability}
${p.category} • ${p.location} • ${p.durationMinutes} min
${p.price.toLocaleString('es-CL')} CLP
${'★★★★★'.slice(0, Math.round(p.rating||5))}
`;
g.appendChild(card);
});
g.querySelectorAll('.fav-btn').forEach(b=> b.addEventListener('click', (ev)=>{
ev.stopPropagation();
const id = b.dataset.id;
const arr = getFallows();
const i = arr.indexOf(id);
if(i>-1){ arr.splice(i,1); } else { arr.push(id); }
setFallows(arr);
applyFilters();
}));
g.querySelectorAll('.details-btn').forEach(b=> b.addEventListener('click', ()=> openDetails(b.dataset.id)));
g.querySelectorAll('.add-cart-btn').forEach(b=> b.addEventListener('click', ()=>{
if(b.disabled) return;
const cart = getCart();
const id = b.dataset.id;
const found = cart.find(i=>i.id===id);
if(found){ found.quantity += 1; } else { cart.push({id, quantity:1}); }
setCart(cart);
toast('Añadido al carrito');
}));
}
function renderPagination(){
const total = Math.ceil(state.filtered.length / state.perPage) || 1;
const p = el('pagination'); p.innerHTML='';
const mkBtn = (label, page, disabled=false, isCurrent=false)=>{
const btn = document.createElement('button');
btn.textContent = label;
btn.className = 'p2aql rounded-full border px-3 py-1 text-sm ' + (isCurrent?'bg-slate-900 text-white border-slate-900 dark:bg-slate-100 dark:text-slate-900 dark:border-slate-100':'border-slate-300 hover:bg-slate-50 dark:border-slate-700 dark:hover:bg-slate-800');
btn.disabled = disabled || isCurrent;
btn.addEventListener('click', ()=>{
state.page = page;
el('page-indicator').textContent = state.page;
renderGrid(); renderPagination();
window.scrollTo({top:0, behavior:'smooth'});
syncURL();
});
return btn;
};
p.appendChild(mkBtn('«', 1, state.page===1));
p.appendChild(mkBtn('‹', Math.max(1, state.page-1), state.page===1));
for(let i=1;i<=total;i++){
if(i===1 || i===total || Math.abs(i-state.page)<=1){
p.appendChild(mkBtn(String(i), i, false, i===state.page));
}else if(Math.abs(i-state.page)===2){
const dot = document.createElement('span'); dot.textContent='…'; dot.className='px-1 text-slate-400';
p.appendChild(dot);
}
}
p.appendChild(mkBtn('›', Math.min(total, state.page+1), state.page===total));
p.appendChild(mkBtn('»', total, state.page===total));
}
function openDetails(id){
const p = state.data.find(x=>x.id===id);
const c = document.getElementById('details-content');
if(!p) return;
c.innerHTML = `
${p.title}
${p.description}
${Array.isArray(p.features)?p.features.map(f=>`- • ${f}
`).join(''):''}
${p.price.toLocaleString('es-CL')} CLP
${p.availability?'Disponible':'Agotado'}
${p.category} • ${p.location} • ${p.durationMinutes} min
chiladros.pro
`;
const dlg = document.getElementById('details-modal');
dlg.showModal();
c.querySelector('.close-modal').onclick = ()=> dlg.close();
c.querySelector('.add-cart-modal')?.addEventListener('click', (e)=> {
const cart = getCart();
const id = e.target.dataset.id;
const found = cart.find(i=>i.id===id);
if(found){ found.quantity += 1; } else { cart.push({id, quantity:1}); }
setCart(cart);
toast('Añadido al carrito');
dlg.close();
});
c.querySelector('.add-fav-modal').onclick = (e)=> {
const arr = getFallows(); const id = e.target.dataset.id;
if(!arr.includes(id)) arr.push(id);
setFallows(arr);
toast('Guardado en favoritos');
applyFilters();
};
dlg.addEventListener('click', (e)=>{
const rect = c.getBoundingClientRect();
const inDialog = e.clientX >= rect.left && e.clientX <= rect.right && e.clientY >= rect.top && e.clientY <= rect.bottom;
if(!inDialog) dlg.close();
}, {once:true});
}
function toast(msg){
let t = document.querySelector('#toast-x3l9p');
if(!t){
t = document.createElement('div');
t.id='toast-x3l9p';
t.className='fixed bottom-4 left-1/2 -translate-x-1/2 z-50';
document.body.appendChild(t);
}
const note = document.createElement('div');
note.className='mb-2 rounded-full bg-slate-900 px-4 py-2 text-white shadow dark:bg-slate-100 dark:text-slate-900';
note.textContent = msg;
t.appendChild(note);
setTimeout(()=>{ note.style.transition='opacity .3s ease, transform .3s ease'; note.style.opacity='0'; note.style.transform='translateY(6px)'; }, 1400);
setTimeout(()=> note.remove(), 1800);
}
['q','category','location','priceMax','durationMin'].forEach(id=> document.getElementById(id).addEventListener('input', ()=> applyFilters()));
document.getElementById('resetFilters').addEventListener('click', ()=>{
['q','category','location','priceMax','durationMin'].forEach(id=> document.getElementById(id).value='');
state.page = 1;
applyFilters();
});
window.addEventListener('keydown', (e)=> {
if(e.key==='Escape'){ document.getElementById('details-modal')?.close(); }
if((e.ctrlKey || e.metaKey) && e.key.toLowerCase()==='k'){ e.preventDefault(); el('q').focus(); }
});
window.addEventListener('hashchange', ()=>{
if(location.hash && location.hash.length>1){
const slug = location.hash.slice(1);
const item = state.data.find(p=> p.slug === slug);
if(item) openDetails(item.id);
}
});
loadCatalog();
function renderLdItemList(){
const itemList = {
"@context":"https://schema.org",
"@type":"ItemList",
"itemListElement": state.data.slice(0,10).map((p,i)=> ({
"@type":"ListItem",
"position": i+1,
"url": "https://chiladros.pro/catalog.html#"+p.slug,
"name": p.title
}))
};
const s = document.createElement('script'); s.type='application/ld+json'; s.textContent = JSON.stringify(itemList);
document.head.appendChild(s);
}