teach/public/js/widgets/hak.js
Ramon fabe721991
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 39s
feat: voeg leerjaarprofielen toe aan taal en rekenen (v0.4.41-beta)
2026-07-20 22:45:01 +02:00

112 lines
4.7 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 */
/* Veelvoorkomende spellingpatronen blijven als herkenbare klankgroep bij elkaar.
De langste groep wint, zodat bijvoorbeeld 'sch' niet als 's' + 'ch' eindigt. */
function soundChunks(word, lang=LANG){
const patterns=(lang==='nl'
? ['sch','aai','ooi','oei','eeuw','ieuw','ng','nk','ch','aa','ee','oo','uu','oe','ie','ei','ij','ou','au','ui','eu']
: ['tch','igh','sh','ch','th','ph','ng','ee','oo','ea','ou','ow','ai','ay','oi']).sort((a,b)=>b.length-a.length);
const chunks=[];
for(let i=0;i<word.length;){
const rest=word.slice(i).toLocaleLowerCase(lang==='nl'?'nl':'en');
const match=patterns.find(pattern=>rest.startsWith(pattern));
const length=match?match.length:1;
chunks.push(word.slice(i,i+length)); i+=length;
}
return chunks;
}
/* =========================================================
Chop & blend widget — words in sounds, with read-aloud
==========================================================*/
function mountHak(root, initState){
const S = { level:1, yearGroup:null, managedLearningLevel:false, card:0, idx:0, order:[], chopped:false };
if(initState){S.level=initState.level||1;S.yearGroup=initState.yearGroup||null;S.managedLearningLevel=!!initState.managedLearningLevel;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>
<p class="play-mission hk-hint"></p>
<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 languageWordsForYear(wordSourceList(S.card),S.yearGroup,S.level); }
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){
soundChunks(w).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 " + (/[aeiouyáéíóúäëïöü]/i.test(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(soundChunks(cur()[0]).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");
el(".hk-hint").textContent = T("hakHint");
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();
root.classList.toggle("managed-learning-level",S.managedLearningLevel);
return { getState: ()=>({card:S.card}) };
}