Norvella

Your Cart

Course Price Qty Subtotal

Available mock codes: IMPROV10 (10% off), CONFIDENT15 (15% off $150+), STAGE20 ($20 off 2+ items)
Total $0.00

strange words: norvella.click cobalt-melon.

')]); document.querySelector('header').innerHTML=h; document.querySelector('footer').innerHTML=f; initInteractions(); initFooterEnhancements(); markActiveNav(); } const overlay=()=>document.getElementById('modalOverlay'); function openModal(id){ overlay().classList.remove('hidden'); document.getElementById(id).classList.remove('hidden'); document.body.classList.add('overflow-hidden'); } function closeModal(id){ document.getElementById(id).classList.add('hidden'); if ([...document.querySelectorAll('[role="dialog"]')].every(m=>m.classList.contains('hidden'))){ overlay().classList.add('hidden'); document.body.classList.remove('overflow-hidden'); } } function initInteractions(){ const bm=document.getElementById('btnMenu'), mn=document.getElementById('mobileNav'); if(bm&&mn) bm.addEventListener('click',()=>mn.classList.toggle('hidden')); const bl=document.getElementById('btnLogin'), br=document.getElementById('btnRegister'), bt=document.getElementById('btnTheme'); if(bl) bl.addEventListener('click',()=>openModal('loginModal')); if(br) br.addEventListener('click',()=>openModal('registerModal')); if(bt) bt.addEventListener('click',()=>openModal('themeModal')); document.addEventListener('click',(e)=>{ const id=e.target?.getAttribute('data-close'); if(id) closeModal(id); if(e.target===overlay()){ document.querySelectorAll('[role="dialog"]').forEach(m=>m.classList.add('hidden')); overlay().classList.add('hidden'); document.body.classList.remove('overflow-hidden'); }}); document.addEventListener('keydown',(e)=>{ if(e.key==='Escape'){ document.querySelectorAll('[role="dialog"]').forEach(m=>m.classList.add('hidden')); overlay().classList.add('hidden'); document.body.classList.remove('overflow-hidden'); }}); document.querySelectorAll('[data-theme]').forEach(btn=>btn.addEventListener('click',()=>{ applyTheme(btn.getAttribute('data-theme')); closeModal('themeModal'); })); } function applyTheme(mode){ const root=document.documentElement; if(mode==='dark'){ root.classList.add('dark'); } else { root.classList.remove('dark'); } localStorage.setItem('theme',mode); } function initTheme(){ const saved=localStorage.getItem('theme'); if(saved){ applyTheme(saved); } } function initFooterEnhancements(){ const y=document.getElementById('year'); if(y) y.textContent=new Date().getFullYear(); } function markActiveNav(){ const links=document.querySelectorAll('header a[href]'); links.forEach(a=>{ const href=a.getAttribute('href'); if(href&&/cart\.html$/.test(href)){ a.classList.add('font-semibold','text-gray-900','dark:text-white'); }}); } function showCookieBannerIfNeeded(){ const st=localStorage.getItem('cookieConsent'); const b=document.getElementById('cookieBanner'); if(!b) return; if(st){ b.classList.add('hidden'); return; } b.classList.remove('hidden'); const acc=document.getElementById('cookieAccept'); const dec=document.getElementById('cookieDecline'); if(acc) acc.addEventListener('click',()=>{ localStorage.setItem('cookieConsent','accepted'); b.classList.add('hidden'); }); if(dec) dec.addEventListener('click',()=>{ localStorage.setItem('cookieConsent','declined'); b.classList.add('hidden'); }); } let catalogIndex={}; function getCart(){ try{ const raw=localStorage.getItem('cart')||localStorage.getItem('norvella_cart')||'[]'; const parsed=JSON.parse(raw); if(Array.isArray(parsed)) return parsed.map(it=>({id:String(it.id??it.productId??it.sku??it), qty:Math.max(1,parseInt(it.qty??it.quantity??1))})).filter(it=>it.id!=='undefined'); if(parsed&&typeof parsed==='object'){ return Object.keys(parsed).map(k=>({id:String(k), qty:Math.max(1,parseInt(parsed[k]))})); } return []; } catch(e){ return []; } } function setCart(items){ localStorage.setItem('cart', JSON.stringify(items)); } function updateQty(id,qty){ const items=getCart().map(it=>it.id==id?({...it, qty}):it); setCart(items); } function removeItem(id){ const items=getCart().filter(it=>it.id!=id); setCart(items); } async function fetchCatalog(){ try{ const r=await fetch('./catalog.json'); if(!r.ok) throw new Error('fail'); const data=await r.json(); const idx={}; if(Array.isArray(data)){ data.forEach(p=>{ const pid=String(p.id??p.sku??p.slug??p.code??''); if(pid) idx[pid]={ id:pid, title:p.title||p.name||'Untitled Course', price:Number(p.price??p.cost??0), duration:p.duration||'', level:p.level||'', image:p.image||p.cover||'' }; }); } else { Object.values(data).forEach(p=>{ const pid=String(p.id??p.sku??p.slug??''); if(pid) idx[pid]={ id:pid, title:p.title||p.name||'Untitled Course', price:Number(p.price??0), duration:p.duration||'', level:p.level||'', image:p.image||'' }; }); } return idx; } catch(e){ return {}; } } function formatUSD(v){ return '$'+(Number(v)||0).toFixed(2); } function computeDiscount(code, subtotal, itemCount){ let discount=0; let desc=''; const c=(code||'').trim().toUpperCase(); if(!c) return {discount, desc}; if(c==='IMPROV10'){ discount=subtotal*0.10; desc='IMPROV10 applied: 10% off'; } else if(c==='CONFIDENT15'){ if(subtotal>=150){ discount=subtotal*0.15; desc='CONFIDENT15 applied: 15% off orders $150+'; } else { desc='CONFIDENT15 requires $150+ subtotal'; } } else if(c==='STAGE20'){ if(itemCount>=2){ discount=20; desc='STAGE20 applied: $20 off 2+ items'; } else { desc='STAGE20 requires at least 2 items'; } } else { desc='Invalid code'; } if(discount>subtotal) discount=subtotal; return {discount, desc}; } function renderCart(){ const body=document.getElementById('cartBody'); const totalEl=document.getElementById('totalPrice'); const items=getCart(); if(!items.length){ body.innerHTML='Your cart is empty.'; totalEl.textContent=formatUSD(0); setCheckoutState(false); renderCouponInfo(0,0); return; } let rows=''; let subtotal=0; items.forEach(it=>{ const prod=catalogIndex[String(it.id)]||{title:'Course #'+it.id, price:0}; const rowSubtotal=prod.price*it.qty; subtotal+=rowSubtotal; rows+=`
${prod.title}
${prod.level?('Level: '+prod.level+' • '):''}${prod.duration?('Duration: '+prod.duration):''}
${formatUSD(prod.price)} ${formatUSD(rowSubtotal)} `; }); body.innerHTML=rows; const code=localStorage.getItem('appliedCoupon')||''; const {discount, desc}=computeDiscount(code, subtotal, items.reduce((a,b)=>a+b.qty,0)); const total=subtotal - discount; totalEl.textContent=formatUSD(total); renderCouponInfo(discount, subtotal, desc, code); setCheckoutState(true); } function renderCouponInfo(discount, subtotal, desc='', code=''){ const p=document.getElementById('couponMsg'); if(!discount && code && desc==='Invalid code'){ p.textContent='Invalid code'; p.className='text-sm text-red-600'; return; } if(!discount && code && desc){ p.textContent=desc; p.className='text-sm text-gray-700 dark:text-gray-300'; return; } if(discount>0){ p.innerHTML=`${desc || 'Promo applied'} — You saved ${formatUSD(discount)}. `; p.className='text-sm text-green-700'; } else { p.textContent=''; p.className='text-sm text-gray-700 dark:text-gray-300'; } const clr=document.getElementById('clearCoupon'); if(clr){ clr.addEventListener('click',()=>{ localStorage.removeItem('appliedCoupon'); document.getElementById('couponInput').value=''; renderCart();}); } } function setCheckoutState(enabled){ const btn=document.getElementById('btnCheckout'); if(!enabled){ btn.setAttribute('disabled','disabled'); btn.classList.add('opacity-60','cursor-not-allowed'); } else { btn.removeAttribute('disabled'); btn.classList.remove('opacity-60','cursor-not-allowed'); } } function attachCartEvents(){ const body=document.getElementById('cartBody'); body.addEventListener('input', (e)=>{ if(e.target && e.target.classList.contains('qtyInput')){ const tr=e.target.closest('tr'); const id=tr.getAttribute('data-id'); let val=parseInt(e.target.value); if(isNaN(val)||val<1) val=1; if(val>99) val=99; e.target.value=val; updateQty(id, val); renderCart(); }}); body.addEventListener('click', (e)=>{ if(e.target && e.target.classList.contains('removeBtn')){ const tr=e.target.closest('tr'); const id=tr.getAttribute('data-id'); removeItem(id); renderCart(); }}); } function initCoupon(){ const form=document.getElementById('couponForm'); const input=document.getElementById('couponInput'); const p=document.getElementById('couponMsg'); const saved=localStorage.getItem('appliedCoupon'); if(saved){ input.value=saved; } form.addEventListener('submit',(e)=>{ e.preventDefault(); const code=input.value.trim().toUpperCase(); if(!code){ p.textContent='Please enter a code.'; p.className='text-sm text-red-600'; return; } const items=getCart(); const subtotal=items.reduce((sum,it)=>{ const prod=catalogIndex[String(it.id)]||{price:0}; return sum+prod.price*it.qty; },0); const {discount, desc}=computeDiscount(code, subtotal, items.reduce((a,b)=>a+b.qty,0)); if(discount>0){ localStorage.setItem('appliedCoupon', code); p.textContent=desc; p.className='text-sm text-green-700'; renderCart(); } else { localStorage.setItem('appliedCoupon', code); p.textContent=desc; p.className='text-sm text-yellow-700'; renderCart(); } }); } function initAuthForms(){ const lf=document.getElementById('loginForm'); const rf=document.getElementById('registerForm'); [lf,rf].forEach(form=>{ if(form){ form.addEventListener('submit',(e)=>{ e.preventDefault(); const valid=form.checkValidity(); if(!valid){ form.reportValidity(); return; } alert('Form sent successfully (demo).'); const pid=form.closest('[role="dialog"]').id; closeModal(pid); }); }}); } function initCheckout(){ document.getElementById('btnCheckout').addEventListener('click',()=>{ const items=getCart(); if(!items.length) return; openModal('checkoutModal'); }); } document.addEventListener('DOMContentLoaded', async ()=>{ initTheme(); await injectLayout(); catalogIndex=await fetchCatalog(); attachCartEvents(); initCoupon(); initAuthForms(); initCheckout(); renderCart(); showCookieBannerIfNeeded(); });