All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 36s
- Geluidsmeter: meet het klasgeluid live via de microfoon in een verticale
meter (rustig/pratend/te luid). Twee aanpasbare streefniveaus ("Rustig
tot" / "Te luid vanaf") laten de leerkracht de meter afstemmen op het
eigen lokaal en apparaat. Microfoon staat standaard uit en start alleen
na een expliciete klik; er wordt niets opgenomen of verstuurd, alles
blijft lokaal in de browser. Stopt de microfoonstream vanzelf zodra de
widget van het bord verdwijnt, ook als "Stop" niet eerst is ingedrukt.
- Gevoelszones: individuele check-in met vier zones (blauw/groen/geel/
rood, Zones of Regulation-stijl) - elke zone geeft een korte uitleg én
een concrete tip om jezelf te helpen. Anders dan de bestaande
Gevoelsmeter (mood.js), die een anonieme klaspoll is.
- Ademhalingsoefening: een groeiende/krimpende cirkel begeleidt rustig
ademhalen in drie instelbare tempo's (4-4, 4-7-8, box breathing),
met rondjesteller.
- Complimentenmachine: trekt een willekeurig compliment uit twintig
kindvriendelijke complimenten, met dezelfde spin-en-onthul-animatie als
de namenkiezer.
- Alle vier volledig tweetalig (nl/en), toegevoegd aan de mobiele-dekking-
test, en geverifieerd in de browser inclusief een echte (fake) micro-
foonstream voor de geluidsmeter.
60 lines
2.3 KiB
JavaScript
60 lines
2.3 KiB
JavaScript
/* widget: Gevoelszones */
|
|
/* =========================================================
|
|
Individual check-in (Zones of Regulation-stijl): kies je zone, krijg een
|
|
passende tip - anders dan de Gevoelsmeter (mood.js), die is een anonieme
|
|
klaspoll, geen persoonlijke check-in met uitleg.
|
|
==========================================================*/
|
|
function mountFeelings(root, initState){
|
|
let picked = (initState && initState.picked) || null;
|
|
const ZONES = ["blue","green","yellow","red"];
|
|
const EMOJI = { blue:"💧", green:"🌿", yellow:"⚡", red:"🔥" };
|
|
root.innerHTML = `
|
|
<div class="fz">
|
|
<p class="play-mission fz-mission"></p>
|
|
<div class="fz-zones"></div>
|
|
<div class="fz-detail" hidden>
|
|
<h3 class="fz-detail-title"></h3>
|
|
<p class="fz-detail-desc"></p>
|
|
<p class="fz-detail-tip"></p>
|
|
</div>
|
|
</div>`;
|
|
const zonesEl = root.querySelector(".fz-zones"), detail = root.querySelector(".fz-detail");
|
|
function pick(id){
|
|
picked = id;
|
|
tone(id==="blue"?294:id==="green"?392:id==="yellow"?523:220, 0.14, 0, "sine", .08);
|
|
draw();
|
|
}
|
|
function draw(){
|
|
zonesEl.innerHTML = "";
|
|
ZONES.forEach(id=>{
|
|
const b = document.createElement("button");
|
|
b.className = "fz-zone " + id + (picked === id ? " active" : "");
|
|
b.setAttribute("aria-pressed", String(picked === id));
|
|
const emoji = document.createElement("span");
|
|
emoji.className = "fz-emoji";
|
|
emoji.textContent = EMOJI[id];
|
|
const label = document.createElement("span");
|
|
label.className = "fz-label";
|
|
label.textContent = T("fzZone_" + id);
|
|
b.append(emoji, label);
|
|
b.addEventListener("click", ()=>pick(id));
|
|
zonesEl.appendChild(b);
|
|
});
|
|
if(picked){
|
|
detail.hidden = false;
|
|
detail.className = "fz-detail " + picked;
|
|
detail.querySelector(".fz-detail-title").textContent = EMOJI[picked] + " " + T("fzZone_" + picked);
|
|
detail.querySelector(".fz-detail-desc").textContent = T("fzDesc_" + picked);
|
|
detail.querySelector(".fz-detail-tip").textContent = T("fzTip_" + picked);
|
|
}else{
|
|
detail.hidden = true;
|
|
}
|
|
}
|
|
function labels(){
|
|
root.querySelector(".fz-mission").textContent = T("wg_feelings_d");
|
|
draw();
|
|
}
|
|
document.addEventListener("langchange", labels);
|
|
labels();
|
|
return { getState: ()=>({ picked }) };
|
|
}
|