/* widgets: Rekenen (+ - x : %) */ /* ========================================================= Math practice engine โ€” add / sub / mul / div / percentages ==========================================================*/ function mountMath(root, initState, mode, opts){ const ROUND = 10; const onProgress = opts && opts.onProgress; const S = { level:1, snd:true, amode:"blocks", q:null, tries:0, done:0, good:0, input:"", busy:false }; if(initState){ S.level = initState.level||1; S.snd = initState.snd!==false; S.amode = initState.amode||"blocks"; } const play = f=>{ if(S.snd) f(); }; const rnd = n => Math.floor(Math.random()*n); root.innerHTML = `
๐ŸŒŸ
`; const el = s => root.querySelector(s); const mtEl = el(".mt"), qEl = el(".mt-q"), aEl = el(".mt-a"), fbEl = el(".mt-fb"), scoreEl = el(".mt-score"), doneEl = el(".mt-done"), sndBtn = el(".lg-snd"), modeBtn = el(".lg-mode"), padEl = el(".mt-pad"), slotsEl = el(".mt-slots"), trayEl = el(".mt-tray"); /* ---- digit blocks with dot patterns (like the letter blocks) ---- */ function makeDigitBlock(d){ const b = document.createElement("div"); b.className = "blok sm"; b.dataset.digit = d; b.innerHTML = `
${d}
`; const dg = dotGrid(d); dg.classList.add("blok-dots"); b.appendChild(dg); attachDigitDrag(b); return b; } "0123456789".split("").forEach(d=>trayEl.appendChild(makeDigitBlock(d))); function makeDigitSlot(d){ const s = document.createElement("div"); s.className = "slot sm"; s.dataset.digit = d; s.innerHTML = `
`; const dg = dotGrid(d); dg.classList.add("slot-dots"); s.appendChild(dg); s.addEventListener("click", ()=>{ const placed = s.querySelector(".blok"); if(placed && Date.now() - (+placed.dataset.placedAt||0) > 400){ placed.remove(); s.classList.remove("filled"); } }); return s; } function attachDigitDrag(block){ block.addEventListener("pointerdown", e=>{ if(e.pointerType==="mouse" && e.button!==0) return; if(S.busy || doneEl.classList.contains("show")) return; e.preventDefault(); try{ block.releasePointerCapture(e.pointerId); }catch(_){} const clone = block.cloneNode(true); clone.id = "dragClone"; document.body.appendChild(clone); const moveTo = ev=>{ const zf = VZ(); clone.style.left = (ev.clientX/zf)+"px"; clone.style.top = (ev.clientY/zf)+"px"; }; moveTo(e); let hovered = null; const hit = ev=>{ const pad = 8; let found = null; slotsEl.querySelectorAll(".slot:not(.filled)").forEach(s=>{ const r = s.getBoundingClientRect(); if(ev.clientX>=r.left-pad && ev.clientX<=r.right+pad && ev.clientY>=r.top-pad && ev.clientY<=r.bottom+pad) found = s; }); return found; }; const move = ev=>{ moveTo(ev); const s = hit(ev); if(hovered && hovered!==s) hovered.classList.remove("droptarget"); hovered = s; if(hovered) hovered.classList.add("droptarget"); }; const up = ev=>{ document.removeEventListener("pointermove",move); document.removeEventListener("pointerup",up); document.removeEventListener("pointercancel",cancel); clone.remove(); if(hovered) hovered.classList.remove("droptarget"); const target = hit(ev); if(target){ if(target.dataset.digit === block.dataset.digit){ const copy = block.cloneNode(true); copy.classList.add("in-slot"); copy.dataset.placedAt = Date.now(); target.appendChild(copy); target.classList.add("filled"); play(sndGood); if(!slotsEl.querySelector(".slot:not(.filled)")) blocksSolved(); }else{ target.classList.add("shake"); setTimeout(()=>target.classList.remove("shake"), 400); play(sndBad); } } }; const cancel = ()=>{ document.removeEventListener("pointermove",move); document.removeEventListener("pointerup",up); document.removeEventListener("pointercancel",cancel); clone.remove(); if(hovered) hovered.classList.remove("droptarget"); }; document.addEventListener("pointermove",move); document.addEventListener("pointerup",up); document.addEventListener("pointercancel",cancel); }); } function blocksSolved(){ S.busy = true; S.good++; fbEl.textContent = T("mGood"); play(sndWin); confetti(el(".mt-card")); setTimeout(next, 1000); } function gen(){ const L = S.level; let a, b, ans, txt; if(mode==="add"){ const max = L===1 ? 10 : L===2 ? 20 : 100; a = rnd(max+1); b = rnd(max-a+1); ans = a+b; txt = `${a} + ${b} =`; }else if(mode==="sub"){ const max = L===1 ? 10 : L===2 ? 20 : 100; a = rnd(max+1); b = rnd(a+1); ans = a-b; txt = `${a} โˆ’ ${b} =`; }else if(mode==="mul"){ const tmax = L===1 ? 5 : L===2 ? 10 : 12; a = 1+rnd(tmax); b = 1+rnd(L===3 ? 12 : 10); ans = a*b; txt = `${a} ร— ${b} =`; }else if(mode==="div"){ const tmax = L===1 ? 5 : L===2 ? 10 : 12; const t = 1+rnd(tmax), q = 1+rnd(L===3 ? 12 : 10); a = t*q; ans = q; txt = `${a} ${S.lang==="nl" || LANG==="nl" ? ":" : "รท"} ${t} =`; }else{ /* pct */ const sets = L===1 ? [10,50,100] : L===2 ? [10,20,25,50,75,100] : [5,10,20,25,30,40,50,60,70,75,80,90]; const p = sets[rnd(sets.length)]; const step = {5:20,10:10,20:5,25:4,30:10,40:5,50:2,60:5,70:10,75:4,80:5,90:10,100:1}[p]; const base = step*(1+rnd(12)); ans = p*base/100; txt = `${p}% ${T("mOf")} ${base} =`; } return { txt, ans }; } function newQuestion(){ let q = gen(); if(S.q && q.txt===S.q.txt) q = gen(); /* avoid the same sum twice in a row */ S.q = q; S.tries = 0; S.input = ""; S.busy = false; qEl.textContent = q.txt; aEl.textContent = ""; aEl.classList.remove("ok"); fbEl.textContent = ""; fbEl.classList.remove("ans"); slotsEl.innerHTML = ""; if(S.amode==="blocks"){ String(q.ans).split("").forEach(d=>slotsEl.appendChild(makeDigitSlot(d))); } updateScore(); } function updateScore(){ scoreEl.textContent = `${S.good} ${T("mGoed")} ยท ${Math.min(S.done+1,ROUND)}/${ROUND}`; } function endRound(){ doneEl.querySelector(".dt").textContent = T("mDoneT"); doneEl.querySelector(".ds").textContent = `${S.good}/${ROUND} ${T("mGoed")}`; doneEl.querySelector(".dg").textContent = S.good>=8 ? "๐ŸŒŸ" : S.good>=5 ? "๐Ÿ‘" : "๐Ÿ’ช"; doneEl.classList.add("show"); if(S.good>=8) play(sndWin); if(onProgress) onProgress({ attempts: ROUND, correct: S.good, stars: 0 }); } function next(){ S.done++; if(S.done >= ROUND){ endRound(); } else newQuestion(); } function check(){ if(S.busy || S.input==="") return; if(+S.input === S.q.ans){ S.busy = true; S.good++; aEl.classList.add("ok"); fbEl.textContent = T("mGood"); play(sndGood); confetti(el(".mt-card")); setTimeout(next, 900); }else{ S.tries++; aEl.classList.add("shake"); setTimeout(()=>aEl.classList.remove("shake"), 400); play(sndBad); S.input = ""; aEl.textContent = ""; if(S.tries >= 3){ S.busy = true; fbEl.textContent = `${T("mAnsWas")} ${S.q.ans}`; fbEl.classList.add("ans"); setTimeout(next, 1800); } } } ["7","8","9","โŒซ","4","5","6","0","1","2","3","โœ“"].forEach(k=>{ const b = document.createElement("button"); b.textContent = k; if(k==="โœ“") b.className = "ok"; b.addEventListener("click", ()=>{ if(doneEl.classList.contains("show")) return; if(k==="โŒซ"){ if(!S.busy){ S.input = S.input.slice(0,-1); aEl.textContent = S.input; } } else if(k==="โœ“") check(); else if(!S.busy && S.input.length<4){ S.input += k; aEl.textContent = S.input; } }); padEl.appendChild(b); }); el(".mt-again").addEventListener("click", ()=>{ S.done = 0; S.good = 0; doneEl.classList.remove("show"); newQuestion(); }); root.querySelectorAll(".lg-levels button").forEach(b=>{ b.addEventListener("click", ()=>{ S.level = +b.dataset.l; S.done = 0; S.good = 0; doneEl.classList.remove("show"); labels(); newQuestion(); }); }); sndBtn.addEventListener("click", ()=>{ S.snd = !S.snd; sndBtn.textContent = S.snd ? "๐Ÿ””" : "๐Ÿ”•"; sndBtn.classList.toggle("off", !S.snd); }); modeBtn.addEventListener("click", ()=>{ S.amode = S.amode==="blocks" ? "type" : "blocks"; labels(); newQuestion(); }); function labels(){ root.querySelectorAll(".lg-levels button").forEach(b=>{ b.textContent = T("lvl"+b.dataset.l); b.classList.toggle("active", +b.dataset.l===S.level); }); sndBtn.textContent = S.snd ? "๐Ÿ””" : "๐Ÿ”•"; sndBtn.classList.toggle("off", !S.snd); sndBtn.title = T("sndWidget"); modeBtn.textContent = S.amode==="blocks" ? "๐Ÿงฑ" : "โŒจ๏ธ"; modeBtn.title = T("amode"); mtEl.classList.toggle("blocks", S.amode==="blocks"); el(".mt-again").textContent = T("mAgain"); updateScore(); } document.addEventListener("langchange", ()=>{ labels(); newQuestion(); }); labels(); newQuestion(); return { getState: ()=>({ level:S.level, snd:S.snd, amode:S.amode }), setState: st=>{ if(st){ S.level=st.level||1; S.snd=st.snd!==false; S.amode=st.amode||"blocks"; labels(); newQuestion(); } } }; }