alalver.pro

Catálogo de cursos

Buscar y filtrar

Atajos: "/" buscar, "f" favoritos, "c" carrito.

odal.classList.remove('hidden'); trapFocus(modal); } function alerta(msg){ const el = document.createElement('div'); el.className='fixed bottom-6 right-6 z-50 px-4 py-2 rounded-md bg-gray-900 text-white dark:bg-white dark:text-gray-900 shadow k0toz'; el.textContent=msg; document.body.appendChild(el); setTimeout(()=>{ el.classList.add('opacity-0'); el.style.transition='opacity .3s'; },1400); setTimeout(()=>el.remove(),1800); } // Close modals on overlay/Escape function escClose(e){ if (e.key==='Escape'){ document.getElementById('modal-detalle').classList.add('hidden'); document.getElementById('modal-comparar').classList.add('hidden'); document.getElementById('cookie-modal').classList.add('hidden'); } } document.addEventListener('keydown', escClose); document.getElementById('modal-detalle').addEventListener('click',(e)=>{ if(e.target.matches('[data-close-detalle], [data-close-detalle] *')) e.currentTarget.classList.add('hidden'); }); document.getElementById('modal-comparar').addEventListener('click',(e)=>{ if(e.target.matches('[data-close-comparar], [data-close-comparar] *')) e.currentTarget.classList.add('hidden'); }); function trapFocus(modal){ const selectors = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'; const focusables = modal.querySelectorAll(selectors); if (!focusables.length) return; const first = focusables[0], last = focusables[focusables.length-1]; function handle(e){ if (e.key!=='Tab') return; if (e.shiftKey && document.activeElement===first){ last.focus(); e.preventDefault(); } else if (!e.shiftKey && document.activeElement===last){ first.focus(); e.preventDefault(); } } modal.addEventListener('keydown', handle, {once:true}); setTimeout(()=>first.focus(), 10); } // Infinite scroll document.getElementById('modo-infinito').addEventListener('change', ()=>{ guardarFiltros(); if (document.getElementById('modo-infinito').checked) { document.getElementById('paginacion').innerHTML=''; } else { renderPaginacion(); window.scrollTo({top:0,behavior:'smooth'}); } }); window.addEventListener('scroll', ()=>{ if (!document.getElementById('modo-infinito').checked) return; const nearBottom = window.innerHeight + window.scrollY >= document.body.offsetHeight - 200; if (nearBottom) { const total = state.filtrados.length; const pags = Math.ceil(total/state.porPagina); if (state.pagina < pags) { state.pagina++; renderGrid(true); } } }); // Controls document.getElementById('btn-aplicar').addEventListener('click', aplicarFiltros); document.getElementById('btn-limpiar').addEventListener('click', ()=>{ document.getElementById('f-buscar').value=''; document.getElementById('f-categoria').value=''; document.getElementById('f-nivel').value=''; document.getElementById('f-online').checked=false; document.getElementById('f-presencial').checked=false; document.getElementById('f-min').value=''; document.getElementById('f-max').value=''; document.getElementById('f-orden').value='relevancia'; document.getElementById('modo-infinito').checked=false; aplicarFiltros(); }); ['f-buscar','f-categoria','f-nivel','f-online','f-presencial','f-min','f-max','f-orden'].forEach(id=>{ const el = document.getElementById(id); el && el.addEventListener('change', ()=>aplicarFiltros()); el && el.addEventListener('keyup', (e)=>{ if (id==='f-buscar' && e.key==='Enter') aplicarFiltros(); }); }); // Newsletter form document.getElementById('form-news').addEventListener('submit', (e)=>{ e.preventDefault(); const name = document.getElementById('news-name').value.trim(); const email = document.getElementById('news-email').value.trim(); const err = document.getElementById('news-error'); const ok = document.getElementById('news-ok'); err.classList.add('hidden'); ok.classList.add('hidden'); if (name.length<2){ err.textContent='Por favor ingresá tu nombre.'; err.classList.remove('hidden'); return; } const emailRe = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/i; if (!emailRe.test(email)){ err.textContent='Ingresá un email válido.'; err.classList.remove('hidden'); return; } const subs = JSON.parse(localStorage.getItem('newsletter-subs')||'[]'); if (!subs.find(s=>s.email.toLowerCase()===email.toLowerCase())) subs.push({name,email,ts:Date.now()}); localStorage.setItem('newsletter-subs', JSON.stringify(subs)); ok.classList.remove('hidden'); e.target.reset(); }); // Cookie consent function showCookieBanner(){ const pref = localStorage.getItem('cookie-consent'); if (!pref) document.getElementById('cookie-banner').classList.remove('hidden'); } function saveCookiePrefs(prefs){ localStorage.setItem('cookie-consent', JSON.stringify(prefs)); document.getElementById('cookie-banner').classList.add('hidden'); document.getElementById('cookie-modal').classList.add('hidden'); } document.getElementById('cookie-accept').addEventListener('click', ()=>saveCookiePrefs({essential:true, analytics:true, ts:Date.now()})); document.getElementById('cookie-settings').addEventListener('click', ()=>{ const pref = JSON.parse(localStorage.getItem('cookie-consent')||'{}'); document.getElementById('cookie-analytics').checked = !!pref.analytics; const m = document.getElementById('cookie-modal'); m.classList.remove('hidden'); trapFocus(m); }); document.getElementById('cookie-modal').addEventListener('click',(e)=>{ if(e.target.matches('[data-close-cookie], [data-close-cookie] *')) e.currentTarget.classList.add('hidden'); }); document.getElementById('cookie-save').addEventListener('click', ()=>{ const analytics = document.getElementById('cookie-analytics').checked; saveCookiePrefs({essential:true, analytics, ts:Date.now()}); }); showCookieBanner(); // SEO JSON-LD dynamic ItemList function injectJSONLD(courses){ try { const list = courses.slice(0,10).map((c,i)=>({ '@type':'ListItem', position: i+1, url: `https://alalver.pro/catalog.html#${encodeURIComponent(c.slug)}`, name: c.title })); const data = { '@context':'https://schema.org', '@type':'ItemList', itemListElement: list }; const s = document.createElement('script'); s.type='application/ld+json'; s.textContent = JSON.stringify(data); document.head.appendChild(s); } catch(e){} } // Countdown to nearest start date function setupCountdown(){ if (state.countdownTimer) { clearInterval(state.countdownTimer); state.countdownTimer = null; } const upcoming = []; const now = new Date(); state.cursos.forEach(c=>{ (c.start_dates||[]).forEach(d=>{ const dt = new Date(d+'T00:00:00'); if (dt > now) upcoming.push({date: dt, title: c.title}); }); }); if (!upcoming.length) return; upcoming.sort((a,b)=>a.date-b.date); const next = upcoming[0]; const bar = document.getElementById('next-cohort-bar'); const nameEl = document.getElementById('next-cohort-name'); const dateEl = document.getElementById('next-cohort-date'); const countdownEl = document.getElementById('next-countdown'); nameEl.textContent = next.title; dateEl.textContent = next.date.toLocaleDateString('es-AR', {year:'numeric', month:'long', day:'numeric'}); bar.classList.remove('hidden'); function tick(){ const now = new Date(); let diff = Math.max(0, next.date - now); const days = Math.floor(diff/(1000*60*60*24)); diff -= days*24*60*60*1000; const hrs = Math.floor(diff/(1000*60*60)); diff -= hrs*60*60*1000; const mins = Math.floor(diff/(1000*60)); diff -= mins*60*1000; const secs = Math.floor(diff/1000); countdownEl.textContent = `${String(days).padStart(2,'0')}d ${String(hrs).padStart(2,'0')}:${String(mins).padStart(2,'0')}:${String(secs).padStart(2,'0')}`; if (next.date <= now){ clearInterval(state.countdownTimer); bar.classList.add('hidden'); } } tick(); state.countdownTimer = setInterval(tick, 1000); } // Init renderLoading(); fetch('./catalog.json').then(r=>r.json()).then(data=>{ state.cursos = data; // fill categories const cats = Array.from(new Set(data.map(c=>c.category))).sort(); const sel = document.getElementById('f-categoria'); cats.forEach(c=>{ const o = document.createElement('option'); o.value=c; o.textContent=c; sel.appendChild(o); }); // filters from storage cargarFiltros(); state.filtrados = data.slice(); aplicarFiltros(); setupCountdown(); injectJSONLD(state.cursos); // hash deep-link detail const slug = decodeURIComponent((location.hash||'').replace('#','')); if (slug) { const item = state.cursos.find(c=>c.slug===slug); if (item) abrirDetalle(item.id); } }); // Accessibility: focus main on load window.addEventListener('load', ()=>{ const main = document.querySelector('main'); main.setAttribute('tabindex','-1'); main.focus({preventScroll:true}); });