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 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 | <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Pomodoro</title>
<style>
* {
margin: 1px;
font-family: monospace;
}
.title {
margin-bottom: 8px;
}
.line {
margin-bottom: 8px;
margin-top: 8px;
}
.indicator {
margin-left: 7px;
}
.line {
display: none;
}
</style>
</head>
<body>
<div id="title" class="title">
<div> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ </div>
<div> | - P - O - M - O - D - O - R - O - | </div>
<div> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ </div>
</div>
<div id="state">
<div id="total-sessions" class="indicator"> </div>
<div id="total-time" class="indicator"> </div>
<div class="line"> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ </div>
<div id="session-type" class="indicator"> </div>
<div class="line"> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ </div>
<div id="time-remaining" class="indicator"> </div>
</div>
</body>
</html>
<script>
// Timer Options
const work_time=25;
const rest_time=5;
const long_rest_time=15;
const long_rest_interval=5;
const second = 1000;
// Language and Translation Options
const work_session_str = "Sesión de trabajo";
const long_rest_str = "Sesión de descanso largo";
const short_rest_str = "Sesión de descanso corto";
const total_sessions_str="Sesiones de trabajo totales";
const total_time_str = "Tiempo trabajado";
const press_ctrl_c_str = "Esperando pulsación CTRL+C";
const hours_str = "horas";
const minutes_str ="minutos";
const seconds_str ="segundos";
const and_str = "y";
const remaining_str ="restantes";
// HTML-linked labels
const session_type_tag = document.getElementById('session-type')
const total_sessions_tag = document.getElementById('total-sessions')
const total_time_tag = document.getElementById('total-time')
const time_remaining_tag = document.getElementById('time-remaining')
// Current URL for adding parameters
const url = new URL(window.location.href);
// Application Variables
let rest_counter=0;
let long_rest_counter=0;
let work_counter=0;
let is_rest_time=0;
let is_long_rest_time=0;
let total_work_minutes=0;
let work_hours =0;
let work_minutes =0
//
let seconds_counter = 0
let seconds_timer = 0
let minutes_counter = 0
let minutes_timer = 0
let hours_timer = 0
//
let load_flag = false;
// Oscillator Settings
var context = new AudioContext();
var oscillator = context.createOscillator();
oscillator.type = "sine";
oscillator.start();
context.suspend()
/*
Generates a beep using the previously created oscillator.
It is controlled by context.suspend() and context.resume()
*/
function beep (frequency, time) {
oscillator.frequency.value = frequency;
oscillator.connect(context.destination);
context.resume();
// Beep for 500 milliseconds
setTimeout(function () {
context.suspend()
}, time);
}
/*
Modify the `time_remaining_tag` to set the times for each iteration.
The main function must wait the same amount of time to synchronize.
*/
async function timer(countdown){
let minutes_remaining;
let seconds_remaining;
countdown = countdown*60
while (countdown > 0) {
countdown--;
minutes_remaining = Math.trunc(countdown / 60);
seconds_remaining = Math.trunc (countdown % 60);
time_remaining_tag.innerHTML=`${minutes_remaining} ${minutes_str} ${seconds_remaining} ${seconds_str} ${remaining_str}...`;
await new Promise(r => setTimeout(r, second));
}
}
/*
Two promises that call out to each other in an intermittent loop that projects a clock onto the screen.
The beep will sound every second.
*/
async function waitingKeypress() {
return new Promise((resolve) => {
let stopped = false;
function onKeyHandler(event) {
if (event.ctrlKey && event.key.toLowerCase() === "c") {
event.preventDefault();
stopped = true;
document.removeEventListener("keydown", onKeyHandler);
context.suspend();
resolve();
}
}
document.addEventListener("keydown", onKeyHandler);
seconds_counter = 0
seconds_timer = 0
minutes_counter = 0
minutes_timer = 0
hours_timer = 0
async function playBeepLoop() {
while (!stopped) {
beep(250, second/2);
seconds_counter++
seconds_timer = Math.trunc(seconds_counter % 60)
minutes_counter = Math.trunc(seconds_counter / 60)
minutes_timer = Math.trunc(minutes_counter % 60)
hours_timer = Math.trunc(minutes_counter / 60)
if (seconds_timer < 10) seconds_timer = `0${seconds_timer}`
if (minutes_timer < 10) minutes_timer = `0${minutes_timer}`
if (hours_timer < 10) hours_timer = `0${hours_timer}`
time_remaining_tag.innerHTML=`${press_ctrl_c_str} (${hours_timer}:${minutes_timer}:${seconds_timer})`;
await new Promise((resolve) => {
setTimeout(resolve, second);
});
}
}
playBeepLoop();
});
}
/*
Load the session based on the values and calculate based on the work counter
*/
function loadSession() {
const params = new URLSearchParams(window.location.search);
work_counter = Number(params.get("work_counter")) || 0;
rest_counter= work_counter*2;
long_rest_counter=work_counter;
is_rest_time=0;
is_long_rest_time=work_counter;
total_work_minutes=work_time*work_counter;
work_hours = Math.trunc (total_work_minutes / 60)
work_minutes = Math.trunc (total_work_minutes % 60)
}
/*
Saves the work counter and updates it in the URL parameters
*/
function saveSession () {
url.searchParams.set("work_counter", work_counter);
window.history.replaceState({}, "", url);
}
async function main (){
await waitingKeypress();
var elements = document.querySelectorAll('.line');
for(var i=0; i<elements.length; i++){
elements[i].style.display = "block"
}
loadSession ()
total_time_tag.innerHTML = `${total_time_str}: ${work_hours} ${hours_str} ${and_str} ${work_minutes} ${minutes_str}`
while (true) {
total_sessions_tag.innerHTML = `${total_sessions_str}: ${work_counter}`
rest_counter++;
is_rest_time = rest_counter%2;
if (is_rest_time == 0) {
long_rest_counter++;
is_long_rest_time = long_rest_counter % long_rest_interval;
if (is_long_rest_time == 0) {
session_type_tag.innerHTML=`${long_rest_str} — ${long_rest_time} ${minutes_str}`
await timer (long_rest_time)
await waitingKeypress();
}
else {
session_type_tag.innerHTML=`${short_rest_str} — ${rest_time} ${minutes_str}`
await timer (rest_time)
await waitingKeypress();
}
}
else {
work_counter++
session_type_tag.innerHTML=`${work_session_str} — ${work_time} ${minutes_str}`
await timer (work_time)
await waitingKeypress();
saveSession ()
}
total_work_minutes = work_counter * work_time;
work_hours = Math.trunc(total_work_minutes / 60)
work_minutes = Math.trunc (total_work_minutes % 60)
total_time_tag.innerHTML = `${total_time_str}: ${work_hours} ${hours_str} ${and_str} ${work_minutes} ${minutes_str}`
}
}
main ()
/*
# Copyright (C) 2025 HURSZ
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
</script>
|