1const loadUpcomingLaunches = async (container) => { 2 container.innerHTML = ` 3 <div style="display: grid; grid-template-columns: 1fr 300px; gap: 20px;"> 4 <div style="overflow: hidden;"> 5 <h3>ð Upcoming Space Launches</h3> 6 <div id="launchesTableView" class="data-table-container"></div> 7 </div> 8 <div> 9 <h3>ð Launch Statistics</h3> 10 <div id="launchStats" style="background: rgba(255,255,255,0.05); padding: 15px; border-radius: 10px; margin-bottom: 15px;"></div> 11 <h4>ð Next Launch Alert</h4> 12 <div id="nextLaunchAlert" style="background: rgba(255,255,0,0.1); padding: 15px; border-radius: 10px; border-left: 4px solid #ffa502;"></div> 13 <h4>ð¨âð Active Astronauts</h4> 14 <div id="activeAstronauts" style="background: rgba(0,255,255,0.1); padding: 15px; border-radius: 10px; border-left: 4px solid #00ffff; margin-top: 15px;"></div> 15 </div> 16 </div> 17 `; 18 19 try { 20 // Use a fallback mechanism for API calls to handle rate limits or errors 21 let launchData, astronautData; 22 23 try { 24 const launchResponse = await fetch('https://ll.thespacedevs.com/2.2.0/launch/upcoming/?limit=20'); 25 if (!launchResponse.ok) throw new Error('API Rate Limit or Error'); 26 launchData = await launchResponse.json(); 27 } catch (e) { 28 console.warn('Using mock launch data due to API error'); 29 launchData = { results: getMockLaunches() }; 30 } 31 32 try { 33 const astronautResponse = await fetch('https://ll.thespacedevs.com/2.2.0/astronaut/?limit=5&status=1'); 34 if (!astronautResponse.ok) throw new Error('API Rate Limit or Error'); 35 astronautData = await astronautResponse.json(); 36 } catch (e) { 37 console.warn('Using mock astronaut data due to API error'); 38 astronautData = { results: getMockAstronauts() }; 39 } 40 41 displayLaunchesTable(launchData.results, container); 42 displayLaunchStats(launchData.results); 43 displayNextLaunchAlert(launchData.results[0]); 44 displayActiveAstronauts(astronautData.results); 45 } catch (error) { 46 console.error('Critical error loading launches:', error); 47 container.innerHTML = '<p>System malfunction. Unable to load launch data.</p>'; 48 } 49}; 50 51function getMockLaunches() { 52 return Array(10).fill(0).map((_, i) => ({ 53 name: `Falcon 9 Block 5 | Starlink Group ${i+12}`, 54 net: new Date(Date.now() + (i * 86400000 * 2)).toISOString(), 55 launch_service_provider: { name: "SpaceX" }, 56 rocket: { configuration: { name: "Falcon 9 Block 5" } }, 57 pad: { name: "SLC-40, Cape Canaveral SFS, FL, USA" }, 58 status: { name: "Go for Launch" }, 59 mission: { type: "Communications" }, 60 vid_urls: [] 61 })); 62} 63 64function getMockAstronauts() { 65 return [ 66 { name: "Oleg Kononenko", nationality: { name: "Russia" }, flights_count: 5 }, 67 { name: "Nikolai Chub", nationality: { name: "Russia" }, flights_count: 1 }, 68 { name: "Loral O'Hara", nationality: { name: "USA" }, flights_count: 1 }, 69 { name: "Andreas Mogensen", nationality: { name: "Denmark" }, flights_count: 2 }, 70 { name: "Jasmin Moghbeli", nationality: { name: "USA" }, flights_count: 1 } 71 ]; 72} 73 74const displayLaunchesCalendar = (launches, container) => { 75 const currentDate = new Date(); 76 let currentMonth = currentDate.getMonth(); 77 let currentYear = currentDate.getFullYear(); 78 79 const monthNames = ["January", "February", "March", "April", "May", "June",
80 "July", "August", "September", "October", "November", "December"]; 81 const dayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; 82 83 const renderCalendar = () => { 84 const firstDay = new Date(currentYear, currentMonth, 1).getDay(); 85 const daysInMonth = new Date(currentYear, currentMonth + 1, 0).getDate(); 86 87 document.getElementById('currentMonth').textContent = `${monthNames[currentMonth]} ${currentYear}`; 88 89 const calendarGrid = document.getElementById('calendarGrid'); 90 calendarGrid.innerHTML = ''; 91 92 // Add day headers 93 dayNames.forEach(day => { 94 const dayHeader = document.createElement('div'); 95 dayHeader.textContent = day; 96 dayHeader.style.cssText = 'text-align: center; font-weight: bold; color: #00ffff; padding: 10px; font-size: 0.9em;'; 97 calendarGrid.appendChild(dayHeader); 98 }); 99 100 // Add empty cells for days before month starts 101 for (let i = 0; i < firstDay; i++) { 102 const emptyDay = document.createElement('div'); 103 emptyDay.style.cssText = 'padding: 10px; height: 60px; background: rgba(255,255,255,0.02); border-radius: 5px;'; 104 calendarGrid.appendChild(emptyDay); 105 } 106 107 // Add days of the month 108 for (let day = 1; day <= daysInMonth; day++) { 109 const dayElement = document.createElement('div'); 110 const dayDate = new Date(currentYear, currentMonth, day); 111 const launchesOnDay = launches.filter(launch => { 112 const launchDate = new Date(launch.net); 113 return launchDate.toDateString() === dayDate.toDateString(); 114 }); 115 116 dayElement.style.cssText = ` 117 padding: 8px; 118 height: 60px; 119 background: ${launchesOnDay.length > 0 ? 'rgba(255,255,0,0.2)' : 'rgba(255,255,255,0.05)'}; 120 border-radius: 5px; 121 cursor: pointer; 122 transition: all 0.3s ease; 123 border: ${dayDate.toDateString() === currentDate.toDateString() ? '2px solid #00ffff' : '1px solid rgba(255,255,255,0.1)'}; 124 position: relative; 125 overflow: hidden; 126 `; 127 128 dayElement.innerHTML = ` 129 <div style="font-weight: bold; color: white; font-size: 0.9em;">${day}</div> 130 ${launchesOnDay.length > 0 ? ` 131 <div style="position: absolute; bottom: 2px; right: 2px; background: #ffa502; color: white; border-radius: 50%; width: 18px; height: 18px; font-size: 0.7em; display: flex; align-items: center; justify-content: center;"> 132 ${launchesOnDay.length} 133 </div> 134 ` : ''} 135 `; 136 137 if (launchesOnDay.length > 0) { 138 dayElement.addEventListener('click', () => displayLaunchDetails(launchesOnDay, dayDate)); 139 dayElement.addEventListener('mouseenter', () => { 140 dayElement.style.background = 'rgba(255,255,0,0.4)'; 141 dayElement.style.transform = 'scale(1.05)'; 142 }); 143 dayElement.addEventListener('mouseleave', () => { 144 dayElement.style.background = 'rgba(255,255,0,0.2)'; 145 dayElement.style.transform = 'scale(1)'; 146 }); 147 } 148 149 calendarGrid.appendChild(dayElement); 150 } 151 }; 152 153 renderCalendar(); 154 155 document.getElementById('prevMonth').addEventListener('click', () => { 156 currentMonth--; 157 if (currentMonth < 0) { 158 currentMonth = 11; 159 currentYear--; 160 } 161 renderCalendar(); 162 }); 163 164 document.getElementById('nextMonth').addEventListener('click', () => { 165 currentMonth++; 166 if (currentMonth > 11) { 167 currentMonth = 0; 168 currentYear++; 169 } 170 renderCalendar(); 171 }); 172}; 173 174// New: Pretty table view for upcoming launches 175const displayLaunchesTable = (launches, container) => { 176 const view = container.querySelector('#launchesTableView'); 177 if (!view) return; 178 179 const tableHtml = ` 180 <div class="table-wrapper"> 181 <table class="cosmic-table"> 182 <thead> 183 <tr> 184 <th>Date (UTC)</th> 185 <th>Mission</th> 186 <th>Provider</th> 187 <th>Rocket</th> 188 <th>Location</th> 189 <th>Status</th> 190 <th>Action</th> 191 </tr> 192 </thead> 193 <tbody> 194 ${launches.map(launch => { 195 const date = new Date(launch.net); 196 const statusColor = launch.status?.name.includes('Go') || launch.status?.name.includes('Success') ? '#2ed573' : '#ffa502';
197 return `<tr> 198 <td data-label="Date">${date.toLocaleDateString()} ${date.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'})}</td> 199 <td data-label="Mission"> 200 <strong style="color:var(--primary-color);">${launch.name.split('|')[0].trim()}</strong> 201 <div style="font-size:0.85em; opacity:0.7;">${launch.name.split('|')[1] || ''}</div> 202 </td> 203 <td data-label="Provider">${launch.launch_service_provider?.name || 'Unknown'}</td> 204 <td data-label="Rocket">${launch.rocket?.configuration?.name || 'Unknown'}</td> 205 <td data-label="Location">${(launch.pad?.location?.name || launch.pad?.name || 'Unknown').split(',')[0]}</td> 206 <td data-label="Status"><span class="status-badge" style="background:${statusColor}20; color:${statusColor}; border:1px solid ${statusColor}40;">${launch.status?.name || 'TBD'}</span></td> 207 <td data-label="Action"> 208 ${launch.vid_urls && launch.vid_urls.length ? `<a href="${launch.vid_urls[0].url}" target="_blank" class="watch-btn">â¶ Watch</a>` : ''} 209 </td> 210 </tr>`; 211 }).join('')} 212 </tbody> 213 </table> 214 </div> 215 `; 216 view.innerHTML = tableHtml; 217}; 218 219const displayLaunchStats = (launches) => { 220 const statsContainer = document.getElementById('launchStats'); 221 const agencies = launches.reduce((acc, launch) => { 222 const agency = launch.launch_service_provider?.name || 'Unknown'; 223 acc[agency] = (acc[agency] || 0) + 1; 224 return acc; 225 }, {}); 226 227 const thisMonth = launches.filter(launch => { 228 const launchDate = new Date(launch.net); 229 const now = new Date(); 230 return launchDate.getMonth() === now.getMonth() && launchDate.getFullYear() === now.getFullYear(); 231 }).length; 232 233 const successRate = Math.floor(Math.random() * 20) + 80; // Simulated success rate 234 235 statsContainer.innerHTML = ` 236 <div style="margin-bottom: 15px;"> 237 <h4 style="margin: 0 0 10px 0; color: #00ffff;">ð Launch Statistics</h4> 238 <div style="margin: 8px 0;"><strong>This Month:</strong> ${thisMonth} launches</div> 239 <div style="margin: 8px 0;"><strong>Next 30 Days:</strong> ${launches.filter(l => { 240 const launchDate = new Date(l.net); 241 const thirtyDaysFromNow = new Date(); 242 thirtyDaysFromNow.setDate(thirtyDaysFromNow.getDate() + 30); 243 return launchDate <= thirtyDaysFromNow; 244 }).length} launches</div> 245 <div style="margin: 8px 0;"><strong>Success Rate:</strong> ${successRate}%</div> 246 </div> 247 <div> 248 <h5 style="margin: 0 0 10px 0; color: #ffff00;">ð¢ Top Agencies</h5> 249 ${Object.entries(agencies).slice(0, 3).map(([agency, count]) => ` 250 <div style="margin: 5px 0; font-size: 0.9em;"> 251 <span style="color: #ffffff;">${agency.substring(0, 20)}:</span> 252 <span style="color: #00ffff;">${count}</span> 253 </div> 254 `).join('')} 255 </div> 256 `; 257}; 258 259const displayNextLaunchAlert = (nextLaunch) => { 260 if (!nextLaunch) return; 261 262 const alertContainer = document.getElementById('nextLaunchAlert'); 263 const launchDate = new Date(nextLaunch.net); 264 const now = new Date(); 265 const timeDiff = launchDate - now; 266 const daysUntil = Math.ceil(timeDiff / (1000 * 60 * 60 * 24)); 267 const hoursUntil = Math.ceil(timeDiff / (1000 * 60 * 60)); 268 269 alertContainer.innerHTML = ` 270 <h4 style="margin: 0 0 10px 0; color: #ffa502;">ð ${nextLaunch.name}</h4> 271 <div style="margin: 5px 0;"><strong>Agency:</strong> ${nextLaunch.launch_service_provider?.name || 'Unknown'}</div> 272 <div style="margin: 5px 0;"><strong>Date:</strong> ${launchDate.toLocaleDateString()} ${launchDate.toLocaleTimeString()}</div> 273 <div style="margin: 5px 0;"><strong>Countdown:</strong> 274 ${timeDiff > 0 ? 275 (daysUntil > 1 ? `${daysUntil} days` : `${hoursUntil} hours`) 276 : 'Launch time passed'} 277 </div> 278 <div style="margin: 10px 0;"> 279 <div style="width: 100%; background: #333; height: 8px; border-radius: 4px; overflow: hidden;"> 280 <div style="width: ${Math.max(0, Math.min(100, 100 - (d
280aysUntil / 30) * 100)}%; height: 100%; background: linear-gradient(90deg, #ffa502, #ff6b6b);"></div> 281 </div> 282 </div> 283 `; 284}; 285 286const displayActiveAstronauts = (astronauts) => { 287 const astronautContainer = document.getElementById('activeAstronauts'); 288 289 astronautContainer.innerHTML = ` 290 <h4 style="margin: 0 0 10px 0; color: #00ffff;">ð¨âð Active Astronauts</h4> 291 ${astronauts.slice(0, 3).map(astronaut => ` 292 <div style="margin: 8px 0; padding: 8px; background: rgba(255,255,255,0.05); border-radius: 5px;"> 293 <div style="font-weight: bold; color: #ffffff; font-size: 0.9em;">${astronaut.name}</div> 294 <div style="color: #00ffff; font-size: 0.8em;">${astronaut.nationality?.name || 'Unknown'}</div> 295 <div style="color: #888; font-size: 0.7em;">Flights: ${astronaut.flights_count || 0}</div> 296 </div> 297 `).join('')} 298 <div style="margin-top: 10px;"> 299 <button onclick="loadMoreAstronauts()" style="background: var(--gradient-accent); border: none; color: white; padding: 5px 10px; border-radius: 15px; cursor: pointer; font-size: 0.8em;"> 300 View More 301 </button> 302 </div> 303 `; 304}; 305 306const displayLaunchDetails = (launches, date) => { 307 const detailsContainer = document.getElementById('launchDetails'); 308 detailsContainer.innerHTML = ` 309 <h3 style="color: #00ffff; margin: 0 0 20px 0;">ð Launches on ${date.toLocaleDateString()}</h3> 310 <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); gap: 20px;"> 311 ${launches.map(launch => ` 312 <div style="background: rgba(255,255,255,0.1); padding: 20px; border-radius: 10px; border-left: 4px solid #ffa502;"> 313 <h4 style="margin: 0 0 15px 0; color: #ffff00;">${launch.name}</h4> 314 <div style="margin: 8px 0;"><strong>ð¢ Agency:</strong> ${launch.launch_service_provider?.name || 'Unknown'}</div> 315 <div style="margin: 8px 0;"><strong>ð Rocket:</strong> ${launch.rocket?.configuration?.name || 'Unknown'}</div> 316 <div style="margin: 8px 0;"><strong>ð Location:</strong> ${launch.pad?.name || 'Unknown'}</div> 317 <div style="margin: 8px 0;"><strong>ð Time:</strong> ${new Date(launch.net).toLocaleTimeString()}</div> 318 <div style="margin: 8px 0;"><strong>ð¯ Status:</strong> 319 <span style="color: ${launch.status?.name === 'Go for Launch' ? '#2ed573' : '#ffa502'};"> 320 ${launch.status?.name || 'Unknown'} 321 </span> 322 </div> 323 ${launch.mission?.description ? ` 324 <div style="margin: 15px 0;"> 325 <strong>ð Mission:</strong> 326 <p style="margin: 5px 0; font-size: 0.9em; line-height: 1.4;"> 327 ${launch.mission.description.substring(0, 200)}${launch.mission.description.length > 200 ? '...' : ''} 328 </p> 329 </div> 330 ` : ''} 331 ${launch.webcast_live && launch.vid_urls?.length > 0 ? ` 332 <div style="margin-top: 15px;"> 333 <a href="${launch.vid_urls[0].url}" target="_blank" 334 style="display: inline-block; background: #ff4757; color: white; padding: 8px 15px; border-radius: 20px; text-decoration: none; font-size: 0.9em;"> 335 ðº Watch Live 336 </a> 337 </div> 338 ` : ''} 339 </div> 340 `).join('')} 341 </div> 342 `; 343}; 344 345const loadCalendarEvents = async (container) => { 346 container.innerHTML = ` 347 <div style="display: grid; grid-template-columns: 1fr 280px; gap: 20px;"> 348 <div style="overflow: hidden;"> 349 <h3>ð Cosmic Events Calendar</h3> 350 <div id="eventsTableView" class="data-table-container"></div> 351 </div> 352 <div> 353 <h3>ð Event Types</h3> 354 <div id="eventTypes" style="background: rgba(255,255,255,0.05); padding: 15px; border-radius: 10px; margin-bottom: 15px;"></div> 355 <h4>ð Observation Tips</h4> 356 <div id="observationTips" style="background: rgba(255,0,255,0.1); padding: 15px; border-radius: 10px; border-left: 4px solid #ff00ff;"></div> 357 <h4>ð°ï¸ Active Spacecraft</h4> 358 <div id="activeSpacecraft" style="background: rgba(0,255,0,0.1); padding: 15px; border-radius: 10px; border-left: 4px solid #00ff00; margin-top: 15px;"></div> 359 </div> 360 </div> 361 `; 362 363 let eventData, spacecraftData; 364 365 try { 366 try { 367 const eventResponse = await fetch('https://ll.thespacedevs.com/2.2.0/event/upcoming/?limit=20'); 368 if (!eventResponse.ok) throw new Error('API Error'); 369 eventData = await eventResponse.json(); 370 } catch (e) { 371 console.warn('Using mock event data'); 372 eventData = { results: getMockEvents() }; 373 } 374 375 try { 376 const spacecraftResponse = await fetch('https://ll.thespacedevs.com/2.2.0/spacecraft/?limit=5&status=1'); 377 if (!spacecraftResponse.ok) throw new Error('API Error'); 378 spacecraftData = await spacecraftResponse.json(); 379 } catch (e) { 380 console.warn('Using mock spacecraft data'); 381 spacecraftData = { results: getMockSpacecraft() }; 382 } 383 384 displayEventsTable(eventData.results, container); 385 displayEventTypes(eventData.results); 386 displayObservationTips(); 387 displayActiveSpacecraft(spacecraftData.results); 388 } catch (error) { 389 console.error('Error fetching calendar events:', error); 390 container.innerHTML += '<p>Partial data load failed. Showing what is available.</p>'; 391 } 392}; 393 394function getMockEvents() { 395 return [ 396 { name: "Full Moon", date: new Date(Date.now() + 86400000 * 2).toISOString(), type: { name: "Lunar" }, description: "Full Moon phase." }, 397 { name: "Perseids Meteor Shower Peak", date: new Date(Date.now() + 86400000 * 15).toISOString(), type: { name: "Meteor Shower" }, description: "High activity meteor shower." }, 398 { name: "SpaceX Crew-8 Return", date: new Date(Date.now() + 86400000 * 20).toISOString(), type: { name: "Spaceflight" }, description: "Dragon capsule return." }, 399 { name: "Planetary Alignment (Mars, Jupiter)", date: new Date(Date.now() + 86400000 * 30).toISOString(), type: { name: "Conjunction" }, description: "Visible in early morning sky." } 400 ]; 401} 402 403function getMockSpacecraft() { 404 return [ 405 { name: "International Space Station", status: { name: "Active" }
405, spacecraft_config: { type: { name: "Space Station" } } }, 406 { name: "Tiangong Space Station", status: { name: "Active" }, spacecraft_config: { type: { name: "Space Station" } } }, 407 { name: "Voyager 1", status: { name: "Active" }, spacecraft_config: { type: { name: "Probe" } } }, 408 { name: "Hubble Space Telescope", status: { name: "Active" }, spacecraft_config: { type: { name: "Telescope" } } } 409 ]; 410} 411 412const displayEventsTable = (events, container) => { 413 const view = container.querySelector('#eventsTableView'); 414 if (!view) return; 415 416 const tableHtml = ` 417 <div class="table-wrapper"> 418 <table class="cosmic-table events-table"> 419 <thead> 420 <tr> 421 <th>Date</th> 422 <th>Event</th> 423 <th>Type</th> 424 <th>Description</th> 425 <th>Live</th> 426 </tr> 427 </thead> 428 <tbody> 429 ${events.map(event => { 430 const date = new Date(event.date); 431 return `<tr> 432 <td data-label="Date" style="white-space:nowrap; color:#ffccff;">${date.toLocaleDateString()}</td> 433 <td data-label="Event"><strong style="color:var(--secondary-color);">${event.name}</strong></td> 434 <td data-label="Type"><span class="type-badge">${event.type?.name || 'General'}</span></td> 435 <td data-label="Description" style="font-size:0.9em; opacity:0.8;">${(event.description || 'No details available.').substring(0, 120)}...</td> 436 <td data-label="Live"> 437 ${event.webcast_live ? `<span style="color:#2ed573;">â Live</span>` : '-'} 438 </td> 439 </tr>`; 440 }).join('')} 441 </tbody> 442 </table> 443 </div> 444 `; 445 view.innerHTML = tableHtml; 446}; 447 448const displayEventTypes = (events) => { 449 const typesContainer = document.getElementById('eventTypes'); 450 const eventTypes = events.reduce((acc, event) => { 451 const type = event.type?.name || 'Other'; 452 acc[type] = (acc[type] || 0) + 1; 453 return acc; 454 }, {}); 455 456 typesContainer.innerHTML = ` 457 <h4 style="margin: 0 0 10px 0; color: #ff00ff;">ð Event Categories</h4> 458 ${Object.entries(eventTypes).map(([type, count]) => ` 459 <div style="margin: 5px 0; font-size: 0.9em; display: flex; justify-content: space-between;"> 460 <span style="color: #ffffff;">${type}:</span> 461 <span style="color: #ff00ff;">${count}</span> 462 </div> 463 `).join('')} 464 `; 465}; 466 467const displayObservationTips = () => { 468 const tipsContainer = document.getElementById('observationTips'); 469 const tips = [ 470 "ð Best viewing during new moon phases", 471 "ð Use red light to preserve night vision", 472 "â° Start observing 30 minutes after sunset", 473 "ð Find dark sky locations away from cities", 474 "ð± Use astronomy apps for object identification" 475 ]; 476 477 tipsContainer.innerHTML = ` 478 <h4 style="margin: 0 0 10px 0; color: #ff00ff;">ð¡ Quick Tips</h4> 479 ${tips.map(tip => ` 480 <div style="margin: 5px 0; font-size: 0.8em; color: #ffffff;">${tip}</div> 481 `).join('')} 482 `; 483}; 484 485const displayActiveSpacecraft = (spacecraft) => { 486 const spacecraftContainer = document.getElementById('activeSpacecraft'); 487 488 spacecraftContainer.innerHTML = ` 489 <h4 style="margin: 0 0 10px 0; color: #00ff00;">ð°ï¸ Active Missions</h4> 490 ${spacecraft.slice(0, 3).map(craft => ` 491 <div style="margin: 8px 0; padding: 8px; background: rgba(255,255,255,0.05); border-radius: 5px;"> 492 <div style="font-weight: bold; color: #ffffff; font-size: 0.9em;">${craft.name}</div> 493 <div style="color: #00ff00; font-size: 0.8em;">${craft.status?.name || 'Active'}</div> 494 <div style="color: #888; font-size: 0.7em;">
494Type: ${craft.spacecraft_config?.type?.name || 'Unknown'}</div> 495 </div> 496 `).join('')} 497 `; 498}; 499 500const displayEventDetails = (events, date) => { 501 const detailsContainer = document.getElementById('eventDetails'); 502 detailsContainer.innerHTML = ` 503 <h3 style="color: #ff00ff; margin: 0 0 20px 0;">ð Events on ${date.toLocaleDateString()}</h3> 504 <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); gap: 20px;"> 505 ${events.map(event => ` 506 <div style="background: rgba(255,255,255,0.1); padding: 20px; border-radius: 10px; border-left: 4px solid #ff00ff;"> 507 <h4 style="margin: 0 0 15px 0; color: #ffff00;">${event.name}</h4> 508 <div style="margin: 8px 0;"><strong>ð Type:</strong> ${event.type?.name || 'Unknown'}</div> 509 <div style="margin: 8px 0;"><strong>ð Date:</strong> ${new Date(event.date).toLocaleDateString()}</div> 510 <div style="margin: 8px 0;"><strong>ð Location:</strong> ${event.location || 'Various'}</div> 511 ${event.description ? ` 512 <div style="margin: 15px 0;"> 513 <strong>ð Description:</strong> 514 <p style="margin: 5px 0; font-size: 0.9em; line-height: 1.4;"> 515 ${event.description.substring(0, 200)}${event.description.length > 200 ? '...' : ''} 516 </p> 517 </div> 518 ` : ''} 519 ${event.webcast_live ? ` 520 <div style="margin-top: 15px;"> 521 <span style="display: inline-block; background: #2ed573; color: white; padding: 5px 10px; border-radius: 15px; font-size: 0.8em;"> 522 ð´ Live Coverage Available 523 </span> 524 </div> 525 ` : ''} 526 </div> 527 `).join('')} 528 </div> 529 `; 530}; 531 532const generateAIAstronomicalEvents = async (container) => { 533 try { 534 const completion = await websim.chat.completions.create({ 535 messages: [{ 536 role: "system", 537 content: "Generate a list of astronomical events for the next 3 months including meteor showers, planetary alignments, eclipses, and other observable phenomena. Format as JSON with date, name, description, and type." 538 }, { 539 role: "user", 540 content: "Create astronomical events calendar for the next 90 days with specific dates and descriptions." 541 }], 542 json: true 543 }); 544 545 const events = JSON.parse(completion.content); 546 displayEventsCalendar(events, container); 547 displayEventTypes(events); 548 displayObservationTips(); 549 } catch (error) { 550 console.error('Error generating AI events:', error); 551 container.innerHTML = '<p>Error loading cosmic events calendar.</p>'; 552 } 553}; 554 555// Global functions for interactivity 556window.loadMoreAstronauts = async () => { 557 try { 558 const response = await fetch('https://ll.thespacedevs.com/2.2.0/astronaut/?limit=20&status=1'); 559 const data = await response.json(); 560 561 const modal = document.createElement('div'); 562 modal.style.cssText = 'position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.9); z-index: 1000; overflow-y: auto; padding: 20px;'; 563 modal.innerHTML = ` 564 <div style="max-width: 800px; margin: 0 auto; background: var(--bg-color); border-radius: 20px; padding: 30px; position: relative;"> 565 <button onclick="this.parentElement.parentElement.remove()" style="position: absolute; top: 15px; right: 20px; background: none; border: none; color: white; font-size: 30px; cursor: pointer;">Ã</button> 566 <h3 style="color: #00ffff; margin: 0 0 20px 0;">ð¨âð Active Astronauts</h3> 567 <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 15px;"> 568 ${data.results.map(astronaut => ` 569 <div style="background: rgba(255,255,255,0.1); padding: 15px; border-radius: 10px;"> 570 <h4 style="margin: 0 0 10px 0; color: #ffffff;">${astronaut.name}</h4> 571 <div style="margin: 5px 0; color: #00ffff;">ð ${astronaut.nationality?.name || 'Unknown'}</div> 572 <div style="margin: 5px 0; color: #ffff00;">ð Flights: ${astronaut.flights_count || 0}</div> 573 <div style="margin: 5px 0; color: #ff00ff;">
573â±ï¸ EVA Time: ${astronaut.eva_time || 'N/A'}</div> 574 ${astronaut.bio ? ` 575 <div style="margin: 10px 0; font-size: 0.8em; color: #aaa;"> 576 ${astronaut.bio.substring(0, 100)}... 577 </div> 578 ` : ''} 579 </div> 580 `).join('')} 581 </div> 582 </div> 583 `; 584 document.body.appendChild(modal); 585 } catch (error) { 586 console.error('Error loading more astronauts:', error); 587 } 588}; 589 590export { loadUpcomingLaunches, loadCalendarEvents };
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.