<!DOCTYPE html> <html lang="en"> <head> <title>Timer</title> <meta charset="utf-8" /> <meta name="description" content="Minute precision timer."> <meta name="viewport" content="width=device-width, initial-scale=1" /> </head> <body> <audio src="432.mp3" id="beep" autostart="false"></audio> <style> :root { color-scheme: light dark; /* DARK THEME */ --dark-main-bg-color: 0, 0, 0; --dark-text-color: 240, 240, 240; /* LIGHT THEME */ --light-main-bg-color: 255, 255, 255; --light-text-color: 0, 0, 0; /* CHOOSE DARK THEME */ --main-bg-color: rgb(var(--dark-main-bg-color)); --text-color: rgb(var(--dark-text-color)); } /* CHOOSE LIGHT THEME */ @media (prefers-color-scheme: light) { :root { --main-bg-color: rgb(var(--light-main-bg-color)); --text-color: rgb(var(--light-text-color)); } } body { background: var(--main-bg-color); color: var(--text-color); } </style> <div style='display: flex; align-items: center;'> <button autofocus id="toggle" style='min-width: 50px'>Start</button> <input type="range" min="1" max="120" value="20" id="range"> <label for="range"><span id='numMinutes'>20</span> minutes</label> </div> <div id='remaining' style='visibility:hidden;'>Time Left: <span id='minutesLeft'>20</span>:<span id='secondsLeft'>00</span></div> <script> (function() { var getEl = document.getElementById.bind(document); var DOM = { // audio element beep: getEl('beep'), // top UI toggle: getEl('toggle'), range: getEl('range'), numMinutes: getEl('numMinutes'), // Time remaining UI remaining: getEl('remaining'), minutesLeft: getEl('minutesLeft'), secondsLeft: getEl('secondsLeft'), } var state = { remaining: 0, counting: false, interval: 0, } var updateInterval = 5 // in seconds function updateRemaining(secondsRemaining) { state.remaining = secondsRemaining DOM.minutesLeft.innerHTML = Math.floor(secondsRemaining / 60) DOM.secondsLeft.innerHTML = (secondsRemaining % 60).toString().padStart(2, '0') } function start() { if (state.interval > 0) { clearInterval(state.interval) state.interval = 0 } updateRemaining(DOM.range.value * 60) DOM.remaining.style.visibility = 'visible' DOM.toggle.innerHTML = 'Stop' state.counting = true DOM.range.disabled = true state.interval = setInterval(function () { updateRemaining(state.remaining - updateInterval) if (state.remaining <= 0) { DOM.beep.play() stop() } }, updateInterval * 1000) } function stop() { if (state.interval > 0) { clearInterval(state.interval) state.interval = 0 } state.counting = false DOM.remaining.style.visibility = 'hidden' DOM.toggle.innerHTML = 'Start' DOM.range.disabled = false } DOM.toggle.addEventListener('click', () => state.counting ? stop() : start()) DOM.range.addEventListener('change', (e) => { // if (state.counting) { // event.stopPropagation() // return false // } DOM.numMinutes.innerHTML = e.target.value }) })() </script> </body> </html>