whycomeimsocool

Untitled

Jul 27th, 2025
10
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 32.76 KB | None | 0 0
  1. // === ANIMATION SYSTEM TOGGLE ===
  2. const USE_JS_ANIMATIONS = true; // Set to false to use CSS animations
  3.  
  4. // === CALENDAR ANIMATOR CLASS ===
  5. class CalendarAnimator {
  6. constructor() {
  7. this.animationDuration = 300;
  8. this.staggerDelay = 50;
  9. }
  10.  
  11. animateMoonPhases(show) {
  12. const moonElements = document.querySelectorAll('.calendar-wrapper .moon');
  13.  
  14. moonElements.forEach((el, index) => {
  15. setTimeout(() => {
  16. if (show) {
  17. el.style.opacity = '0';
  18. el.style.transform = 'scale(0.5) rotate(-180deg)';
  19. el.style.display = 'inline';
  20.  
  21. requestAnimationFrame(() => {
  22. el.style.transition = `opacity ${this.animationDuration}ms ease, transform ${this.animationDuration}ms ease`;
  23. el.style.opacity = '1';
  24. el.style.transform = 'scale(1) rotate(0deg)';
  25. });
  26. } else {
  27. el.style.transition = `opacity ${this.animationDuration}ms ease, transform ${this.animationDuration}ms ease`;
  28. el.style.opacity = '0';
  29. el.style.transform = 'scale(0.5) rotate(180deg)';
  30.  
  31. setTimeout(() => {
  32. el.style.display = 'none';
  33. }, this.animationDuration);
  34. }
  35. }, index * this.staggerDelay);
  36. });
  37. }
  38.  
  39. animateEclipses(eclipseElements, show, type) {
  40. const rotationDirection = type === 'solar' ? 360 : -360;
  41.  
  42. eclipseElements.forEach((el, index) => {
  43. setTimeout(() => {
  44. if (show) {
  45. el.style.opacity = '0';
  46. el.style.transform = `rotate(${rotationDirection}deg) scale(0.3)`;
  47. el.style.display = 'inline';
  48.  
  49. requestAnimationFrame(() => {
  50. el.style.transition = `opacity ${this.animationDuration}ms ease, transform ${this.animationDuration}ms cubic-bezier(0.68, -0.55, 0.265, 1.55)`;
  51. el.style.opacity = '1';
  52. el.style.transform = 'rotate(0deg) scale(1)';
  53. });
  54. } else {
  55. el.style.transition = `opacity ${this.animationDuration}ms ease, transform ${this.animationDuration}ms ease`;
  56. el.style.opacity = '0';
  57. el.style.transform = `rotate(${-rotationDirection}deg) scale(0.3)`;
  58.  
  59. setTimeout(() => {
  60. el.style.display = 'none';
  61. }, this.animationDuration);
  62. }
  63. }, index * this.staggerDelay);
  64. });
  65. }
  66.  
  67. animateCalendarTransition() {
  68. return new Promise((resolve) => {
  69. const calendarWrapper = document.querySelector('.calendar-wrapper');
  70.  
  71. // Fade out
  72. calendarWrapper.style.transition = `opacity ${this.animationDuration}ms ease`;
  73. calendarWrapper.style.opacity = '0';
  74.  
  75. setTimeout(() => {
  76. // Fade back in
  77. calendarWrapper.style.opacity = '1';
  78.  
  79. setTimeout(() => {
  80. resolve();
  81. }, this.animationDuration);
  82. }, this.animationDuration / 2);
  83. });
  84. }
  85. }
  86.  
  87. // Initialize the animator
  88. const calendarAnimator = new CalendarAnimator();
  89.  
  90. // === ENHANCED TOGGLE FUNCTIONS ===
  91. function toggleMoonPhases(show) {
  92. if (USE_JS_ANIMATIONS) {
  93. calendarAnimator.animateMoonPhases(show);
  94. } else {
  95. // Use CSS classes
  96. document.querySelector('.calendar-wrapper')
  97. .classList.toggle('moon-phases-hidden', !show);
  98. }
  99. }
  100.  
  101. function toggleLunarEclipses(show) {
  102. if (USE_JS_ANIMATIONS) {
  103. const lunarEclipses = document.querySelectorAll('.calendar-wrapper .lunar-eclipse');
  104. calendarAnimator.animateEclipses(lunarEclipses, show, 'lunar');
  105. } else {
  106. // Use CSS classes
  107. document.querySelector('.calendar-wrapper')
  108. .classList.toggle('lunar-eclipses-hidden', !show);
  109. }
  110. }
  111.  
  112. function toggleSolarEclipses(show) {
  113. if (USE_JS_ANIMATIONS) {
  114. const solarEclipses = document.querySelectorAll('.calendar-wrapper .solar-eclipse');
  115. calendarAnimator.animateEclipses(solarEclipses, show, 'solar');
  116. } else {
  117. // Use CSS classes
  118. document.querySelector('.calendar-wrapper')
  119. .classList.toggle('solar-eclipses-hidden', !show);
  120. }
  121. }
  122.  
  123. function triggerCalendarTransition() {
  124. if (USE_JS_ANIMATIONS) {
  125. return calendarAnimator.animateCalendarTransition();
  126. } else {
  127. // Use CSS fade
  128. const calendarWrapper = document.querySelector('.calendar-wrapper');
  129. calendarWrapper.classList.remove('fade-in');
  130. calendarWrapper.offsetHeight; // Force reflow
  131. calendarWrapper.classList.add('fade-in');
  132. return Promise.resolve(); // Return resolved promise for consistency
  133. }
  134. }
  135.  
  136. // Current date state
  137. let currentMonth = new Date().getMonth(); // 0-based
  138. let currentYear = new Date().getFullYear();
  139.  
  140. // Timezone functionality
  141. let currentTimezoneOffset = -(new Date().getTimezoneOffset() / 60); // Convert to UTC offset
  142.  
  143. // Map quarter number to phase name and symbol
  144. const MOON_PHASES = [
  145. { name: "New Moon", symbol: '<svg> <use href="#moonnew" /> </svg>' },
  146. {
  147. name: "First Quarter",
  148. symbol: '<svg> <use href="#moonfirstquarter" /> </svg>',
  149. },
  150. { name: "Full Moon", symbol: '<svg> <use href="#moonfull" /> </svg>' },
  151. {
  152. name: "Last Quarter",
  153. symbol: '<svg> <use href="#moonlastquarter" /> </svg>',
  154. },
  155. ];
  156.  
  157. // Eclipse symbols by type - customize these as needed
  158. const ECLIPSE_SYMBOLS = {
  159. lunar: {
  160. penumbral: '<svg> <use href="#lunarpenumbral" /> </svg>',
  161. partial: '<svg> <use href="#lunarpartial" /> </svg>',
  162. total: '<svg> <use href="#lunartotal" /> </svg>',
  163. default: '<svg> <use href="#lunardefault" /> </svg>',
  164. },
  165. solar: {
  166. partial: '<svg> <use href="#solarpartial" /> </svg>',
  167. annular: '<svg> <use href="#solarannular" /> </svg>',
  168. total: '<svg> <use href="#solartotal" /> </svg>',
  169. hybrid: '<svg> <use href="#solarhybrid" /> </svg>',
  170. default: '<svg> <use href="#solardefault" /> </svg>',
  171. },
  172. };
  173.  
  174. // Location and astronomy functionality
  175. let currentLocation = { lat: 0, lon: 0, isUTC: true }; // Default to UTC (lat/lon 0,0)
  176.  
  177. function updateTimezoneDisplay() {
  178. const display = document.getElementById("timezoneDisplay");
  179. const sign = currentTimezoneOffset >= 0 ? "+" : "";
  180. display.textContent = `UTC${sign}${currentTimezoneOffset}`;
  181. }
  182.  
  183. function adjustTimezone(direction) {
  184. const newOffset = currentTimezoneOffset + direction;
  185.  
  186. // Limit timezone offset to valid range: -12 to +14
  187. if (newOffset >= -12 && newOffset <= 14) {
  188. currentTimezoneOffset = newOffset;
  189. updateTimezoneDisplay();
  190. renderCalendar();
  191. }
  192. }
  193.  
  194. // Helper function to convert UTC time to selected timezone
  195. function convertToSelectedTimezone(utcDate) {
  196. // Apply the timezone offset
  197. const adjustment = currentTimezoneOffset * 60 * 60 * 1000;
  198. const adjustedTimestamp = utcDate.getTime() + adjustment;
  199. const adjustedDate = new Date(adjustedTimestamp);
  200.  
  201. // Use UTC methods to get the correct date parts
  202. const result = {
  203. day: adjustedDate.getUTCDate(),
  204. month: adjustedDate.getUTCMonth(),
  205. year: adjustedDate.getUTCFullYear(),
  206. timestamp: adjustedTimestamp,
  207. originalDate: adjustedDate,
  208. };
  209.  
  210. return result;
  211. }
  212.  
  213. // Add this function to trigger the fade animation
  214. function triggerCalendarFade() {
  215. const calendarWrapper = document.querySelector(".calendar-wrapper");
  216.  
  217. // Remove the class if it exists (to restart animation)
  218. calendarWrapper.classList.remove("fade-in");
  219.  
  220. // Force a reflow to ensure the class removal is processed
  221. calendarWrapper.offsetHeight;
  222.  
  223. // Add the class to trigger the animation
  224. calendarWrapper.classList.add("fade-in");
  225. }
  226.  
  227. // Find all principal moon phases in the given month
  228. function getPrincipalMoonPhasesLocal(year, month) {
  229. const phases = [];
  230.  
  231. // Search a wider range to catch moon phases that might appear in different days due to timezone
  232. const searchStartUTC = new Date(Date.UTC(year, month, 1, 0, 0, 0));
  233. searchStartUTC.setUTCDate(searchStartUTC.getUTCDate() - 2);
  234.  
  235. const searchEndUTC = new Date(Date.UTC(year, month + 1, 0, 23, 59, 59));
  236. searchEndUTC.setUTCDate(searchEndUTC.getUTCDate() + 2);
  237.  
  238. try {
  239. let q = Astronomy.SearchMoonQuarter(searchStartUTC);
  240. let iterations = 0;
  241. const maxIterations = 50;
  242.  
  243. while (q && iterations < maxIterations) {
  244. const phaseUTC = q.time.date;
  245.  
  246. // Stop searching if we're way past our end date
  247. if (phaseUTC > searchEndUTC) {
  248. break;
  249. }
  250.  
  251. // Use the corrected conversion approach
  252. const phaseConverted = convertToSelectedTimezone(phaseUTC);
  253.  
  254. // Check if moon phase falls in target month
  255. if (phaseConverted.year === year && phaseConverted.month === month) {
  256. const phaseData = {
  257. day: phaseConverted.day,
  258. month: phaseConverted.month + 1,
  259. year: phaseConverted.year,
  260. quarter: q.quarter,
  261. name: MOON_PHASES[q.quarter].name,
  262. symbol: MOON_PHASES[q.quarter].symbol,
  263. exactTime: phaseConverted.originalDate,
  264. };
  265.  
  266. phases.push(phaseData);
  267. }
  268.  
  269. try {
  270. q = Astronomy.NextMoonQuarter(q);
  271. } catch (e) {
  272. break;
  273. }
  274. iterations++;
  275. }
  276. } catch (e) {
  277. console.warn("Error finding moon phases:", e);
  278. }
  279.  
  280. return phases;
  281. }
  282.  
  283. // Find solar eclipses in the given month
  284. function getSolarEclipsesLocal(year, month) {
  285. const eclipses = [];
  286.  
  287. // Search a wider range
  288. const searchStartUTC = new Date(Date.UTC(year, month, 1, 0, 0, 0));
  289. searchStartUTC.setUTCDate(searchStartUTC.getUTCDate() - 2);
  290.  
  291. const searchEndUTC = new Date(Date.UTC(year, month + 1, 0, 23, 59, 59));
  292. searchEndUTC.setUTCDate(searchEndUTC.getUTCDate() + 2);
  293.  
  294. try {
  295. let eclipse = Astronomy.SearchGlobalSolarEclipse(searchStartUTC);
  296. let iterations = 0;
  297. const maxIterations = 50;
  298.  
  299. while (eclipse && iterations < maxIterations) {
  300. const eclipseUTC = eclipse.peak.date;
  301.  
  302. if (eclipseUTC > searchEndUTC) {
  303. break;
  304. }
  305.  
  306. // Use the corrected conversion approach
  307. const eclipseConverted = convertToSelectedTimezone(eclipseUTC);
  308.  
  309. // Check if eclipse falls in target month
  310. if (eclipseConverted.year === year && eclipseConverted.month === month) {
  311. let typeDescription = "";
  312. switch (eclipse.kind) {
  313. case "partial":
  314. typeDescription = "Partial Solar Eclipse";
  315. break;
  316. case "annular":
  317. typeDescription = "Annular Solar Eclipse";
  318. break;
  319. case "total":
  320. typeDescription = "Total Solar Eclipse";
  321. break;
  322. case "hybrid":
  323. typeDescription = "Hybrid Solar Eclipse";
  324. break;
  325. default:
  326. typeDescription = "Solar Eclipse";
  327. }
  328.  
  329. const eclipseData = {
  330. day: eclipseConverted.day,
  331. month: eclipseConverted.month + 1,
  332. year: eclipseConverted.year,
  333. type: "solar",
  334. kind: eclipse.kind,
  335. description: typeDescription,
  336. exactTime: eclipseConverted.originalDate,
  337. };
  338.  
  339. eclipses.push(eclipseData);
  340. }
  341.  
  342. try {
  343. eclipse = Astronomy.NextGlobalSolarEclipse(eclipse.peak);
  344. } catch (e) {
  345. break;
  346. }
  347. iterations++;
  348. }
  349. } catch (e) {
  350. console.warn("Error finding solar eclipses:", e);
  351. }
  352.  
  353. return eclipses;
  354. }
  355.  
  356. // Find lunar eclipses in the given month
  357. function getLunarEclipsesLocal(year, month) {
  358. const eclipses = [];
  359.  
  360. // Search a wider range
  361. const searchStartUTC = new Date(Date.UTC(year, month, 1, 0, 0, 0));
  362. searchStartUTC.setUTCDate(searchStartUTC.getUTCDate() - 2);
  363.  
  364. const searchEndUTC = new Date(Date.UTC(year, month + 1, 0, 23, 59, 59));
  365. searchEndUTC.setUTCDate(searchEndUTC.getUTCDate() + 2);
  366.  
  367. try {
  368. let eclipse = Astronomy.SearchLunarEclipse(searchStartUTC);
  369. let iterations = 0;
  370. const maxIterations = 50;
  371.  
  372. while (eclipse && iterations < maxIterations) {
  373. const eclipseUTC = eclipse.peak.date;
  374.  
  375. if (eclipseUTC > searchEndUTC) {
  376. break;
  377. }
  378.  
  379. // Use the corrected conversion approach
  380. const eclipseConverted = convertToSelectedTimezone(eclipseUTC);
  381.  
  382. // Check if eclipse falls in target month
  383. if (eclipseConverted.year === year && eclipseConverted.month === month) {
  384. let typeDescription = "";
  385. switch (eclipse.kind) {
  386. case "penumbral":
  387. typeDescription = "Penumbral Lunar Eclipse";
  388. break;
  389. case "partial":
  390. typeDescription = "Partial Lunar Eclipse";
  391. break;
  392. case "total":
  393. typeDescription = "Total Lunar Eclipse";
  394. break;
  395. default:
  396. typeDescription = "Lunar Eclipse";
  397. }
  398.  
  399. const eclipseData = {
  400. day: eclipseConverted.day,
  401. month: eclipseConverted.month + 1,
  402. year: eclipseConverted.year,
  403. type: "lunar",
  404. kind: eclipse.kind,
  405. description: typeDescription,
  406. exactTime: eclipseConverted.originalDate,
  407. };
  408.  
  409. eclipses.push(eclipseData);
  410. }
  411.  
  412. try {
  413. eclipse = Astronomy.NextLunarEclipse(eclipse.peak);
  414. } catch (e) {
  415. break;
  416. }
  417. iterations++;
  418. }
  419. } catch (e) {
  420. console.warn("Error finding lunar eclipses:", e);
  421. }
  422.  
  423. return eclipses;
  424. }
  425.  
  426. async function renderCalendar() {
  427. const monthNames = [
  428. "January",
  429. "February",
  430. "March",
  431. "April",
  432. "May",
  433. "June",
  434. "July",
  435. "August",
  436. "September",
  437. "October",
  438. "November",
  439. "December",
  440. ];
  441.  
  442. document.getElementById("monthYear").textContent = `${monthNames[currentMonth]} ${currentYear}`;
  443.  
  444. const firstDay = new Date(currentYear, currentMonth, 1).getDay();
  445. const daysInMonth = new Date(currentYear, currentMonth + 1, 0).getDate();
  446.  
  447. // Get all principal phases for this month (in selected timezone)
  448. const phases = getPrincipalMoonPhasesLocal(currentYear, currentMonth);
  449. // Map day -> phase info
  450. const phaseByDay = {};
  451. for (const p of phases) {
  452. phaseByDay[p.day] = p;
  453. }
  454.  
  455. // Get eclipses for this month
  456. const solarEclipses = getSolarEclipsesLocal(currentYear, currentMonth);
  457. const lunarEclipses = getLunarEclipsesLocal(currentYear, currentMonth);
  458. const allEclipses = [...solarEclipses, ...lunarEclipses];
  459.  
  460. // Map day -> eclipse info
  461. const eclipseByDay = {};
  462. for (const e of allEclipses) {
  463. if (!eclipseByDay[e.day]) {
  464. eclipseByDay[e.day] = [];
  465. }
  466. eclipseByDay[e.day].push(e);
  467. }
  468.  
  469. let date = 1;
  470. const calendarBody = document.getElementById("calendarBody");
  471. calendarBody.innerHTML = "";
  472.  
  473. for (let i = 0; i < 6; i++) {
  474. const row = document.createElement("tr");
  475.  
  476. for (let j = 0; j < 7; j++) {
  477. const cell = document.createElement("td");
  478.  
  479. if (i === 0 && j < firstDay) {
  480. cell.textContent = "";
  481. } else if (date > daysInMonth) {
  482. cell.textContent = "";
  483. } else {
  484. let cellContent = `<div><strong>${date}</strong>`;
  485.  
  486. // Add moon phase if present
  487. if (phaseByDay[date]) {
  488. const { name, symbol } = phaseByDay[date];
  489. cellContent += `<span class="moon" data-phase="${name}" title="${name}">${symbol}</span></div>`;
  490.  
  491. // Add eclipses if present on the same day as moon phase
  492. if (eclipseByDay[date]) {
  493. for (const eclipse of eclipseByDay[date]) {
  494. const eclipseSymbol =
  495. ECLIPSE_SYMBOLS[eclipse.type][eclipse.kind] || ECLIPSE_SYMBOLS[eclipse.type].default;
  496. const eclipseClass = eclipse.type === "solar" ? "solar-eclipse" : "lunar-eclipse";
  497. const tooltipClass =
  498. eclipse.type === "solar" ? "solar-eclipse-tooltip" : "lunar-eclipse-tooltip";
  499.  
  500. cellContent += `<span class="${eclipseClass}" title="${eclipse.description}">${eclipseSymbol}<span class="${tooltipClass}">${eclipse.description}</span></span>`;
  501. }
  502. }
  503. } else if (eclipseByDay[date]) {
  504. // Add eclipses if present but no moon phase
  505. for (const eclipse of eclipseByDay[date]) {
  506. const eclipseSymbol =
  507. ECLIPSE_SYMBOLS[eclipse.type][eclipse.kind] || ECLIPSE_SYMBOLS[eclipse.type].default;
  508. const eclipseClass = eclipse.type === "solar" ? "solar-eclipse" : "lunar-eclipse";
  509. const tooltipClass =
  510. eclipse.type === "solar" ? "solar-eclipse-tooltip" : "lunar-eclipse-tooltip";
  511.  
  512. cellContent += `<span class="${eclipseClass}" title="${eclipse.description}">${eclipseSymbol}<span class="${tooltipClass}">${eclipse.description}</span></span>`;
  513. }
  514. }
  515.  
  516. cell.innerHTML = cellContent;
  517. date++;
  518. }
  519.  
  520. row.appendChild(cell);
  521. }
  522.  
  523. calendarBody.appendChild(row);
  524.  
  525. if (date > daysInMonth) break;
  526. }
  527.  
  528. // Apply hemisphere setting to the newly rendered calendar
  529. handleHemisphereChange();
  530.  
  531. // Use the new transition system
  532. if (USE_JS_ANIMATIONS) {
  533. await triggerCalendarTransition();
  534.  
  535. // Apply current toggle states with animations
  536. const moonToggle = document.getElementById('moonPhasesToggle');
  537. const lunarToggle = document.getElementById('lunarEclipsesToggle');
  538. const solarToggle = document.getElementById('solarEclipsesToggle');
  539.  
  540. if (moonToggle && !moonToggle.checked) {
  541. setTimeout(() => toggleMoonPhases(false), 100);
  542. }
  543. if (lunarToggle && !lunarToggle.checked) {
  544. setTimeout(() => toggleLunarEclipses(false), 150);
  545. }
  546. if (solarToggle && !solarToggle.checked) {
  547. setTimeout(() => toggleSolarEclipses(false), 200);
  548. }
  549. } else {
  550. triggerCalendarFade(); // Your existing CSS animation
  551.  
  552. // Apply CSS toggle states
  553. document.getElementById("moonPhasesToggle").dispatchEvent(new Event("change"));
  554. document.getElementById("lunarEclipsesToggle").dispatchEvent(new Event("change"));
  555. document.getElementById("solarEclipsesToggle").dispatchEvent(new Event("change"));
  556. }
  557. }
  558.  
  559. function handleHemisphereChange() {
  560. const isNorth = document.getElementById("northRadio").checked;
  561.  
  562. // Get all moon symbols
  563. const moonElements = document.querySelectorAll(".moon[data-phase]");
  564.  
  565. moonElements.forEach((moon) => {
  566. const phase = moon.getAttribute("data-phase");
  567.  
  568. if (phase === "First Quarter") {
  569. moon.innerHTML = isNorth ? MOON_PHASES[1].symbol : MOON_PHASES[3].symbol;
  570. } else if (phase === "Last Quarter") {
  571. moon.innerHTML = isNorth ? MOON_PHASES[3].symbol : MOON_PHASES[1].symbol;
  572. }
  573. // New Moon and Full Moon remain unchanged
  574. });
  575.  
  576. // Update legend symbols as well
  577. const legendFirstQuarter = document.getElementById("legend-first-quarter");
  578. const legendLastQuarter = document.getElementById("legend-last-quarter");
  579.  
  580. if (legendFirstQuarter) {
  581. legendFirstQuarter.innerHTML = isNorth ? MOON_PHASES[1].symbol : MOON_PHASES[3].symbol;
  582. }
  583. if (legendLastQuarter) {
  584. legendLastQuarter.innerHTML = isNorth ? MOON_PHASES[3].symbol : MOON_PHASES[1].symbol;
  585. }
  586. }
  587.  
  588. function requestPreciseLocation() {
  589. const btn = document.getElementById("preciseLocationBtn");
  590. const status = document.getElementById("locationStatus");
  591.  
  592. if (!navigator.geolocation) {
  593. updateLocationStatus("error", "Geolocation is not supported by this browser.");
  594. return;
  595. }
  596.  
  597. // Update UI to show we're requesting location
  598. btn.disabled = true;
  599. btn.textContent = "🔍 Getting Location...";
  600. updateLocationStatus("info", "Requesting your location...");
  601.  
  602. navigator.geolocation.getCurrentPosition(
  603. function (position) {
  604. // Success!
  605. currentLocation = {
  606. lat: position.coords.latitude,
  607. lon: position.coords.longitude,
  608. isUTC: false,
  609. accuracy: position.coords.accuracy,
  610. };
  611.  
  612. // Update UI
  613. btn.textContent = "✅ Location Set";
  614. btn.disabled = false;
  615.  
  616. const accuracy = Math.round(position.coords.accuracy);
  617. updateLocationStatus("success", `Location set! Accuracy: ±${accuracy}m. Times are now location-specific.`);
  618.  
  619. // Show coordinates
  620. const coordsDisplay = document.getElementById("coordinatesDisplay");
  621. if (coordsDisplay) {
  622. coordsDisplay.textContent = `Coordinates: ${currentLocation.lat.toFixed(
  623. 4
  624. )}°, ${currentLocation.lon.toFixed(4)}°`;
  625. }
  626.  
  627. // Recalculate times with precise location
  628. calculateSunMoonTimes();
  629. },
  630. function (error) {
  631. // Error handling
  632. let errorMsg = "Location request failed: ";
  633. switch (error.code) {
  634. case error.PERMISSION_DENIED:
  635. errorMsg += "Permission denied. You can still use UTC times.";
  636. break;
  637. case error.POSITION_UNAVAILABLE:
  638. errorMsg += "Location unavailable. Using UTC times.";
  639. break;
  640. case error.TIMEOUT:
  641. errorMsg += "Request timed out. Using UTC times.";
  642. break;
  643. default:
  644. errorMsg += "Unknown error. Using UTC times.";
  645. break;
  646. }
  647.  
  648. updateLocationStatus("warning", errorMsg);
  649. btn.textContent = "📍 Try Again";
  650. btn.disabled = false;
  651. },
  652. {
  653. enableHighAccuracy: true,
  654. timeout: 10000,
  655. maximumAge: 600000, // Cache for 10 minutes
  656. }
  657. );
  658. }
  659.  
  660. function updateLocationStatus(type, message) {
  661. const status = document.getElementById("locationStatus");
  662. if (status) {
  663. status.className = `location-status status-${type}`;
  664. status.textContent = message;
  665. }
  666. }
  667.  
  668. function calculateSunMoonTimes() {
  669. console.log("calculateSunMoonTimes called");
  670.  
  671. // Make sure Astronomy library is loaded
  672. if (typeof Astronomy === "undefined") {
  673. console.error("Astronomy library not loaded");
  674. return;
  675. }
  676.  
  677. console.log("Astronomy library is loaded, currentLocation:", currentLocation);
  678.  
  679. const today = new Date();
  680. console.log("Today:", today);
  681.  
  682. try {
  683. const observer = new Astronomy.Observer(currentLocation.lat, currentLocation.lon, 0);
  684. console.log("Observer created:", observer);
  685.  
  686. // Check what Direction constants are available
  687. console.log("Available Astronomy constants:", Object.keys(Astronomy));
  688.  
  689. // Try different ways to access Direction constants
  690. let riseDirection, setDirection;
  691.  
  692. if (Astronomy.Direction) {
  693. console.log("Direction object:", Astronomy.Direction);
  694. riseDirection = Astronomy.Direction.Rise;
  695. setDirection = Astronomy.Direction.Set;
  696. } else {
  697. // Try direct constants
  698. riseDirection = Astronomy.Rise || 1;
  699. setDirection = Astronomy.Set || -1;
  700. }
  701.  
  702. console.log("Rise direction:", riseDirection, "Set direction:", setDirection);
  703.  
  704. // Calculate sunrise/sunset
  705. console.log("Calculating sunrise...");
  706. const sunrise = Astronomy.SearchRiseSet(Astronomy.Body.Sun, observer, riseDirection, today, 1);
  707. console.log("Sunrise result:", sunrise);
  708.  
  709. console.log("Calculating sunset...");
  710. const sunset = Astronomy.SearchRiseSet(Astronomy.Body.Sun, observer, setDirection, today, 1);
  711. console.log("Sunset result:", sunset);
  712.  
  713. // Calculate moonrise/moonset
  714. console.log("Calculating moonrise...");
  715. const moonrise = Astronomy.SearchRiseSet(Astronomy.Body.Moon, observer, riseDirection, today, 1);
  716. console.log("Moonrise result:", moonrise);
  717.  
  718. console.log("Calculating moonset...");
  719. const moonset = Astronomy.SearchRiseSet(Astronomy.Body.Moon, observer, setDirection, today, 1);
  720. console.log("Moonset result:", moonset);
  721.  
  722. // Update the horizontal bar display
  723. const sunriseEl = document.getElementById("sunriseTime");
  724. const sunsetEl = document.getElementById("sunsetTime");
  725. const moonriseEl = document.getElementById("moonriseTime");
  726. const moonsetEl = document.getElementById("moonsetTime");
  727.  
  728. if (sunriseEl) sunriseEl.textContent = sunrise ? formatTime(sunrise.date) : "None";
  729. if (sunsetEl) sunsetEl.textContent = sunset ? formatTime(sunset.date) : "None";
  730. if (moonriseEl) moonriseEl.textContent = moonrise ? formatTime(moonrise.date) : "None";
  731. if (moonsetEl) moonsetEl.textContent = moonset ? formatTime(moonset.date) : "None";
  732.  
  733. // Update timezone note in the bar
  734. const timezoneNote = document.getElementById("timezoneNote");
  735. if (timezoneNote) {
  736. if (currentLocation.isUTC) {
  737. timezoneNote.textContent = "UTC Times";
  738. } else {
  739. timezoneNote.textContent = `Local Times (${currentLocation.lat.toFixed(
  740. 1
  741. )}°, ${currentLocation.lon.toFixed(1)}°)`;
  742. }
  743. }
  744. } catch (error) {
  745. console.error("Detailed astronomy calculation error:", error);
  746. console.error("Error stack:", error.stack);
  747.  
  748. // Show error in horizontal bar
  749. const sunriseEl = document.getElementById("sunriseTime");
  750. const sunsetEl = document.getElementById("sunsetTime");
  751. const moonriseEl = document.getElementById("moonriseTime");
  752. const moonsetEl = document.getElementById("moonsetTime");
  753.  
  754. if (sunriseEl) sunriseEl.textContent = "Error";
  755. if (sunsetEl) sunsetEl.textContent = "Error";
  756. if (moonriseEl) moonriseEl.textContent = "Error";
  757. if (moonsetEl) moonsetEl.textContent = "Error";
  758. }
  759. }
  760.  
  761. // Alternative approach if the above doesn't work
  762. function calculateSunMoonTimesAlternative() {
  763. console.log("calculateSunMoonTimesAlternative called");
  764.  
  765. if (typeof Astronomy === "undefined") {
  766. console.error("Astronomy library not loaded");
  767. return;
  768. }
  769.  
  770. const today = new Date();
  771.  
  772. try {
  773. const observer = new Astronomy.Observer(currentLocation.lat, currentLocation.lon, 0);
  774.  
  775. // Try using numeric constants directly (common in some versions)
  776. // 1 = Rise, -1 = Set (or vice versa depending on library version)
  777.  
  778. console.log("Trying with numeric direction constants...");
  779.  
  780. let sunrise, sunset, moonrise, moonset;
  781.  
  782. try {
  783. sunrise = Astronomy.SearchRiseSet(Astronomy.Body.Sun, observer, 1, today, 1);
  784. sunset = Astronomy.SearchRiseSet(Astronomy.Body.Sun, observer, -1, today, 1);
  785. moonrise = Astronomy.SearchRiseSet(Astronomy.Body.Moon, observer, 1, today, 1);
  786. moonset = Astronomy.SearchRiseSet(Astronomy.Body.Moon, observer, 1, today, 1);
  787. }
  788.  
  789. // Update display
  790. const sunriseEl = document.getElementById("sunriseTime");
  791. const sunsetEl = document.getElementById("sunsetTime");
  792. const moonriseEl = document.getElementById("moonriseTime");
  793. const moonsetEl = document.getElementById("moonsetTime");
  794.  
  795. if (sunriseEl) sunriseEl.textContent = sunrise ? formatTime(sunrise.date) : "None";
  796. if (sunsetEl) sunsetEl.textContent = sunset ? formatTime(sunset.date) : "None";
  797. if (moonriseEl) moonriseEl.textContent = moonrise ? formatTime(moonrise.date) : "None";
  798. if (moonsetEl) moonsetEl.textContent = moonset ? formatTime(moonset.date) : "None";
  799.  
  800. const timezoneNote = document.getElementById("timezoneNote");
  801. if (timezoneNote) {
  802. if (currentLocation.isUTC) {
  803. timezoneNote.textContent = "UTC Times";
  804. } else {
  805. timezoneNote.textContent = `Local Times (${currentLocation.lat.toFixed(
  806. 1
  807. )}°, ${currentLocation.lon.toFixed(1)}°)`;
  808. }
  809. }
  810.  
  811. console.log("Alternative calculation completed successfully");
  812. } catch (error) {
  813. console.error("Alternative calculation failed:", error);
  814.  
  815. // Show error in display
  816. ["sunriseTime", "sunsetTime", "moonriseTime", "moonsetTime"].forEach((id) => {
  817. const el = document.getElementById(id);
  818. if (el) el.textContent = "Error";
  819. });
  820. }
  821. }
  822.  
  823. function formatTime(date) {
  824. // Format as HH:MM UTC
  825. return date.toISOString().substr(11, 5) + " UTC";
  826. }
  827.  
  828. function initCalendar() {
  829. // Initialize timezone display
  830. updateTimezoneDisplay();
  831.  
  832. renderCalendar();
  833.  
  834. // Add event listeners for navigation
  835. document.getElementById("yearPrev").addEventListener("click", () => {
  836. currentYear--;
  837. renderCalendar();
  838. });
  839.  
  840. document.getElementById("yearNext").addEventListener("click", () => {
  841. currentYear++;
  842. renderCalendar();
  843. });
  844.  
  845. document.getElementById("monthPrev").addEventListener("click", () => {
  846. currentMonth--;
  847. if (currentMonth < 0) {
  848. currentMonth = 11;
  849. currentYear--;
  850. }
  851. renderCalendar();
  852. });
  853.  
  854. document.getElementById("monthNext").addEventListener("click", () => {
  855. currentMonth++;
  856. if (currentMonth > 11) {
  857. currentMonth = 0;
  858. currentYear++;
  859. }
  860. renderCalendar();
  861. });
  862.  
  863. document.getElementById("resetButton").addEventListener("click", () => {
  864. const now = new Date();
  865. currentMonth = now.getMonth();
  866. currentYear = now.getFullYear();
  867. renderCalendar();
  868. });
  869.  
  870. // Timezone control event listeners
  871. document.getElementById("timezonePrev").addEventListener("click", () => adjustTimezone(-1));
  872. document.getElementById("timezoneNext").addEventListener("click", () => adjustTimezone(1));
  873.  
  874. // Timezone reset button
  875. document.getElementById("timezoneResetButton").addEventListener("click", () => {
  876. currentTimezoneOffset = -(new Date().getTimezoneOffset() / 60);
  877. updateTimezoneDisplay();
  878. renderCalendar();
  879. });
  880.  
  881. // Hemisphere change listeners
  882. document.getElementById("northRadio").addEventListener("change", handleHemisphereChange);
  883. document.getElementById("southRadio").addEventListener("change", handleHemisphereChange);
  884.  
  885. // Set up the precise location button
  886. const preciseLocationBtn = document.getElementById("preciseLocationBtn");
  887. if (preciseLocationBtn) {
  888. preciseLocationBtn.addEventListener("click", requestPreciseLocation);
  889. }
  890.  
  891. // Calculate initial UTC times
  892. calculateSunMoonTimes();
  893. }
  894.  
  895. initCalendar();
  896.  
  897. // === NEW UNIFIED TOGGLE LISTENERS ===
  898. document.getElementById("moonPhasesToggle").addEventListener("change", function () {
  899. toggleMoonPhases(this.checked);
  900. });
  901.  
  902. document.getElementById("lunarEclipsesToggle").addEventListener("change", function () {
  903. toggleLunarEclipses(this.checked);
  904. });
  905.  
  906. document.getElementById("solarEclipsesToggle").addEventListener("change", function () {
  907. toggleSolarEclipses(this.checked);
  908. });
Advertisement
Add Comment
Please, Sign In to add comment