1/* ========================= 2 FECHAS 3Para cambiar el contador de portada, cambiar la constante hero (linea 7) 4 5========================= */ 6const dates = { 7 hero: new Date("August 2, 2027 10:50:00").getTime(), 8 y26: new Date("August 12, 2026 19:30:00").getTime(), 9 y27: new Date("August 2, 2027 10:50:00").getTime(), 10 y28: new Date("January 26, 2028 16:50:00").getTime() 11}; 12 13/* ========================= 14 I18N CENTRALIZADO 15========================= */ 16const i18n = { 17 es: { 18 explore: "Explorar", 19 explore_details: "Explorar detalles" 20 }, 21 en: { 22 explore: "Explore", 23 explore_details: "Explore details" 24 }, 25 ca: { 26 explore: "Explorar", 27 explore_details: "Explorar detalls" 28 }, 29 gl: { 30 explore: "Explorar", 31 explore_details: "Explorar detalles" 32 }, 33 eu: { 34 explore: "Arakatu", 35 explore_details: "Xehetasunak arakatu" 36 } 37}; 38 39function getLang() { 40 const select = document.getElementById("edit-lang-dropdown-select"); 41 if (select) return select.options[select.selectedIndex].value; 42 43 // Fallback: el atributo lang del <html> lo establece Drupal en servidor 44 // con el idioma activo de la página (ej. "ca", "en", "eu", "gl", "es") 45 return (document.documentElement.lang || "es").split("-")[0]; 46} 47 48function t(key) { 49 const lang = getLang(); 50 return i18n[lang]?.[key] || i18n["en"]?.[key] || key; 51} 52 53/* ========================= 54 TRADUCCIÃN AUTOMÃTICA HTML 55========================= */ 56function translatePage() { 57 document.querySelectorAll("[data-i18n]").forEach(el => { 58 const key = el.getAttribute("data-i18n"); 59 el.innerText = t(key); 60 }); 61} 62 63/* ========================= 64 UTILIDADES TIEMPO 65========================= */ 66function calcTime(target, now) { 67 const diff = target - now; 68 69 if (diff <= 0) { 70 return { d: "00", h: "00", m: "00", s: "00" }; 71 } 72 73 return { 74 d: String(Math.floor(diff / (1000 * 60 * 60 * 24))).padStart(2, "0"), 75 h: String(Math.floor((diff / (1000 * 60 * 60)) % 24)).padStart(2, "0"), 76 m: String(Math.floor((diff / (1000 * 60)) % 60)).padStart(2, "0"), 77 s: String(Math.floor((diff / 1000) % 60)).padStart(2, "0") 78 }; 79} 80 81/* ========================= 82 UPDATE CON ANIMACIÃN 83========================= */ 84function updateElAnimated(id, value) { 85 const el = document.getElementById(id); 86 if (!el || el.textContent === value) return; 87 88 el.classList.add("num-change"); 89 setTimeout(() => { 90 el.textContent = value; 91 el.classList.remove("num-change"); 92 }, 150); 93} 94 95/* ========================= 96 FLIP CLOCK 97========================= */ 98function updateFlip(el, value) { 99 if (!el) return; 100 101 const top = el.querySelector(".flip-top"); 102 const bottom = el.querySelector(".flip-bottom"); 103 if (!top || !bottom || top.textContent === value) return; 104 105 bottom.textContent = value; 106 el.classList.remove("flip-animate"); 107 void el.offsetWidth; 108 el.classList.add("flip-animate"); 109 110 setTimeout(() => { 111 top.textContent = value; 112 el.classList.remove("flip-animate"); 113 }, 400); 114} 115 116/* ========================= 117 TIMERS 118========================= */ 119function updateTimers(now) { 120 const tHero = calcTime(dates.hero, now); 121 122 updateElAnimated("days-h", tHero.d); 123 updateElAnimated("hours-h", tHero.h); 124 updateElAnimated("minutes-h", tHero.m); 125 126 const flipSeconds = document.getElementById("flip-seconds-h"); 127 if (flipSeconds) { 128 updateFlip(flipSeconds, tHero.s); 129 } else { 130 updateElAnimated("seconds-h", tHero.s); 131 } 132 133 const t26 = calcTime(dates.y26, now); 134 updateElAnimated("d-26", t26.d); 135 updateElAnimated("h-26", t26.h); 136 updateElAnimated("m-26", t26.m); 137 138 const t27 = calcTime(dates.y27, now); 139 updateElAnimated("d-27", t27.d); 140 updateElAnimated("h-27", t27.h); 141 updateElAnimated("m-27", t27.m); 142 143 const t28 = calcTime(dates.y28, now); 144 updateElAnimated("d-28", t28.d); 145 updateElAnimated("h-28", t28.h); 146 updateElAnimated("m-28", t28.m); 147} 148 149/* ========================= 150 LOOP 151========================= */ 152let lastSecond = null; 153 154function rafLoop() { 155 const now = new Date(); 156 const sec = now.getSeconds(); 157 158 if (sec !== lastSecond) { 159 updateTimers(now.getTime()); 160 lastSecond = sec; 161 } 162 163 requestAnimationFrame(rafLoop); 164} 165 166requestAnimationFrame(rafLoop); 167 168/* ========================= 169 MAPAS / CAPAS 170========================= */ 171let activeYearMap = null; 172 173function toggleLayer(y) { 174 const l = document.getElementById('layer-' + y); 175 const b = document.getElementById('btn-' + y); 176 const detailsBtn = document.getElementById('mapDetailsBtn'); 177 178 ['26', '27', '28'].forEach(code => { 179 180 const layer = document.getElementById('layer-' + code); 181 const btn = document.getElementById('btn-' + code); 182 183 if (layer) { 184 layer.classList.remove('visible'); 185 layer.hidden = true; 186 } 187 188 if (btn) { 189 btn.classList.remove('active-' + code); 190 btn.setAttribute('aria-selected', 'false'); 191 } 192 }); 193 194 if (l) { 195 l.classList.add('visible'); 196 l.hidden = false;
197 } 198 199 if (b) { 200 b.classList.add('active-' + y); 201 b.setAttribute('aria-selected', 'true'); 202 } 203 204 activeYearMap = '20' + y; 205 206 if (detailsBtn) { 207 const lang = getLang(); 208 detailsBtn.classList.add('active'); 209 detailsBtn.innerText = 210 t("explore").toLocaleUpperCase(lang) + " 20" + y; 211 } 212} 213 214 215 216function activateMap(y) { 217 toggleLayer(y.slice(-2)); 218} 219 220function openCurrentIgn() { 221 if (!activeYearMap) return; 222 window.open(`https://visualizadores.ign.es/eclipses/${activeYearMap}`, '_blank'); 223} 224 225/* ========================= 226 EVENTOS INICIALES 227========================= */ 228document.addEventListener("DOMContentLoaded", () => { 229 230 translatePage(); 231 232 const langSelect = document.getElementById("edit-lang-dropdown-select"); 233 234 if (langSelect) { 235 langSelect.addEventListener("change", () => { 236 translatePage(); 237 238 if (activeYearMap) { 239 const y = activeYearMap.slice(-2); 240 toggleLayer(y); 241 } 242 }); 243 } 244 245 toggleLayer('26'); 246 247 const toggler = document.querySelector('.navbar-toggler'); 248 if (toggler) { 249 toggler.addEventListener('click', () => { 250 toggler.setAttribute( 251 'aria-label', 252 toggler.getAttribute('aria-expanded') === 'true' 253 ? 'Cerrar menú' 254 : 'Abrir menú' 255 ); 256 }); 257 } 258}); 259 260 261document.addEventListener("change", (e) => { 262 if (e.target && e.target.id === "edit-lang-dropdown-select") { 263 264 translatePage(); 265 266 if (activeYearMap) { 267 const y = activeYearMap.slice(-2); 268 toggleLayer(y); 269 } 270 } 271}); 272 273/* ========================= 274 MODAL IGN 275========================= */ 276function openIgnMap(year) { 277 const modal = document.getElementById('ignModal'); 278 const name = document.getElementById('ignEventName'); 279 const dur = document.getElementById('ignDuration'); 280 const link = document.getElementById('ignLink'); 281 282 const data = { 283 "2026": ["ECLIPSE TOTAL 2026", "1m 50s"], 284 "2027": ["ECLIPSE TOTAL 2027", "4m 40s"], 285 "2028": ["ECLIPSE ANULAR 2028", "7m 10s"] 286 }; 287 288 name.innerText = data[year][0]; 289 dur.innerText = data[year][1]; 290 link.href = `https://visualizadores.ign.es/eclipses/${year}`; 291 modal.classList.add('active'); 292} 293 294function closeIgnMap() { 295 document.getElementById('ignModal').classList.remove('active'); 296} 297 298/* ========================= 299 SIMULADOR 300========================= */ 301const phasesTotal = [ 302 { title: "C1 Parcial", desc: "Inicio" }, 303 { title: "C2 Totalidad", desc: "Corona Visible" }, 304 { title: "C3 Fin Total", desc: "Anillo Diamantes" } 305]; 306 307const phasesAnnular = [ 308 { title: "C1 Parcial", desc: "Inicio" }, 309 { title: "Max Anular", desc: "Anillo de Fuego" }, 310 { title: "C3 Salida", desc: "Fin Anillo" } 311]; 312 313let curSimType = '', curStep = 0; 314 315function openSim(t) { 316 curSimType = t; 317 curStep = 0; 318 document.getElementById('simStage').className = 319 'sim-stage ' + (t === 'annular' ? 'sim-type-annular' : ''); 320 document.getElementById('simOverlay').classList.add('active'); 321 updSim(); 322} 323 324function closeSim(e) { 325 if (e) e.stopPropagation(); 326 document.getElementById('simOverlay').classList.remove('active'); 327} 328 329function nextPhase() { 330 curStep = (curStep + 1) % 3; 331 updSim(); 332} 333 334function updSim() { 335 const d = (curSimType === 'total' ? phasesTotal : phasesAnnular)[curStep]; 336 document.getElementById('simTitle').innerText = d.title; 337 document.getElementById('simDesc').innerText = d.desc; 338 const s = document.getElementById('simStage'); 339 s.className = 'sim-stage sim-state-' + (curStep + 1); 340} 341 342/* ========================= 343 ANIMACIÃN CCAA 344========================= */ 345(function () { 346 const items = document.querySelectorAll('.ccaa-item'); 347 348 if (!('IntersectionObserver' in window)) { 349 items.forEach(el => el.classList.add('is-visible')); 350 return; 351 } 352 353 const observer = new IntersectionObserver((entries, obs) => { 354 entries.forEach(entry => { 355 if (entry.isIntersecting) { 356 entry.target.classList.add('is-visible'); 357 obs.unobserve(entry.target); 358 } 359 }); 360 }, { threshold: 0.15 }); 361 362 items.forEach(el => observer.observe(el)); 363})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.