All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 8s
94 lines
3.6 KiB
JavaScript
94 lines
3.6 KiB
JavaScript
/* 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}) };
|
||
}
|
||
|