File size: 3,743 Bytes
5a264f5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
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>
  )
}