From c287b48480f447d19f0ddecba64489643f01d2c2 Mon Sep 17 00:00:00 2001 From: Chisel Date: Thu, 21 May 2026 15:11:51 +0100 Subject: [PATCH 1/5] feat: add current time to timer screen --- changelog.js | 10 +++++++++- index.html | 15 +++++++++------ script.js | 19 +++++++++++++++++++ styles.css | 29 +++++++++++++++++++++++++---- 4 files changed, 62 insertions(+), 11 deletions(-) diff --git a/changelog.js b/changelog.js index 2b1f1c8..6a52fdb 100644 --- a/changelog.js +++ b/changelog.js @@ -1,7 +1,15 @@ // Version tracking -export const APP_VERSION = '1.0.28'; +export const APP_VERSION = '1.0.29'; export const CHANGELOG = { + '1.0.29': { + date: '2026-05-21', + changes: { + features: [ + 'Show the current time underneath the Wake Up button on the timer screen (24-hour clock)', + ], + }, + }, '1.0.28': { date: '2026-04-02', changes: { diff --git a/index.html b/index.html index 9fa82eb..45978ee 100644 --- a/index.html +++ b/index.html @@ -158,12 +158,15 @@ > Nominations open in 0s - +
+ + +
Timer controls diff --git a/script.js b/script.js index a47e66a..0f1a267 100644 --- a/script.js +++ b/script.js @@ -39,6 +39,24 @@ let minutesDisplay, changeHistoryDialog, closeChangeHistoryBtn; +function initCurrentTimeDisplay() { + const currentTimeEl = document.getElementById('currentTime'); + if (!currentTimeEl) return; + + const updateCurrentTime = () => { + const now = new Date(); + currentTimeEl.dateTime = now.toISOString(); + currentTimeEl.textContent = now.toLocaleTimeString(undefined, { + hour: '2-digit', + minute: '2-digit', + hour12: false, + }); + }; + + updateCurrentTime(); + setInterval(updateCurrentTime, 1000); +} + // Utility functions const connectivityUtils = { isOnline: () => navigator.onLine, @@ -695,6 +713,7 @@ document.addEventListener('DOMContentLoaded', async () => { startBtn = document.getElementById('startBtn'); updateStartButtonText(BUTTON_LABELS.WAKE_UP); startBtn.disabled = false; // Ensure Wake Up button is enabled on load + initCurrentTimeDisplay(); resetBtn = document.getElementById('resetBtn'); resetBtn.textContent = BUTTON_LABELS.RESET; resetBtn.disabled = true; // Reset button should be disabled initially diff --git a/styles.css b/styles.css index 713d7c6..927b2c4 100644 --- a/styles.css +++ b/styles.css @@ -478,12 +478,33 @@ html.fonts-ready .timer-display .time { background-color: var(--colour-accent); } -#startBtn { +.timer-actions { position: absolute; top: 50%; right: 0.5rem; transform: translateY(-50%); z-index: 2; + display: flex; + flex-direction: column; + align-items: center; + gap: 0.4rem; +} + +.current-time { + font-family: 'Azeret Mono', ui-monospace, 'Cascadia Mono', monospace; + font-size: 0.95rem; + font-weight: 500; + font-variant-numeric: tabular-nums; + color: rgba(255, 255, 255, 0.85); + text-shadow: 0 1px 3px rgba(0, 0, 0, 0.4); + line-height: 1; + min-width: 140px; + text-align: center; +} + +#startBtn { + position: static; + transform: none; background-color: var(--colour-accent); color: white; padding: 1.5rem 1rem; @@ -547,18 +568,18 @@ html.fonts-ready .timer-display .time { #startBtn:hover { background-color: var(--colour-accent-hover); - transform: translateY(calc(-50% + 1px)); + transform: translateY(1px); } #startBtn:active { background-color: var(--colour-accent-hover); - transform: translateY(calc(-50% + 2px)) scale(0.98); + transform: translateY(2px) scale(0.98); } #startBtn:disabled { background-color: #cccccc; cursor: not-allowed; - transform: translateY(-50%); + transform: none; } #resetBtn { From 5ab4d1b062ea124fdf894480e7657403731992c4 Mon Sep 17 00:00:00 2001 From: Chisel Date: Thu, 21 May 2026 15:15:49 +0100 Subject: [PATCH 2/5] feat: add settings for clock display --- changelog.js | 3 ++- index.html | 27 +++++++++++++++++++++++++++ script.js | 25 +++++++++++++++++++++---- styles.css | 6 +++++- 4 files changed, 55 insertions(+), 6 deletions(-) diff --git a/changelog.js b/changelog.js index 6a52fdb..45055da 100644 --- a/changelog.js +++ b/changelog.js @@ -6,7 +6,8 @@ export const CHANGELOG = { date: '2026-05-21', changes: { features: [ - 'Show the current time underneath the Wake Up button on the timer screen (24-hour clock)', + 'Show the current time underneath the Wake Up button on the timer screen', + 'Add Time settings tab with 12-hour and 24-hour clock display options', ], }, }, diff --git a/index.html b/index.html index 45978ee..2736bdc 100644 --- a/index.html +++ b/index.html @@ -365,6 +365,7 @@

