Skip to content

Commit dd986f6

Browse files
committed
- add pushup timer for personal use
1 parent affcc17 commit dd986f6

File tree

1 file changed

+269
-0
lines changed

1 file changed

+269
-0
lines changed

playground/pushup_timer.html

Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>Pushup Timer</title>
7+
<style>
8+
body {
9+
font-family: Arial, sans-serif;
10+
display: flex;
11+
flex-direction: column;
12+
align-items: center;
13+
justify-content: center;
14+
min-height: 100vh;
15+
margin: 0;
16+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
17+
color: white;
18+
}
19+
20+
.container {
21+
text-align: center;
22+
background: rgba(255, 255, 255, 0.1);
23+
padding: 40px;
24+
border-radius: 20px;
25+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
26+
max-width: 500px;
27+
width: 90%;
28+
}
29+
30+
.counter {
31+
font-size: 4rem;
32+
font-weight: bold;
33+
margin-bottom: 20px;
34+
}
35+
36+
.phase {
37+
font-size: 2.5rem;
38+
font-weight: bold;
39+
margin: 20px 0;
40+
padding: 20px;
41+
border-radius: 15px;
42+
transition: all 0.3s ease;
43+
}
44+
45+
.phase.down {
46+
background: #ff6b6b;
47+
box-shadow: 0 4px 15px rgba(255, 107, 107, 0.4);
48+
}
49+
50+
.phase.hold {
51+
background: #ffd93d;
52+
color: #333;
53+
box-shadow: 0 4px 15px rgba(255, 217, 61, 0.4);
54+
}
55+
56+
.phase.up {
57+
background: #6bcf7f;
58+
box-shadow: 0 4px 15px rgba(107, 207, 127, 0.4);
59+
}
60+
61+
.phase.rest {
62+
background: #4ecdc4;
63+
box-shadow: 0 4px 15px rgba(78, 205, 196, 0.4);
64+
}
65+
66+
.phase.countdown {
67+
background: #9b59b6;
68+
box-shadow: 0 4px 15px rgba(155, 89, 182, 0.4);
69+
animation: pulse 1s infinite;
70+
}
71+
72+
@keyframes pulse {
73+
0% { transform: scale(1); }
74+
50% { transform: scale(1.05); }
75+
100% { transform: scale(1); }
76+
}
77+
78+
.timer {
79+
font-size: 3rem;
80+
font-weight: bold;
81+
margin: 20px 0;
82+
}
83+
84+
.progress-bar {
85+
width: 100%;
86+
height: 20px;
87+
background: rgba(255, 255, 255, 0.2);
88+
border-radius: 10px;
89+
overflow: hidden;
90+
margin: 20px 0;
91+
}
92+
93+
.progress-fill {
94+
height: 100%;
95+
background: white;
96+
transition: width 0.1s linear;
97+
}
98+
99+
.controls {
100+
margin-top: 30px;
101+
}
102+
103+
button {
104+
font-size: 1.2rem;
105+
padding: 15px 30px;
106+
margin: 10px;
107+
border: none;
108+
border-radius: 10px;
109+
background: rgba(255, 255, 255, 0.2);
110+
color: white;
111+
cursor: pointer;
112+
transition: all 0.3s ease;
113+
}
114+
115+
button:hover {
116+
background: rgba(255, 255, 255, 0.3);
117+
transform: translateY(-2px);
118+
}
119+
120+
.completed {
121+
font-size: 2rem;
122+
color: #6bcf7f;
123+
margin-top: 20px;
124+
}
125+
</style>
126+
</head>
127+
<body>
128+
<div class="container">
129+
<div class="counter">
130+
Pushup <span id="currentPushup">0</span> / 15
131+
</div>
132+
133+
<div id="phase" class="phase rest">
134+
Ready to Start
135+
</div>
136+
137+
<div class="timer" id="timer">0</div>
138+
139+
<div class="progress-bar">
140+
<div class="progress-fill" id="progressFill"></div>
141+
</div>
142+
143+
<div class="controls">
144+
<button onclick="startWorkout()">Start</button>
145+
<button onclick="pauseWorkout()">Pause</button>
146+
<button onclick="resetWorkout()">Reset</button>
147+
</div>
148+
149+
<div id="completed" class="completed" style="display: none;">
150+
🎉 Great job! All 15 pushups completed! 🎉
151+
</div>
152+
</div>
153+
154+
<script>
155+
let currentPushup = 0;
156+
let currentPhase = 'ready'; // ready, countdown, down, hold, up
157+
let timeLeft = 0;
158+
let totalTime = 0;
159+
let intervalId = null;
160+
let isRunning = false;
161+
162+
const phases = {
163+
countdown: { duration: 5, text: 'GET READY', class: 'countdown' },
164+
down: { duration: 2, text: 'GO DOWN', class: 'down' },
165+
hold: { duration: 1, text: 'HOLD', class: 'hold' },
166+
up: { duration: 1, text: 'PUSH UP', class: 'up' }
167+
};
168+
169+
function updateDisplay() {
170+
document.getElementById('currentPushup').textContent = currentPushup;
171+
document.getElementById('timer').textContent = timeLeft;
172+
173+
const phaseElement = document.getElementById('phase');
174+
const progressFill = document.getElementById('progressFill');
175+
176+
if (currentPhase === 'ready') {
177+
phaseElement.textContent = 'Ready to Start';
178+
phaseElement.className = 'phase rest';
179+
progressFill.style.width = '0%';
180+
} else if (currentPhase === 'completed') {
181+
phaseElement.textContent = 'Workout Complete!';
182+
phaseElement.className = 'phase rest';
183+
progressFill.style.width = '100%';
184+
document.getElementById('completed').style.display = 'block';
185+
} else if (currentPhase === 'countdown') {
186+
phaseElement.textContent = `GET READY - ${timeLeft}`;
187+
phaseElement.className = 'phase countdown';
188+
const progress = ((totalTime - timeLeft) / totalTime) * 100;
189+
progressFill.style.width = `${progress}%`;
190+
} else {
191+
const phase = phases[currentPhase];
192+
phaseElement.textContent = phase.text;
193+
phaseElement.className = `phase ${phase.class}`;
194+
195+
const progress = ((totalTime - timeLeft) / totalTime) * 100;
196+
progressFill.style.width = `${progress}%`;
197+
}
198+
}
199+
200+
function nextPhase() {
201+
if (currentPhase === 'ready') {
202+
currentPhase = 'countdown';
203+
} else if (currentPhase === 'countdown') {
204+
currentPushup = 1;
205+
currentPhase = 'down';
206+
} else if (currentPhase === 'down') {
207+
currentPhase = 'hold';
208+
} else if (currentPhase === 'hold') {
209+
currentPhase = 'up';
210+
} else if (currentPhase === 'up') {
211+
if (currentPushup >= 15) {
212+
currentPhase = 'completed';
213+
clearInterval(intervalId);
214+
isRunning = false;
215+
updateDisplay();
216+
return;
217+
} else {
218+
currentPushup++;
219+
currentPhase = 'down';
220+
}
221+
}
222+
223+
timeLeft = phases[currentPhase].duration;
224+
totalTime = timeLeft;
225+
updateDisplay();
226+
}
227+
228+
function tick() {
229+
timeLeft--;
230+
updateDisplay();
231+
232+
if (timeLeft <= 0) {
233+
nextPhase();
234+
}
235+
}
236+
237+
function startWorkout() {
238+
if (!isRunning) {
239+
if (currentPhase === 'ready') {
240+
nextPhase();
241+
}
242+
intervalId = setInterval(tick, 1000);
243+
isRunning = true;
244+
}
245+
}
246+
247+
function pauseWorkout() {
248+
if (isRunning) {
249+
clearInterval(intervalId);
250+
isRunning = false;
251+
}
252+
}
253+
254+
function resetWorkout() {
255+
clearInterval(intervalId);
256+
isRunning = false;
257+
currentPushup = 0;
258+
currentPhase = 'ready';
259+
timeLeft = 0;
260+
totalTime = 0;
261+
document.getElementById('completed').style.display = 'none';
262+
updateDisplay();
263+
}
264+
265+
// Initialize display
266+
updateDisplay();
267+
</script>
268+
</body>
269+
</html>

0 commit comments

Comments
 (0)