// ========== DIGITAL CLOCK WIDGET ========== (function() { const widget = document.getElementById('clock-widget'); const render = document.getElementById('clock-render'); if (!widget || !render) return; const DAYS = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday']; const MONTHS = ['January','February','March','April','May','June','July','August','September','October','November','December']; const ROMAN = ['XII','I','II','III','IV','V','VI','VII','VIII','IX','X','XI']; let currentStyle = safeGet('clock-style') || 'default'; let lastChimeHour = -1; let chimePlaying = false; let prevFlipDigits = ''; let activeLayout = ''; let digitalRefs = null; let tickTimer = null; // ===== CHIMES ===== function playChimes(count) { if (chimePlaying) return; if (safeGet('clock-chimes') !== 'true') return; chimePlaying = true; const vol = parseInt(safeGet('clock-chime-volume') || '50', 10) / 100; let i = 0; function strike() { if (i >= count) { chimePlaying = false; return; } const bell = new Audio('/assets/sounds/church-bell.mp3'); bell.volume = vol; bell.play().catch(() => {}); i++; if (i < count) setTimeout(strike, 2500); else setTimeout(() => { chimePlaying = false; }, 2500); } strike(); } // ===== DATE STRING ===== function dateStr(now) { return DAYS[now.getDay()] + ', ' + MONTHS[now.getMonth()] + ' ' + now.getDate() + ', ' + now.getFullYear(); } function resetRenderLayout() { activeLayout = ''; digitalRefs = null; } function ensureDigitalLayout() { if (activeLayout !== 'digital') { render.innerHTML = '
' + '
'; digitalRefs = { main: render.querySelector('.clock-main'), seconds: render.querySelector('.clock-seconds'), ampm: render.querySelector('.clock-ampm'), date: render.querySelector('.clock-date') }; activeLayout = 'digital'; } return digitalRefs; } // ===== STYLE RENDERERS ===== function renderDefault(now) { const h24 = now.getHours(), m = now.getMinutes(), s = now.getSeconds(); const ampm = h24 >= 12 ? 'PM' : 'AM'; const h = h24 % 12 || 12; const refs = ensureDigitalLayout(); refs.main.textContent = `${h}:${String(m).padStart(2,'0')}`; refs.seconds.textContent = `:${String(s).padStart(2,'0')}`; refs.ampm.textContent = ampm; refs.date.textContent = dateStr(now); } function renderLcd(now, cls) { const h24 = now.getHours(), m = now.getMinutes(), s = now.getSeconds(); const ampm = h24 >= 12 ? 'PM' : 'AM'; const h = h24 % 12 || 12; const refs = ensureDigitalLayout(); refs.main.textContent = `${String(h).padStart(2,'0')}:${String(m).padStart(2,'0')}`; refs.seconds.textContent = `:${String(s).padStart(2,'0')}`; refs.ampm.textContent = ampm; refs.date.textContent = dateStr(now); } function renderFlip(now) { const h24 = now.getHours(), m = now.getMinutes(), s = now.getSeconds(); const ampm = h24 >= 12 ? 'PM' : 'AM'; const h = h24 % 12 || 12; const digits = String(h).padStart(2,' ') + String(m).padStart(2,'0') + String(s).padStart(2,'0'); let html = '
'; // Hours html += flipCard(digits[0], 0); html += flipCard(digits[1], 1); html += ':'; // Minutes html += flipCard(digits[2], 2); html += flipCard(digits[3], 3); html += ':'; // Seconds html += flipCard(digits[4], 4); html += flipCard(digits[5], 5); html += `${ampm}`; html += '
'; html += `
${dateStr(now)}
`; render.innerHTML = html; activeLayout = 'flip'; // Trigger flip animation on changed digits if (prevFlipDigits) { for (let i = 0; i < 6; i++) { if (digits[i] !== prevFlipDigits[i]) { const card = render.querySelector(`.flip-card[data-idx="${i}"]`); if (card) card.classList.add('flipping'); } } } prevFlipDigits = digits; } function flipCard(digit, idx) { const d = digit === ' ' ? '' : digit; return `
${d}
${d}
`; } function renderBinary(now) { const h24 = now.getHours(), m = now.getMinutes(), s = now.getSeconds(); const h = h24 % 12 || 12; const ampm = h24 >= 12 ? 'PM' : 'AM'; // 6 columns: H tens, H ones, M tens, M ones, S tens, S ones const cols = [ Math.floor(h / 10), h % 10, Math.floor(m / 10), m % 10, Math.floor(s / 10), s % 10 ]; let html = '
'; // Labels html += '
HHMMSS
'; // 4 rows for bits 8,4,2,1 for (let bit = 3; bit >= 0; bit--) { html += '
'; for (let col = 0; col < 6; col++) { const on = (cols[col] >> bit) & 1; html += `
`; } html += '
'; } // Value row html += '
'; for (let col = 0; col < 6; col++) { html += `${cols[col]}`; } html += '
'; html += `
${ampm}
`; html += '
'; html += `
${dateStr(now)}
`; render.innerHTML = html; activeLayout = 'binary'; } function renderAnalog(now, useRoman) { const h = now.getHours(), m = now.getMinutes(), s = now.getSeconds(); const size = 120; const cx = size / 2, cy = size / 2; // Angles const sAngle = (s / 60) * 360 - 90; const mAngle = ((m + s / 60) / 60) * 360 - 90; const hAngle = (((h % 12) + m / 60) / 12) * 360 - 90; // Number labels let labels = ''; for (let i = 1; i <= 12; i++) { const angle = (i / 12) * 2 * Math.PI - Math.PI / 2; const r = 47; const x = cx + r * Math.cos(angle); const y = cy + r * Math.sin(angle); const label = useRoman ? ROMAN[i % 12] : i; const fs = useRoman ? '7' : '9'; labels += `${label}`; } // Tick marks let ticks = ''; for (let i = 0; i < 60; i++) { const angle = (i / 60) * 2 * Math.PI - Math.PI / 2; const outer = 56; const inner = i % 5 === 0 ? 52 : 54; const x1 = cx + inner * Math.cos(angle); const y1 = cy + inner * Math.sin(angle); const x2 = cx + outer * Math.cos(angle); const y2 = cy + outer * Math.sin(angle); const w = i % 5 === 0 ? 1.5 : 0.5; ticks += ``; } const svg = ` ${ticks} ${labels} `; const ampm = now.getHours() >= 12 ? 'PM' : 'AM'; render.innerHTML = `
${svg}
${(now.getHours() % 12 || 12)}:${String(m).padStart(2,'0')} ${ampm}${dateStr(now)}
`; activeLayout = 'analog'; } // ===== MAIN TICK ===== function tick() { const now = new Date(); const h = now.getHours() % 12 || 12; const m = now.getMinutes(); const s = now.getSeconds(); // Set style class on widget const nextClassName = 'clock-widget' + (currentStyle !== 'default' ? ' ' + currentStyle : ''); if (widget.className !== nextClassName) { widget.className = nextClassName; } switch (currentStyle) { case 'lcd': renderLcd(now); break; case 'lcd-blue': renderLcd(now); break; case 'lcd-amber': renderLcd(now); break; case 'lcd-retro': renderLcd(now); break; case 'lcd-taxi': renderLcd(now); break; case 'flip': renderFlip(now); break; case 'binary': renderBinary(now); break; case 'analog': renderAnalog(now, false); break; case 'roman': renderAnalog(now, true); break; default: renderDefault(now); } // Hourly chimes if (m === 0 && s === 0 && h !== lastChimeHour) { lastChimeHour = h; playChimes(h); } if (m !== 0) lastChimeHour = -1; } function scheduleNextTick() { clearTimeout(tickTimer); const interval = document.hidden ? 60000 : 1000; const delay = interval - (Date.now() % interval) + 25; tickTimer = setTimeout(() => { tick(); scheduleNextTick(); }, delay); } document.addEventListener('visibilitychange', () => { prevFlipDigits = ''; resetRenderLayout(); tick(); scheduleNextTick(); }); tick(); scheduleNextTick(); // ===== SETTINGS MODAL ===== const STYLES = [ { id: 'default', label: 'Default', icon: '🕐' }, { id: 'lcd', label: 'LCD Green', icon: '💚' }, { id: 'lcd-blue', label: 'LCD Blue', icon: '💙' }, { id: 'lcd-amber', label: 'LCD Amber', icon: '🟠' }, { id: 'lcd-retro', label: 'LCD Retro', icon: '🟩' }, { id: 'lcd-taxi', label: 'LCD Taxi', icon: '🟡' }, { id: 'flip', label: 'Flip Clock', icon: '📟' }, { id: 'binary', label: 'Binary', icon: '💻' }, { id: 'analog', label: 'Analog', icon: '⏰' }, { id: 'roman', label: 'Roman', icon: '🏛️' }, ]; let styleOptionsHtml = '
'; STYLES.forEach(s => { styleOptionsHtml += ``; }); styleOptionsHtml += '
'; injectModal('clock-settings-modal', `

