From 232d732c47fe34cc3a585cbafbe927328261f4b3 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Thu, 4 Jun 2026 22:25:07 +0000 Subject: [PATCH 1/3] Modernize UI and add spectacular hourly eclipse simulation - Implemented glassmorphism-based countdown container. - Added dynamic background with rotating nebula and twinkling stars. - Created a multi-phase hourly eclipse simulation with corona and diamond ring effects. - Ensured full responsiveness across devices using vmin and clamp units. - Restored Screen Wake Lock API for permanent tablet display. - Improved code readability by reformatting translations and city data. - Fixed typo in Italian translation. Co-authored-by: j4c0s <46329710+j4c0s@users.noreply.github.com> --- index.html | 977 ++++++++++++++++++++++------------------------------- 1 file changed, 395 insertions(+), 582 deletions(-) diff --git a/index.html b/index.html index ae1e838..b4d36b9 100644 --- a/index.html +++ b/index.html @@ -9,84 +9,106 @@ Eclipse Countdown +
@@ -358,22 +444,16 @@
- + -
- +
+ : - + : - +
-
+
@@ -394,20 +474,21 @@
- +
-
+
This app works offline - add to your home screen to use as an app
- +
-
+
+
@@ -448,6 +529,36 @@

Solar Eclipse Countdown

