/* widget: Ademhalingsoefening */
/* =========================================================
Rustgevende ademhaling met een groeiende/krimpende cirkel, drie tempo's.
De cirkelgrootte wordt via inline style/transition-duration gestuurd
(niet via CSS-klassen) zodat elke fase precies aansluit op de vorige
zonder terugspringen.
==========================================================*/
const BREATHE_PATTERNS = { "4-4": [["in",4],["out",4]], "4-7-8": [["in",4],["hold",7],["out",8]], "4-4-4-4": [["in",4],["hold",4],["out",4],["hold",4]] };
const BREATHE_SCALE = { in: 1.4, hold: null, out: .7 };
function mountBreathe(root, initState){
let pattern = (initState && BREATHE_PATTERNS[initState.pattern]) ? initState.pattern : "4-4";
let running = false, phase = -1, phaseStart = 0, rafId = null, rounds = 0;
root.innerHTML = `
`;
const circle = root.querySelector(".bre-circle"), word = root.querySelector(".bre-word"),
count = root.querySelector(".bre-count"), roundsEl = root.querySelector(".bre-rounds"),
patternSel = root.querySelector(".bre-pattern"), toggle = root.querySelector(".bre-toggle");
Object.keys(BREATHE_PATTERNS).forEach(k=>patternSel.appendChild(new Option("", k)));
patternSel.value = pattern;
function updateRounds(){ roundsEl.textContent = T("breRounds").replace("{n}", rounds); }
function enterPhase(index){
const [name, dur] = BREATHE_PATTERNS[pattern][index];
circle.style.transitionDuration = dur + "s";
circle.classList.toggle("in", name === "in");
circle.classList.toggle("out", name === "out");
if(BREATHE_SCALE[name] !== null) circle.style.transform = "scale(" + BREATHE_SCALE[name] + ")";
word.textContent = T("bre_" + name);
}
function step(ts){
if(!running || !document.body.contains(root)) return;
const steps = BREATHE_PATTERNS[pattern];
if(phase < 0){ phase = 0; phaseStart = ts; enterPhase(phase); }
const [, dur] = steps[phase];
const elapsed = (ts - phaseStart) / 1000;
count.textContent = Math.max(0, Math.ceil(dur - elapsed)) || "";
if(elapsed >= dur){
phase = (phase + 1) % steps.length;
phaseStart = ts;
if(phase === 0){ rounds++; updateRounds(); }
enterPhase(phase);
}
rafId = requestAnimationFrame(step);
}
function start(){
running = true; phase = -1; rounds = 0;
updateRounds();
labels();
rafId = requestAnimationFrame(step);
}
function stop(){
running = false;
if(rafId) cancelAnimationFrame(rafId);
rafId = null;
circle.classList.remove("in","out");
circle.style.transform = ""; circle.style.transitionDuration = "";
word.textContent = ""; count.textContent = "";
labels();
}
toggle.addEventListener("click", ()=>{ if(running) stop(); else start(); });
patternSel.addEventListener("change", ()=>{ pattern = patternSel.value; if(running) stop(); });
function labels(){
root.querySelector(".bre-mission").textContent = T("wg_breathe_d");
toggle.textContent = running ? "⏸ " + T("breStop") : "🌬️ " + T("breStart");
[...patternSel.options].forEach(o=>{ o.textContent = T("brePattern_" + o.value); });
updateRounds();
}
document.addEventListener("langchange", labels);
labels();
return { getState: ()=>({ pattern }) };
}