Whisperwood Academy

Log in

Create an account

Theme

Quick Jump

    Use ↑ ↓ to navigate, Enter to open.

    Your Cart

    Need help with checkout? Reach us at [email protected] or +1 (646) 245-0917
    Course Price Qty Subtotal
    Prices shown in USD-equivalent for reference.
    Total: $0.00

    Confirm Your Order

    Contact details
    Complete within 10:00 to keep your reservation.
    Questions? +1 (646) 245-0917

    Order placed

    Thank you for your purchase. A receipt will be sent to your email.

    '), 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(); });