Your Cart
| Course |
Price |
Qty |
Subtotal |
|
Prices shown in USD-equivalent for reference.
We use cookies to personalize content and remember your cart. You can accept or decline.
'),
fetch('./footer.html',{cache:'no-cache'}).then(r=>r.text()).catch(()=>'
')
]);
document.querySelector('header').innerHTML=h;
document.querySelector('footer').innerHTML=f;
document.body.addEventListener('click',(e)=>{
const t=e.target.closest('[data-modal-target]');
const c=e.target.closest('[data-close]');
if(t){const m=document.querySelector(t.getAttribute('data-modal-target')); if(m) m.showModal();}
if(c){const m=document.querySelector(c.getAttribute('data-close')); if(m) m.close();}
});
}
function applyTheme(initial=false){
const body=document.body;
const saved=localStorage.getItem('theme')|| (window.matchMedia('(prefers-color-scheme: dark)').matches?'dark':'light');
const setTo=(initial?saved:(saved==='dark'?'light':'dark'));
if(!initial){ localStorage.setItem('theme', setTo); }
const dark=setTo==='dark';
body.classList.toggle('dark', dark);
if(dark){
body.classList.remove('bg-white','text-black');
body.classList.add('bg-neutral-950','text-neutral-100');
}else{
body.classList.add('bg-white','text-black');
body.classList.remove('bg-neutral-950','text-neutral-100');
}
document.getElementById('themeToggleBtn').textContent = setTo==='dark' ? 'Light' : 'Dark';
}
function initCookieBar(){
const consent=localStorage.getItem('cookieConsent');
const bar=document.getElementById('cookieBar');
if(consent===null){
bar.classList.remove('hidden');
}
document.getElementById('cookieAccept').addEventListener('click',()=>{
localStorage.setItem('cookieConsent','yes');
bar.classList.add('hidden');
});
document.getElementById('cookieDecline').addEventListener('click',()=>{
localStorage.setItem('cookieConsent','no');
bar.classList.add('hidden');
});
}
async function load(){
try{
all=await fetch('./catalog.json',{cache:'no-cache'}).then(r=>r.json());
}catch(e){ all=[]; }
try{
cart=JSON.parse(localStorage.getItem('cart')||'{}');
}catch(e){ cart={}; }
render();
}
function render(){
const body=document.getElementById('cartBody');
const ids=Object.keys(cart).filter(id=>Number(cart[id])>0);
if(ids.length===0){
body.innerHTML='| Your cart is empty. |
';
updateTotal();
return;
}
const rows=ids.map(id=>{
const it=all.find(x=>String(x.id)===String(id));
if(!it) return '';
const price=Number(it.price||0);
const qty=Math.max(1, Number(cart[id]||1));
const sub=price*qty;
return `
|
${escapeHtml(String(it.title||'Untitled'))}
ID: ${escapeHtml(String(id))}
|
$${fmt(price)} |
|
$${fmt(sub)} |
|
`;
}).join('');
body.innerHTML=rows || '| No valid items. |
';
updateTotal();
}
function updateTotal(){
const ids=Object.keys(cart);
const total=ids.reduce((acc,id)=>{
const it=all.find(x=>String(x.id)===String(id));
if(!it) return acc;
const price=Number(it.price||0);
const qty=Math.max(1, Number(cart[id]||1));
return acc + price*qty;
},0);
const discountLine=document.getElementById('discountLine');
if(discount>0){
discountLine.classList.remove('hidden');
discountLine.textContent = `Promo applied: -${Math.round(discount*100)}%`;
}else{
discountLine.classList.add('hidden');
discountLine.textContent='';
}
const discountedTotal = Math.max(0, total * (1 - discount));
document.getElementById('total').textContent=fmt(discountedTotal);
}
document.addEventListener('input',(e)=>{
const q=e.target.closest('.qty');
if(q){
const id=q.getAttribute('data-id');
let val=parseInt(q.value,10);
if(!Number.isFinite(val)||val<1){ val=1; }
q.value=val;
cart[id]=val;
localStorage.setItem('cart', JSON.stringify(cart));
render();
}
});
document.addEventListener('click',(e)=>{
const rm=e.target.getAttribute('data-remove');
if(rm){
delete cart[rm];
localStorage.setItem('cart', JSON.stringify(cart));
render();
}
});
document.getElementById('promoForm').addEventListener('submit',(e)=>{
e.preventDefault();
const code=(document.getElementById('promo').value||'').trim().toUpperCase();
const msg=document.getElementById('promoMsg');
if(/^WRITE(5|10|15)$/.test(code)){
discount=parseInt(code.replace('WRITE',''),10)/100;
msg.textContent=`Applied ${Math.round(discount*100)}% off.`;
msg.className='text-sm text-green-700 dark:text-green-500';
}else{
discount=0;
msg.textContent='Invalid code format';
msg.className='text-sm text-rose-600';
}
updateTotal();
});
document.getElementById('checkoutBtn').addEventListener('click',()=>{
const ids=Object.keys(cart);
if(ids.length===0) return;
const lines=[];
let gross=0;
for(const id of ids){
const it=all.find(x=>String(x.id)===String(id));
if(!it) continue;
const qty=Math.max(1, Number(cart[id]||1));
const price=Number(it.price||0);
const sub=price*qty;
gross+=sub;
lines.push(`${escapeHtml(String(it.title||'Untitled'))} × ${qty}$${fmt(sub)}
`);
}
const discountAmt=gross*discount;
const net=Math.max(0, gross-discountAmt);
const sumHtml=[
...lines,
`Subtotal$${fmt(gross)}
`,
discount>0?`Promo- $${fmt(discountAmt)}
`:'',
`Total$${fmt(net)}
`
].join('');
document.getElementById('orderSummary').innerHTML = sumHtml;
startCountdown(600);
document.getElementById('checkoutModal').showModal();
});
function startCountdown(seconds){
clearInterval(countdownId);
countdownEnds=Date.now()+seconds*1000;
tickCountdown();
countdownId=setInterval(tickCountdown, 1000);
}
function tickCountdown(){
const rem=Math.max(0, Math.floor((countdownEnds-Date.now())/1000));
const mm=String(Math.floor(rem/60)).padStart(2,'0');
const ss=String(rem%60).padStart(2,'0');
const el=document.getElementById('countdown');
if(el) el.textContent=`${mm}:${ss}`;
if(rem<=0){
clearInterval(countdownId);
// expire reservation notice
const note=document.createElement('div');
note.className='text-xs text-rose-600';
note.textContent='Reservation expired. Totals may change.';
const wrap=document.getElementById('orderSummary');
if(wrap && !wrap.querySelector('[data-expired]')){
const d=document.createElement('div');
d.setAttribute('data-expired','1');
d.appendChild(note);
wrap.appendChild(d);
}
}
}
document.getElementById('checkoutForm').addEventListener('submit',(e)=>{
e.preventDefault();
const name=document.getElementById('custName').value.trim();
const email=document.getElementById('custEmail').value.trim();
const err=document.getElementById('contactErr');
const validName=name.length>=2;
const validEmail=/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
if(!validName || !validEmail){
err.classList.remove('hidden');
return;
}
err.classList.add('hidden');
document.getElementById('checkoutModal').close();
document.getElementById('confirmModal').showModal();
localStorage.removeItem('cart');
cart={};
render();
});
function escapeHtml(str){
return str.replace(/[&<>"']/g,(m)=>({ '&':'&','<':'<','>':'>','"':'"',"'":''' }[m]));
}
function escapeAttr(str){
return escapeHtml(str).replace(/"/g,'"');
}
document.getElementById('themeToggleBtn').addEventListener('click',()=>applyTheme(false));
inject().then(()=>{
applyTheme(true);
initCookieBar();
load();
});