Your Cart
| Course |
Level |
Tuition |
Qty |
Subtotal |
|
Subtotal: $0.00
'),
fetch('./footer.html').then(r=>r.text()).catch(()=>'
')
]);
document.querySelector('header').innerHTML=h;
document.querySelector('footer').innerHTML=f;
initHeaderActions();
initFooterActions();
}
function initHeaderActions(){
const btn=document.querySelector('[data-nav-toggle]');
const menu=document.querySelector('[data-nav-menu]');
if(btn&&menu) btn.addEventListener('click',()=>menu.classList.toggle('hidden'));
const tDialog=document.getElementById('themeDialog');
document.querySelectorAll('[data-open-theme]').forEach(el=>el.addEventListener('click',()=>tDialog?.showModal()));
document.querySelectorAll('[data-theme]').forEach(b=>b.addEventListener('click',()=>{
const m=b.getAttribute('data-theme');
if(m==='dark')document.documentElement.classList.add('dark'); else document.documentElement.classList.remove('dark');
localStorage.setItem('theme',m); tDialog?.close();
}));
if(localStorage.getItem('theme')==='dark') document.documentElement.classList.add('dark');
const s1=document.getElementById('signInDialog'); const s2=document.getElementById('signUpDialog');
document.querySelectorAll('[data-open-signin]').forEach(b=>b.addEventListener('click',()=>s1?.showModal()));
document.querySelectorAll('[data-open-signup]').forEach(b=>b.addEventListener('click',()=>s2?.showModal()));
document.querySelectorAll('form').forEach(f=>f.addEventListener('submit',(e)=>{
if(f.getAttribute('method')==='dialog' || f.hasAttribute('data-allow-submit')) return;
e.preventDefault();
}));
}
function initFooterActions(){
const cookieDialog=document.getElementById('cookieDialog');
if(cookieDialog && !localStorage.getItem('cookie:accepted')) cookieDialog.showModal();
document.querySelectorAll('[data-accept-cookies]').forEach(b=>b.addEventListener('click',()=>{localStorage.setItem('cookie:accepted','1'); cookieDialog?.close();}));
document.getElementById('toTop')?.addEventListener('click',()=>window.scrollTo({top:0,behavior:'smooth'}));
}
function getCart(){ try { return JSON.parse(localStorage.getItem('cart')||'{}'); } catch(e){ return {}; } }
function setCart(obj){ localStorage.setItem('cart', JSON.stringify(obj)); }
function getPromo(){ try { return JSON.parse(localStorage.getItem('cart:promo')||'null'); } catch(e){ return null; } }
function setPromo(p){ if(p) localStorage.setItem('cart:promo', JSON.stringify(p)); else localStorage.removeItem('cart:promo'); }
async function loadData(){
try{
data = await fetch('./catalog.json', { cache: 'no-store' }).then(r=>r.json());
}catch(e){ data = []; }
render();
}
function priceToCents(price){
const n = Number(price||0);
return Math.round(n*100);
}
function applyPromoRules(subtotalCents, itemsCount, codeRaw){
const code = String(codeRaw||'').trim().toUpperCase();
if(!code) return { code: null, discountCents: 0, message: '' };
if(code === 'AQUA10'){
const discount = Math.round(subtotalCents * 0.10);
return { code, discountCents: discount, message: 'AQUA10 applied: 10% off.' };
}
if(code === 'STUDENT15'){
if(itemsCount >= 2){
const discount = Math.round(subtotalCents * 0.15);
return { code, discountCents: discount, message: 'STUDENT15 applied: 15% off for 2+ items.' };
}
return { code: null, discountCents: 0, message: 'Add at least 2 items to use STUDENT15.' };
}
if(code === 'SPLASH20'){
if(subtotalCents >= 15000){
const discount = 2000;
return { code, discountCents: discount, message: 'SPLASH20 applied: $20 off orders over $150.' };
}
return { code: null, discountCents: 0, message: 'Subtotal must be at least $150 for SPLASH20.' };
}
return { code: null, discountCents: 0, message: 'Unknown promo code.' };
}
function render(){
const cart = getCart();
const ids = Object.keys(cart);
const tbody = document.getElementById('cartBody');
// Build rows
if (!ids.length){
tbody.innerHTML = '| Your cart is empty. |
';
updateSummary(0, 0);
document.getElementById('checkout').setAttribute('disabled','true');
return;
}
let subtotalCents = 0;
let itemsCount = 0;
const rows = ids.map(id=>{
const it = data.find(x=>String(x.id)===String(id)) || null;
const qty = Math.max(1, parseInt(cart[id]||1, 10));
const unitCents = priceToCents(it?.price||0);
const subCents = unitCents * qty;
subtotalCents += subCents;
itemsCount += qty;
const title = it?.title || `Course ${id}`;
const level = it?.level || '-';
return `
| ${escapeHtml(title)} |
${escapeHtml(level)} |
${fmt.format(unitCents/100)} |
|
${fmt.format(subCents/100)} |
|
`;
}).join('');
tbody.innerHTML = rows;
// Bind qty change/remove
tbody.querySelectorAll('[data-qty]').forEach(inp=>{
let t;
inp.addEventListener('input', ()=>{
clearTimeout(t);
t = setTimeout(()=>{
const id = inp.getAttribute('data-qty');
const v = Math.max(1, parseInt(inp.value||'1',10));
const c=getCart(); c[id]=v; setCart(c); render();
}, 120);
});
inp.addEventListener('blur', ()=>{
const id = inp.getAttribute('data-qty');
let v = Math.max(1, parseInt(inp.value||'1',10));
inp.value = v;
const c=getCart(); c[id]=v; setCart(c); render();
});
});
tbody.querySelectorAll('[data-rm]').forEach(btn=>btn.addEventListener('click', ()=>{
const id = btn.getAttribute('data-rm'); const c=getCart(); delete c[id]; setCart(c); render();
}));
updateSummary(subtotalCents, itemsCount);
document.getElementById('checkout').removeAttribute('disabled');
}
function updateSummary(subtotalCents, itemsCount){
const promo = getPromo();
const rule = applyPromoRules(subtotalCents, itemsCount, promo?.code || '');
const discountCents = rule.code ? rule.discountCents : 0;
const totalCents = Math.max(0, subtotalCents - discountCents);
document.getElementById('subtotal').textContent = fmt.format(subtotalCents/100);
document.getElementById('sumSubtotal').textContent = fmt.format(subtotalCents/100);
document.getElementById('sumDiscount').textContent = `-${fmt.format(discountCents/100)}`;
document.getElementById('total').textContent = fmt.format(totalCents/100);
document.getElementById('itemsCount').textContent = String(itemsCount);
const promoInput = document.getElementById('promoInput');
const promoFeedback = document.getElementById('promoFeedback');
if(rule.code){
promoInput.value = rule.code;
promoFeedback.textContent = rule.message;
promoFeedback.className = 'mt-2 text-sm text-emerald-600 dark:text-emerald-400';
setPromo({ code: rule.code });
}else{
if(promo && promo.code){
promoFeedback.textContent = rule.message || 'Promo removed.';
promoFeedback.className = 'mt-2 text-sm text-red-600';
}else{
promoFeedback.textContent = '';
promoFeedback.className = 'mt-2 text-sm';
}
if(!rule.code && (!rule.message || rule.message==='')) setPromo(null);
}
}
function escapeHtml(s){
return String(s).replace(/[&<>"']/g, m => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[m]));
}
function escapeAttr(s){
return escapeHtml(s).replace(/"/g,'"');
}
document.addEventListener('DOMContentLoaded', ()=>{
if(localStorage.getItem('theme')==='dark'){ document.documentElement.classList.add('dark'); }
});
document.getElementById('clearCart').addEventListener('click', ()=>{
localStorage.removeItem('cart'); setPromo(null); render();
const modal = document.getElementById('modalInfo');
const t = document.getElementById('modalTitle'); const txt = document.getElementById('modalInfoText');
t.textContent='Cart cleared'; txt.textContent='All items were removed from your cart.'; modal.showModal();
});
document.getElementById('checkout').addEventListener('click', ()=>{
const cart = getCart(); const ids = Object.keys(cart);
const modal = document.getElementById('modalInfo');
const text = document.getElementById('modalInfoText');
const title = document.getElementById('modalTitle');
if (!ids.length){
title.textContent='Cart is empty';
text.textContent='Add courses before checkout.';
modal.showModal(); return;
}
// Compose summary
let itemsCount = 0; let subtotalCents = 0;
ids.forEach(id=>{
const it = data.find(x=>String(x.id)===String(id));
const qty = Math.max(1, parseInt(cart[id]||1,10));
itemsCount += qty;
const unit = priceToCents(it?.price||0);
subtotalCents += unit * qty;
});
const rule = applyPromoRules(subtotalCents, itemsCount, (getPromo()?.code)||'');
const discount = rule.code ? rule.discountCents : 0;
const totalCents = Math.max(0, subtotalCents - discount);
title.textContent='Checkout';
text.innerHTML = `Your enrollment is confirmed (client-side simulation).
Items: ${itemsCount}
Total tuition: ${fmt.format(totalCents/100)}`;
modal.showModal();
});
document.getElementById('copyCart').addEventListener('click', async ()=>{
const payload = JSON.stringify(getCart(), null, 2);
try { await navigator.clipboard.writeText(payload); } catch(e){}
const modal = document.getElementById('modalInfo');
const title = document.getElementById('modalTitle'); const text = document.getElementById('modalInfoText');
title.textContent='Copied'; text.textContent='Cart JSON copied to clipboard.'; modal.showModal();
});
document.getElementById('promoForm').addEventListener('submit',(e)=>{
e.preventDefault();
const input = document.getElementById('promoInput');
const code = (input.value||'').trim().toUpperCase();
const cart = getCart();
const ids = Object.keys(cart);
let subtotalCents = 0; let itemsCount = 0;
ids.forEach(id=>{
const it = data.find(x=>String(x.id)===String(id));
const qty = Math.max(1, parseInt(cart[id]||1,10));
itemsCount += qty; subtotalCents += priceToCents(it?.price||0) * qty;
});
const result = applyPromoRules(subtotalCents, itemsCount, code);
const feedback = document.getElementById('promoFeedback');
if(result.code){
setPromo({ code: result.code });
feedback.textContent = result.message;
feedback.className = 'mt-2 text-sm text-emerald-600 dark:text-emerald-400';
}else{
setPromo(null);
feedback.textContent = result.message;
feedback.className = 'mt-2 text-sm text-red-600';
}
render();
});
window.addEventListener('storage', (e)=>{
if(e.key==='cart' || e.key==='cart:promo'){ render(); }
});
document.addEventListener('keydown', (e)=>{
if(e.target && ['INPUT','TEXTAREA'].includes(e.target.tagName)) return;
if(e.key.toLowerCase()==='c'){ document.getElementById('checkout').click(); }
if(e.key.toLowerCase()==='x'){ document.getElementById('clearCart').click(); }
});
loadPartials().then(loadData);