teach/public/js/widgets/hak.js
Ramon 5cd1c80073
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 10s
v0.2.05-beta: bronwaarde widgets als string bewaren + projectafspraken toegevoegd
- ball.js/flits.js/hak.js: S.card bewaart de geselecteerde bronwaarde nu altijd als string i.p.v. te casten naar number, zodat de bronselectie correct blijft werken
- AGENTS.md en CLAUDE.md toegevoegd met projectafspraken

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-13 08:04:50 +02:00

94 lines
3.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* widget: Hakken & plakken */
/* =========================================================
Chop & blend widget — words in sounds, with read-aloud
==========================================================*/
function mountHak(root, initState){
const S = { card:0, idx:0, order:[], chopped:false };
if(initState) S.card = initState.card ?? 0;
const canSpeak = "speechSynthesis" in window;
root.innerHTML = `
<div class="mt">
<div class="mt-top">
<select class="fl-sel hk-src"></select>
<div class="mt-score hk-cnt"></div>
</div>
<div class="mt-card">
<div class="fl-pic hk-pic"></div>
<div class="hk-word"></div>
</div>
<div class="trow" style="display:flex;gap:8px;justify-content:center;flex-wrap:wrap;flex:none;">
<button class="tbtn hk-say" ${canSpeak?"":'style="display:none"'}></button>
<button class="tbtn ghost hk-chop"></button>
<button class="tbtn ghost hk-glue"></button>
<button class="tbtn ghost hk-next">▸</button>
</div>
</div>`;
const el = s => root.querySelector(s);
const srcSel = el(".hk-src"), wordEl = el(".hk-word"),
picEl = el(".hk-pic"), cntEl = el(".hk-cnt");
function list(){ S.card = normWordSource(S.card); return wordSourceList(S.card); }
function buildSrc(){ fillWordSourceSel(srcSel, S.card); }
function cur(){ return list()[S.order[S.idx]]; }
function newRound(){ S.order = shuffle(list().map((_,i)=>i)); S.idx = 0; }
function display(){
const [w, pic] = cur();
wordEl.innerHTML = "";
if(S.chopped){
w.split("").forEach((l,i)=>{
if(i>0){
const sep = document.createElement("span");
sep.className = "hk-sep"; sep.textContent = "";
wordEl.appendChild(sep);
}
const s = document.createElement("span");
s.className = "hk-l " + (VOWELS.includes(l) ? "v" : "c");
s.textContent = l;
s.style.animationDelay = (i*0.08)+"s";
wordEl.appendChild(s);
});
}else{
const s = document.createElement("span");
s.className = "hk-whole";
s.textContent = w;
wordEl.appendChild(s);
}
setPic(picEl, pic);
cntEl.textContent = `${T("word")} ${S.idx+1}/${S.order.length}`;
}
function speak(text, rate){
if(!canSpeak) return;
speechSynthesis.cancel();
const u = new SpeechSynthesisUtterance(text);
u.lang = LANG==="nl" ? "nl-NL" : "en-GB";
u.rate = rate;
speechSynthesis.speak(u);
}
el(".hk-say").addEventListener("click", ()=>{
if(S.chopped) speak(cur()[0].split("").join(", "), 0.7);
else speak(cur()[0], 0.85);
});
el(".hk-chop").addEventListener("click", ()=>{ S.chopped = true; display(); play(sndGood); });
el(".hk-glue").addEventListener("click", ()=>{ S.chopped = false; display(); play(sndGood); });
el(".hk-next").addEventListener("click", ()=>{
S.idx++; if(S.idx >= S.order.length) newRound();
S.chopped = false; display();
});
const play = f=>f();
srcSel.addEventListener("change", ()=>{
S.card = srcSel.value;
newRound(); S.chopped = false; display();
});
function labels(){
el(".hk-say").textContent = T("hakSay");
el(".hk-chop").textContent = T("hakChop");
el(".hk-glue").textContent = T("hakGlue");
buildSrc();
}
document.addEventListener("langchange", ()=>{ S.card=0; labels(); newRound(); S.chopped=false; display(); });
document.addEventListener("wordschange", ()=>{ buildSrc(); newRound(); S.chopped=false; display(); });
labels(); newRound(); display();
return { getState: ()=>({card:S.card}) };
}