Cart

Course Price Qty Subtotal Action
Total: $0.00

Order Confirmation

Your order details are ready. This is a confirmation preview since no server processing occurs here.

We use cookies

We use essential and analytics cookies to improve your experience. You can accept to continue.

') ]); document.querySelector('header').innerHTML = h; document.querySelector('footer').innerHTML = f; bindHeader(); maybeShowCookies(); } function bindHeader(){ document.body.addEventListener('click',(e)=>{ if (e.target.closest('[data-close]')) e.target.closest('dialog')?.close(); }); const themeBtn=document.getElementById('btn-theme'); const themeDialog=document.getElementById('modal-theme'); const themeClose=document.getElementById('theme-close'); if (themeBtn&&themeDialog) themeBtn.addEventListener('click',()=>themeDialog.showModal()); if (themeClose&&themeDialog) themeClose.addEventListener('click',()=>themeDialog.close()); document.getElementById('theme-form')?.addEventListener('submit',(e)=>{ e.preventDefault(); const val=new FormData(e.target).get('theme'); if (val==='dark') document.documentElement.classList.add('dark'); else document.documentElement.classList.remove('dark'); localStorage.setItem('theme', val); themeDialog?.close(); }); } function maybeShowCookies(){ const ok=localStorage.getItem('cookiesAccepted'); const dlg=document.getElementById('modal-cookies'); if (!ok && dlg && typeof dlg.showModal==='function') dlg.showModal(); document.getElementById('btn-accept-cookies')?.addEventListener('click',()=>{localStorage.setItem('cookiesAccepted','1'); document.getElementById('modal-cookies')?.close();}); } (function initThemeOnLoad(){ const saved=localStorage.getItem('theme')||'light'; if (saved==='dark') document.documentElement.classList.add('dark'); else document.documentElement.classList.remove('dark'); })(); async function init(){ await injectPartials(); try{ catalog = await fetch('catalog.json').then(r=>r.json()); }catch(e){ catalog = []; } cart = JSON.parse(localStorage.getItem('cart')||'[]'); normalizeCart(); render(); document.getElementById('coupon-form').addEventListener('submit',(e)=>{ e.preventDefault(); const code = new FormData(e.target).get('coupon')?.toString().trim().toUpperCase(); const note = document.createElement('div'); if (code === 'SAVE10') { localStorage.setItem('coupon','SAVE10'); note.textContent = 'Coupon applied: 10% off.'; note.className = 'text-green-700'; } else { localStorage.removeItem('coupon'); note.textContent = 'Invalid coupon.'; note.className = 'text-red-700'; } e.target.appendChild(note); setTimeout(()=> note.remove(), 1800); render(); }); document.getElementById('checkout-form').addEventListener('submit',onCheckoutSubmit); document.getElementById('btn-finalize').addEventListener('click',finalizeOrder); } function normalizeCart(){ cart = cart .filter(x=>x && typeof x.id!=='undefined') .map(x=>({id:x.id, qty: Math.max(1, parseInt(x.qty||1,10))})); localStorage.setItem('cart', JSON.stringify(cart)); } function render(){ const body = document.getElementById('cart-body'); body.innerHTML = ''; const items = cart.map(ci=>{ const c = catalog.find(x=>String(x.id)===String(ci.id)); return c ? { ...c, qty: ci.qty||1 } : null; }).filter(Boolean); if (items.length===0){ document.getElementById('cart-empty').classList.remove('hidden'); } else { document.getElementById('cart-empty').classList.add('hidden'); } items.forEach(it=>{ const tr = document.createElement('tr'); tr.innerHTML = `
${escapeHTML(it.title||'Untitled')}
${escapeHTML(it.level||'')}
$${Number(it.priceUSD||0).toFixed(2)} $${(Number(it.priceUSD||0)*it.qty).toFixed(2)} `; body.appendChild(tr); }); body.querySelectorAll('.qty').forEach(inp=>{ inp.addEventListener('change', ()=>{ const id = inp.getAttribute('data-id'); const val = Math.max(1, parseInt(inp.value||'1',10)); const f = cart.find(x=>String(x.id)===String(id)); if (f) f.qty = val; localStorage.setItem('cart', JSON.stringify(cart)); render(); const cartEl = document.getElementById('count-cart'); if (cartEl) cartEl.textContent = cart.reduce((a,c)=>a+(c.qty||1),0); }); inp.addEventListener('input', ()=>{ if (inp.value === '') return; if (parseInt(inp.value,10) < 1) inp.value = '1'; }); }); body.querySelectorAll('.remove').forEach(btn=> btn.addEventListener('click', ()=>{ const id = btn.getAttribute('data-id'); cart = cart.filter(x=>String(x.id)!==String(id)); localStorage.setItem('cart', JSON.stringify(cart)); render(); const cartEl = document.getElementById('count-cart'); if (cartEl) cartEl.textContent = cart.reduce((a,c)=>a+(c.qty||1),0); })); const coupon = localStorage.getItem('coupon'); let total = items.reduce((a,c)=> a + Number(c.priceUSD||0) * c.qty, 0); if (coupon === 'SAVE10') total = total * 0.9; document.getElementById('total').textContent = total.toFixed(2); const badge = document.getElementById('coupon-badge'); if (coupon === 'SAVE10') { badge.classList.remove('hidden'); } else { badge.classList.add('hidden'); } const cartEl = document.getElementById('count-cart'); if (cartEl) cartEl.textContent = items.reduce((a,c)=>a+(c.qty||1),0); } function onCheckoutSubmit(e){ e.preventDefault(); if (!validateForm(e.target)) return; const formData = new FormData(e.target); const name = String(formData.get('name')||'').trim(); const email = String(formData.get('email')||'').trim(); const summaryNode = document.getElementById('confirm-summary'); const items = cart.map(ci=>{ const c = catalog.find(x=>String(x.id)===String(ci.id)); return c ? { ...c, qty: ci.qty||1 } : null; }).filter(Boolean); const coupon = localStorage.getItem('coupon'); let subtotal = items.reduce((a,c)=> a + Number(c.priceUSD||0) * c.qty, 0); let discount = coupon==='SAVE10' ? subtotal * 0.10 : 0; let total = subtotal - discount; const orderId = 'SSA-' + Math.random().toString(36).slice(2,7).toUpperCase() + '-' + Date.now().toString().slice(-4); summaryNode.innerHTML = `
Customer
${escapeHTML(name)} • ${escapeHTML(email)}
Items
Subtotal$${subtotal.toFixed(2)}
Discount ${coupon? '('+coupon+')':''}-${discount.toFixed(2)}
Total$${total.toFixed(2)}
Order ID: ${orderId}
`; const dlg = document.getElementById('modal-confirm'); if (!dlg.open) dlg.showModal(); dlg.dataset.orderId = orderId; } function finalizeOrder(){ const dlg = document.getElementById('modal-confirm'); dlg.close(); localStorage.removeItem('cart'); localStorage.removeItem('coupon'); cart = []; render(); const t = document.getElementById('toast'); t.textContent = 'Order '+ (dlg.dataset.orderId||'') +' placed successfully'; t.classList.remove('hidden'); setTimeout(()=> t.classList.add('hidden'), 2400); const form = document.getElementById('checkout-form'); form.reset(); } function validateForm(form){ const name = form.querySelector('input[name="name"]'); const email = form.querySelector('input[name="email"]'); let ok = true; clearFieldError(name); clearFieldError(email); if (!/^[A-Za-z][A-Za-z\s'.-]{1,}$/.test(name.value.trim())){ setFieldError(name,'Please enter your full name.'); ok = false; } if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.value.trim())){ setFieldError(email,'Please enter a valid email.'); ok = false; } return ok; } function setFieldError(input, msg){ input.classList.add('border-red-500'); let hint = input.nextElementSibling; if (!hint || !hint.classList || !hint.classList.contains('uj4sw')){ hint = document.createElement('div'); hint.className = 'text-red-600 text-sm uj4sw'; input.insertAdjacentElement('afterend', hint); } hint.textContent = msg; } function clearFieldError(input){ input.classList.remove('border-red-500'); let hint = input.nextElementSibling; if (hint && hint.classList && hint.classList.contains('uj4sw')) hint.remove(); } function escapeHTML(s){ return String(s).replace(/[&<>"']/g, m=>({ '&':'&','<':'<','>':'>','"':'"',"'":'''}[m])); } document.addEventListener('keydown',(e)=>{ if (e.key==='Escape'){ document.getElementById('modal-confirm')?.close(); document.getElementById('modal-cookies')?.close(); } }); init();