BREATHE / frontend /src /components /DeepBreathWidget.jsx
tannuiscoding's picture
added app.py
5a264f5
import { useState, useEffect, useRef } from 'react'
const PHASES = [
{ label: 'Inhale', duration: 4, scale: 1.55 },
{ label: 'Hold', duration: 4, scale: 1.55 },
{ label: 'Exhale', duration: 6, scale: 1.0 },
{ label: 'Hold', duration: 2, scale: 1.0 },
]
export default function DeepBreathWidget() {
const [running, setRunning] = useState(false)
const [phaseIdx, setPhaseIdx] = useState(0)
const [tick, setTick] = useState(PHASES[0].duration)
const [breaths, setBreaths] = useState(0)
const intervalRef = useRef(null)
function stop() {
clearInterval(intervalRef.current)
setRunning(false)
setPhaseIdx(0)
setTick(PHASES[0].duration)
}
function start() {
setRunning(true)
setPhaseIdx(0)
setTick(PHASES[0].duration)
}
useEffect(() => {
if (!running) return
intervalRef.current = setInterval(() => {
setTick(t => {
if (t > 1) return t - 1
setPhaseIdx(pi => {
const next = (pi + 1) % PHASES.length
if (next === 0) setBreaths(b => b + 1)
setTick(PHASES[next].duration)
return next
})
return PHASES[0].duration
})
}, 1000)
return () => clearInterval(intervalRef.current)
}, [running])
const phase = PHASES[phaseIdx]
const pct = running ? (tick / phase.duration) : 1
return (
<div className="dbw">
<div className="dbw-orb-wrap">
<div className={`dbw-ring dbw-ring-1 ${running ? `dbw-ring--${phaseIdx}` : ''}`} />
<div className={`dbw-ring dbw-ring-2 ${running ? `dbw-ring--${phaseIdx}` : ''}`} />
<div
className="dbw-orb"
style={{
transform: running ? `scale(${phase.scale})` : 'scale(1)',
transition: phaseIdx === 0
? `transform ${phase.duration}s cubic-bezier(.4,0,.2,1)`
: phaseIdx === 2
? `transform ${phase.duration}s cubic-bezier(.4,0,.2,1)`
: 'none',
}}
>
<img src="/logo.svg" alt="" className="dbw-logo" />
</div>
{running && (
<svg className="dbw-arc" viewBox="0 0 120 120">
<circle cx="60" cy="60" r="54" fill="none" stroke="rgba(255,255,255,.08)" strokeWidth="4"/>
<circle
cx="60" cy="60" r="54"
fill="none"
stroke="url(#arcGrad)"
strokeWidth="4"
strokeLinecap="round"
strokeDasharray={`${2 * Math.PI * 54}`}
strokeDashoffset={`${2 * Math.PI * 54 * (1 - pct)}`}
transform="rotate(-90 60 60)"
/>
<defs>
<linearGradient id="arcGrad" x1="0" y1="0" x2="1" y2="1">
<stop offset="0%" stopColor="#FF4A26"/>
<stop offset="100%" stopColor="#7B4ABE"/>
</linearGradient>
</defs>
</svg>
)}
</div>
<div className="dbw-info">
<div className="dbw-phase">{running ? phase.label : 'Box Breathing'}</div>
<div className="dbw-tick">{running ? tick : '—'}</div>
<div className="dbw-counter">
{breaths > 0 || running
? `${breaths} breath${breaths !== 1 ? 's' : ''} completed`
: 'Tap start to begin'}
</div>
</div>
<div className="dbw-controls">
{!running
? <button className="dbw-btn dbw-btn--start" onClick={start}>▶ Start</button>
: <button className="dbw-btn dbw-btn--stop" onClick={stop}>■ Stop</button>
}
{!running && breaths > 0 && (
<button className="dbw-btn dbw-btn--reset" onClick={() => setBreaths(0)}>Reset</button>
)}
</div>
</div>
)
}