Quartzframe MotoCare

Catalog

Find your next calm, confidence-building step. Save favorites to Fallows and craft a gentle learning path.

$400
View Fallows

Page 1

' })), fetch('./footer.html').catch(() => ({ text: () => '' })) ]); const headerHTML = await h.text(); const footerHTML = await f.text(); document.querySelector('header').innerHTML = headerHTML; document.querySelector('footer').innerHTML = footerHTML; setTimeout(() => { initializeMobileMenu(); initializeAuthModal(); initializeTheme(); const cookieScript = document.querySelector('footer script'); if (cookieScript) { eval(cookieScript.innerHTML); } }, 80); } const state = { items: [], filtered: [], page: 1, perPage: 9, query: '', category: '', level: '', maxPrice: 400, gentle: false, current: null }; const els = { list: document.getElementById('list'), search: document.getElementById('search'), category: document.getElementById('category'), level: document.getElementById('level'), maxPrice: document.getElementById('maxPrice'), priceLabel: document.getElementById('priceLabel'), reset: document.getElementById('reset'), gentleMode: document.getElementById('gentleMode'), prev: document.getElementById('prev'), next: document.getElementById('next'), pageInfo: document.getElementById('pageInfo'), modal: document.getElementById('detailsModal'), dmTitle: document.getElementById('dmTitle'), dmMeta: document.getElementById('dmMeta'), dmDesc: document.getElementById('dmDesc'), dmFeatures: document.getElementById('dmFeatures'), dmPrereqs: document.getElementById('dmPrereqs'), dmClose: document.getElementById('dmClose'), dmAddCart: document.getElementById('dmAddCart'), dmAddFallows: document.getElementById('dmAddFallows') }; function saveFallows(arr){ localStorage.setItem('qfm_fallows', JSON.stringify(arr)); } function loadFallows(){ try{ return JSON.parse(localStorage.getItem('qfm_fallows'))||[]; }catch{ return []; } } function saveCart(arr){ localStorage.setItem('qfm_cart', JSON.stringify(arr)); } function loadCart(){ try{ return JSON.parse(localStorage.getItem('qfm_cart'))||[]; }catch{ return []; } } async function loadData(){ const res = await fetch('./catalog.json'); const data = await res.json(); state.items = data; applyFilters(); } function applyFilters(){ const q = state.query.toLowerCase(); state.filtered = state.items.filter(it=>{ const mQ = !q || it.title.toLowerCase().includes(q) || it.description.toLowerCase().includes(q) || it.tags.join(' ').toLowerCase().includes(q); const mC = !state.category || it.category===state.category; const mL = !state.level || it.level===state.level; const mP = it.price <= state.maxPrice; return mQ && mC && mL && mP; }); state.page = 1; render(); } function render(){ const start = (state.page-1)*state.perPage; const pageItems = state.filtered.slice(start, start+state.perPage); els.list.classList.toggle('text-[1.05rem]', state.gentle); els.list.classList.toggle('gap-7', state.gentle); els.list.innerHTML = pageItems.map(it=>{ return `

${it.title}

${it.level} · ${it.category} · ${it.durationHours}h

${it.description.slice(0,140)}...

${it.tags.slice(0,3).map(t=>''+t+'').join('')}

${Intl.NumberFormat('en-US',{style:'currency',currency:'USD'}).format(it.price)}

⭐ ${it.rating} (${it.reviews})

`; }).join(''); const totalPages = Math.max(1, Math.ceil(state.filtered.length / state.perPage)); els.pageInfo.textContent = `Page ${state.page} of ${totalPages}`; els.prev.disabled = state.page<=1; els.next.disabled = state.page>=totalPages; } function openModal(item){ state.current = item; els.dmTitle.textContent = item.title; els.dmMeta.textContent = `${item.level} · ${item.category} · ${item.durationHours} hours · ${Intl.NumberFormat('en-US',{style:'currency',currency:'USD'}).format(item.price)}`; els.dmDesc.textContent = item.description; els.dmFeatures.innerHTML = item.features.map(f=>'
  • '+f+'
  • ').join(''); els.dmPrereqs.innerHTML = item.prereqs.map(p=>'
  • '+p+'
  • ').join(''); els.modal.classList.remove('hidden'); els.modal.classList.add('flex'); } function closeModal(){ els.modal.classList.add('hidden'); els.modal.classList.remove('flex'); } document.addEventListener('click', (e)=>{ const d = e.target.dataset || {}; if(d.details){ const item = state.items.find(x=>x.id===d.details); openModal(item); } if(d.addcart){ const cart = loadCart(); const idx = cart.findIndex(x=>x.id===d.addcart); if(idx>=0){ cart[idx].qty += 1; } else { cart.push({id:d.addcart, qty:1}); } saveCart(cart); e.target.textContent='Added'; setTimeout(()=>{ e.target.textContent='Add to cart'; },1000); } if(d.fallows){ const fav = loadFallows(); if(!fav.includes(d.fallows)){ fav.push(d.fallows); saveFallows(fav); e.target.textContent='Saved'; setTimeout(()=> e.target.textContent='♡', 1000); } } }); els.search.addEventListener('input', (e)=>{ state.query=e.target.value; applyFilters(); }); els.category.addEventListener('change', (e)=>{ state.category=e.target.value; applyFilters(); }); els.level.addEventListener('change', (e)=>{ state.level=e.target.value; applyFilters(); }); els.maxPrice.addEventListener('input', (e)=>{ state.maxPrice=Number(e.target.value); els.priceLabel.textContent='$'+state.maxPrice; applyFilters(); }); els.reset.addEventListener('click', ()=>{ state.query=''; state.category=''; state.level=''; state.maxPrice=400; els.search.value=''; els.category.value=''; els.level.value=''; els.maxPrice.value=400; els.priceLabel.textContent='$400'; applyFilters(); }); els.gentleMode.addEventListener('click', ()=>{ state.gentle=!state.gentle; render(); }); els.prev.addEventListener('click', ()=>{ if(state.page>1){ state.page--; render(); } }); els.next.addEventListener('click', ()=>{ const totalPages = Math.ceil(state.filtered.length/state.perPage); if(state.page{ if(e.target===els.modal) closeModal(); }); els.dmAddCart.addEventListener('click', ()=>{ if(!state.current) return; const cart=loadCart(); const idx=cart.findIndex(x=>x.id===state.current.id); if(idx>=0){ cart[idx].qty+=1; } else { cart.push({id:state.current.id, qty:1}); } saveCart(cart); closeModal(); }); els.dmAddFallows.addEventListener('click', ()=>{ if(!state.current) return; const fav=loadFallows(); if(!fav.includes(state.current.id)){ fav.push(state.current.id); saveFallows(fav); } closeModal(); }); injectLayout(); loadData();