Clock Settings

${styleOptionsHtml}
Strikes the number of the hour (e.g., 3 bells at 3:00)
🔈 🔊
`); const modal = document.getElementById('clock-settings-modal'); const chimesToggle = document.getElementById('clock-chimes-toggle'); const volumeSlider = document.getElementById('clock-chime-volume'); const volumeSection = document.getElementById('clock-volume-section'); function loadSettings() { const style = safeGet('clock-style') || 'default'; const radio = modal.querySelector(`input[value="${style}"]`); if (radio) radio.checked = true; chimesToggle.checked = safeGet('clock-chimes') === 'true'; volumeSlider.value = safeGet('clock-chime-volume') || '50'; volumeSection.style.opacity = chimesToggle.checked ? '1' : '0.4'; } chimesToggle?.addEventListener('change', () => { volumeSection.style.opacity = chimesToggle.checked ? '1' : '0.4'; }); document.getElementById('clock-settings')?.addEventListener('click', () => { loadSettings(); modal.classList.add('show'); }); document.getElementById('clock-chime-test')?.addEventListener('click', () => { const vol = parseInt(volumeSlider.value, 10) / 100; const bell = new Audio('/assets/sounds/church-bell.mp3'); bell.volume = vol; bell.play().catch(() => {}); }); document.getElementById('clock-settings-save')?.addEventListener('click', () => { const radio = modal.querySelector('input[name="clock-style-radio"]:checked'); const style = radio ? radio.value : 'default'; safeSet('clock-style', style); safeSet('clock-chimes', String(chimesToggle.checked)); safeSet('clock-chime-volume', volumeSlider.value); currentStyle = style; prevFlipDigits = ''; resetRenderLayout(); tick(); scheduleNextTick(); modal.classList.remove('show'); showNotification('Clock settings saved', 'success', 2000); }); document.getElementById('clock-settings-cancel')?.addEventListener('click', () => { modal.classList.remove('show'); }); wireModal(modal); // Live preview when clicking style options modal?.querySelectorAll('input[name="clock-style-radio"]').forEach(radio => { radio.addEventListener('change', () => { currentStyle = radio.value; prevFlipDigits = ''; resetRenderLayout(); tick(); }); }); })();