teach/public/js/widgets/hak.js
bes-r d1d3265c6c
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 8s
v0.1.02: opsplitsing in css/, js/core|data|board|app en js/widgets/* (geen functionele wijziging)
2026-07-09 21:56:49 +02:00

94 lines
3.6 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.startsWith("t") ? srcSel.value : +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}) };
}