import { Countdown, integer, adjustTime } from './timer.mjs'; import { SETTINGS_KEY, serializeSettings, parseSettings } from './settings.mjs'; const timer = new Countdown(); const minutes = document.querySelector('#minutes'); const seconds = document.querySelector('#seconds'); const counter = document.querySelector('.counter'); const startStop = document.querySelector('#start-stop'); const reset = document.querySelector('#reset'); const status = document.querySelector('#status'); const fields = [minutes, seconds]; const title = document.title; let ticker; function saveSettings() { try { localStorage.setItem(SETTINGS_KEY, serializeSettings(minutes.value, seconds.value)); } catch { // The timer still works when storage is disabled or full. } } function resize(field) { field.style.width = `${Math.max(field === seconds ? 2 : 1, field.value.length)}ch`; field.dataset.valid = field.value; } function render(updateFields = true) { if (updateFields) { const display = timer.display(); minutes.value = display.minutes; seconds.value = display.seconds; } fields.forEach(field => { field.readOnly = timer.state === 'running'; resize(field); }); counter.classList.toggle('finished', timer.finished); document.title = timer.finished ? `🔔 ${title}` : title; startStop.textContent = timer.state === 'running' ? 'Stop' : 'Start'; const message = timer.finished ? 'Countdown finished' : timer.state === 'running' ? 'Running' : timer.state === 'paused' ? 'Paused' : 'Idle'; if (status.textContent !== message) status.textContent = message; if (timer.state !== 'running') { clearInterval(ticker); ticker = undefined; } } function edited() { timer.edit(minutes.value, seconds.value); saveSettings(); render(false); } for (const field of fields) { resize(field); field.addEventListener('input', () => { if (field.readOnly || !/^\d*$/.test(field.value)) { field.value = field.dataset.valid; return; } edited(); }); field.addEventListener('keydown', event => { if (field.readOnly || !['ArrowUp', 'ArrowDown'].includes(event.key)) return; event.preventDefault(); const step = event.shiftKey ? 10n : 1n; const adjusted = adjustTime(minutes.value, seconds.value, field.id, event.key === 'ArrowUp' ? step : -step); minutes.value = adjusted.minutes; seconds.value = adjusted.seconds; edited(); }); field.addEventListener('blur', () => { if (field.readOnly) return; field.value = String(integer(field.value)); if (field === seconds) field.value = field.value.padStart(2, '0'); resize(field); }); } startStop.addEventListener('click', () => { timer.toggle(); render(); if (timer.state === 'running' && ticker === undefined) { // This is only a display refresh; elapsed time comes from the deadline. ticker = setInterval(() => { timer.update(); render(); }, 50); } }); reset.addEventListener('click', () => { timer.reset(); render(); }); document.addEventListener('visibilitychange', () => { if (timer.state === 'running') { timer.update(); render(); } }); try { const saved = parseSettings(localStorage.getItem(SETTINGS_KEY)); minutes.value = saved.minutes; seconds.value = saved.seconds; timer.edit(minutes.value, seconds.value); } catch { // Storage may be unavailable; retain the default idle countdown. } render(false);