// workout.jsx — treino com timers automáticos + bips/voz (sem tocar para contar reps) const { useState: useW, useEffect: useWE, useRef: useWR } = React; function fmt(s) { if (s >= 60) { const m = Math.floor(s / 60), r = s % 60; return `${m}:${String(r).padStart(2, '0')}`; } return String(s); } /* mini player de música (simulado) */ function MusicBar({ playing, onToggle }) { return (
CAPA
Beast Mode · Mix de Treino
136 BPM · PLAYLIST PRINCIPAL
); } function SoundToggle({ muted, onToggle }) { return ( ); } function Workout({ user, onExit, onFinish }) { const W = TODAY_WORKOUT; const blocks = W.blocks; const level = user.level || 'iniciante'; const sex = user.sex || 'masc'; const [bi, setBi] = useW(0); const [set, setSet] = useW(1); const [phase, setPhase] = useW('ready'); // ready | exercise | rest | complete const [readyLeft, setReadyLeft] = useW(3); const [exLeft, setExLeft] = useW(0); const [exDur, setExDur] = useW(1); const [restLeft, setRestLeft] = useW(0); const [running, setRunning] = useW(true); const [music, setMusic] = useW(true); const [muted, setMuted] = useW(false); const mref = useWR(muted); mref.current = muted; const block = blocks[bi]; const dur = (b) => blockDuration(b, level); const say = (text) => { if (!mref.current) ZAudio.speak(text, sex); }; const ping = (fn) => { if (!mref.current) fn(); }; // progresso geral const totalSets = blocks.reduce((a, b) => a + b.sets, 0); const doneSets = blocks.slice(0, bi).reduce((a, b) => a + b.sets, 0) + (set - 1); const overall = phase === 'complete' ? 1 : doneSets / totalSets; // inicia uma série: fase de preparação function startReady(nbi, nset) { setBi(nbi); setSet(nset); setReadyLeft(3); setPhase('ready'); setRunning(true); const b = blocks[nbi]; say(`${b.name}. Série ${nset}. Prepare-se.`); } // monta o primeiro exercício ao abrir useWE(() => { ZAudio.ensure(); startReady(0, 1); /* eslint-disable-next-line */ }, []); // ---- contagem de PREPARAÇÃO ---- useWE(() => { if (phase !== 'ready') return; if (readyLeft <= 0) { const d = dur(block); setExDur(d); setExLeft(d); setPhase('exercise'); ping(ZAudio.beepGo); say('Vai!'); return; } const id = setTimeout(() => { const nv = readyLeft - 1; setReadyLeft(nv); if (nv > 0) { ping(ZAudio.beepPrep); say(ZAudio.numWord(nv)); } }, 1000); return () => clearTimeout(id); }, [phase, readyLeft]); // ---- contagem do EXERCÍCIO ---- useWE(() => { if (phase !== 'exercise' || !running) return; if (exLeft <= 0) { finishExercise(); return; } const id = setTimeout(() => { const nv = exLeft - 1; setExLeft(nv); if (nv > 0 && nv <= 5) { ping(ZAudio.beepExercise); say(ZAudio.numWord(nv)); } // aviso 5s finais }, 1000); return () => clearTimeout(id); }, [phase, running, exLeft]); // ---- contagem do DESCANSO ---- useWE(() => { if (phase !== 'rest') return; if (restLeft <= 0) { advance(); return; } const id = setTimeout(() => { const nv = restLeft - 1; setRestLeft(nv); if (nv > 0 && nv <= 3) { ping(ZAudio.beepRest); say(ZAudio.numWord(nv)); } // aviso 3s finais }, 1000); return () => clearTimeout(id); }, [phase, restLeft]); function finishExercise() { const lastSet = set >= block.sets; const lastBlock = bi >= blocks.length - 1; if (lastSet && lastBlock) { setPhase('complete'); ping(ZAudio.beepDone); say('Treino concluído. Mandou muito bem!'); return; } const rest = block.rest || 0; if (rest > 0) { setRestLeft(rest); setPhase('rest'); say('Descanse.'); } else { advance(); } } function advance() { if (set >= block.sets) startReady(bi + 1, 1); else startReady(bi, set + 1); } function skip() { ZAudio.stopVoice(); if (phase === 'rest') advance(); else finishExercise(); } function exitWorkout() { ZAudio.stopVoice(); onExit(); } function finishWorkout() { ZAudio.stopVoice(); onFinish(); } const isTime = block && block.type === 'time'; const nextLabel = (() => { if (set < block.sets) return `Série ${set + 1} · ${block.name}`; if (bi < blocks.length - 1) return blocks[bi + 1].name; return 'Fim do treino'; })(); /* ---------- COMPLETE ---------- */ if (phase === 'complete') { return (
TREINO CONCLUÍDO

Dia {W.day}
finalizado

{[['+120','XP'],['~30','MIN'],[user.streak+1,'STREAK']].map(([v,l]) => (
{v}
{l}
))}

"{PROGRAM_30[W.day-1].frase}"

); } /* ---------- REST ---------- */ if (phase === 'rest') { const restTotal = block.rest || 1; const near = restLeft <= 3; return (
DESCANSO { setMuted(m => !m); ZAudio.ensure(); }} />
{restLeft}
SEGUNDOS
A SEGUIR
{nextLabel}
); } /* ---------- READY (preparação) ---------- */ if (phase === 'ready') { return (
{ setMuted(m => !m); ZAudio.ensure(); }} />
{block.phase.toUpperCase()} · SÉRIE {set} DE {block.sets}

{block.name}

{isTime ? `SEGURE ${block.target}s` : `${block.target} REPS · ~${dur(block)}s`}

{readyLeft}
PREPARE-SE
); } /* ---------- EXERCISE ---------- */ const prog = (exDur - exLeft) / exDur; const near = exLeft <= 5; return (
{/* top bar */}
{ setMuted(m => !m); ZAudio.ensure(); }} />
{block.phase.toUpperCase()} · SÉRIE {set} DE {block.sets}

{block.name}

{block.cue}

ANIMAÇÃO DO EXERCÍCIO{block.name}
{/* timer automático */}
{/* controle de pausa */}
setMusic(m => !m)} />
); } Object.assign(window, { Workout });