teach/public/js/widgets/feelings.js
Ramon 9920fd3855
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 36s
feat: vier sociaal-emotionele widgets, waaronder een geluidsmeter (v0.4.21-beta)
- 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.
2026-07-19 00:44:39 +02:00

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 }) };
}