diff --git a/changelog.js b/changelog.js index 22264d6..b3c6bf5 100644 --- a/changelog.js +++ b/changelog.js @@ -1,7 +1,16 @@ // Version tracking -export const APP_VERSION = '1.0.23'; +export const APP_VERSION = '1.0.24'; export const CHANGELOG = { + '1.0.24': { + date: '2026-02-02', + changes: { + features: [ + 'Add Discover More link to info dialog', + 'Add auto-open-nominations', + ], + }, + }, '1.0.23': { date: '2026-02-02', changes: { diff --git a/index.html b/index.html index acde052..de37838 100644 --- a/index.html +++ b/index.html @@ -133,6 +133,16 @@ >00 + + @@ -672,6 +715,15 @@

About Tower Timer

/> +

This timer is an open source project and is not affiliated with { // Initialize audio endSound = new Audio(`sounds/end-of-day/${endOfDaySound}`); wakeUpSound = new Audio(`sounds/wake-up/${wakeUpSoundFile}`); + nominationsOpenSound = new Audio( + `sounds/nominations-open/${nominationsOpenSoundFile}` + ); // Add connectivity listeners connectivityUtils.addStatusListener( @@ -897,8 +906,12 @@ document.addEventListener('DOMContentLoaded', async () => { .forEach((btn) => btn.classList.remove('playing')); } - // Create and play the new preview - previewSound = new Audio(`sounds/${type}/${soundFile}`); + // Create and play the new preview (nominations-open uses its own folder) + const previewPath = + type === 'nominations-open' + ? `sounds/nominations-open/${soundFile}` + : `sounds/${type}/${soundFile}`; + previewSound = new Audio(previewPath); button.classList.add('playing'); previewSound.addEventListener( @@ -944,6 +957,54 @@ document.addEventListener('DOMContentLoaded', async () => { saveSettings(); }); + document + .getElementById('nominationsOpenSound') + .addEventListener('change', (e) => { + if (previewSound) { + previewSound.pause(); + previewSound.currentTime = 0; + document + .querySelectorAll('.preview-sound') + .forEach((btn) => btn.classList.remove('playing')); + } + nominationsOpenSoundFile = e.target.value; + nominationsOpenSound = new Audio( + `sounds/nominations-open/${nominationsOpenSoundFile}` + ); + saveSettings(); + }); + + document + .getElementById('autoOpenNominations') + .addEventListener('change', (e) => { + autoOpenNominations = e.target.checked; + document.getElementById('autoOpenNominationsDelay').disabled = + !autoOpenNominations; + const nominationsOpenSelect = document.getElementById( + 'nominationsOpenSound' + ); + const nominationsOpenLabelEl = document.querySelector( + 'label:has(#nominationsOpenSound)' + ); + if (nominationsOpenSelect) { + nominationsOpenSelect.disabled = !autoOpenNominations; + } + if (nominationsOpenLabelEl) { + nominationsOpenLabelEl.classList.toggle( + 'inactive', + !playSoundEffects || !autoOpenNominations + ); + } + saveSettings(); + }); + + document + .getElementById('autoOpenNominationsDelay') + .addEventListener('change', (e) => { + autoOpenNominationsDelay = parseInt(e.target.value, 10); + saveSettings(); + }); + // Initialize settings and update display loadSettings(); updateClocktowerPresets(); @@ -1013,6 +1074,13 @@ function loadSettings() { settings.youtubePlaylistUrl || DEFAULT_YOUTUBE_PLAYLIST; endOfDaySound = settings.endOfDaySound || 'cathedral-bell-v2.mp3'; wakeUpSoundFile = settings.wakeUpSoundFile || 'chisel-bell-01-loud-v2.mp3'; + nominationsOpenSoundFile = + settings.nominationsOpenSoundFile || 'nominations-open-laura.mp3'; + autoOpenNominations = settings.autoOpenNominations || false; + autoOpenNominationsDelay = + settings.autoOpenNominationsDelay !== undefined + ? settings.autoOpenNominationsDelay + : 60; acceptedPortraitWarning = settings.acceptedPortraitWarning || false; keyboardShortcuts = settings.keyboardShortcuts || { ...DEFAULT_KEYBOARD_SHORTCUTS, @@ -1041,6 +1109,9 @@ function loadSettings() { currentDay = 1; endSound = new Audio(`sounds/end-of-day/${endOfDaySound}`); wakeUpSound = new Audio(`sounds/wake-up/${wakeUpSoundFile}`); + nominationsOpenSound = new Audio( + `sounds/nominations-open/${nominationsOpenSoundFile}` + ); } // Always update UI to reflect settings @@ -1063,6 +1134,33 @@ function loadSettings() { ).textContent = `${soundEffectsVolume}%`; document.getElementById('endOfDaySound').value = endOfDaySound; document.getElementById('wakeUpSound').value = wakeUpSoundFile; + document.getElementById('nominationsOpenSound').value = + nominationsOpenSoundFile; + document.getElementById('autoOpenNominations').checked = autoOpenNominations; + document.getElementById('autoOpenNominationsDelay').value = + autoOpenNominationsDelay; + document.getElementById('autoOpenNominationsDelay').disabled = + !autoOpenNominations; + + const nominationsOpenSoundSelect = document.getElementById( + 'nominationsOpenSound' + ); + const nominationsOpenLabel = document.querySelector( + 'label:has(#nominationsOpenSound)' + ); + if (nominationsOpenSoundSelect) { + nominationsOpenSoundSelect.disabled = !autoOpenNominations; + } + if (nominationsOpenLabel) { + nominationsOpenLabel.classList.toggle( + 'inactive', + !playSoundEffects || !autoOpenNominations + ); + } + + nominationsOpenSound = new Audio( + `sounds/nominations-open/${nominationsOpenSoundFile}` + ); // Enable/disable volume controls based on their respective settings const musicVolumeInput = document.getElementById('musicVolume'); @@ -1202,6 +1300,9 @@ function saveSettings() { lastSeenVersion: APP_VERSION, endOfDaySound, wakeUpSoundFile, + nominationsOpenSoundFile, + autoOpenNominations, + autoOpenNominationsDelay, acceptedPortraitWarning, keyboardShortcuts, }; @@ -1421,6 +1522,60 @@ function playEndSound() { ); } +// Play nominations open sound +function playNominationsOpenSound() { + if (!playSoundEffects || !nominationsOpenSound) return; + nominationsOpenSound.currentTime = 0; + nominationsOpenSound.volume = soundEffectsVolume / 100; + nominationsOpenSound.play().catch((error) => { + console.log('Error playing nominations open sound:', error); + }); +} + +// Clear the nominations countdown (interval and UI) +function clearNominationsCountdown() { + if (autoOpenNominationsInterval) { + clearInterval(autoOpenNominationsInterval); + autoOpenNominationsInterval = null; + } + const el = document.getElementById('nominationsCountdown'); + if (el) { + el.hidden = true; + } +} + +// Start the nominations countdown display and schedule the sound +function startNominationsCountdown() { + clearNominationsCountdown(); + if (!autoOpenNominations || autoOpenNominationsDelay <= 0) return; + + const countdownEl = document.getElementById('nominationsCountdown'); + const secondsEl = document.getElementById('nominationsCountdownSeconds'); + if (!countdownEl || !secondsEl) return; + + nominationsCountdownRemaining = autoOpenNominationsDelay; + secondsEl.textContent = nominationsCountdownRemaining; + countdownEl.hidden = false; + countdownEl.setAttribute( + 'aria-label', + `Nominations open in ${nominationsCountdownRemaining} seconds` + ); + + autoOpenNominationsInterval = setInterval(() => { + nominationsCountdownRemaining--; + secondsEl.textContent = nominationsCountdownRemaining; + countdownEl.setAttribute( + 'aria-label', + `Nominations open in ${nominationsCountdownRemaining} seconds` + ); + + if (nominationsCountdownRemaining <= 0) { + clearNominationsCountdown(); + playNominationsOpenSound(); + } + }, 1000); +} + // Create beep sound (fallback if mp3 fails to load) function createBeep() { if (!playSoundEffects) return; @@ -1520,6 +1675,10 @@ function accelerateTime() { updateDayDisplay('dusk'); } updateDisplay(); // Make sure to update display one final time + + if (autoOpenNominations && autoOpenNominationsDelay > 0) { + startNominationsCountdown(); + } } }, currentInterval); @@ -1572,6 +1731,7 @@ function resetTimer() { clearTimeout(wakeUpTimeout); wakeUpTimeout = null; } + clearNominationsCountdown(); // Reset to the full day countdown time (use current day's preset, which includes pace) const day = currentDay ?? 1; @@ -1679,6 +1839,7 @@ function playWakeUpSound() { if (wakeUpTimeout) { clearTimeout(wakeUpTimeout); } + clearNominationsCountdown(); if (playSoundEffects) { // Stop music if playing and not set to play at night @@ -1803,6 +1964,10 @@ function startCountdown() { updateDayDisplay('dusk'); } updateDisplay(); + + if (autoOpenNominations && autoOpenNominationsDelay > 0) { + startNominationsCountdown(); + } } }, normalInterval); } @@ -1810,6 +1975,7 @@ function startCountdown() { function startNewGame() { // Reset timer state clearInterval(timerId); + clearNominationsCountdown(); timeLeft = 0; isRunning = false; currentInterval = normalInterval; @@ -2333,6 +2499,10 @@ function updateSoundEffects() { element: document.querySelector('label:has(#wakeUpSound)'), type: 'label', }, + { + element: document.querySelector('label:has(#nominationsOpenSound)'), + type: 'label', + }, { element: document.querySelector('label:has(#soundEffectsVolume)'), type: 'label', @@ -2340,17 +2510,31 @@ function updateSoundEffects() { ]; // Apply inactive state to all dependent elements + const nominationsOpenLabel = document.querySelector( + 'label:has(#nominationsOpenSound)' + ); + const nominationsOpenSelect = document.getElementById('nominationsOpenSound'); + soundDependentElements.forEach(({ element, type }) => { if (element) { - element.classList.toggle('inactive', !playSoundEffects); + const isNominationsOpen = element === nominationsOpenLabel; + const inactive = isNominationsOpen + ? !playSoundEffects || !autoOpenNominations + : !playSoundEffects; + element.classList.toggle('inactive', inactive); element.setAttribute( 'data-inactive-message', - 'Enable "Play Sound Effects" first' + isNominationsOpen && !autoOpenNominations + ? 'Enable "Automatically open nominations" in Game settings first' + : 'Enable "Play Sound Effects" first' ); if (type === 'label') { const input = element.querySelector('select, input'); if (input) { - input.setAttribute('aria-hidden', !playSoundEffects); + input.setAttribute('aria-hidden', inactive); + if (input === nominationsOpenSelect) { + input.disabled = !autoOpenNominations; + } } } } diff --git a/sounds/nominations-open/nominations-open-laura.mp3 b/sounds/nominations-open/nominations-open-laura.mp3 new file mode 100644 index 0000000..42cf03d Binary files /dev/null and b/sounds/nominations-open/nominations-open-laura.mp3 differ diff --git a/sounds/nominations-open/nominations-open-russell.mp3 b/sounds/nominations-open/nominations-open-russell.mp3 new file mode 100644 index 0000000..26b1793 Binary files /dev/null and b/sounds/nominations-open/nominations-open-russell.mp3 differ diff --git a/sounds/nominations-open/nominations-open-serafina.mp3 b/sounds/nominations-open/nominations-open-serafina.mp3 new file mode 100644 index 0000000..f8bfdd6 Binary files /dev/null and b/sounds/nominations-open/nominations-open-serafina.mp3 differ diff --git a/styles.css b/styles.css index 155e43d..1858e8b 100644 --- a/styles.css +++ b/styles.css @@ -215,6 +215,10 @@ body[data-pace='blitz'] .disclaimer a { color: #f44336; } +body[data-pace='blitz'] .main-site-link a { + color: #f44336; +} + body[data-pace='blitz'] .portrait-warning-dialog h2 { color: #f44336; } @@ -308,6 +312,21 @@ h1 { margin-bottom: -0.5em; } +.nominations-countdown { + font-size: min(4vw, 1.5rem); + color: rgba(255, 255, 255, 0.9); + text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3); + margin-top: 0.5rem; + padding: 0.25rem 0.75rem; + background: rgba(76, 175, 80, 0.25); + border-radius: 6px; + line-height: 1.2; +} + +.nominations-countdown[hidden] { + display: none; +} + @keyframes pulse-text { 0% { opacity: 1; @@ -790,6 +809,12 @@ h1 { font-weight: normal; } +.setting-group label.auto-nominations-row .auto-nominations-label { + display: flex; + align-items: center; + gap: 0.5rem; +} + body[data-pace='blitz'] .info-value { color: #f44336; } @@ -1388,6 +1413,20 @@ body:has(.clocktower-settings.visible) #regularTimerControls { text-align: center; } +.main-site-link { + margin: 0; + text-align: center; +} + +.main-site-link a { + color: #4caf50; + text-decoration: none; +} + +.main-site-link a:hover { + text-decoration: underline; +} + .kofi-container img { height: 36px; width: auto;