Settings

+ @@ -475,6 +476,32 @@

Display Settings

+ +
+
+

Clock Display

+ + +
+
+
diff --git a/script.js b/script.js index 0f1a267..e93c2e9 100644 --- a/script.js +++ b/script.js @@ -39,22 +39,24 @@ let minutesDisplay, changeHistoryDialog, closeChangeHistoryBtn; +let updateCurrentTimeDisplay = () => {}; + function initCurrentTimeDisplay() { const currentTimeEl = document.getElementById('currentTime'); if (!currentTimeEl) return; - const updateCurrentTime = () => { + updateCurrentTimeDisplay = () => { const now = new Date(); currentTimeEl.dateTime = now.toISOString(); currentTimeEl.textContent = now.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit', - hour12: false, + hour12: clockFormat === '12', }); }; - updateCurrentTime(); - setInterval(updateCurrentTime, 1000); + updateCurrentTimeDisplay(); + setInterval(updateCurrentTimeDisplay, 1000); } // Utility functions @@ -418,6 +420,7 @@ let backgroundTheme = 'medieval-cartoon'; // Default background theme let youtubePlaylistUrl = DEFAULT_YOUTUBE_PLAYLIST; // Default playlist let keepDisplayOn = true; // Default to true for wake lock let showPlayerCountQr = false; // Optional QR linking to count.arcane-scripts.net +let clockFormat = '24'; // '12' or '24' let youtubePlayer = null; let endOfDaySound = 'cathedral-bell-v2.mp3'; // Default end of day sound let wakeUpSoundFile = 'chisel-bell-01-loud-v2.mp3'; // Default wake up sound @@ -918,6 +921,14 @@ document.addEventListener('DOMContentLoaded', async () => { .classList.toggle('visible', showPlayerCountQr); saveSettings(); }); + document.querySelectorAll('input[name="clockFormat"]').forEach((input) => { + input.addEventListener('change', (e) => { + if (!e.target.checked) return; + clockFormat = e.target.value; + saveSettings(); + updateCurrentTimeDisplay(); + }); + }); // Add keyboard shortcuts event listeners document.querySelectorAll('.shortcut-input').forEach((input) => { @@ -1195,6 +1206,7 @@ function applyParsedSettings(settings) { keepDisplayOn = settings.keepDisplayOn === undefined ? true : settings.keepDisplayOn; showPlayerCountQr = settings.showPlayerCountQr === true; + clockFormat = settings.clockFormat === '12' ? '12' : '24'; youtubeVolume = settings.youtubeVolume || 15; backgroundTheme = settings.backgroundTheme || 'medieval-cartoon'; youtubePlaylistUrl = settings.youtubePlaylistUrl || DEFAULT_YOUTUBE_PLAYLIST; @@ -1266,6 +1278,10 @@ function applySettingsToForm() { document.getElementById('musicVolume').value = youtubeVolume; document.getElementById('soundEffectsVolume').value = soundEffectsVolume; document.getElementById('backgroundTheme').value = backgroundTheme; + document.querySelector( + `input[name="clockFormat"][value="${clockFormat}"]` + ).checked = true; + updateCurrentTimeDisplay(); document.querySelector( 'label:has(#musicVolume) .volume-value' ).textContent = `${youtubeVolume}%`; @@ -1442,6 +1458,7 @@ function saveSettings() { soundEffectsVolume, keepDisplayOn, showPlayerCountQr, + clockFormat, youtubeVolume, youtubePlaylistUrl, backgroundTheme, diff --git a/styles.css b/styles.css index 927b2c4..3582704 100644 --- a/styles.css +++ b/styles.css @@ -1934,12 +1934,16 @@ button:disabled { } /* Restore checkbox styling */ -.setting-group input[type='checkbox'] { +.setting-group input[type='checkbox'], +.setting-group input[type='radio'] { width: 24px; height: 24px; accent-color: var(--colour-accent); cursor: pointer; position: relative; +} + +.setting-group input[type='checkbox'] { border-radius: var(--radius-md); } From ae4c3cc4f94c68f6505824ca2a7621bb59127929 Mon Sep 17 00:00:00 2001 From: Chisel Date: Thu, 21 May 2026 15:19:03 +0100 Subject: [PATCH 3/5] feat: add on/off setting for current time display --- changelog.js | 2 +- index.html | 4 ++++ script.js | 37 ++++++++++++++++++++++++++++++++++++- 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/changelog.js b/changelog.js index 45055da..0713614 100644 --- a/changelog.js +++ b/changelog.js @@ -7,7 +7,7 @@ export const CHANGELOG = { changes: { features: [ 'Show the current time underneath the Wake Up button on the timer screen', - 'Add Time settings tab with 12-hour and 24-hour clock display options', + 'Add Time settings tab to show or hide the clock and choose 12-hour or 24-hour format', ], }, }, diff --git a/index.html b/index.html index 2736bdc..8f0a428 100644 --- a/index.html +++ b/index.html @@ -480,6 +480,10 @@

Display Settings

Clock Display

+
diff --git a/script.js b/script.js index 2b221ea..42c7346 100644 --- a/script.js +++ b/script.js @@ -40,8 +40,68 @@ let minutesDisplay, closeChangeHistoryBtn; let updateCurrentTimeDisplay = () => {}; +let updateSessionCountdownDisplay = () => {}; -function updateClockSettingsUi() { +const ONE_HOUR_MS = 60 * 60 * 1000; +const ONE_MINUTE_MS = 60 * 1000; +const SESSION_COUNTDOWN_HOUR_THRESHOLD_MS = 90 * ONE_MINUTE_MS; + +function getSessionEndDate(now, hour, minute) { + const end = new Date(now); + end.setHours(hour, minute, 0, 0); + + if (end <= now) { + if (hour < 12 && now.getHours() >= 12) { + end.setDate(end.getDate() + 1); + } else { + return null; + } + } + + return end; +} + +function formatSessionCountdownText(remainingMs) { + if (remainingMs <= 0) { + return { text: "TIME'S UP!", phase: 'times-up' }; + } + + if (remainingMs >= SESSION_COUNTDOWN_HOUR_THRESHOLD_MS) { + const hours = Math.floor(remainingMs / ONE_HOUR_MS); + return { + text: `${hours} hour${hours === 1 ? '' : 's'} remaining`, + phase: 'hours', + }; + } + + const minutes = Math.max(1, Math.ceil(remainingMs / ONE_MINUTE_MS)); + return { + text: `${minutes} minute${minutes === 1 ? '' : 's'} remaining`, + phase: 'minutes', + }; +} + +function initSessionEndTimeSelectors() { + const hourSelect = document.getElementById('sessionEndHour'); + const minuteSelect = document.getElementById('sessionEndMinute'); + if (!hourSelect || !minuteSelect || hourSelect.options.length > 0) return; + + for (let hour = 0; hour < 24; hour += 1) { + const option = document.createElement('option'); + option.value = hour; + option.textContent = String(hour).padStart(2, '0'); + hourSelect.appendChild(option); + } + + for (let minute = 0; minute < 60; minute += 1) { + const option = document.createElement('option'); + option.value = minute; + option.textContent = String(minute).padStart(2, '0'); + minuteSelect.appendChild(option); + } +} + +function updateTimeSettingsUi() { const showCurrentTimeInput = document.getElementById('showCurrentTime'); if (showCurrentTimeInput) { showCurrentTimeInput.checked = showCurrentTime; @@ -62,16 +122,50 @@ function updateClockSettingsUi() { } }); + const showSessionCountdownInput = document.getElementById( + 'showSessionCountdown' + ); + if (showSessionCountdownInput) { + showSessionCountdownInput.checked = showSessionCountdown; + } + + const sessionEndHourSelect = document.getElementById('sessionEndHour'); + const sessionEndMinuteSelect = document.getElementById('sessionEndMinute'); + if (sessionEndHourSelect) { + sessionEndHourSelect.value = String(sessionEndHour); + sessionEndHourSelect.disabled = !showSessionCountdown; + } + if (sessionEndMinuteSelect) { + sessionEndMinuteSelect.value = String(sessionEndMinute); + sessionEndMinuteSelect.disabled = !showSessionCountdown; + } + + const sessionEndTimeLabel = document.getElementById('sessionEndTimeLabel'); + if (sessionEndTimeLabel) { + sessionEndTimeLabel.classList.toggle('inactive', !showSessionCountdown); + } + + const sessionCountdownEl = document.getElementById('sessionCountdown'); + if (sessionCountdownEl) { + sessionCountdownEl.hidden = !showSessionCountdown; + } + if (showCurrentTime) { updateCurrentTimeDisplay(); } + if (showSessionCountdown) { + updateSessionCountdownDisplay(); + } } -function initCurrentTimeDisplay() { +function initTimeDisplays() { + initSessionEndTimeSelectors(); + const currentTimeEl = document.getElementById('currentTime'); - if (!currentTimeEl) return; + const sessionCountdownEl = document.getElementById('sessionCountdown'); updateCurrentTimeDisplay = () => { + if (!currentTimeEl) return; const now = new Date(); currentTimeEl.dateTime = now.toISOString(); currentTimeEl.textContent = now.toLocaleTimeString(undefined, { @@ -81,8 +175,31 @@ function initCurrentTimeDisplay() { }); }; - updateCurrentTimeDisplay(); - setInterval(updateCurrentTimeDisplay, 1000); + updateSessionCountdownDisplay = () => { + if (!sessionCountdownEl) return; + + const now = new Date(); + const sessionEnd = getSessionEndDate(now, sessionEndHour, sessionEndMinute); + const remainingMs = sessionEnd ? sessionEnd - now : 0; + const { text, phase } = formatSessionCountdownText(remainingMs); + + sessionCountdownEl.textContent = text; + sessionCountdownEl.setAttribute('aria-label', text); + sessionCountdownEl.classList.toggle('final-hour', phase === 'minutes'); + sessionCountdownEl.classList.toggle('times-up', phase === 'times-up'); + }; + + const tickTimeDisplays = () => { + if (showCurrentTime) { + updateCurrentTimeDisplay(); + } + if (showSessionCountdown) { + updateSessionCountdownDisplay(); + } + }; + + tickTimeDisplays(); + setInterval(tickTimeDisplays, 1000); } // Utility functions @@ -446,8 +563,11 @@ let backgroundTheme = 'medieval-cartoon'; // Default background theme let youtubePlaylistUrl = DEFAULT_YOUTUBE_PLAYLIST; // Default playlist let keepDisplayOn = true; // Default to true for wake lock let showPlayerCountQr = false; // Optional QR linking to count.arcane-scripts.net -let showCurrentTime = true; // Show wall clock under Wake Up button +let showCurrentTime = true; // Show wall clock under day display let clockFormat = '24'; // '12' or '24' +let showSessionCountdown = false; +let sessionEndHour = 23; +let sessionEndMinute = 0; let youtubePlayer = null; let endOfDaySound = 'cathedral-bell-v2.mp3'; // Default end of day sound let wakeUpSoundFile = 'chisel-bell-01-loud-v2.mp3'; // Default wake up sound @@ -743,7 +863,7 @@ document.addEventListener('DOMContentLoaded', async () => { startBtn = document.getElementById('startBtn'); updateStartButtonText(BUTTON_LABELS.WAKE_UP); startBtn.disabled = false; // Ensure Wake Up button is enabled on load - initCurrentTimeDisplay(); + initTimeDisplays(); resetBtn = document.getElementById('resetBtn'); resetBtn.textContent = BUTTON_LABELS.RESET; resetBtn.disabled = true; // Reset button should be disabled initially @@ -951,7 +1071,24 @@ document.addEventListener('DOMContentLoaded', async () => { document.getElementById('showCurrentTime').addEventListener('change', (e) => { showCurrentTime = e.target.checked; saveSettings(); - updateClockSettingsUi(); + updateTimeSettingsUi(); + }); + document + .getElementById('showSessionCountdown') + .addEventListener('change', (e) => { + showSessionCountdown = e.target.checked; + saveSettings(); + updateTimeSettingsUi(); + }); + document.getElementById('sessionEndHour').addEventListener('change', (e) => { + sessionEndHour = Number.parseInt(e.target.value, 10); + saveSettings(); + updateSessionCountdownDisplay(); + }); + document.getElementById('sessionEndMinute').addEventListener('change', (e) => { + sessionEndMinute = Number.parseInt(e.target.value, 10); + saveSettings(); + updateSessionCountdownDisplay(); }); document.querySelectorAll('input[name="clockFormat"]').forEach((input) => { input.addEventListener('change', (e) => { @@ -1241,6 +1378,19 @@ function applyParsedSettings(settings) { showCurrentTime = settings.showCurrentTime === undefined ? true : settings.showCurrentTime; clockFormat = settings.clockFormat === '12' ? '12' : '24'; + showSessionCountdown = settings.showSessionCountdown === true; + sessionEndHour = + Number.isInteger(settings.sessionEndHour) && + settings.sessionEndHour >= 0 && + settings.sessionEndHour <= 23 + ? settings.sessionEndHour + : 23; + sessionEndMinute = + Number.isInteger(settings.sessionEndMinute) && + settings.sessionEndMinute >= 0 && + settings.sessionEndMinute <= 59 + ? settings.sessionEndMinute + : 0; youtubeVolume = settings.youtubeVolume || 15; backgroundTheme = settings.backgroundTheme || 'medieval-cartoon'; youtubePlaylistUrl = settings.youtubePlaylistUrl || DEFAULT_YOUTUBE_PLAYLIST; @@ -1315,7 +1465,7 @@ function applySettingsToForm() { document.querySelector( `input[name="clockFormat"][value="${clockFormat}"]` ).checked = true; - updateClockSettingsUi(); + updateTimeSettingsUi(); document.querySelector( 'label:has(#musicVolume) .volume-value' ).textContent = `${youtubeVolume}%`; @@ -1494,6 +1644,9 @@ function saveSettings() { showPlayerCountQr, showCurrentTime, clockFormat, + showSessionCountdown, + sessionEndHour, + sessionEndMinute, youtubeVolume, youtubePlaylistUrl, backgroundTheme, diff --git a/styles.css b/styles.css index 3582704..d476cb5 100644 --- a/styles.css +++ b/styles.css @@ -490,6 +490,19 @@ html.fonts-ready .timer-display .time { gap: 0.4rem; } +.day-info { + position: absolute; + top: 50%; + left: 0.5rem; + transform: translateY(-50%); + z-index: 2; + display: flex; + flex-direction: column; + align-items: center; + gap: 0.4rem; + width: 120px; +} + .current-time { font-family: 'Azeret Mono', ui-monospace, 'Cascadia Mono', monospace; font-size: 0.95rem; @@ -498,10 +511,35 @@ html.fonts-ready .timer-display .time { color: rgba(255, 255, 255, 0.85); text-shadow: 0 1px 3px rgba(0, 0, 0, 0.4); line-height: 1; + width: 100%; + text-align: center; +} + +.session-countdown { + font-size: 0.85rem; + font-weight: 600; + font-variant-numeric: tabular-nums; + color: rgba(255, 255, 255, 0.85); + text-shadow: 0 1px 3px rgba(0, 0, 0, 0.4); + line-height: 1.2; min-width: 140px; text-align: center; } +.session-countdown[hidden] { + display: none; +} + +.session-countdown.final-hour { + color: var(--colour-gold); +} + +.session-countdown.times-up { + color: #ff6b6b; + font-weight: 700; + letter-spacing: 0.02em; +} + #startBtn { position: static; transform: none; @@ -1153,10 +1191,8 @@ body[data-pace='blitz'] .info-value { } .day-display { - position: absolute; - top: 50%; - left: 0.5rem; - transform: translateY(-50%); + position: static; + transform: none; z-index: 2; font-size: 1.25rem; font-weight: 700; @@ -1710,6 +1746,40 @@ body:has(.clocktower-settings.visible) #regularTimerControls { box-shadow: 0 0 0 2px rgba(76, 175, 80, 0.2); } +.session-end-time-inputs { + display: flex; + align-items: center; + gap: 0.35rem; +} + +.session-end-select { + min-width: 4.5rem; + padding: 0.5rem; + font-size: 1rem; + font-family: 'Azeret Mono', ui-monospace, 'Cascadia Mono', monospace; + border: 1px solid #4a4a4a; + border-radius: var(--radius-md); + background-color: var(--colour-button-secondary); + color: white; + cursor: pointer; +} + +.session-end-select:hover { + background-color: var(--colour-button-secondary-hover); +} + +.session-end-select:focus { + outline: none; + border-color: var(--colour-accent); + box-shadow: 0 0 0 2px rgba(76, 175, 80, 0.2); +} + +.session-end-separator { + color: rgba(255, 255, 255, 0.8); + font-family: 'Azeret Mono', ui-monospace, 'Cascadia Mono', monospace; + font-weight: 600; +} + .game-pace-display { font-size: 1rem; color: var(--colour-gold);