let eclipseDate = new Date('2026-08-12T18:28:41Z'); let locationName = "Cascante"; let currentLanguage = "en"; +let isAnimating = false; +let wakeLock = null; + +const requestWakeLock = async () => { + if (!('wakeLock' in navigator)) return; + try { + wakeLock = await navigator.wakeLock.request('screen'); + wakeLock.addEventListener('release', () => { + console.log('Wake Lock was released'); + }); + console.log('Wake Lock is active'); + } catch (err) { + console.error(`${err.name}, ${err.message}`); + } +}; + +const releaseWakeLock = async () => { + if (wakeLock !== null) { + await wakeLock.release(); + wakeLock = null; + } +}; + +const handleVisibilityChange = async () => { + if (wakeLock !== null && document.visibilityState === 'visible') { + await requestWakeLock(); + } +}; + +document.addEventListener('visibilitychange', handleVisibilityChange); const translations = { en: { @@ -484,7 +595,7 @@

Solar Eclipse Countdown

hourlyAnimation: "Animacja co godzinę:", saveSettings: "Zapisz ustawienia", offlineInfo: "Aplikacja działa również offline - dodaj do ekranu głównego, aby korzystać jak z aplikacji.", - resetSettings: "Resetuj ustawienia" + resetSettings: "Resetuj ustawienia" }, es: { title: "Cuenta atrás para el eclipse solar", @@ -520,7 +631,7 @@

Solar Eclipse Countdown

hourlyAnimation: "Stündliche Animation:", saveSettings: "Einstellungen speichern", offlineInfo: "Diese App funktioniert offline - füge sie deinem Startbildschirm hinzu, um sie als App zu nutzen.", - resetSettings: "Einstellungen zurücksetzen" + resetSettings: "Einstellungen zurücksetzen" }, fr: { title: "Compte à rebours pour l'éclipse solaire", @@ -574,7 +685,7 @@

Solar Eclipse Countdown

hourlyAnimation: "Animação por hora:", saveSettings: "Salvar configurações", offlineInfo: "Este aplicativo funciona offline - adicione à sua tela inicial para usá-lo como um aplicativo.", - resetSettings: "Redefinir configurações" + resetSettings: "Redefinir configurações" }, is: { title: "Niðurtalning til sólmyrkva", @@ -592,568 +703,270 @@

Solar Eclipse Countdown

hourlyAnimation: "Klukkutíma animation:", saveSettings: "Vista stillingar", offlineInfo: "Þessi app virkar án nettengingar - bættu við á heimaskjá til að nota sem app.", - resetSettings: "Endurstilla stillingar" + resetSettings: "Endurstilla stillingar" } }; const eclipseTimes = { - oviedo: "2026-08-12T20:27:05+02:00", - santander: "2026-08-12T20:26:57+02:00", - bilbao: "2026-08-12T20:27:23+02:00", - vitoria: "2026-08-12T20:27:43+02:00", - leon: "2026-08-12T20:28:20+02:00", - burgos: "2026-08-12T20:28:24+02:00", - zamora: "2026-08-12T20:30:53+02:00", - valladolid: "2026-08-12T20:29:53+02:00", - zaragoza: "2026-08-12T20:29:02+02:00", - castellon: "2026-08-12T20:31:19+02:00", - palma: "2026-08-12T20:31:05+02:00", - coruna: "2026-08-12T20:27:40+02:00", - soria: "2026-08-12T20:29:06+02:00", - tarragona: "2026-08-12T20:29:29+02:00", - reykjavik: "2026-08-12T17:48:16+00:00", - akranes: "2026-08-12T17:47:52+00:00", - keflavik: "2026-08-12T17:48:05+00:00", - olafsvik: "2026-08-12T17:45:54+00:00" + oviedo: "2026-08-12T20:27:05+02:00", + santander: "2026-08-12T20:26:57+02:00", + bilbao: "2026-08-12T20:27:23+02:00", + vitoria: "2026-08-12T20:27:43+02:00", + leon: "2026-08-12T20:28:20+02:00", + burgos: "2026-08-12T20:28:24+02:00", + zamora: "2026-08-12T20:30:53+02:00", + valladolid: "2026-08-12T20:29:53+02:00", + zaragoza: "2026-08-12T20:29:02+02:00", + castellon: "2026-08-12T20:31:19+02:00", + palma: "2026-08-12T20:31:05+02:00", + coruna: "2026-08-12T20:27:40+02:00", + soria: "2026-08-12T20:29:06+02:00", + tarragona: "2026-08-12T20:29:29+02:00", + reykjavik: "2026-08-12T17:48:16+00:00", + akranes: "2026-08-12T17:47:52+00:00", + keflavik: "2026-08-12T17:48:05+00:00", + olafsvik: "2026-08-12T17:45:54+00:00" }; function updateLanguage(lang) { currentLanguage = lang; - document.getElementById('mainTitle').textContent = translations[lang].title; - document.getElementById('daysLabel').textContent = translations[lang].days; - document.getElementById('hoursLabel').textContent = translations[lang].hours; - document.getElementById('minutesLabel').textContent = translations[lang].minutes; - document.getElementById('secondsLabel').textContent = translations[lang].seconds; - document.getElementById('locationLabel').textContent = translations[lang].location + const trans = translations[lang]; + document.getElementById('mainTitle').textContent = trans.title; + document.getElementById('daysLabel').textContent = trans.days; + document.getElementById('hoursLabel').textContent = trans.hours; + document.getElementById('minutesLabel').textContent = trans.minutes; + document.getElementById('secondsLabel').textContent = trans.seconds; + document.getElementById('locationLabel').textContent = trans.location; document.getElementById('locationName').textContent = locationName; - document.getElementById('dateLabel').textContent = translations[lang].dateLabel; - document.getElementById('timeLabel').textContent = translations[lang].timeLabel; - document.getElementById('locationSelectLabel').textContent = translations[lang].locationSelect; - document.getElementById('customOption').textContent = translations[lang].customOption; - document.getElementById('customLocationLabel').textContent = translations[lang].customLocation; - document.getElementById('keepAwakeLabel').textContent = translations[lang].keepAwake; - document.getElementById('hourlyAnimationLabel').textContent = translations[lang].hourlyAnimation; - document.getElementById('saveSettings').textContent = translations[lang].saveSettings; - document.getElementById('offlineInfo').textContent = translations[lang].offlineInfo; - document.getElementById('resetSettings').textContent = translations[lang].resetSettings; + document.getElementById('dateLabel').textContent = trans.dateLabel; + document.getElementById('timeLabel').textContent = trans.timeLabel; + document.getElementById('locationSelectLabel').textContent = trans.locationSelect; + document.getElementById('customOption').textContent = trans.customOption; + document.getElementById('customLocationLabel').textContent = trans.customLocation; + document.getElementById('keepAwakeLabel').textContent = trans.keepAwake; + document.getElementById('hourlyAnimationLabel').textContent = trans.hourlyAnimation; + document.getElementById('saveSettings').textContent = trans.saveSettings; + document.getElementById('offlineInfo').textContent = trans.offlineInfo; + document.getElementById('resetSettings').textContent = trans.resetSettings; localStorage.setItem('language', lang); } function populateTimeDropdowns() { - const hoursSelect = document.getElementById('eclipseTimeHours'); - const minutesSelect = document.getElementById('eclipseTimeMinutes'); - const secondsSelect = document.getElementById('eclipseTimeSeconds'); - - hoursSelect.innerHTML = ''; - minutesSelect.innerHTML = ''; - secondsSelect.innerHTML = ''; - - for (let i = 0; i < 24; i++) { - const option = document.createElement('option'); - option.value = i; - option.textContent = String(i).padStart(2, '0'); - hoursSelect.appendChild(option); - } + const hSelect = document.getElementById('eclipseTimeHours'); + const mSelect = document.getElementById('eclipseTimeMinutes'); + const sSelect = document.getElementById('eclipseTimeSeconds'); + hSelect.innerHTML = mSelect.innerHTML = sSelect.innerHTML = ''; + for (let i = 0; i < 24; i++) hSelect.add(new Option(String(i).padStart(2, '0'), i)); for (let i = 0; i < 60; i++) { - const minuteOption = document.createElement('option'); - minuteOption.value = i; - minuteOption.textContent = String(i).padStart(2, '0'); - minutesSelect.appendChild(minuteOption); - - const secondOption = document.createElement('option'); - secondOption.value = i; - secondOption.textContent = String(i).padStart(2, '0'); - secondsSelect.appendChild(secondOption); + mSelect.add(new Option(String(i).padStart(2, '0'), i)); + sSelect.add(new Option(String(i).padStart(2, '0'), i)); } } function updateEclipseDateFromInput() { - const eclipseDateInput = document.getElementById('eclipseDate').value; - const hours = parseInt(document.getElementById('eclipseTimeHours').value) || 0; - const minutes = parseInt(document.getElementById('eclipseTimeMinutes').value) || 0; - const seconds = parseInt(document.getElementById('eclipseTimeSeconds').value) || 0; - - if (eclipseDateInput) { - const dateParts = eclipseDateInput.split("-"); - const year = parseInt(dateParts[0]); - const month = parseInt(dateParts[1]) - 1; - const day = parseInt(dateParts[2]); - - const localDate = new Date(year, month, day, hours, minutes, seconds); - const utcDate = new Date(localDate.getTime() - localDate.getTimezoneOffset() * 60000); - eclipseDate = localDate; + const dateInput = document.getElementById('eclipseDate').value; + const h = parseInt(document.getElementById('eclipseTimeHours').value) || 0; + const m = parseInt(document.getElementById('eclipseTimeMinutes').value) || 0; + const s = parseInt(document.getElementById('eclipseTimeSeconds').value) || 0; + if (dateInput) { + const [y, mon, d] = dateInput.split("-").map(Number); + eclipseDate = new Date(y, mon - 1, d, h, m, s); localStorage.setItem('eclipseDate', eclipseDate.toISOString()); } } -function setDefaultDateTimeValues() { - const defaultDate = new Date('2026-08-12T18:28:41Z'); - - const year = defaultDate.getFullYear(); - const month = String(defaultDate.getMonth() + 1).padStart(2, '0'); - const day = String(defaultDate.getDate()).padStart(2, '0'); - const hours = defaultDate.getHours(); - const minutes = defaultDate.getMinutes(); - const seconds = defaultDate.getSeconds(); - - document.getElementById('eclipseDate').value = `${year}-${month}-${day}`; - document.getElementById('eclipseTimeHours').value = hours; - document.getElementById('eclipseTimeMinutes').value = minutes; - document.getElementById('eclipseTimeSeconds').value = seconds; - document.getElementById('customLocation').value = "Cascante"; -} - function loadSettings() { const savedDate = localStorage.getItem('eclipseDate'); - const savedLocation = localStorage.getItem('locationName'); - const savedLanguage = localStorage.getItem('language'); + const savedLoc = localStorage.getItem('locationName'); + const savedLang = localStorage.getItem('language'); populateTimeDropdowns(); - - if (savedLanguage && translations[savedLanguage]) { - updateLanguage(savedLanguage); - } else { - updateLanguage("en"); - } + updateLanguage(savedLang && translations[savedLang] ? savedLang : "en"); if (savedDate) { eclipseDate = new Date(savedDate); - const dateObj = new Date(savedDate); - const year = dateObj.getFullYear(); - const month = String(dateObj.getMonth() + 1).padStart(2, '0'); - const day = String(dateObj.getDate()).padStart(2, '0'); - const hours = dateObj.getHours(); - const minutes = dateObj.getMinutes(); - const seconds = dateObj.getSeconds(); - - document.getElementById('eclipseDate').value = `${year}-${month}-${day}`; - document.getElementById('eclipseTimeHours').value = hours; - document.getElementById('eclipseTimeMinutes').value = minutes; - document.getElementById('eclipseTimeSeconds').value = seconds; } else { - setDefaultDateTimeValues(); + eclipseDate = new Date('2026-08-12T18:28:41Z'); } - if (savedLocation) { - locationName = savedLocation; - document.getElementById('customLocation').value = savedLocation; - document.getElementById('locationSelect').value = "custom"; + const d = eclipseDate; + document.getElementById('eclipseDate').value = `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`; + document.getElementById('eclipseTimeHours').value = d.getHours(); + document.getElementById('eclipseTimeMinutes').value = d.getMinutes(); + document.getElementById('eclipseTimeSeconds').value = d.getSeconds(); + + locationName = savedLoc || "Cascante"; + document.getElementById('customLocation').value = locationName; + if (savedLoc) document.getElementById('locationSelect').value = "custom"; + + document.getElementById('locationName').textContent = locationName; + + const savedKA = localStorage.getItem('keepAwake'); + if (savedKA !== null) { + const isEnabled = savedKA === 'true'; + document.getElementById('keepAwake').checked = isEnabled; + if (isEnabled) requestWakeLock(); } else { - locationName = "Cascante"; - document.getElementById('customLocation').value = locationName; + // Default is checked + requestWakeLock(); } - document.getElementById('locationLabel').textContent = translations[currentLanguage].location; - document.getElementById('locationName').textContent = locationName; - document.getElementById('locationSelect').dispatchEvent(new Event('change')); + const savedHA = localStorage.getItem('hourlyAnimation'); + if (savedHA !== null) document.getElementById('hourlyAnimation').checked = savedHA === 'true'; } function createStars() { - const starsContainer = document.getElementById('stars'); - const numberOfStars = 400; - - for (let i = 0; i < numberOfStars; i++) { + const container = document.getElementById('stars'); + const colors = ['#ffffff', '#fff4e6', '#e6f2ff', '#fff0f0', '#f0faff']; + for (let i = 0; i < 400; i++) { const star = document.createElement('div'); - star.classList.add('star'); - - const left = Math.random() * 100; - const top = Math.random() * 100; - - const size = Math.random() * 3 + 1; - - star.style.left = `${left}%`; - star.style.top = `${top}%`; - star.style.width = `${size}px`; - star.style.height = `${size}px`; - - starsContainer.appendChild(star); + star.className = 'star'; + const size = Math.random() * 2 + 0.5; + const color = colors[Math.floor(Math.random() * colors.length)]; + star.style.cssText = `left:${Math.random()*100}%; top:${Math.random()*100}%; width:${size}px; height:${size}px; background-color:${color}; box-shadow:0 0 ${size*2}px ${color};`; + if (Math.random() > 0.3) star.style.animation = `twinkle ${Math.random()*3+2}s infinite ease-in-out ${Math.random()*5}s`; + container.appendChild(star); } } -let isAnimating = false; + function updateCountdown() { const now = new Date(); const timeLeft = eclipseDate - now; if (timeLeft <= 0) { - document.getElementById('days').textContent = '0'; - document.getElementById('hours').textContent = '0'; - document.getElementById('minutes').textContent = '0'; - document.getElementById('seconds').textContent = '0'; + ['days', 'hours', 'minutes', 'seconds'].forEach(id => document.getElementById(id).textContent = '0'); document.getElementById('moon').style.transform = 'translateX(0)'; - document.body.style.backgroundColor = '#070707'; + document.getElementById('corona').style.opacity = '1'; + document.getElementById('corona').style.animation = 'coronaFlare 10s infinite linear'; + document.body.style.backgroundColor = '#050510'; document.getElementById('stars').style.opacity = '1'; + document.getElementById('nebula').style.opacity = '0.5'; return; } - const days = Math.floor(timeLeft / (1000 * 60 * 60 * 24)); - const hours = Math.floor((timeLeft % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); - const minutes = Math.floor((timeLeft % (1000 * 60 * 60)) / (1000 * 60)); - const seconds = Math.floor((timeLeft % (1000 * 60)) / 1000); - - document.getElementById('days').textContent = days; - document.getElementById('hours').textContent = hours; - document.getElementById('minutes').textContent = minutes; - document.getElementById('seconds').textContent = seconds; - - const hourlyAnimationEnabled = document.getElementById('hourlyAnimation').checked; - - if (hourlyAnimationEnabled && minutes === 0 && seconds < 4 && days >= 30) { - triggerHourlyAnimation(); + const d = Math.floor(timeLeft / 86400000); + const h = Math.floor((timeLeft % 86400000) / 3600000); + const m = Math.floor((timeLeft % 3600000) / 60000); + const s = Math.floor((timeLeft % 60000) / 1000); + + updateValue('days', d); + updateValue('hours', h); + updateValue('minutes', m); + updateValue('seconds', s); + + function updateValue(id, val) { + const el = document.getElementById(id); + const valStr = String(val).padStart(2, '0'); + if (el.textContent !== valStr) { + if (el.textContent === "--") { + el.textContent = valStr; + return; + } + el.style.transform = 'translateY(-10px)'; + el.style.opacity = '0'; + setTimeout(() => { + el.textContent = valStr; + el.style.transform = 'translateY(0)'; + el.style.opacity = '1'; + }, 150); + } } - const totalAnimationTime = 30 * 24 * 60 * 60 * 1000; - let animationProgress = 0; - - if (timeLeft < totalAnimationTime) { - animationProgress = 1 - (timeLeft / totalAnimationTime); - const moonX = 200 - (animationProgress * 200); - document.getElementById('moon').style.transform = `translateX(${moonX}px)`; - } else { - document.getElementById('moon').style.transform = 'translateX(200px)'; + if (document.getElementById('hourlyAnimation').checked && m === 0 && s < 2 && d >= 1) { + triggerHourlyAnimation(); } - const twentyDays = 20 * 24 * 60 * 60 * 1000; - const tenDays = 10 * 24 * 60 * 60 * 1000; - if (!isAnimating) { - if (timeLeft <= twentyDays) { - const remainingFraction = timeLeft / twentyDays; - const startColor = { r: 160, g: 160, b: 240 }; - const endColor = { r: 10, g: 10, b: 32 }; - const r = Math.floor(startColor.r + (endColor.r - startColor.r) * (1 - remainingFraction)); - const g = Math.floor(startColor.g + (endColor.g - startColor.g) * (1 - remainingFraction)); - const b = Math.floor(startColor.b + (endColor.b - startColor.b) * (1 - remainingFraction)); - document.body.style.backgroundColor = `rgb(${r}, ${g}, ${b})`; + const monthMs = 2592000000; + if (timeLeft < monthMs) { + const progress = 1 - (timeLeft / monthMs); + document.getElementById('moon').style.transform = `translateX(calc(var(--eclipse-offset) - (var(--eclipse-offset) * ${progress})))`; } else { - const hourOfDay = now.getHours() + now.getMinutes() / 60 + now.getSeconds() / 3600; - const keyframes = [ - [3, [10, 10, 32]], - [5, [32, 32, 64]], - [5.5, [64, 64, 128]], - [6.5, [180, 230, 255]], - [19, [180, 230, 255]], - [19.5, [255, 120, 80]], - [20, [100, 80, 100]], - [21, [10, 10, 32]], - ]; - function interpolateColor(time, keyframes) { - for (let i = 0; i < keyframes.length - 1; i++) { - const [t1, c1] = keyframes[i]; - const [t2, c2] = keyframes[i + 1]; - - if (time >= t1 && time < t2) { - const ratio = (time - t1) / (t2 - t1); - const r = c1[0] * (1 - ratio) + c2[0] * ratio; - const g = c1[1] * (1 - ratio) + c2[1] * ratio; - const b = c1[2] * (1 - ratio) + c2[2] * ratio; - return [r, g, b]; - } - } - return keyframes[keyframes.length - 1][1]; - } - - const [r, g, b] = interpolateColor(hourOfDay, keyframes); - - document.body.style.backgroundColor = `rgb(${Math.floor(r)}, ${Math.floor(g)}, ${Math.floor(b)})`; + document.getElementById('moon').style.transform = 'translateX(var(--eclipse-offset))'; } - } - if (!isAnimating) { - if (timeLeft <= tenDays) { - const starOpacity = 1 - (timeLeft / tenDays); - document.getElementById('stars').style.opacity = starOpacity.toFixed(2); + const last10d = 864000000; + if (timeLeft < last10d) { + const p = 1 - (timeLeft / last10d); + document.getElementById('stars').style.opacity = p; + document.getElementById('nebula').style.opacity = p * 0.5; + document.body.style.backgroundColor = `rgb(${10 + (20 * (1-p))}, ${10 + (20 * (1-p))}, ${32 + (100 * (1-p))})`; } else { - const hour = now.getHours(); - document.getElementById('stars').style.opacity = (hour >= 21 || hour < 5) ? '0.6' : '0'; + const hr = now.getHours(); + const isNight = hr >= 21 || hr < 5; + document.getElementById('stars').style.opacity = isNight ? '0.6' : '0'; + document.getElementById('nebula').style.opacity = isNight ? '0.3' : '0'; + document.body.style.backgroundColor = isNight ? '#050510' : '#1a1a2e'; } } } -function getStarsOpacityBasedOnCountdown() { - const now = new Date(); - const timeLeft = eclipseDate - now; - const last10days = 10 * 24 * 60 * 60 * 1000; - if (timeLeft < last10days) { - const progress = 1 - timeLeft / last10days; - return progress.toFixed(1); - } - return '0'; -} - function triggerHourlyAnimation() { if (isAnimating) return; isAnimating = true; - - const body = document.body; - const stars = document.getElementById('stars'); - const sun = document.getElementById('sun'); - const moon = document.getElementById('moon'); - const originalBg = getComputedStyle(body).backgroundColor; - - body.style.transition = 'background-color 3s ease-in-out'; - stars.style.transition = 'opacity 3s ease-in-out'; - stars.style.opacity = '1'; - - const styleSheet = document.createElement('style'); - document.head.appendChild(styleSheet); - sun.style.animation = 'sunPulse 3s ease-in-out'; - moon.style.animation = 'moonSway 4s ease-in-out'; - body.style.backgroundColor = 'rgb(10, 10, 32)'; - setTimeout(() => { - body.style.backgroundColor = originalBg; - stars.style.opacity = getStarsOpacityBasedOnCountdown(); - }, 3000); - setTimeout(() => { - body.style.transition = ''; - stars.style.transition = ''; - sun.style.animation = ''; - moon.style.animation = ''; - document.head.removeChild(styleSheet); - isAnimating = false; - }, 6000); -} - -let wakeLock = null; -let wakeLockSupported = false; -let activeWakeLock = false; -wakeLockSupported = 'wakeLock' in navigator; - -async function keepScreenAwake(enabled) { - if (!wakeLockSupported) { - return; - } - - if (enabled) { - if (!activeWakeLock) { - try { - wakeLock = await navigator.wakeLock.request('screen'); - activeWakeLock = true; - wakeLock.addEventListener('release', () => { - activeWakeLock = false; - }); - - document.addEventListener('visibilitychange', handleVisibilityChange); - } catch (err) { - if (err.name === 'NotAllowedError') { - } - } - } - } else { - if (wakeLock !== null && activeWakeLock) { - try { - await wakeLock.release(); - wakeLock = null; - activeWakeLock = false; - document.removeEventListener('visibilitychange', handleVisibilityChange); - } catch (err) { - } - } - } -} - -async function handleVisibilityChange() { - if (wakeLockSupported && document.getElementById('keepAwake').checked) { - if (document.visibilityState === 'visible' && !activeWakeLock) { - try { - wakeLock = await navigator.wakeLock.request('screen'); - activeWakeLock = true; - } catch (err) { - } - } - } -} - - -function toggleFullscreen() { - if (document.fullscreenElement) { - document.exitFullscreen(); - } else { - document.documentElement.requestFullscreen().catch(err => { - }); - } + const body = document.body, stars = document.getElementById('stars'), nebula = document.getElementById('nebula'), sun = document.getElementById('sun'), moon = document.getElementById('moon'), corona = document.getElementById('corona'), dr = document.getElementById('diamondRing'), content = document.querySelector('.content-wrapper'); + const origBg = body.style.backgroundColor; + + body.style.backgroundColor = '#050510'; + stars.style.opacity = '1'; nebula.style.opacity = '0.6'; content.style.opacity = '0.1'; + moon.style.transition = 'transform 6s cubic-bezier(0.4, 0, 0.2, 1)'; + setTimeout(() => moon.style.transform = 'translateX(0px)', 100); + + setTimeout(() => { dr.style.left = 'calc(50% + (var(--eclipse-size)/2.6))'; dr.style.top = 'calc(25% - (var(--eclipse-size)/2.6))'; dr.style.opacity = '1'; }, 5400); + setTimeout(() => { dr.style.opacity = '0'; corona.style.opacity = '1'; corona.style.animation = 'coronaFlare 10s infinite linear'; sun.style.boxShadow = '0 0 2vmin #000'; }, 6000); + setTimeout(() => { dr.style.left = 'calc(50% - (var(--eclipse-size)/2.6))'; dr.style.top = 'calc(25% + (var(--eclipse-size)/2.6))'; dr.style.opacity = '1'; corona.style.opacity = '0'; sun.style.boxShadow = ''; }, 10000); + setTimeout(() => { dr.style.opacity = '0'; moon.style.transform = 'translateX(calc(-1 * var(--eclipse-offset)))'; }, 10800); + setTimeout(() => { body.style.backgroundColor = origBg; content.style.opacity = '1'; updateCountdown(); }, 12000); + setTimeout(() => { moon.style.transition = 'none'; moon.style.transform = 'translateX(var(--eclipse-offset))'; isAnimating = false; }, 17000); } +window.triggerHourlyAnimation = triggerHourlyAnimation; -function setupSettingsPanel() { - const settingsBtn = document.getElementById('settingsBtn'); - const closeBtn = document.getElementById('closeBtn'); - const settingsPanel = document.getElementById('settingsPanel'); - const saveBtn = document.getElementById('saveSettings'); - const locationSelect = document.getElementById('locationSelect'); - const customLocationFields = document.getElementById('customLocationFields'); - const keepAwakeToggle = document.getElementById('keepAwake'); - const fullscreenBtn = document.getElementById('fullscreenBtn'); - const languageSelect = document.getElementById('languageSelect'); - const resetBtn = document.getElementById('resetSettings'); - - resetBtn.addEventListener('click', resetAllSettings); +function setupUI() { + const panel = document.getElementById('settingsPanel'), sBtn = document.getElementById('settingsBtn'); + sBtn.onclick = () => { panel.classList.add('open'); sBtn.classList.add('hide'); }; + document.getElementById('closeBtn').onclick = () => { panel.classList.remove('open'); sBtn.classList.remove('hide'); }; + document.getElementById('fullscreenBtn').onclick = () => { if (document.fullscreenElement) document.exitFullscreen(); else document.documentElement.requestFullscreen(); }; - if (currentLanguage && translations[currentLanguage]) { - languageSelect.value = currentLanguage; - } - - languageSelect.addEventListener('change', () => { - const selectedLang = languageSelect.value; - if (translations[selectedLang]) { - updateLanguage(selectedLang); - } - }); - - fullscreenBtn.addEventListener('click', toggleFullscreen); - - settingsBtn.addEventListener('click', () => { - if (currentLanguage) { - languageSelect.value = currentLanguage; - } - - if (locationSelect.value === "custom") { - document.getElementById('customLocationFields').style.display = 'block'; - } else { - document.getElementById('customLocationFields').style.display = 'none'; - } - - settingsPanel.classList.add('open'); - settingsBtn.classList.add('hide'); - }); - - closeBtn.addEventListener('click', () => { - settingsPanel.classList.remove('open'); - settingsBtn.classList.remove('hide'); - }); - - locationSelect.addEventListener('change', () => { - const selected = locationSelect.value; - const isCustom = selected === 'custom'; - const dateFields = [ - document.getElementById('eclipseDate'), - document.getElementById('eclipseTimeHours'), - document.getElementById('eclipseTimeMinutes'), - document.getElementById('eclipseTimeSeconds') - ]; - let locationText; - - if (isCustom) { - customLocationFields.style.display = 'block'; - dateFields.forEach(field => field.disabled = false); - } else { - customLocationFields.style.display = 'none'; - dateFields.forEach(field => field.disabled = true); - - if (eclipseTimes[selected]) { - const dateTimeStr = eclipseTimes[selected]; - const [datePart, timeWithOffset] = dateTimeStr.split('T'); - const [timePart] = timeWithOffset.split('+').length > 1 - ? timeWithOffset.split('+') - : timeWithOffset.split('-'); - const [hours, minutes, seconds] = timePart.split(':'); - const [year, month, day] = datePart.split('-'); - - document.getElementById('eclipseDate').value = `${year}-${month}-${day}`; - document.getElementById('eclipseTimeHours').value = parseInt(hours); - document.getElementById('eclipseTimeMinutes').value = parseInt(minutes); - document.getElementById('eclipseTimeSeconds').value = parseInt(seconds); - } - locationText = locationSelect.options[locationSelect.selectedIndex].text; - } - - document.getElementById('locationLabel').textContent = translations[currentLanguage].location; - document.getElementById('locationName').textContent = locationText; - }); - - saveBtn.addEventListener('click', () => { - const selectedCity = locationSelect.value; - let newLocationName; - - if (selectedCity !== "custom" && eclipseTimes[selectedCity]) { - newLocationName = locationSelect.options[locationSelect.selectedIndex].text; - } else { - newLocationName = document.getElementById("customLocation").value || "Custom"; - } - - locationName = newLocationName; + document.getElementById('locationSelect').onchange = (e) => { + const val = e.target.value; + const isC = val === 'custom'; + document.getElementById('customLocationFields').style.display = isC ? 'block' : 'none'; + if (!isC && eclipseTimes[val]) { + const d = new Date(eclipseTimes[val]); + document.getElementById('eclipseDate').value = `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`; + document.getElementById('eclipseTimeHours').value = d.getHours(); + document.getElementById('eclipseTimeMinutes').value = d.getMinutes(); + document.getElementById('eclipseTimeSeconds').value = d.getSeconds(); + } + }; + + document.getElementById('saveSettings').onclick = () => { + const locSel = document.getElementById('locationSelect'); + locationName = locSel.value === 'custom' ? (document.getElementById('customLocation').value || "Custom") : locSel.options[locSel.selectedIndex].text; localStorage.setItem('locationName', locationName); - - document.getElementById('locationLabel').textContent = translations[currentLanguage].location; document.getElementById('locationName').textContent = locationName; + updateEclipseDateFromInput(); + localStorage.setItem('language', document.getElementById('languageSelect').value); + updateLanguage(document.getElementById('languageSelect').value); - if (selectedCity !== "custom" && eclipseTimes[selectedCity]) { - eclipseDate = new Date(eclipseTimes[selectedCity]); - localStorage.setItem('eclipseDate', eclipseDate.toISOString()); - } else { - updateEclipseDateFromInput(); - } - - const selectedLang = languageSelect.value; - if (translations[selectedLang]) { - localStorage.setItem('language', selectedLang); - } - + const keepAwakeEnabled = document.getElementById('keepAwake').checked; + localStorage.setItem('keepAwake', keepAwakeEnabled); + if (keepAwakeEnabled) requestWakeLock(); else releaseWakeLock(); + localStorage.setItem('hourlyAnimation', document.getElementById('hourlyAnimation').checked); - keepScreenAwake(keepAwakeToggle.checked); - localStorage.setItem('keepAwake', keepAwakeToggle.checked); - - settingsPanel.classList.remove('open'); - settingsBtn.classList.remove('hide'); - }); - - const savedKeepAwake = localStorage.getItem('keepAwake'); - if (savedKeepAwake !== null) { - keepAwakeToggle.checked = savedKeepAwake === 'true'; - keepScreenAwake(keepAwakeToggle.checked); - } - - const savedHourlyAnimation = localStorage.getItem('hourlyAnimation'); - if (savedHourlyAnimation !== null) { - document.getElementById('hourlyAnimation').checked = savedHourlyAnimation === 'true'; - } -} + panel.classList.remove('open'); sBtn.classList.remove('hide'); + }; -function initAdRemoval() { - const isLikelyOnline = window.location.hostname !== 'localhost' && - window.location.hostname !== '127.0.0.1' && - !window.location.protocol.includes('file'); - if (isLikelyOnline) { - const adObserver = new MutationObserver((mutations) => { - const adElement = document.querySelector('div[style*="position: fixed"][style*="bottom: 0px"][style*="width: 100%"][style*="height: 55px"][style*="z-index: 9999"]'); - - if (adElement) { - adElement.remove(); - } - }); - - adObserver.observe(document.body, { - childList: true, - subtree: true - }); - - setTimeout(() => { - adObserver.disconnect(); - }, 6000); - } + document.getElementById('resetSettings').onclick = () => { localStorage.clear(); location.reload(); }; } document.addEventListener('DOMContentLoaded', () => { createStars(); - populateTimeDropdowns(); loadSettings(); - setupSettingsPanel(); + setupUI(); updateCountdown(); setInterval(updateCountdown, 1000); - - document.getElementById('eclipseDate').addEventListener('change', updateEclipseDateFromInput); - document.getElementById('eclipseTimeHours').addEventListener('change', updateEclipseDateFromInput); - document.getElementById('eclipseTimeMinutes').addEventListener('change', updateEclipseDateFromInput); - document.getElementById('eclipseTimeSeconds').addEventListener('change', updateEclipseDateFromInput); - initAdRemoval(); }); - -function resetAllSettings() { - localStorage.removeItem('eclipseDate'); - localStorage.removeItem('locationName'); - localStorage.removeItem('language'); - localStorage.removeItem('hourlyAnimation'); - localStorage.removeItem('keepAwake'); - window.location.reload(); -} From 75e3bc5fcf60c64b652ce6d603209693e325cbae Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Thu, 4 Jun 2026 22:49:52 +0000 Subject: [PATCH 2/3] Modernize eclipse countdown with glassmorphism and animations - Overhauled the UI with a modern glassmorphism aesthetic. - Added a dynamic background with stars and a rotating nebula. - Implemented an hourly eclipse animation (moon transit, corona, diamond ring). - Added Screen Wake Lock API support for permanent tablet displays. - Improved responsiveness and layout to ensure visibility on all devices. - Added automatic language detection and a settings panel. Co-authored-by: j4c0s <46329710+j4c0s@users.noreply.github.com> --- index.html | 54 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/index.html b/index.html index b4d36b9..d12e729 100644 --- a/index.html +++ b/index.html @@ -12,6 +12,7 @@ :root { --eclipse-size: 40vmin; --eclipse-offset: 50vmin; + --eclipse-top: 12%; } body { @@ -30,7 +31,7 @@ } .content-wrapper { - width: 90%; + width: 92%; max-width: 800px; display: flex; flex-direction: column; @@ -41,11 +42,12 @@ background: rgba(255, 255, 255, 0.05); backdrop-filter: blur(15px); -webkit-backdrop-filter: blur(15px); - padding: 40px; + padding: 30px; border-radius: 30px; border: 1px solid rgba(255, 255, 255, 0.1); box-shadow: 0 20px 50px rgba(0,0,0,0.3); - margin-top: 40vh; + margin-top: 62vh; + box-sizing: border-box; } h1 { @@ -137,7 +139,7 @@ z-index: 10; transition: all 1s ease-in-out; position: absolute; - top: 25%; + top: var(--eclipse-top); } .moon { @@ -150,7 +152,7 @@ transition: transform 1s ease-in-out; box-shadow: inset 0 0 2vmin rgba(0,0,0,0.5); position: absolute; - top: 25%; + top: var(--eclipse-top); } .corona { @@ -163,7 +165,7 @@ opacity: 0; transition: opacity 1.5s ease-in-out; position: absolute; - top: 25%; + top: var(--eclipse-top); } .diamond-ring { @@ -360,32 +362,36 @@ } @media (max-width: 600px) { + .content-wrapper { + padding: 20px 10px; + margin-top: 45vh; + width: 95%; + } h1 { - font-size: 8vw; - margin: 15px 0; + font-size: 6vw; + margin: 5px 0 10px 0; + } + .countdown { + gap: 5px; } .time-block { - padding: 10px 5px; - min-width: 25%; + padding: 5px; + flex: 1 0 21%; + min-width: unset; } .time-value { - font-size: 10vw; + font-size: 8vw; } .time-label { - font-size: 2.5vw; + font-size: 2.2vw; } .location-info { - font-size: 4vw; - padding: 10px 20px; + font-size: 3.2vw; + padding: 4px 12px; + margin-top: 10px; } - } - - @media (orientation: portrait) { - .time-block { - flex: 1 0 40%; - } - h1 { - font-size: 9vw; + :root { + --eclipse-size: 30vmin; } } @@ -912,9 +918,9 @@

Solar Eclipse Countdown

moon.style.transition = 'transform 6s cubic-bezier(0.4, 0, 0.2, 1)'; setTimeout(() => moon.style.transform = 'translateX(0px)', 100); - setTimeout(() => { dr.style.left = 'calc(50% + (var(--eclipse-size)/2.6))'; dr.style.top = 'calc(25% - (var(--eclipse-size)/2.6))'; dr.style.opacity = '1'; }, 5400); + setTimeout(() => { dr.style.left = 'calc(50% + (var(--eclipse-size)/2.6))'; dr.style.top = 'calc(var(--eclipse-top) - (var(--eclipse-size)/2.6))'; dr.style.opacity = '1'; }, 5400); setTimeout(() => { dr.style.opacity = '0'; corona.style.opacity = '1'; corona.style.animation = 'coronaFlare 10s infinite linear'; sun.style.boxShadow = '0 0 2vmin #000'; }, 6000); - setTimeout(() => { dr.style.left = 'calc(50% - (var(--eclipse-size)/2.6))'; dr.style.top = 'calc(25% + (var(--eclipse-size)/2.6))'; dr.style.opacity = '1'; corona.style.opacity = '0'; sun.style.boxShadow = ''; }, 10000); + setTimeout(() => { dr.style.left = 'calc(50% - (var(--eclipse-size)/2.6))'; dr.style.top = 'calc(var(--eclipse-top) + (var(--eclipse-size)/2.6))'; dr.style.opacity = '1'; corona.style.opacity = '0'; sun.style.boxShadow = ''; }, 10000); setTimeout(() => { dr.style.opacity = '0'; moon.style.transform = 'translateX(calc(-1 * var(--eclipse-offset)))'; }, 10800); setTimeout(() => { body.style.backgroundColor = origBg; content.style.opacity = '1'; updateCountdown(); }, 12000); setTimeout(() => { moon.style.transition = 'none'; moon.style.transform = 'translateX(var(--eclipse-offset))'; isAnimating = false; }, 17000); From d5718f2ccbea8d1521cf1cb088a01efd25829bea Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Thu, 4 Jun 2026 23:17:57 +0000 Subject: [PATCH 3/3] Improve eclipse countdown UI and animations - Implemented a responsive Flexbox layout with `min-height: 100dvh` and `clamp()` to prevent UI clipping on mobile devices. - Enhanced the "Diamond Ring" animation with multi-layered shadows and dedicated entry/exit phases. - Standardized celestial body centering using `translate(-50%, -50%)`. - Fixed a bug where a "ghost ring" (box-shadow) remained on the sun after animation by implementing explicit style resets. - Added a clickable trigger to the sun element for manual animation testing. - Bumped Service Worker version for cache invalidation. Co-authored-by: j4c0s <46329710+j4c0s@users.noreply.github.com> --- index.html | 908 +++++++++++++++++++---------------------------------- 1 file changed, 316 insertions(+), 592 deletions(-) diff --git a/index.html b/index.html index d12e729..9b744ed 100644 --- a/index.html +++ b/index.html @@ -2,7 +2,7 @@ - + @@ -11,274 +11,256 @@ @@ -406,8 +326,9 @@
- - + + +
@@ -449,22 +370,20 @@
-
- - -
- - : - - : - -
-
- - -
+ + +
+ + : + + : +
+
-
- -
-
- This app works offline - add to your home screen to use as an app -
-
- -
+ +
-
+
-
+

Solar Eclipse Countdown

@@ -518,15 +430,15 @@

Solar Eclipse Countdown

-
Location:
-
Cascante
+ Location: + Cascante