PageSourceSearch

https://nasa.on.websim.com/api-features.js

js nasa.on.websim.com collected 2026-10-03 05:06:21 UTC 29,268 bytes, 590 lines download raw bytes

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.