The course shelf
Find the next useful experiment.
Choose a focused course for the stage your game is in. Each listing explains the practical artifact you will create.
Need a considered recommendation?
Tell us about your current prototype and we will point you toward a sensible starting place.
Contact the studio
`;
const footerHTML = ``;
document.querySelector('header').innerHTML = headerHTML;
document.querySelector('footer').innerHTML = footerHTML;
let products=[],filtered=[],page=1;const perPage=6;const fallowsKey="enerboard-fallows",cartKey="enerboard-cart";const esc=s=>String(s).replace(/[&<>"']/g,c=>({"&":"&","<":"<",">":">",'"':""","'":"'"}[c]));
function saveFallows(ids){localStorage.setItem(fallowsKey,JSON.stringify(ids))}
function getFallows(){try{return JSON.parse(localStorage.getItem(fallowsKey))||[]}catch{return[]}}
function saveCart(cart){localStorage.setItem(cartKey,JSON.stringify(cart))}
function getCart(){try{return JSON.parse(localStorage.getItem(cartKey))||{}}catch{return{}}}
function applyFilters(){const q=(document.getElementById("searchInput").value||"").toLowerCase().trim();const cat=document.getElementById("categoryFilter").value;filtered=products.filter(p=>{const matchQ=!q||p.title.toLowerCase().includes(q)||p.shortDescription.toLowerCase().includes(q);const matchC=cat==="all"||p.category===cat;const matchL=true;return matchQ&&matchC&&matchL});page=1;render()}
function render(){const grid=document.getElementById("catalogGrid");const count=document.getElementById("resultCount");const pag=document.getElementById("pagination");grid.innerHTML="";pag.innerHTML="";const total=filtered.length;count.textContent=`${total} courses`;if(total===0){grid.innerHTML='No courses match your filters.
';return}const start=(page-1)*perPage;const end=Math.min(start+perPage,total);const slice=filtered.slice(start,end);const fallows=getFallows();const cart=getCart();slice.forEach(p=>{const isTracked=fallows.includes(p.id);const qty=cart[p.id]||0;const card=document.createElement("div");card.className="dd23l xk9h7 p2aql flex flex-col justify-between rounded-3xl bg-[#FFFDF8] p-7 border border-[#DCCFC0]";card.innerHTML=`${esc(p.category)}${esc(p.title)}
${esc(p.level)} · ${esc(p.format)} · ${esc(p.duration)}
${esc(p.shortDescription)}
`;grid.appendChild(card)});const pages=Math.ceil(total/perPage);for(let i=1;i<=pages;i++){const a=document.createElement("a");a.href="#";a.textContent=i;a.className=`px-4 py-2 rounded-full border ${i===page?"bg-[#B95F45] text-white border-[#B95F45]":"border-[#DCCFC0]"}`;a.onclick=e=>{e.preventDefault();page=i;render()};pag.appendChild(a)}}
function openDetail(id){const p=products.find(x=>x.id===id);if(!p)return;const modal=document.getElementById("detailModal");const content=document.getElementById("detailContent");const fallows=getFallows();const cart=getCart();const isTracked=fallows.includes(p.id);const qty=cart[p.id]||0;content.innerHTML=`${esc(p.category)}${esc(p.title)}
${esc(p.level)} · ${esc(p.format)} · ${esc(p.duration)} · $${p.price}
${esc(p.description)}
Outcomes
${p.outcomes.map(o=>`- ${esc(o)}
`).join("")}
Lessons
${p.lessons.map(l=>`- ${esc(l)}
`).join("")}
Go to cart `;modal.classList.remove("hidden");modal.classList.add("flex");modal.onclick=e=>{if(e.target===modal)modal.classList.add("hidden")};content.querySelectorAll("[data-track-modal]").forEach(b=>b.onclick=()=>{const ids=getFallows();const idx=ids.indexOf(p.id);if(idx>-1)ids.splice(idx,1);else ids.push(p.id);saveFallows(ids);openDetail(p.id);render()});content.querySelectorAll("[data-cart-modal]").forEach(b=>b.onclick=()=>{const c=getCart();c[p.id]=(c[p.id]||0)+1;saveCart(c);openDetail(p.id);render()})}
function bindEvents(){document.getElementById("searchInput").oninput=applyFilters;document.getElementById("categoryFilter").onchange=applyFilters;document.getElementById("closeDetail").onclick=()=>document.getElementById("detailModal").classList.add("hidden");document.getElementById("catalogGrid").onclick=e=>{const t=e.target;if(t.dataset.detail)openDetail(t.dataset.detail);if(t.dataset.track){const ids=getFallows();const id=t.dataset.track;const idx=ids.indexOf(id);if(idx>-1)ids.splice(idx,1);else ids.push(id);saveFallows(ids);render()}if(t.dataset.cart){const c=getCart();const id=t.dataset.cart;c[id]=(c[id]||0)+1;saveCart(c);render()}}}
async function init(){try{const r=await fetch("./catalog.json");if(!r.ok)throw Error();products=await r.json();[...new Set(products.map(x=>x.category))].forEach(c=>document.getElementById("categoryFilter").insertAdjacentHTML("beforeend",``));applyFilters();bindEvents()}catch(e){document.getElementById("catalogGrid").innerHTML='The catalog could not be loaded. Please contact the studio for assistance.
'}}
init();