1// loadingAnimation.js - Beautiful aurora-themed loading animations 2 3/** 4 * Creates a beautiful aurora-themed loading animation 5 * @param {string} containerId - The ID of the container to add the loader to 6 * @param {string} message - The loading message to display 7 * @param {string} type - Type of loader: 'globe' or 'population' 8 * @returns {HTMLElement} The loader element 9 */ 10export function createLoadingAnimation(containerId, message = 'Loading...', type = 'globe') { 11 const container = document.getElementById(containerId); 12 if (!container) { 13 console.error(`Container ${containerId} not found`); 14 return null; 15 } 16 17 // Create loader wrapper 18 const loaderWrapper = document.createElement('div'); 19 loaderWrapper.className = `aurora-loader ${type}-loader`; 20 loaderWrapper.id = `${type}-loader`; 21 22 // Different animations based on type 23 let loaderContent = ''; 24 25 if (type === 'globe') { 26 loaderContent = ` 27 <div class="aurora-loader-content"> 28 <div class="aurora-globe-spinner"> 29 <div class="globe-ring ring-1"></div> 30 <div class="globe-ring ring-2"></div> 31 <div class="globe-ring ring-3"></div> 32 <div class="globe-core"></div> 33 </div> 34 <div class="aurora-loader-text"> 35 <div class="loader-message">${message}</div> 36 <div class="loader-submessage">Preparing your view of Earth...</div> 37 </div> 38 <div class="aurora-particles"> 39 ${Array.from({ length: 20 }, (_, i) => `<div class="particle particle-${i}"></div>`).join('')} 40 </div> 41 </div> 42 `; 43 } else if (type === 'population') { 44 loaderContent = ` 45 <div class="aurora-loader-content population-content"> 46 <div class="aurora-wave-spinner"> 47 <div class="wave-bar bar-1"></div> 48 <div class="wave-bar bar-2"></div> 49 <div class="wave-bar bar-3"></div> 50 <div class="wave-bar bar-4"></div> 51 <div class="wave-bar bar-5"></div> 52 </div> 53 <div class="aurora-loader-text"> 54 <div class="loader-message">${message}</div> 55 <div class="loader-submessage">Analyzing global aurora visibility...</div> 56 </div> 57 </div> 58 `; 59 } 60 61 loaderWrapper.innerHTML = loaderContent; 62 container.appendChild(loaderWrapper); 63 64 // Trigger fade-in animation 65 requestAnimationFrame(() => { 66 loaderWrapper.classList.add('fade-in'); 67 }); 68 69 return loaderWrapper; 70} 71 72/** 73 * Shows the loading animation 74 * @param {string} type - Type of loader to show 75 */ 76export function showLoader(type = 'globe') { 77 const loader = document.getElementById(`${type}-loader`); 78 if (loader) { 79 loader.style.display = 'flex'; 80 loader.classList.add('fade-in'); 81 } 82} 83 84/** 85 * Hides and removes the loading animation with a fade-out effect 86 * @param {string} type - Type of loader to hide 87 */ 88export function hideLoader(type = 'globe') { 89 const loader = document.getElementById(`${type}-loader`); 90 if (loader) { 91 clearLoaderWatchdog(type); 92 loader.classList.add('fade-out'); 93 // Stop swallowing clicks the moment we start fading; the controls beneath 94 // are already live and a half-second of dead input feels broken. 95 loader.style.pointerEvents = 'none'; 96 setTimeout(() => { 97 if (loader && loader.parentNode) { 98 loader.parentNode.removeChild(loader); 99 } 100 }, 250); // Match the fade-out animation in loading-animation.css 101 } 102} 103 104// The globe loader is a full-viewport overlay, so anything that prevents 105// hideLoader() from running leaves the whole UI unclickable. Tile loading 106// depends on a third-party imagery host and `tilesLoaded` may simply never 107// fire, so every loader gets a bounded lifetime. 108const LOADER_MAX_MS = 20000; 109const watchdogs = new Map(); 110 111export function armLoaderWatchdog(type = 'globe', maxMs = LOADER_MAX_MS) { 112 clearLoaderWatchdog(type); 113 watchdogs.set(type, setTimeout(() => { 114 const loader = document.getElementById(`${type}-loader`); 115 if (loader) { 116 console.warn(`[loader] ${type} loader exceeded ${maxMs}ms; dismissing so the UI stays usable.`); 117 hideLoader(type); 118 } 119 }, maxMs)); 120} 121 122export function clearLoaderWatchdog(type = 'globe') { 123 const timer = watchdogs.get(type); 124 if (timer) { 125 clearTimeout(timer); 126 watchdogs.delete(type); 127 } 128} 129 130/** 131 * Updates the loading message 132 * @param {string} type - Type of loader 133 * @param {string} message - New message 134 * @param {string} submessage - New submessage (optional) 135 */ 136export function updateLoaderMessage(type, message, submessage = null) { 137 const loader = document.getElementById(`${type}-loader`); 138 if (loader) { 139 const messageEl = loader.querySelector('.loader-message'); 140 if (messageEl) messageEl.textContent = message; 141 142 if (submessage !== null) { 143 const submessageEl = loader.querySelector('.loader-submessage'); 144 if (submessageEl) submessageEl.textContent = submessage; 145 } 146 } 147}
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.