PageSourceSearch

https://dynamic-salmiakki-095693.netlify.app/fancy-ui-preview.js

js dynamic-salmiakki-095693.netlify.app collected 2026-10-03 10:40:25 UTC 140,601 bytes, 3,291 lines download raw bytes

1// 🎯 Fancy UI Preview Component for WikiLive Infobox Visualization
2// Creates beautiful visual layouts with search term in center and infoboxes arranged around it
3
4// Function to create a fancy UI preview using the extracted JSON data
5function createFancyUIPreview(data, chatContainer) {
6    console.log('[FancyUI] 🎯 createFancyUIPreview called with data:', data);
7    
8    // 🔍 DEBUG: Show complete input data structure
9    console.log('🎯 [Gallery JSON for carousel] Input data to createFancyUIPreview:');
10    console.log('📊 Complete Input JSON:', JSON.stringify({
11        hasWikipediaImages: !!(data?.wikipediaImages),
12        wikipediaImagesCount: data?.wikipediaImages?.length || 0,
13        hasWikipediaData: !!(data?.wikipediaData),
14        wikipediaDataCount: data?.wikipediaData?.length || 0,
15        hasInfoboxes: !!(data?.infoboxes),
16        infoboxesCount: data?.infoboxes?.length || 0,
17        hasRealtimeFacts: !!(data?.realtime_facts),
18        realtimeFactsCount: data?.realtime_facts?.length || 0,
19        hasFactsByEntity: !!(data?.factsByEntity),
20        factsByEntityKeys: data?.factsByEntity ? Object.keys(data.factsByEntity) : [],
21        sampleWikipediaImage: data?.wikipediaImages?.[0] || null,
22        sampleWikipediaData: data?.wikipediaData?.[0] || null
23    }, null, 2));
24    
25    if (data?.wikipediaImages?.length > 0) {
26        console.log('🎯 [Gallery JSON for carousel] Wikipedia Images Sample:');
27        data.wikipediaImages.forEach((img, index) => {
28            console.log(`Image ${index + 1}:`, JSON.stringify(img, null, 2));
29        });
30    }
31    
32    if (!data) {
33        console.error('[FancyUI] ❌ No data provided to createFancyUIPreview');
34        return;
35    }
36    
37    try {
38        console.log("🎯 [createFancyUIPreview] FUNCTION CALLED - Starting fancy UI visualization...");
39        
40        if (!data) {
41            console.error("🎯 [createFancyUIPreview] ERROR: No data provided");
42            return;
43        }
44        
45        // 🗺️ CRITICAL FIX: Process and classify entities before storing for relationship map
46        const processedData = {
47            ...data,
48            timestamp: new Date().toISOString(),
49            totalResults: (data.infoboxes?.length || 0) + (data.wikipediaData?.length || 0)
50        };
51        
52        // 🎯 ENHANCED: Apply entity type classification to all data sources
53        if (processedData.infoboxes && Array.isArray(processedData.infoboxes)) {
54            processedData.infoboxes = processedData.infoboxes.map(entity => {
55                const entityType = classifyEntityType(entity, entity.title, entity.summary);
56                const relevanceScore = calculateEntityRelevance(entity, entity.title, entity.summary, entity.imageUrl, 'infoboxes');
57                return {
58                    ...entity,
59                    type: entityType,
60                    relevance: relevanceScore
61                };
62            });
63        }
64        
65        if (processedData.wikipediaData && Array.isArray(processedData.wikipediaData)) {
66            processedData.wikipediaData = processedData.wikipediaData.map(entity => {
67                const entityType = classifyEntityType(entity, entity.title, entity.summary);
68                const relevanceScore = calculateEntityRelevance(entity, entity.title, entity.summary, entity.imageUrl, 'wikipediaData');
69                return {
70                    ...entity,
71                    type: entityType,
72                    relevance: relevanceScore
73                };
74            });
75        }
76        
77        if (processedData.factsByEntity && typeof processedData.factsByEntity === 'object') {
78            Object.keys(processedData.factsByEntity).forEach(entityKey => {
79                if (Array.isArray(processedData.factsByEntity[entityKey])) {
80                    processedData.factsByEntity[entityKey] = processedData.factsByEntity[entityKey].map(entity => {
81                        const entityType = classifyEntityType(entity, entity.title, entity.summary);
82                        const relevanceScore = calculateEntityRelevance(entity, entity.title, entity.summary, entity.imageUrl, 'factsByEntity');
83                        return {
84                            ...entity,
85                            type: entityType,
86                            relevance: relevanceScore
87                        };
88                    });
89                }
90            });
91        }
92        
93        // Store the processed data with classified entity types
94        window.currentFancyData = processedData;
95        
96        console.log('🗺️ [FancyUI] Data stored for relationship map:', {
97            hasInfoboxes: !!(data.infoboxes && data.infoboxes.length > 0),
98            infoboxCount: data.infoboxes?.length || 0,
99            hasFactsByEntity: !!(data.factsByEntity),
100            factsByEntityKeys: data.factsByEntity ? Object.keys(data.factsByEntity).length : 0,
101            searchTerm: data.searchTerm,
102            timestamp: window.currentFancyData.timestamp
103        });
104        
105        if (!chatContainer) {
106            console.error("🎯 [createFancyUIPreview] ERROR: No chatContainer provided");
107            return;
108        }
109
110        // Remove existing preview if present
111        const existingPreview = chatContainer.querySelector('.fancy-ui-preview');
112        if (existingPreview) {
113            existingPreview.remove();
114        }
115        
116        // Create main preview container
117        const previewContainer = document.createElement('div');
118        previewContainer.className = 'fancy-ui-preview';
119        previewContainer.style.cssText = `
120            margin: 20px 0;
121            padding: 30px;
122            background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%);
123            border-radius: 20px;
124            box-shadow: 0 20px 40px rgba(0,0,0,0.1);
125            color: white;
126            position: relative;
127            overflow: hidden;
128        `;
129        
130        // Create header with controls
131        const header = document.createElement('div');
132        header.style.cssText = `
133            display: flex;
134            justify-content: space-between;
135            align-items: center;
136            margin-bottom: 20px;
137            padding: 0 10px;
138        `;
139        
140        const title = document.createElement('h2');
141        title.textContent = '✨ WikiLive';
142        title.style.cssText = `
143            margin: 0;
144            color: white;
145            font-size: 24px;
146            text-shadow: 2px 2px 4px rgba(0,0,0,0.5);
147        `;
148        
149        const closeBtn = document.createElement('button');
150        closeBtn.innerHTML = '✕';
151        closeBtn.style.cssText = `
152            padding: 10px 15px;
153            background: rgba(255,255,255,0.2);
154            color: white;
155            border: none;
156            border-radius: 50%;
157            cursor: pointer;
158            font-size: 18px;
159            font-weight: bold;
160            transition: all 0.3s ease;
161        `;
162        
163        closeBtn.addEventListener('click', () => {
164            previewContainer.remove();
165        });
166        
167        header.appendChild(title);
168        header.appendChild(closeBtn);
169        previewContainer.appendChild(header);
170        
171        // Create central search term
172        const centralTerm = document.createElement('div');
173        centralTerm.style.cssText = `
174            position: relative;
175            z-index: 1;
176            text-align: center;
177            margin: 40px auto;
178            padding: 20px 30px;
179            background: rgba(255,255,255,0.15);
180            border: 2px solid rgba(255,255,255,0.3);
181            border-radius: 50px;
182            font-size: 1.5em;
183            font-weight: bold;
184            backdrop-filter: blur(10px);
185            max-width: 400px;
186            box-shadow: 0 8px 32px rgba(0,0,0,0.1);
187        `;
188        centralTerm.textContent = data.searchTerm || 'Search Results';
189        previewContainer.appendChild(centralTerm);
190        
191        // Unify data structure - Fix parameter mapping
192        const unifiedData = unifyDataForFancyUI(
193            data.wikipediaData || data.infoboxes || [], 
194            data.realtimeFacts || data.realtime_facts || [], 
195            data.factsByEntity || {}, 
196            data
197        );
198        
199        // 🔥 ENHANCED: Image enrichment with multiple data sources and comprehensive logging
200        console.log('[FancyUI] 🖼️ Starting comprehensive image enrichment process');
201        console.log('[FancyUI] 📊 Data sources available:', {
202            hasInputData: !!data,
203            hasWindowMetadata: !!(window.wikipediaMetadata),
204            inputDataKeys: data ? Object.keys(data) : [],
205            windowMetadataKeys: window.wikipediaMetadata ? Object.keys(window.wikipediaMetadata) : []
206        });
207
208        // 🔥 CRITICAL DEBUG: Log the actual structure of Wikipedia images
209        console.log('[FancyUI] 🔍 DETAILED IMAGE DATA ANALYSIS:');
210        const inputImages = data && data.wikipediaImages;
211        if (inputImages) {
212            console.log('[FancyUI] Input data.wikipediaImages:', inputImages);
213            console.log('[FancyUI] Input data.wikipediaImages type:', typeof inputImages);
214            console.log('[FancyUI] Input data.wikipediaImages length:', inputImages.length);
215            if (inputImages.length > 0) {
216                console.log('[FancyUI] First image sample:', inputImages[0]);
217                console.log('[FancyUI] First image keys:', Object.keys(inputImages[0]));
218            }
219        }
220        
221        if (window.wikipediaMetadata?.wikipediaImages) {
222            console.log('[FancyUI] Window metadata images:', window.wikipediaMetadata.wikipediaImages);
223            console.log('[FancyUI] Window metadata images type:', typeof window.wikipediaMetadata.wikipediaImages);
224            console.log('[FancyUI] Window metadata images length:', window.wikipediaMetadata.wikipediaImages.length);
225            if (window.wikipediaMetadata.wikipediaImages.length > 0) {
226                console.log('[FancyUI] First window image sample:', window.wikipediaMetadata.wikipediaImages[0]);
227                console.log('[FancyUI] First window image keys:', Object.keys(window.wikipediaMetadata.wikipediaImages[0]));
228            }
229        }
230
231        // 🔥 DEBUG: Log unified data structure before enrichment
232        console.log('[FancyUI] 📋 Unified data before enrichment:', {
233            count: unifiedData.length,
234            entities: unifiedData.map(e => ({ title: e.title, hasImage: !!e.imageUrl }))
235        });
236
237        // 1. Process images from input data (if available)
238        const dataImages = data && data.wikipediaImages;
239        if (dataImages && Array.isArray(dataImages)) {
240            console.log(`[FancyUI] Processing ${dataImages.length} images from input data`);
241            dataImages.forEach((imageData, index) => {
242                console.log(`[FancyUI] 🔍 Processing input image[${index}]:`, {
243                    imageData: imageData,
244                    pageTitle: imageData.page_title || imageData.pageTitle || imageData.title,
245                    imageUrl: imageData.imageUrl || imageData.image_url || imageData.url,
246                    hasImageUrl: !!(imageData.imageUrl || imageData.image_url || imageData.url),
247                    imageDataKeys: Object.keys(imageData)
248                });
249                
250                // 🔥 ENHANCED: Try ALL possible title field combinations
251                const pageTitle = imageData.page_title || 
252                                imageData.pageTitle || 
253                                imageData.title || 
254                                imageData.name ||
255                                imageData.entity ||
256                                imageData.wikipedia_title ||
257                                imageData.article_title;
258                
259                // 🔥 ENHANCED: Try ALL possible image URL field combinations  
260                const imageUrl = imageData.imageUrl || 
261                               imageData.image_url || 
262                               imageData.url || 
263                               imageData.src || 
264                               imageData.image || 
265                               imageData.thumbnail || 
266                               imageData.thumb ||
267                               imageData.main_image ||
268                               imageData.main_image_url ||
269                               imageData.wikipedia_image ||
270                               imageData.photo ||
271                               imageData.picture;
272                
273                console.log(`[FancyUI] 🔍 Enhanced field extraction for image[${index}]:`, {
274                    extractedPageTitle: pageTitle,
275                    extractedImageUrl: imageUrl,
276                    allFields: Object.keys(imageData),
277                    titleFields: Object.keys(imageData).filter(k => k.toLowerCase().includes('title') || k.toLowerCase().includes('name')),
278                    imageFields: Object.keys(imageData).filter(k => k.toLowerCase().includes('image') || k.toLowerCase().includes('url') || k.toLowerCase().includes('src') || k.toLowerCase().includes('thumb'))
279                });
280                
281                if (pageTitle && imageUrl) {
282                    // Find matching entity with multiple title variations
283                    const matchingEntity = unifiedData.find(entity => {
284                        const matches = [
285                            entity.title === pageTitle,
286                            entity.page_title === pageTitle,
287                            entity.title === pageTitle.replace(/_/g, ' '),
288                            entity.title.toLowerCase() === pageTitle.toLowerCase(),
289                            entity.title.toLowerCase() === pageTitle.replace(/_/g, ' ').toLowerCase(),
290                            // Additional fuzzy matching
291                            entity.title.includes(pageTitle) || pageTitle.includes(entity.title),
292                            entity.title.toLowerCase().includes(pageTitle.toLowerCase()) || pageTitle.toLowerCase().includes(entity.title.toLowerCase()),
293                            // Even more flexible matching
294                            entity.title.replace(/[^a-zA-Z0-9]/g, '').toLowerCase() === pageTitle.replace(/[^a-zA-Z0-9]/g, '').toLowerCase()
295                        ];
296                        return matches.some(match => match);
297                    });
298                    
299                    if (matchingEntity) {
300                        matchingEntity.main_image_url = imageUrl;
301                        matchingEntity.imageUrl = imageUrl;
302                        matchingEntity.image_url = imageUrl;
303                        matchingEntity.thumbnail = imageUrl;
304                        matchingEntity.wikipediaImage = imageUrl;
305                        console.log(`[FancyUI] ✅ INPUT DATA: Applied image to entity "${matchingEntity.title}": ${imageUrl}`);
306                    } else {
307                        console.log(`[FancyUI] ⚠️ INPUT DATA: No matching entity found for "${pageTitle}"`);
308                        console.log(`[FancyUI] Available entity titles:`, unifiedData.map(e => e.title));
309                    }
310                } else {
311                    console.log(`[FancyUI] ⚠️ INPUT DATA: Missing pageTitle or imageUrl for image[${index}]`, {
312                        pageTitle: pageTitle,
313                        imageUrl: imageUrl,
314                        availableFields: Object.keys(imageData)
315                    });
316                }
317            });
318        } else {
319            console.log('[FancyUI] ⚠️ No wikipediaImages found in input data');
320        }
321
322        // Check window metadata as fallback (SECONDARY SOURCE)
323        if (window.wikipediaMetadata?.wikipediaImages && Array.isArray(window.wikipediaMetadata.wikipediaImages) && window.wikipediaMetadata.wikipediaImages.length > 0) {
324            console.log(`[FancyUI] ✅ Found ${window.wikipediaMetadata.wikipediaImages.length} Wikipedia images in window metadata (fallback)`);
325            
326            window.wikipediaMetadata.wikipediaImages.forEach((imageData, index) => {
327                console.log(`[FancyUI] 🔍 Processing window image[${index}]:`, {
328                    imageData: imageData,
329                    pageTitle: imageData.page_title || imageData.pageTitle || imageData.title,
330                    imageUrl: imageData.imageUrl || imageData.image_url || imageData.url,
331                    hasImageUrl: !!(imageData.imageUrl || imageData.image_url || imageData.url)
332                });
333                
334                const pageTitle = imageData.page_title || imageData.pageTitle || imageData.title;
335                const imageUrl = imageData.imageUrl || imageData.image_url || imageData.url;
336                
337                if (pageTitle && imageUrl) {
338                    // Find matching entity
339                    const matchingEntity = unifiedData.find(entity => {
340                        const matches = [
341                            entity.title === pageTitle,
342                            entity.page_title === pageTitle,
343                            entity.title === pageTitle.replace(/_/g, ' '),
344                            entity.title.toLowerCase() === pageTitle.toLowerCase(),
345                            entity.title.toLowerCase() === pageTitle.replace(/_/g, ' ').toLowerCase(),
346                            // Additional fuzzy matching
347                            entity.title.includes(pageTitle) || pageTitle.includes(entity.title),
348                            entity.title.toLowerCase().includes(pageTitle.toLowerCase()) || pageTitle.toLowerCase().includes(entity.title.toLowerCase())
349                        ];
350                        return matches.some(match => match);
351                    });
352                    
353                    if (matchingEntity && !matchingEntity.main_image_url) {
354                        matchingEntity.main_image_url = imageUrl;
355                        matchingEntity.imageUrl = imageUrl;
356                        matchingEntity.image_url = imageUrl;
357                        matchingEntity.thumbnail = imageUrl;
358                        matchingEntity.wikipediaImage = imageUrl;
359                        console.log(`[FancyUI] ✅ WINDOW FALLBACK: Applied image to entity "${matchingEntity.title}": ${imageUrl}`);
360                    }
361                }
362            });
363        } else {
364            console.log('[FancyUI] ⚠️ No wikipediaImages found in window metadata');
365        }
366
367        // 🔥 FINAL DEBUG: Log unified data structure after enrichment
368        console.log('[FancyUI] 📋 Unified data after enrichment:', {
369            count: unifiedData.length,
370            entitiesWithImages: unifiedData.filter(e => e.imageUrl || e.main_image_url || e.image_url || e.thumbnail || e.wikipediaImage || e.pageimage || e.image).length,
371            entities: unifiedData.map(e => ({ 
372                title: e.title, 
373                hasImage: !!(e.imageUrl || e.main_image_url || e.image_url || e.thumbnail || e.wikipediaImage || e.pageimage || e.image), 
374                imageUrl: (e.imageUrl || e.main_image_url || e.image_url || e.thumbnail || e.wikipediaImage || e.pageimage || e.image) ? 
375                         (e.imageUrl || e.main_image_url || e.image_url || e.thumbnail || e.wikipediaImage || e.pageimage || e.image).substring(0, 50) + '...' : null,
376                allImageProps: {
377                    imageUrl: !!e.imageUrl,
378                    main_image_url: !!e.main_image_url,
379                    image_url: !!e.image_url,
380                    thumbnail: !!e.thumbnail,
381                    wikipediaImage: !!e.wikipediaImage,
382                    pageimage: !!e.pageimage,
383                    image: !!e.image
384                }
385            }))
386        });
387        
388        // Create infoboxes grid
389        const infoboxesGrid = document.createElement('div');
390        infoboxesGrid.style.cssText = `
391            position: relative;
392            z-index: 1;
393            display: grid;
394            grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
395            gap: 20px;
396            margin-top: 40px;
397        `;
398        
399        unifiedData.forEach((infobox, index) => {
400            const infoboxCard = createEnhancedFancyInfoboxCard(infobox, index);
401            infoboxesGrid.appendChild(infoboxCard);
402        });
403        
404        previewContainer.appendChild(infoboxesGrid);
405        
406        // Create image carousel section
407        const imageCarouselSection = createStandaloneImageCarousel(unifiedData);
408        previewContainer.appendChild(imageCarouselSection);
409        
410        // Create Wikipedia Story section
411        const wikipediaStorySection = createWikipediaStorySection(data);
412        previewContainer.appendChild(wikipediaStorySection);
413        
414        // 🌳 Create biographical relationship viewer BEFORE adding the main preview
415        if (typeof createBiographicalRelationshipViewer === 'function') {
416            console.log('[FancyUI] 🌳 Creating biographical relationship viewer...');
417            createBiographicalRelationshipViewer(data, chatContainer);
418        }
419        
420        // Add main preview to chat container (biographical viewer is already added above)
421        chatContainer.appendChild(previewContainer);
422        
423        // Scroll to the biographical viewer if it exists, otherwise to the preview
424        const bioViewer = chatContainer.querySelector('.biographical-relationship-viewer');
425        if (bioViewer) {
426            bioViewer.scrollIntoView({ behavior: 'smooth', block: 'start' });
427        } else {
428            previewContainer.scrollIntoView({ behavior: 'smooth', block: 'start' });
429        }
430        
431        // Store data for relationship analysis
432        storeInfoboxData(data);
433        
434    } catch (error) {
435        console.error("🎯 [createFancyUIPreview] CRITICAL ERROR:", error);
436    }
437}
438
439// Helper function to create individual fancy infobox cards
440function createEnhancedFancyInfoboxCard(unifiedEntity, index) {
441    console.log(`[FancyUI] 🎨 Creating enhanced infobox card for: ${unifiedEntity.title}`);
442    
443    const card = document.createElement('div');
444    card.className = 'fancy-infobox-card';
445    card.style.cssText = `
446        background: rgba(255,255,255,0.1);
447        border: 1px solid rgba(255,255,255,0.2);
448        border-radius: 15px;
449        padding: 20px;
450        backdrop-filter: blur(10px);
451        transition: all 0.3s ease;
452        cursor: pointer;
453        position: relative;
454        overflow: hidden;
455        margin-bottom: 20px;
456        display: none; /* TEMPORARILY HIDDEN - WikiLive entity cards visually hidden */
457    `;
458    
459    // Create header with title
460    const header = document.createElement('div');
461    header.style.cssText = `
462        display: flex;
463        align-items: center;
464        margin-bottom: 15px;
465        border-bottom: 1px solid rgba(255,255,255,0.1);
466        padding-bottom: 10px;
467    `;
468
469    const title = document.createElement('h3');
470    title.style.cssText = `
471        margin: 0;
472        color: rgba(255,255,255,0.95);
473        font-size: 1.1em;
474        font-weight: 600;
475        flex: 1;
476    `;
477    title.textContent = unifiedEntity.title;
478    header.appendChild(title);
479
480    // Add Wikipedia link button
481    if (unifiedEntity.url) {
482        const linkButton = document.createElement('a');
483        linkButton.href = unifiedEntity.url;
484        linkButton.target = '_blank';
485        linkButton.rel = 'noopener noreferrer';
486        linkButton.style.cssText = `
487            background: rgba(255,255,255,0.2);
488            color: rgba(255,255,255,0.9);
489            padding: 6px 12px;
490            border-radius: 8px;
491            text-decoration: none;
492            font-size: 0.8em;
493            transition: all 0.2s ease;
494            margin-left: 10px;
495        `;
496        linkButton.textContent = '🔗 Wikipedia';
497        header.appendChild(linkButton);
498    }
499    
500    // Add Relationship Tree button
501    const relationshipTreeButton = document.createElement('a');
502    relationshipTreeButton.style.cssText = `
503        background: rgba(255,255,255,0.2);
504        color: rgba(255,255,255,0.9);
505        padding: 6px 12px;
506        border-radius: 8px;
507        text-decoration: none;
508        font-size: 0.8em;
509        transition: all 0.2s ease;
510        margin-left: 10px;
511        cursor: pointer;
512    `;
513    relationshipTreeButton.textContent = '🌳 Tree';
514    
515    relationshipTreeButton.addEventListener('click', (e) => {
516        e.preventDefault();
517        const entityName = unifiedEntity.title || unifiedEntity.page_title || unifiedEntity.name;
518        console.log(`🌳 [Fancy UI] Opening relationship tree for: ${entityName}`);
519        if (window.openRelationshipTreeOverlay) {
520            window.openRelationshipTreeOverlay(entityName);
521        } else {
522            console.error('🌳 [Fancy UI] openRelationshipTreeOverlay function not available');
523        }
524    });
525    
526    relationshipTreeButton.addEventListener('mouseenter', () => {
527        relationshipTreeButton.style.background = 'rgba(255,255,255,0.3)';
528    });
529    
530    relationshipTreeButton.addEventListener('mouseleave', () => {
531        relationshipTreeButton.style.background = 'rgba(255,255,255,0.2)';
532    });
533    
534    header.appendChild(relationshipTreeButton);
535
536    card.appendChild(header);
537
538    // Add image if available
539    if (unifiedEntity.imageUrl) {
540        const imageContainer = document.createElement('div');
541        imageContainer.style.cssText = `
542            width: 100%;
543            max-height: 200px;
544            overflow: hidden;
545            border-radius: 10px;
546            margin-bottom: 15px;
547        `;
548
549        const img = document.createElement('img');
550        img.alt = unifiedEntity.title;
551        img.crossOrigin = 'anonymous'; // Enable CORS for Wikipedia images
552        img.style.cssText = `
553            width: 100%;
554            height: 150px;
555            object-fit: cover;
556            background: rgba(255,255,255,0.1);
557            border-radius: 5px;
558        `;
559        
560        // Add loading and error handling
561        img.addEventListener('load', () => {
562            console.log(`[FancyUI] ✅ Image loaded for: ${unifiedEntity.title}`);
563            img.style.opacity = '1';
564        });
565        
566        img.addEventListener('error', (e) => {
567            console.log(`[FancyUI] ❌ Image failed to load for: ${unifiedEntity.title}, trying fallback`);
568            
569            // Try fallback image URL (remove thumbnail sizing)
570            const originalUrl = unifiedEntity.imageUrl.replace(/\/thumb\//, '/').replace(/\/\d+px-[^/]+$/, '');
571            if (originalUrl !== unifiedEntity.imageUrl && !img.dataset.fallbackTried) {
572                img.dataset.fallbackTried = 'true';
573                console.log(`[FancyUI] 🔄 Trying fallback URL for ${unifiedEntity.title}: ${originalUrl}`);
574                img.src = originalUrl;
575                return;
576            }
577            
578            // Final fallback: show placeholder with entity initial
579            img.style.display = 'none';
580            const placeholder = document.createElement('div');
581            placeholder.style.cssText = `
582                width: 100%;
583                height: 150px;
584                background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
585                display: flex;
586                align-items: center;
587                justify-content: center;
588                font-size: 48px;
589                font-weight: bold;
590                color: white;
591                border-radius: 5px;
592            `;
593            placeholder.textContent = unifiedEntity.title.charAt(0).toUpperCase();
594            img.parentNode.insertBefore(placeholder, img);
595            console.log(`[FancyUI] 📷 Using placeholder for: ${unifiedEntity.title}`);
596        });
597        
598        // Set image source after event listeners are attached
599        img.src = unifiedEntity.imageUrl;
600        img.style.opacity = '0.7'; // Start with slight transparency
601        img.style.transition = 'opacity 0.3s ease';
602        
603        imageContainer.appendChild(img);
604        card.appendChild(imageContainer);
605    }
606
607    // Add summary
608    const summaryText = unifiedEntity.summary || 
609                       unifiedEntity.wikipedia_narrative || 
610                       unifiedEntity.description || 
611                       unifiedEntity.extract ||
612                       null;
613
614    if (summaryText) {
615        const summaryDiv = document.createElement('div');
616        summaryDiv.style.cssText = `
617            color: rgba(255,255,255,0.8);
618            line-height: 1.6;
619            margin-bottom: 15px;
620            font-size: 0.9em;
621        `;
622        
623        const truncatedSummary = summaryText.length > 200 ? 
624            summaryText.substring(0, 200) + '...' : 
625            summaryText;
626        
627        summaryDiv.textContent = truncatedSummary;
628        card.appendChild(summaryDiv);
629    }
630
631    // Add hover effects
632    card.addEventListener('mouseenter', () => {
633        card.style.transform = 'translateY(-5px) scale(1.02)';
634        card.style.boxShadow = '0 10px 30px rgba(0,0,0,0.3)';
635    });
636
637    card.addEventListener('mouseleave', () => {
638        card.style.transform = 'translateY(0) scale(1)';
639        card.style.boxShadow = 'none';
640    });
641
642    return card;
643}
644
645// Unify data structure between main UI and fancy UI with optimization
646function unifyDataForFancyUI(wikipediaData, realtimeFacts, factsByEntity, fullData = null) {
647    console.log('[FancyUI] 🔄 Starting optimized data unification process...');
648    
649    const unifiedData = [];
650    const processedEntities = new Set();
651    const MAX_ENTITIES = 50; // 🎯 ENHANCED: Increased limit to show more cards as requested
652
653    // Process wikipediaData first (highest priority)
654    if (wikipediaData && Array.isArray(wikipediaData)) {
655        wikipediaData.forEach(entity => {
656            const unifiedEntity = createUnifiedEntityData(entity, 'wikipediaData');
657            if (unifiedEntity && !processedEntities.has(unifiedEntity.id) && unifiedData.length < MAX_ENTITIES) {
658                unifiedData.push(unifiedEntity);
659                processedEntities.add(unifiedEntity.id);
660            }
661        });
662    }
663
664    // Process factsByEntity (medium priority)
665    if (factsByEntity && typeof factsByEntity === 'object' && unifiedData.length < MAX_ENTITIES) {
666        Object.entries(factsByEntity).forEach(([entityKey, entityData]) => {
667            if (entityData && Array.isArray(entityData) && unifiedData.length < MAX_ENTITIES) {
668                entityData.forEach(entity => {
669                    const unifiedEntity = createUnifiedEntityData(entity, 'factsByEntity');
670                    if (unifiedEntity && !processedEntities.has(unifiedEntity.id) && unifiedData.length < MAX_ENTITIES) {
671                        unifiedData.push(unifiedEntity);
672                        processedEntities.add(unifiedEntity.id);
673                    }
674                });
675            }
676        });
677    }
678
679    // Process realtime_facts (lower priority)
680    if (realtimeFacts && Array.isArray(realtimeFacts) && unifiedData.length < MAX_ENTITIES) {
681        realtimeFacts.forEach(entity => {
682            const unifiedEntity = createUnifiedEntityData(entity, 'realtimeFacts');
683            if (unifiedEntity && !processedEntities.has(unifiedEntity.id) && unifiedData.length < MAX_ENTITIES) {
684                unifiedData.push(unifiedEntity);
685                processedEntities.add(unifiedEntity.id);
686            }
687        });
688    }
689
690    // Sort by relevance and filter high-quality entities
691    const filteredData = unifiedData
692        .filter(entity => entity.relevance >= 30) // Only keep entities with decent relevance
693        .sort((a, b) => (b.relevance || 0) - (a.relevance || 0))
694        .slice(0, MAX_ENTITIES);
695
696    console.log(`[FancyUI] 🎯 Optimized data unification complete: ${filteredData.length}/${unifiedData.length} entities (filtered from ${unifiedData.length} total)`);
697    console.log(`[FancyUI] 📊 Entity types distribution:`, 
698        filteredData.reduce((acc, entity) => {
699            acc[entity.type] = (acc[entity.type] || 0) + 1;
700            return acc;
701        }, {})
702    );
703    
704    return filteredData;
705}
706
707// Create unified entity data structure with type classification and relevance scoring
708function createUnifiedEntityData(rawEntity, source) {
709    if (!rawEntity) return null;
710
711    const title = rawEntity.title || 
712                  rawEntity.page_title || 
713                  rawEntity.name || 
714                  'Unknown Entity';
715
716    const id = `${title.toLowerCase().replace(/\s+/g, '_')}_${source}`;
717
718    const summary = rawEntity.summary || 
719                   rawEntity.wikipedia_narrative || 
720                   rawEntity.description || 
721                   rawEntity.extract ||
722                   null;
723
724    const url = rawEntity.url || 
725               rawEntity.wikipedia_url || 
726               (rawEntity.page_title ? `https://en.wikipedia.org/wiki/${encodeURIComponent(rawEntity.page_title.replace(/ /g, '_'))}` : null);
727
728    const imageUrl = rawEntity.main_image_url || 
729                     rawEntity.imageUrl || 
730                     rawEntity.image_url || 
731                     rawEntity.thumbnail || 
732                     null;
733
734    // 🎯 ENHANCED: Intelligent entity type classification
735    const entityType = classifyEntityType(rawEntity, title, summary);
736    
737    // 🎯 ENHANCED: Calculate relevance score
738    const relevanceScore = calculateEntityRelevance(rawEntity, title, summary, imageUrl, source);
739
740    return {
741        id,
742        title,
743        page_title: rawEntity.page_title || title,
744        summary,
745        url,
746        imageUrl,
747        image_url: imageUrl,
748        thumbnail: imageUrl,
749        main_image_url: imageUrl,
750        source: source,
751        type: entityType,
752        relevance: relevanceScore
753    };
754}
755
756// 🎯 ENHANCED: Intelligent entity type classification
757function classifyEntityType(rawEntity, title, summary) {
758    // Check explicit type fields first
759    if (rawEntity.type && rawEntity.type !== 'unknown') {
760        return rawEntity.type.toLowerCase();
761    }
762    
763    if (rawEntity.entity_type) {
764        return rawEntity.entity_type.toLowerCase();
765    }
766    
767    if (rawEntity.category) {
768        return rawEntity.category.toLowerCase();
769    }
770
771    // Analyze title and summary for type indicators
772    const titleLower = title.toLowerCase();
773    const summaryLower = (summary || '').toLowerCase();
774    const combinedText = `${titleLower} ${summaryLower}`;
775
776    // Person indicators
777    const personIndicators = [
778        'actor', 'actress', 'director', 'producer', 'writer', 'author', 'singer', 'musician',
779        'politician', 'president', 'minister', 'senator', 'governor', 'mayor',
780        'scientist', 'researcher', 'professor', 'doctor', 'engineer',
781        'athlete', 'player', 'coach', 'champion', 'winner',
782        'artist', 'painter', 'sculptor', 'photographer',
783        'born', 'died', 'age', 'years old', 'biography'
784    ];
785
786    // Film/Movie indicators
787    const filmIndicators = [
788        'film', 'movie', 'cinema', 'directed by', 'starring', 'cast',
789        'box office', 'premiere', 'released', 'screenplay', 'cinematography'
790    ];
791
792    // Organization indicators
793    const organizationIndicators = [
794        'company', 'corporation', 'organization', 'foundation', 'institute',
795        'university', 'college', 'school', 'hospital', 'museum',
796        'government', 'agency', 'department', 'ministry',
797        'founded', 'headquarters', 'ceo', 'president'
798    ];
799
800    // Location indicators
801    const locationIndicators = [
802        'city', 'town', 'village', 'country', 'state', 'province',
803        'region', 'district', 'county', 'municipality',
804        'capital', 'population', 'area', 'located', 'geography'
805    ];
806
807    // Award indicators
808    const awardIndicators = [
809        'award', 'prize', 'trophy', 'medal', 'honor', 'recognition',
810        'academy award', 'oscar', 'grammy', 'emmy', 'golden globe',
811        'nobel', 'pulitzer', 'winner', 'recipient'
812    ];
813
814    // Event indicators
815    const eventIndicators = [
816        'event', 'festival', 'conference', 'summit', 'meeting',
817        'war', 'battle', 'conflict', 'revolution', 'movement',
818        'occurred', 'happened', 'took place', 'date'
819    ];
820
821    // Work/Creation indicators
822    const workIndicators = [
823        'book', 'novel', 'album', 'song', 'painting', 'sculpture',
824        'play', 'opera', 'symphony', 'composition',
825        'published', 'written', 'created', 'composed'
826    ];
827
828    // Count matches for each category
829    const scores = {
830        person: personIndicators.filter(indicator => combinedText.includes(indicator)).length,
831        film: filmIndicators.filter(indicator => combinedText.includes(indicator)).length,
832        organization: organizationIndicators.filter(indicator => combinedText.includes(indicator)).length,
833        location: locationIndicators.filter(indicator => combinedText.includes(indicator)).length,
834        award: awardIndicators.filter(indicator => combinedText.includes(indicator)).length,
835        event: eventIndicators.filter(indicator => combinedText.includes(indicator)).length,
836        work: workIndicators.filter(indicator => combinedText.includes(indicator)).length
837    };
838
839    // Find the category with the highest score
840    const maxScore = Math.max(...Object.values(scores));
841    if (maxScore > 0) {
842        const bestType = Object.keys(scores).find(key => scores[key] === maxScore);
843        return bestType;
844    }
845
846    // Default fallback
847    return 'unknown';
848}
849
850// 🎯 ENHANCED: Calculate entity relevance score (0-100)
851function calculateEntityRelevance(rawEntity, title, summary, imageUrl, source) {
852    let score = 50; // Base score
853
854    // Source priority scoring
855    const sourcePriority = {
856        'wikipediaData': 20,
857        'factsByEntity': 15,
858        'realtimeFacts': 10
859    };
860    score += sourcePriority[source] || 0;
861
862    // Content quality scoring
863    if (summary && summary.length > 100) score += 15;
864    if (summary && summary.length > 300) score += 10;
865    if (imageUrl) score += 10;
866    if (rawEntity.url || rawEntity.wikipedia_url) score += 5;
867
868    // Title quality scoring
869    if (title && title.length > 3 && title !== 'Unknown Entity') score += 10;
870    if (title && !title.includes('disambiguation')) score += 5;
871
872    // Penalize low-quality indicators
873    if (!summary || summary.length < 50) score -= 15;
874    if (title && title.toLowerCase().includes('list of')) score -= 20;
875    if (title && title.toLowerCase().includes('category:')) score -= 25;
876    if (!imageUrl) score -= 5;
877
878    // Additional metadata scoring
879    if (rawEntity.categories && Array.isArray(rawEntity.categories)) {
880        score += Math.min(rawEntity.categories.length * 2, 10);
881    }
882    
883    if (rawEntity.infobox && Object.keys(rawEntity.infobox || {}).length > 5) {
884        score += 10;
885    }
886
887    // Ensure score is within bounds
888    return Math.max(0, Math.min(100, score));
889}
890
891// Function to refresh the image gallery display with new images
892function refreshImageGallery(carouselSection, allImageSources) {
893    // Remove duplicates based on image URL
894    const uniqueImages = [];
895    const seenUrls = new Set();
896    
897    allImageSources.forEach(imageSource => {
898        if (imageSource.imageUrl && !seenUrls.has(imageSource.imageUrl)) {
899            seenUrls.add(imageSource.imageUrl);
900            uniqueImages.push(imageSource);
901        }
902    });
903    
904    console.log(`[Gallery Refresh] Displaying ${uniqueImages.length} unique images (${allImageSources.length - uniqueImages.length} duplicates removed)`);
905    
906    // Find the existing carousel container
907    const existingCarousel = carouselSection.querySelector('.image-carousel-container');
908    if (existingCarousel) {
909        // Update the existing carousel with new images
910        const carousel = existingCarousel.querySelector('.image-carousel');
911        if (carousel) {
912            // Clear existing images
913            carousel.innerHTML = '';
914            
915            // Add all unique images
916            uniqueImages.forEach((imageSource, index) => {
917                const imageCard = document.createElement('div');
918                imageCard.className = 'image-card';
919                imageCard.style.cssText = `
920                    min-width: 200px;
921                    margin-right: 15px;
922                    border-radius: 10px;
923                    overflow: hidden;
924                    cursor: pointer;
925                    transition: transform 0.3s ease;
926                    background: rgba(255,255,255,0.1);
927                    border: 1px solid rgba(255,255,255,0.2);
928                    display: flex;
929                    flex-direction: column;
930                `;
931                
932                // Create image container
933                const imageContainer = document.createElement('div');
934                imageContainer.style.cssText = `
935                    width: 200px;
936                    height: 150px;
937                    overflow: hidden;
938                    border-radius: 10px 10px 0 0;
939                `;
940                
941                const img = document.createElement('img');
942                img.src = imageSource.imageUrl;
943                img.alt = imageSource.title || 'Image';
944                img.style.cssText = `
945                    width: 100%;
946                    height: 100%;
947                    object-fit: cover;
948                `;
949                
950                // Create text container below image
951                const textContainer = document.createElement('div');
952                textContainer.className = 'image-caption';
953                textContainer.style.cssText = `
954                    padding: 8px;
955                    color: white;
956                    font-size: 11px;
957                    font-weight: 500;
958                    background: rgba(255,255,255,0.1);
959                    text-align: center;
960                    line-height: 1.2;
961                    width: 200px;
962                    max-width: 200px;
963                    min-height: 40px;
964                    max-height: 40px;
965                    overflow: hidden;
966                    text-overflow: ellipsis;
967                    display: -webkit-box;
968                    -webkit-line-clamp: 2;
969                    -webkit-box-orient: vertical;
970                    word-wrap: break-word;
971                    word-break: break-word;
972                    box-sizing: border-box;
973                `;
974                
975                // Clean up and truncate the title for better display
976                let displayTitle = imageSource.title || 'Image';
977                
978                // Handle duplicated titles (e.g., "File:xyz.jpg - File:xyz.jpg")
979                const parts = displayTitle.split(' - ');
980                if (parts.length === 2 && parts[0] === parts[1]) {
981                    displayTitle = parts[0];
982                }
983                
984                // Remove "File:" prefix if present
985                if (displayTitle.startsWith('File:')) {
986                    displayTitle = displayTitle.substring(5);
987                }
988                
989                // Remove file extensions for cleaner display
990                displayTitle = displayTitle.replace(/\.(jpg|jpeg|png|gif|webp|svg)$/i, '');
991                
992                // Replace underscores with spaces for better readability
993                displayTitle = displayTitle.replace(/_/g, ' ');
994                
995                // If still too long, truncate intelligently
996                if (displayTitle.length > 50) {
997                    displayTitle = displayTitle.substring(0, 47) + '...';
998                }
999                
1000                textContainer.textContent = displayTitle;
1001                
1002                imageContainer.appendChild(img);
1003                imageCard.appendChild(imageContainer);
1004                imageCard.appendChild(textContainer);
1005                
1006                // Add click handler for Wikipedia link
1007                if (imageSource.wikipediaUrl) {
1008                    imageCard.addEventListener('click', () => {
1009                        window.open(imageSource.wikipediaUrl, '_blank');
1010                    });
1011                    imageCard.style.cursor = 'pointer';
1012                }
1013                
1014                // Add hover effect
1015                imageCard.addEventListener('mouseenter', () => {
1016                    imageCard.style.transform = 'scale(1.05)';
1017                });
1018                imageCard.addEventListener('mouseleave', () => {
1019                    imageCard.style.transform = 'scale(1)';
1020                });
1021                
1022                carousel.appendChild(imageCard);
1023            });
1024            
1025            // Update the header to show total count
1026            const header = carouselSection.querySelector('h3');
1027            if (header) {
1028                header.textContent = `🖼️ Image Gallery (${uniqueImages.length} images)`;
1029            }
1030        }
1031    }
1032}
1033
1034// Wikipedia API image fetching function
1035async function fetchWikipediaImages(entityTitle) {
1036    try {
1037        console.log(`[Gallery API] 🔍 Fetching images for: ${entityTitle}`);
1038        
1039        // Step 1: Get all image titles (filenames) from the Wikipedia page
1040        const pageTitle = entityTitle.replace(/ /g, '_');
1041        const imagesApiUrl = `https://en.wikipedia.org/w/api.php?action=query&titles=${encodeURIComponent(pageTitle)}&prop=images&format=json&imlimit=50&origin=*`;
1042        
1043        console.log(`[Gallery API] 📡 API Request: ${imagesApiUrl}`);
1044        
1045        const imagesResponse = await fetch(imagesApiUrl, {
1046            method: 'GET',
1047            headers: {
1048                'Accept': 'application/json',
1049            },
1050            timeout: 10000 // 10 second timeout
1051        });
1052        
1053        if (!imagesResponse.ok) {
1054            throw new Error(`HTTP ${imagesResponse.status}: ${imagesResponse.statusText}`);
1055        }
1056        
1057        const imagesData = await imagesResponse.json();
1058        
1059        const pages = imagesData.query?.pages;
1060        if (!pages) {
1061            console.log(`[Gallery API] No pages found for: ${entityTitle}`);
1062            return [];
1063        }
1064        
1065        const pageId = Object.keys(pages)[0];
1066        const images = pages[pageId]?.images;
1067        
1068        if (!images || images.length === 0) {
1069            console.log(`[Gallery API] No images found for: ${entityTitle}`);
1070            return [];
1071        }
1072        
1073        console.log(`[Gallery API] 📸 Found ${images.length} image files for: ${entityTitle}`);
1074        
1075        // Step 2: Get image URLs for each file (limit to first 20 to avoid overwhelming)
1076        const imagesToProcess = images.slice(0, 20);
1077        console.log(`[Gallery API] 🔄 Processing ${imagesToProcess.length} images for: ${entityTitle}`);
1078        
1079        const imagePromises = imagesToProcess.map(async (image, index) => {
1080            try {
1081                const imageTitle = image.title;
1082                const imageInfoUrl = `https://en.wikipedia.org/w/api.php?action=query&titles=${encodeURIComponent(imageTitle)}&prop=imageinfo&iiprop=url|size|mime&format=json&origin=*`;
1083                
1084                const imageInfoResponse = await fetch(imageInfoUrl, {
1085                    method: 'GET',
1086                    headers: {
1087                        'Accept': 'application/json',
1088                    },
1089                    timeout: 8000 // 8 second timeout per image
1090                });
1091                
1092                if (!imageInfoResponse.ok) {
1093                    console.warn(`[Gallery API] ⚠️ HTTP ${imageInfoResponse.status} for image: ${imageTitle}`);
1094                    return null;
1095                }
1096                
1097                const imageInfoData = await imageInfoResponse.json();
1098                
1099                const imagePages = imageInfoData.query?.pages;
1100                if (!imagePages) return null;
1101                
1102                const imagePageId = Object.keys(imagePages)[0];
1103                const imageInfo = imagePages[imagePageId]?.imageinfo?.[0];
1104                
1105                if (!imageInfo?.url) return null;
1106                
1107                // Filter out non-image files and very small images
1108                const mime = imageInfo.mime || '';
1109                const width = imageInfo.width || 0;
1110                const height = imageInfo.height || 0;
1111                
1112                if (!mime.startsWith('image/') || width < 100 || height < 100) {
1113                    return null;
1114                }
1115                
1116                // Skip common Wikipedia interface images
1117                const filename = imageTitle.toLowerCase();
1118                const skipPatterns = [
1119                    'commons-logo', 'wikimedia', 'edit-icon', 'folder', 'crystal',
1120                    'nuvola', 'tango', 'oxygen', 'gnome', 'kde', 'ambox', 'info',
1121                    'warning', 'question', 'merge', 'split', 'delete', 'redirect'
1122                ];
1123                
1124                if (skipPatterns.some(pattern => filename.includes(pattern))) {
1125                    return null;
1126                }
1127                
1128                return {
1129                    title: `${entityTitle} - ${imageTitle.replace('File:', '').replace(/\.[^/.]+$/, '')}`,
1130                    imageUrl: imageInfo.url,
1131                    source: 'wikipedia_api',
1132                    entity: { title: entityTitle },
1133                    infoboxField: 'wikipedia_api_images',
1134                    wikipediaUrl: `https://en.wikipedia.org/wiki/${encodeURIComponent(pageTitle)}`,
1135                    width: width,
1136                    height: height,
1137                    mime: mime
1138                };
1139            } catch (error) {
1140                console.warn(`[Gallery API] Error fetching image info for ${image.title}:`, error);
1141                return null;
1142            }
1143        });
1144        
1145        // Wait for all image info requests to complete
1146        const imageResults = await Promise.all(imagePromises);
1147        const validImages = imageResults.filter(img => img !== null);
1148        
1149        console.log(`[Gallery API] ✅ Successfully fetched ${validImages.length} valid images for: ${entityTitle}`);
1150        return validImages;
1151        
1152    } catch (error) {
1153        console.error(`[Gallery API] Error fetching images for ${entityTitle}:`, error);
1154        return [];
1155    }
1156}
1157
1158// Create standalone image carousel
1159function createStandaloneImageCarousel(unifiedData) {
1160    const carouselSection = document.createElement('div');
1161    carouselSection.className = 'standalone-image-carousel';
1162    carouselSection.style.cssText = `
1163        margin-top: 40px;
1164        padding: 30px;
1165        background: rgba(255,255,255,0.1);
1166        border: 1px solid rgba(255,255,255,0.2);
1167        border-radius: 15px;
1168        backdrop-filter: blur(10px);
1169    `;
1170    
1171    const header = document.createElement('h3');
1172    header.textContent = '🖼️ Image Gallery';
1173    header.style.cssText = `
1174        margin: 0 0 20px 0;
1175        color: white;
1176        font-size: 20px;
1177        text-align: center;
1178    `;
1179    carouselSection.appendChild(header);
1180    
1181    const carousel = document.createElement('div');
1182    carousel.style.cssText = `
1183        display: grid;
1184        grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
1185        gap: 15px;
1186        padding: 10px 0;
1187        max-height: 600px;
1188        overflow-y: auto;
1189        scroll-behavior: smooth;
1190    `;
1191    
1192    // 🔥 SUPER ENHANCED: Aggregate ALL possible images from every source
1193    const allImageSources = [];
1194    
1195    // Helper function to extract images from any object recursively
1196    function extractImagesFromObject(obj, entityTitle, source, entity, prefix = '') {
1197        if (!obj || typeof obj !== 'object') return;
1198        
1199        Object.entries(obj).forEach(([key, value]) => {
1200            const fullKey = prefix ? `${prefix}.${key}` : key;
1201            
1202            // Check if this key suggests an image
1203            const isImageKey = key.toLowerCase().includes('image') || 
1204                              key.toLowerCase().includes('photo') || 
1205                              key.toLowerCase().includes('picture') ||
1206                              key.toLowerCase().includes('thumbnail') ||
1207                              key.toLowerCase().includes('logo') ||
1208                              key.toLowerCase().includes('poster') ||
1209                              key.toLowerCase().includes('cover') ||
1210                              key.toLowerCase().includes('avatar') ||
1211                              key.toLowerCase().includes('banner');
1212            
1213            if (isImageKey) {
1214                let imageUrl = null;
1215                
1216                // Handle string URLs
1217                if (typeof value === 'string') {
1218                    if (value.startsWith('http') || 
1219                        value.includes('.jpg') || 
1220                        value.includes('.png') || 
1221                        value.includes('.jpeg') ||
1222                        value.includes('.gif') ||
1223                        value.includes('.webp') ||
1224                        value.includes('.svg')) {
1225                        imageUrl = value;
1226                    }
1227                }
1228                // Handle object with URL property
1229                else if (typeof value === 'object' && value !== null) {
1230                    imageUrl = value.url || value.src || value.href || value.link;
1231                }
1232                
1233                if (imageUrl) {
1234                    // Better title and Wikipedia URL handling with field name cleanup
1235                    let finalTitle = entityTitle;
1236                    let finalWikipediaUrl = null;
1237                    
1238                    // Clean up technical field names from titles
1239                    const technicalFields = ['main_image_url', 'imageUrl', 'thumbnail', 'image', 'photo', 'picture', 'logo', 'poster', 'cover', 'avatar', 'banner'];
1240                    const isTechnicalField = technicalFields.some(field => key.toLowerCase().includes(field.toLowerCase()));
1241                    
1242                    // Only add field name to title if it's not a technical field and provides meaningful context
1243                    if (!isTechnicalField && key !== 'url' && key !== 'src' && key !== 'href') {
1244                        finalTitle = `${entityTitle} - ${key}`;
1245                    }
1246                    
1247                    // If entity has proper Wikipedia info, use it
1248                    if (entity && (entity.wikipediaUrl || entity.url)) {
1249                        finalWikipediaUrl = entity.wikipediaUrl || entity.url;
1250                    }
1251                    // If entity has a title, construct Wikipedia URL
1252                    else if (entity && entity.title) {
1253                        finalWikipediaUrl = `https://en.wikipedia.org/wiki/${encodeURIComponent(entity.title.replace(/ /g, '_'))}`;
1254                    }
1255                    // If entityTitle looks like a Wikipedia page title, construct URL
1256                    else if (entityTitle && entityTitle !== 'Global Metadata' && !entityTitle.includes('Image')) {
1257                        finalWikipediaUrl = `https://en.wikipedia.org/wiki/${encodeURIComponent(entityTitle.replace(/ /g, '_'))}`;
1258                    }
1259                    
1260                    allImageSources.push({
1261                        title: finalTitle,
1262                        imageUrl: imageUrl,
1263                        source: source,
1264                        entity: entity,
1265                        infoboxField: fullKey,
1266                        wikipediaUrl: finalWikipediaUrl
1267                    });
1268                }
1269            }
1270            
1271            // Recursively search nested objects and arrays
1272            if (typeof value === 'object' && value !== null) {
1273                if (Array.isArray(value)) {
1274                    value.forEach((item, index) => {
1275                        extractImagesFromObject(item, entityTitle, source, entity, `${fullKey}[${index}]`);
1276                    });
1277                } else {
1278                    extractImagesFromObject(value, entityTitle, source, entity, fullKey);
1279                }
1280            }
1281        });
1282    }
1283    
1284    // 1. Collect images from main entities (expanded search)
1285    unifiedData.forEach(entity => {
1286        // Main image fields (expanded list)
1287        const mainImageFields = [
1288            'imageUrl', 'main_image_url', 'image_url', 'thumbnail', 'wikipediaImage',
1289            'pageimage', 'image', 'photo', 'picture', 'logo', 'poster', 'cover',
1290            'avatar', 'banner', 'profileImage', 'mainImage', 'primaryImage'
1291        ];
1292        
1293        mainImageFields.forEach(field => {
1294            const imageValue = entity[field];
1295            if (imageValue && typeof imageValue === 'string' && 
1296                (imageValue.startsWith('http') || imageValue.includes('.jpg') || 
1297                 imageValue.includes('.png') || imageValue.includes('.jpeg'))) {
1298                allImageSources.push({
1299                    title: entity.title,
1300                    imageUrl: imageValue,
1301                    source: 'main_entity',
1302                    entity: entity,
1303                    wikipediaUrl: entity.wikipediaUrl || entity.url
1304                });
1305            }
1306        });
1307        
1308        // 2. Deep extraction from infobox data
1309        if (entity.infobox) {
1310            extractImagesFromObject(entity.infobox, entity.title, 'infobox', entity);
1311        }
1312        
1313        // 3. Deep extraction from entire entity (catch any missed images)
1314        extractImagesFromObject(entity, entity.title, 'entity_deep_scan', entity);
1315        
1316        // 4. Check for related_links images (enhanced)
1317        if (entity.related_links && Array.isArray(entity.related_links)) {
1318            entity.related_links.forEach(link => {
1319                // Check multiple image fields in related links
1320                const imageFields = ['image', 'imageUrl', 'thumbnail', 'photo', 'picture', 'logo', 'poster'];
1321                imageFields.forEach(field => {
1322                    const linkImage = link[field];
1323                    if (linkImage && typeof linkImage === 'string' && 
1324                        (linkImage.startsWith('http') || linkImage.includes('.jpg') || 
1325                         linkImage.includes('.png') || linkImage.includes('.jpeg'))) {
1326                        allImageSources.push({
1327                            title: link.title || `${entity.title} - Related`,
1328                            imageUrl: linkImage,
1329                            source: 'related_links',
1330                            entity: entity,
1331                            relatedEntity: link,
1332                            wikipediaUrl: link.url || link.wikipediaUrl
1333                        });
1334                    }
1335                });
1336                
1337                // Deep scan related link objects for nested images
1338                extractImagesFromObject(link, link.title || `${entity.title} - Related`, 'related_links_deep', entity);
1339            });
1340        }
1341    });
1342    
1343    // 5. Use images fetched by image-gallery.js for progressive loading
1344    console.log(`[Gallery API] Checking for globally fetched images from image-gallery.js`);
1345    console.log(`[Gallery API] Available global images:`, window.fetchedGalleryImages);
1346    
1347    // 🔍 ENHANCED DEBUG: Show entity titles vs stored image keys
1348    if (window.fetchedGalleryImages) {
1349        const storedKeys = Object.keys(window.fetchedGalleryImages);
1350        const entityTitles = unifiedData.map(e => e.title).filter(Boolean);
1351        
1352        console.log(`[Gallery API] 🔍 TITLE MATCHING DEBUG:`);
1353        console.log(`[Gallery API] 📋 Stored image keys (${storedKeys.length}):`, storedKeys);
1354        console.log(`[Gallery API] 🎯 Entity titles (${entityTitles.length}):`, entityTitles);
1355        
1356        // Check for exact matches
1357        const exactMatches = entityTitles.filter(title => storedKeys.includes(title));
1358        const missingMatches = entityTitles.filter(title => !storedKeys.includes(title));
1359        
1360        console.log(`[Gallery API] ✅ Exact matches (${exactMatches.length}):`, exactMatches);
1361        console.log(`[Gallery API] ❌ Missing matches (${missingMatches.length}):`, missingMatches);
1362        
1363        // Check for partial matches (case insensitive)
1364        missingMatches.forEach(entityTitle => {
1365            const partialMatches = storedKeys.filter(key => 
1366                key.toLowerCase().includes(entityTitle.toLowerCase()) || 
1367                entityTitle.toLowerCase().includes(key.toLowerCase())
1368            );
1369            if (partialMatches.length > 0) {
1370                console.log(`[Gallery API] 🔍 Potential partial match for "${entityTitle}":`, partialMatches);
1371            }
1372        });
1373    }
1374    
1375    // Process entities to get their fetched images
1376    const batchSize = 5; // Process 5 entities at a time
1377    const entityBatches = [];
1378    
1379    for (let i = 0; i < unifiedData.length; i += batchSize) {
1380        entityBatches.push(unifiedData.slice(i, i + batchSize));
1381    }
1382    
1383    // Process each batch sequentially with delay
1384    let totalApiFetched = 0;
1385    
1386    const processBatch = async (batchIndex) => {
1387        if (batchIndex >= entityBatches.length) {
1388            console.log(`[Gallery API] ✅ Completed all batches. Total API images fetched: ${totalApiFetched}`);
1389            console.log(`[Gallery API] 🎯 Final gallery refresh with ALL ${allImageSources.length} images`);
1390            // NOW refresh gallery with ALL accumulated images
1391            refreshImageGallery(carouselSection, allImageSources);
1392            return;
1393        }
1394        
1395        const batch = entityBatches[batchIndex];
1396        console.log(`[Gallery API] Processing batch ${batchIndex + 1}/${entityBatches.length} (${batch.length} entities)`);
1397        
1398        const batchPromises = batch.map(async (entity) => {
1399            if (!entity.title || !window.fetchedGalleryImages) {
1400                console.log(`[Gallery API] No title or global images available for entity:`, entity);
1401                return { entity: entity.title || 'unknown', images: [] };
1402            }
1403            
1404            // 🔍 ENHANCED MATCHING: Try multiple matching strategies
1405            let globalImages = null;
1406            let matchedKey = null;
1407            
1408            // Strategy 1: Exact match
1409            if (window.fetchedGalleryImages[entity.title]) {
1410                globalImages = window.fetchedGalleryImages[entity.title];
1411                matchedKey = entity.title;
1412                console.log(`[Gallery API] ✅ Exact match found for: ${entity.title}`);
1413            }
1414            // Strategy 2: Case-insensitive exact match
1415            else {
1416                const storedKeys = Object.keys(window.fetchedGalleryImages);
1417                const caseInsensitiveMatch = storedKeys.find(key => 
1418                    key.toLowerCase() === entity.title.toLowerCase()
1419                );
1420                if (caseInsensitiveMatch) {
1421                    globalImages = window.fetchedGalleryImages[caseInsensitiveMatch];
1422                    matchedKey = caseInsensitiveMatch;
1423                    console.log(`[Gallery API] ✅ Case-insensitive match: "${entity.title}" -> "${caseInsensitiveMatch}"`);
1424                }
1425            }
1426            // Strategy 3: Partial match (contains)
1427            if (!globalImages) {
1428                const storedKeys = Object.keys(window.fetchedGalleryImages);
1429                const partialMatch = storedKeys.find(key => 
1430                    key.toLowerCase().includes(entity.title.toLowerCase()) || 
1431                    entity.title.toLowerCase().includes(key.toLowerCase())
1432                );
1433                if (partialMatch) {
1434                    globalImages = window.fetchedGalleryImages[partialMatch];
1435                    matchedKey = partialMatch;
1436                    console.log(`[Gallery API] ✅ Partial match: "${entity.title}" -> "${partialMatch}"`);
1437                }
1438            }
1439            
1440            if (globalImages && globalImages.imageUrls && globalImages.imageUrls.length > 0) {
1441                console.log(`[Gallery API] 🎯 Found ${globalImages.totalImages || globalImages.imageUrls.length} globally stored images for: ${entity.title} (matched via: ${matchedKey})`);
1442                
1443                // Convert global image URLs to the expected format
1444                const formattedImages = globalImages.imageUrls.map(url => ({
1445                    url: url,
1446                    title: `Image from ${entity.title}`,
1447                    source: 'global_fetch',
1448                    entity: entity.title,
1449                    matchedKey: matchedKey
1450                }));
1451                
1452                return { entity: entity.title, images: formattedImages };
1453            } else {
1454                console.log(`[Gallery API] ❌ No globally stored images found for: ${entity.title}`);
1455                return { entity: entity.title, images: [] };
1456            }
1457        });
1458        
1459        try {
1460            const batchResults = await Promise.all(batchPromises);
1461            
1462            // Add batch results to gallery
1463            batchResults.forEach(result => {
1464                if (result.images.length > 0) {
1465                    console.log(`[Gallery API] Adding ${result.images.length} images for ${result.entity}`);
1466                    result.images.forEach(apiImage => {
1467                        allImageSources.push(apiImage);
1468                    });
1469                    totalApiFetched += result.images.length;
1470                }
1471            });
1472            
1473            // Progressive loading: refresh gallery after each batch with accumulated images
1474            console.log(`[Gallery API] Batch ${batchIndex + 1} completed. Current total images: ${allImageSources.length}`);
1475            console.log(`[Gallery API] 🔄 Progressive refresh with ${allImageSources.length} images`);
1476            refreshImageGallery(carouselSection, allImageSources);
1477            
1478            // Process next batch after a short delay
1479            setTimeout(() => {
1480                processBatch(batchIndex + 1);
1481            }, 1000); // 1 second delay between batches
1482            
1483        } catch (error) {
1484            console.error(`[Gallery API] Error processing batch ${batchIndex + 1}:`, error);
1485            // Continue with next batch even if this one fails
1486            setTimeout(() => {
1487                processBatch(batchIndex + 1);
1488            }, 2000);
1489        }
1490    };
1491    
1492    // Start processing batches
1493    processBatch(0);
1494    
1495    // 6. Add images from global Wikipedia metadata (WITH CORRECT PAGE ASSOCIATIONS)
1496    if (window.wikipediaMetadata) {
1497        console.log(`[Gallery] 🔍 Scanning window.wikipediaMetadata:`, window.wikipediaMetadata);
1498        
1499        // Create a mapping of available entities for proper page association
1500        const entityMapping = new Map();
1501        unifiedData.forEach(entity => {
1502            if (entity.title) {
1503                const normalizedTitle = entity.title.toLowerCase();
1504                entityMapping.set(normalizedTitle, entity);
1505                entityMapping.set(entity.title.replace(/ /g, '_').toLowerCase(), entity);
1506                entityMapping.set(entity.title.replace(/_/g, ' ').toLowerCase(), entity);
1507            }
1508        });
1509        
1510        console.log(`[Gallery] Available entities for page association:`, Array.from(entityMapping.keys()));
1511        
1512        // Extract from wikipediaImages array with proper page association and filtering
1513        if (window.wikipediaMetadata.wikipediaImages && Array.isArray(window.wikipediaMetadata.wikipediaImages)) {
1514            console.log(`[Gallery] Found ${window.wikipediaMetadata.wikipediaImages.length} images in wikipediaImages array`);
1515            window.wikipediaMetadata.wikipediaImages.forEach((imageData, index) => {
1516                // Better page title extraction
1517                let pageTitle = imageData.page_title || imageData.pageTitle || imageData.title;
1518                let wikipediaUrl = imageData.wikipediaUrl || imageData.url;
1519                
1520                // If no proper page title, try to extract from URL or use a more descriptive fallback
1521                if (!pageTitle || pageTitle.includes('main_image_url') || pageTitle.includes('image_url')) {
1522                    if (wikipediaUrl && wikipediaUrl.includes('wikipedia.org/wiki/')) {
1523                        // Extract page title from Wikipedia URL
1524                        const urlParts = wikipediaUrl.split('/wiki/');
1525                        if (urlParts.length > 1) {
1526                            pageTitle = decodeURIComponent(urlParts[1]).replace(/_/g, ' ');
1527                        }
1528                    } else {
1529                        // Skip images without proper page association
1530                        console.log(`[Gallery] Skipping image without proper page title: ${imageData}`);
1531                        return;
1532                    }
1533                }
1534                
1535                // 🎯 CRITICAL FIX: Show ALL images regardless of entity matching
1536                // This ensures all fetched Wikipedia images appear in the gallery
1537                const pageTitleLower = pageTitle.toLowerCase();
1538                let associatedEntity = null;
1539                
1540                // Try to find the correct entity for this image's page
1541                associatedEntity = entityMapping.get(pageTitleLower) ||
1542                    entityMapping.get(pageTitleLower.replace(/ /g, '_')) ||
1543                    entityMapping.get(pageTitleLower.replace(/_/g, ' '));
1544                
1545                // 🔥 ALWAYS INCLUDE IMAGES: Create entity representation for ALL images
1546                if (!associatedEntity) {
1547                    console.log(`[Gallery] ✅ Including image from: ${pageTitle} (creating entity representation)`);
1548                    associatedEntity = {
1549                        title: pageTitle,
1550                        wikipediaUrl: wikipediaUrl || `https://en.wikipedia.org/wiki/${encodeURIComponent(pageTitle.replace(/ /g, '_'))}`
1551                    };
1552                } else {
1553                    console.log(`[Gallery] ✅ Including image from matched entity: ${associatedEntity.title}`);
1554                }
1555                
1556                console.log(`[Gallery] Associating image with correct entity: ${associatedEntity.title}`);
1557                
1558                console.log(`[Gallery] Adding image from wikipediaImages: ${imageData.url || imageData.imageUrl}`);
1559                allImageSources.push({
1560                    title: `${associatedEntity.title} - ${imageData.title || 'Image'}`,
1561                    imageUrl: imageData.url || imageData.imageUrl,
1562                    source: 'global_metadata_wikipedia_images',
1563                    entity: associatedEntity,
1564                    infoboxField: 'wikipediaImages',
1565                    wikipediaUrl: associatedEntity.wikipediaUrl || `https://en.wikipedia.org/wiki/${encodeURIComponent(associatedEntity.title.replace(/ /g, '_'))}`
1566                });
1567            });
1568        }
1569        
1570        // Extract from any other image collections in metadata (WITH CORRECT PAGE ASSOCIATIONS)
1571        Object.keys(window.wikipediaMetadata).forEach(key => {
1572            if (key.toLowerCase().includes('image') && Array.isArray(window.wikipediaMetadata[key])) {
1573                console.log(`[Gallery] Found additional image array: ${key} with ${window.wikipediaMetadata[key].length} items`);
1574                window.wikipediaMetadata[key].forEach((item, index) => {
1575                    if (typeof item === 'string' && (item.startsWith('http') || item.includes('.jpg') || item.includes('.png'))) {
1576                        // Skip string URLs without entity association - they can't be properly associated
1577                        console.log(`[Gallery] Skipping unassociated string image URL from ${key}`);
1578                        return;
1579                    } else if (typeof item === 'object' && item) {
1580                        const imgUrl = item.url || item.imageUrl || item.src;
1581                        if (imgUrl) {
1582                            // Check if this image has page association
1583                            const itemPageTitle = item.page_title || item.pageTitle || item.title;
1584                            if (itemPageTitle) {
1585                                const itemPageTitleLower = itemPageTitle.toLowerCase();
1586                                let itemAssociatedEntity = entityMapping.get(itemPageTitleLower) ||
1587                                    entityMapping.get(itemPageTitleLower.replace(/ /g, '_')) ||
1588                                    entityMapping.get(itemPageTitleLower.replace(/_/g, ' '));
1589                                
1590                                if (!itemAssociatedEntity) {
1591                                    console.log(`[Gallery] ✅ Including image from extra metadata: ${itemPageTitle} (creating entity representation)`);
1592                                    itemAssociatedEntity = {
1593                                        title: itemPageTitle,
1594                                        wikipediaUrl: `https://en.wikipedia.org/wiki/${encodeURIComponent(itemPageTitle.replace(/ /g, '_'))}`
1595                                    };
1596                                }
1597                                
1598                                console.log(`[Gallery] Associating extra metadata image with: ${itemAssociatedEntity.title}`);
1599                                
1600                                allImageSources.push({
1601                                    title: `${itemAssociatedEntity.title} - ${item.title || item.name || 'Image'}`,
1602                                    imageUrl: imgUrl,
1603                                    source: 'global_metadata_extra',
1604                                    entity: itemAssociatedEntity,
1605                                    wikipediaUrl: itemAssociatedEntity.wikipediaUrl || `https://en.wikipedia.org/wiki/${encodeURIComponent(itemAssociatedEntity.title.replace(/ /g, '_'))}`
1606                                });
1607                            } else {
1608                                console.log(`[Gallery] Skipping image without page association from ${key}`);
1609                                return;
1610                            }
1611                        }
1612                    }
1613                });
1614            }
1615        });
1616        
1617        // Enhanced metadata scanning - check for page-specific image data (WITH CORRECT ASSOCIATIONS)
1618        if (window.wikipediaMetadata.factsByEntity && Array.isArray(window.wikipediaMetadata.factsByEntity)) {
1619            console.log(`[Gallery] Scanning factsByEntity for page-specific images`);
1620            window.wikipediaMetadata.factsByEntity.forEach(entityData => {
1621                if (entityData && entityData.pageTitle) {
1622                    const pageTitle = entityData.pageTitle;
1623                    const pageTitleLower = pageTitle.toLowerCase();
1624                    
1625                    // CRITICAL FIX: Ensure correct entity association for factsByEntity data
1626                    let factsAssociatedEntity = entityMapping.get(pageTitleLower) ||
1627                        entityMapping.get(pageTitleLower.replace(/ /g, '_')) ||
1628                        entityMapping.get(pageTitleLower.replace(/_/g, ' '));
1629                    
1630                    if (!factsAssociatedEntity) {
1631                        console.log(`[Gallery] No matching entity found for factsByEntity page: ${pageTitle} - skipping`);
1632                        return;
1633                    }
1634                    
1635                    console.log(`[Gallery] Processing factsByEntity data for: ${factsAssociatedEntity.title}`);
1636                    
1637                    // Extract images from this entity's data with correct association
1638                    extractImagesFromObject(entityData, factsAssociatedEntity.title, 'global_metadata_facts', factsAssociatedEntity);
1639                }
1640            });
1641        }
1642        
1643        // Check for any other page-specific data structures (WITH CORRECT ASSOCIATIONS)
1644        Object.keys(window.wikipediaMetadata).forEach(key => {
1645            const data = window.wikipediaMetadata[key];
1646            if (Array.isArray(data)) {
1647                data.forEach((item, index) => {
1648                    if (item && typeof item === 'object' && (item.pageTitle || item.page_title)) {
1649                        const pageTitle = item.pageTitle || item.page_title;
1650                        const pageTitleLower = pageTitle.toLowerCase();
1651                        
1652                        // CRITICAL FIX: Ensure correct entity association for other metadata
1653                        let otherAssociatedEntity = entityMapping.get(pageTitleLower) ||
1654                            entityMapping.get(pageTitleLower.replace(/ /g, '_')) ||
1655                            entityMapping.get(pageTitleLower.replace(/_/g, ' '));
1656                        
1657                        if (!otherAssociatedEntity) {
1658                            console.log(`[Gallery] ✅ Including image from ${key}: ${pageTitle} (creating entity representation)`);
1659                            otherAssociatedEntity = {
1660                                title: pageTitle,
1661                                wikipediaUrl: `https://en.wikipedia.org/wiki/${encodeURIComponent(pageTitle.replace(/ /g, '_'))}`
1662                            };
1663                        }
1664                        
1665                        console.log(`[Gallery] Processing ${key} data for: ${otherAssociatedEntity.title}`);
1666                        
1667                        // Extract images from this page-specific item with correct association
1668                        extractImagesFromObject(item, otherAssociatedEntity.title, `global_metadata_${key}`, otherAssociatedEntity);
1669                    }
1670                });
1671            }
1672        });
1673        
1674        // Deep scan the entire metadata object for any missed images
1675        extractImagesFromObject(window.wikipediaMetadata, 'Global Metadata', 'metadata_deep_scan', null);
1676    }
1677    
1678    // Remove duplicates based on image URL
1679    const uniqueImages = [];
1680    const seenUrls = new Set();
1681    
1682    allImageSources.forEach(imageSource => {
1683        if (!seenUrls.has(imageSource.imageUrl)) {
1684            seenUrls.add(imageSource.imageUrl);
1685            uniqueImages.push(imageSource);
1686        }
1687    });
1688    
1689    console.log(`[Gallery] 🔥 SUPER ENHANCED IMAGE AGGREGATION RESULTS:`);
1690    console.log(`[Gallery] Total image sources found: ${allImageSources.length}`);
1691    console.log(`[Gallery] Unique images after deduplication: ${uniqueImages.length}`);
1692    
1693    const sourceBreakdown = {
1694        main_entity: allImageSources.filter(s => s.source === 'main_entity').length,
1695        infobox: allImageSources.filter(s => s.source === 'infobox').length,
1696        entity_deep_scan: allImageSources.filter(s => s.source === 'entity_deep_scan').length,
1697        related_links: allImageSources.filter(s => s.source === 'related_links').length,
1698        related_links_deep: allImageSources.filter(s => s.source === 'related_links_deep').length,
1699        global_metadata: allImageSources.filter(s => s.source === 'global_metadata').length,
1700        global_metadata_extra: allImageSources.filter(s => s.source === 'global_metadata_extra').length,
1701        entity_metadata: allImageSources.filter(s => s.source === 'entity_metadata').length,
1702        page_specific_metadata: allImageSources.filter(s => s.source === 'page_specific_metadata').length,
1703        metadata_deep_scan: allImageSources.filter(s => s.source === 'metadata_deep_scan').length
1704    };
1705    
1706    console.log(`[Gallery] 📊 Image sources breakdown:`, sourceBreakdown);
1707    
1708    // Show sample images from each source
1709    Object.keys(sourceBreakdown).forEach(source => {
1710        const sourceImages = allImageSources.filter(s => s.source === source);
1711        if (sourceImages.length > 0) {
1712            console.log(`[Gallery] 🖼️ Sample ${source} images:`, sourceImages.slice(0, 3).map(img => ({
1713                title: img.title,
1714                url: img.imageUrl.substring(0, 80) + (img.imageUrl.length > 80 ? '...' : '')
1715            })));
1716        }
1717    });
1718    
1719    console.log(`[Gallery] 🎆 FINAL GALLERY: Will display ${uniqueImages.length} unique images`);
1720    
1721    const entitiesWithImages = uniqueImages;
1722    
1723    console.log(`[Gallery] Found ${entitiesWithImages.length} entities with images out of ${unifiedData.length} total entities`);
1724    
1725    if (entitiesWithImages.length === 0) {
1726        console.log('[Gallery] No images available - showing fallback message');
1727        const noImagesMsg = document.createElement('div');
1728        noImagesMsg.textContent = 'No images available';
1729        noImagesMsg.style.cssText = `
1730            color: rgba(255,255,255,0.6);
1731            text-align: center;
1732            padding: 20px;
1733            font-style: italic;
1734        `;
1735        carouselSection.appendChild(noImagesMsg);
1736        return carouselSection;
1737    }
1738    
1739    console.log(`[Gallery] Creating carousel with ${entitiesWithImages.length} images`);
1740    
1741    // 🔍 DEBUG: Show Gallery JSON for carousel
1742    console.log('🎯 [Gallery JSON for carousel] Complete data structure:');
1743    console.log('📊 Gallery JSON:', JSON.stringify({
1744        totalEntities: entitiesWithImages.length,
1745        entities: entitiesWithImages.map(entity => ({
1746            title: entity.title,
1747            imageUrl: entity.imageUrl,
1748            main_image_url: entity.main_image_url,
1749            image_url: entity.image_url,
1750            thumbnail: entity.thumbnail,
1751            wikipediaImage: entity.wikipediaImage,
1752            pageimage: entity.pageimage,
1753            image: entity.image,
1754            selectedImageUrl: entity.imageUrl || entity.main_image_url || entity.image_url || entity.thumbnail || entity.wikipediaImage || entity.pageimage || entity.image
1755        }))
1756    }, null, 2));
1757    
1758    console.log('🎯 [Gallery JSON for carousel] Enhanced image sources being used:');
1759    entitiesWithImages.forEach((imageSource, index) => {
1760        console.log(`${index + 1}. ${imageSource.title} (${imageSource.source}): ${imageSource.imageUrl}`);
1761    });
1762    
1763    entitiesWithImages.forEach((imageSource, index) => {
1764        // Use the pre-selected image URL from our enhanced aggregation
1765        const imageUrl = imageSource.imageUrl;
1766        
1767        console.log(`[Gallery] Creating carousel item ${index + 1} for "${imageSource.title}" with image: ${imageUrl}`);
1768        
1769        const item = document.createElement('div');
1770        item.style.cssText = `
1771            flex: 0 0 200px;
1772            background: rgba(255,255,255,0.1);
1773            border: 1px solid rgba(255,255,255,0.2);
1774            border-radius: 10px;
1775            overflow: hidden;
1776            cursor: pointer;
1777            transition: transform 0.3s ease;
1778            position: relative;
1779            overflow: hidden;
1780            margin-bottom: 20px;
1781        `;
1782        
1783        const img = document.createElement('img');
1784        img.src = imageUrl;
1785        img.alt = imageSource.title;
1786        img.crossOrigin = 'anonymous'; // Enable CORS for Wikipedia images
1787        img.style.cssText = `
1788            width: 100%;
1789            height: 150px;
1790            object-fit: cover;
1791            background: rgba(255,255,255,0.1);
1792            border-radius: 5px;
1793        `;
1794        
1795        // Add loading and error handling
1796        img.addEventListener('load', () => {
1797            console.log(`[FancyUI] ✅ Image loaded for: ${imageSource.title}`);
1798            img.style.opacity = '1';
1799        });
1800        
1801        img.addEventListener('error', (e) => {
1802            console.log(`[FancyUI] ❌ Image failed to load for: ${imageSource.title}, trying fallback`);
1803            
1804            // Try fallback image URL (remove thumbnail sizing)
1805            const originalUrl = imageUrl.replace(/\/thumb\//, '/').replace(/\/\d+px-[^/]+$/, '');
1806            if (originalUrl !== imageUrl && !img.dataset.fallbackTried) {
1807                img.dataset.fallbackTried = 'true';
1808                console.log(`[FancyUI] 🔄 Trying fallback URL for ${imageSource.title}: ${originalUrl}`);
1809                img.src = originalUrl;
1810            } else {
1811                console.log(`[FancyUI] ⚠️ All image URLs failed for: ${imageSource.title}`);
1812                img.style.display = 'none';
1813            }
1814        });
1815        
1816        const caption = document.createElement('div');
1817        caption.textContent = imageSource.title;
1818        caption.style.cssText = `
1819            padding: 10px;
1820            color: white;
1821            font-size: 14px;
1822            text-align: center;
1823            background: rgba(0,0,0,0.7);
1824            position: absolute;
1825            bottom: 0;
1826            left: 0;
1827            right: 0;
1828            backdrop-filter: blur(5px);
1829        `;
1830        
1831        item.appendChild(img);
1832        item.appendChild(caption);
1833        
1834        item.addEventListener('mouseenter', () => {
1835            item.style.transform = 'scale(1.05)';
1836        });
1837        
1838        item.addEventListener('mouseleave', () => {
1839            item.style.transform = 'scale(1)';
1840        });
1841        
1842        // 🔗 Make image clickable - link to Wikipedia page
1843        item.addEventListener('click', () => {
1844            const wikipediaUrl = imageSource.wikipediaUrl || (imageSource.entity && (imageSource.entity.wikipediaUrl || imageSource.entity.url));
1845            if (wikipediaUrl) {
1846                console.log(`[FancyUI] 🔗 Opening Wikipedia page: ${wikipediaUrl}`);
1847                window.open(wikipediaUrl, '_blank');
1848            } else {
1849                console.log(`[FancyUI] ⚠️ No Wikipedia URL found for: ${imageSource.title}`);
1850            }
1851        });
1852        
1853        // Add visual feedback for clickable items
1854        item.style.cursor = 'pointer';
1855        item.title = `Click to view ${imageSource.title} on Wikipedia`;
1856        
1857        carousel.appendChild(item);
1858    });
1859    
1860    carouselSection.appendChild(carousel);
1861    return carouselSection;
1862}
1863
1864// Create Wikipedia Story section
1865function createWikipediaStorySection(data) {
1866    const storySection = document.createElement('div');
1867    storySection.className = 'wikipedia-story-section';
1868    storySection.style.cssText = `
1869        margin-top: 40px;
1870        padding: 30px;
1871        background: rgba(255,255,255,0.1);
1872        border: 1px solid rgba(255,255,255,0.2);
1873        border-radius: 15px;
1874        backdrop-filter: blur(10px);
1875        position: relative;
1876        overflow: hidden;
1877    `;
1878    
1879    // Create header
1880    const header = document.createElement('div');
1881    header.style.cssText = `
1882        display: flex;
1883        align-items: center;
1884        margin-bottom: 20px;
1885        padding: 0 10px;
1886    `;
1887    
1888    const headerIcon = document.createElement('span');
1889    headerIcon.textContent = '📖';
1890    headerIcon.style.cssText = `
1891        font-size: 24px;
1892        margin-right: 10px;
1893    `;
1894    
1895    const headerTitle = document.createElement('h3');
1896    headerTitle.textContent = `Wikipedia Story: ${data.searchTerm}`;
1897    headerTitle.style.cssText = `
1898        margin: 0;
1899        font-size: 20px;
1900        font-weight: 600;
1901        color: white;
1902        flex: 1;
1903    `;
1904    
1905    const generateButton = document.createElement('button');
1906    generateButton.textContent = '📖 Generate Story';
1907    generateButton.className = 'generate-story-btn';
1908    generateButton.style.cssText = `
1909        padding: 12px 24px;
1910        background: linear-gradient(135deg, #4CAF50, #45a049);
1911        color: white;
1912        border: none;
1913        border-radius: 25px;
1914        font-size: 14px;
1915        font-weight: 600;
1916        cursor: pointer;
1917        transition: all 0.3s ease;
1918        margin-right: 10px;
1919        box-shadow: 0 2px 10px rgba(76,175,80,0.3);
1920    `;
1921    
1922    let contentArea;
1923    generateButton.addEventListener('mouseover', () => {
1924        generateButton.style.transform = 'scale(1.05)';
1925        generateButton.style.boxShadow = '0 5px 15px rgba(76,175,80,0.4)';
1926    });
1927    
1928    generateButton.addEventListener('mouseleave', () => {
1929        generateButton.style.transform = 'scale(1)';
1930        generateButton.style.boxShadow = '0 2px 10px rgba(76,175,80,0.3)';
1931    });
1932    
1933    generateButton.addEventListener('click', () => {
1934        generateWikipediaStory(data, contentArea, generateButton);
1935    });
1936
1937    // Create Wikipedia Map button
1938    const mapButton = document.createElement('button');
1939    mapButton.textContent = '🗺️ Wikipedia Map';
1940    mapButton.className = 'wikipedia-map-btn';
1941    mapButton.style.cssText = `
1942        padding: 12px 24px;
1943        background: linear-gradient(135deg, #ff6b6b, #ee5a24);
1944        color: white;
1945        border: none;
1946        border-radius: 25px;
1947        font-size: 14px;
1948        font-weight: 600;
1949        cursor: pointer;
1950        transition: all 0.3s ease;
1951        margin-right: 10px;
1952        box-shadow: 0 2px 10px rgba(255,107,107,0.3);
1953    `;
1954    
1955    mapButton.addEventListener('mouseover', () => {
1956        mapButton.style.transform = 'scale(1.05)';
1957        mapButton.style.boxShadow = '0 5px 15px rgba(255,107,107,0.4)';
1958    });
1959    
1960    mapButton.addEventListener('mouseleave', () => {
1961        mapButton.style.transform = 'scale(1)';
1962        mapButton.style.boxShadow = '0 2px 10px rgba(255,107,107,0.3)';
1963    });
1964    
1965    mapButton.addEventListener('click', () => {
1966        openRelationshipMap();
1967    });
1968    
1969    const closeBtn = document.createElement('button');
1970    closeBtn.innerHTML = '✕';
1971    closeBtn.style.cssText = `
1972        padding: 10px 15px;
1973        background: rgba(255,255,255,0.2);
1974        color: white;
1975        border: none;
1976        border-radius: 50%;
1977        cursor: pointer;
1978        font-size: 18px;
1979        font-weight: bold;
1980        transition: all 0.3s ease;
1981    `;
1982    
1983    closeBtn.addEventListener('mouseover', () => {
1984        closeBtn.style.background = 'rgba(255,255,255,0.3)';
1985    });
1986    
1987    closeBtn.addEventListener('mouseout', () => {
1988        closeBtn.style.background = 'rgba(255,255,255,0.2)';
1989    });
1990    
1991    closeBtn.addEventListener('click', () => {
1992        previewContainer.remove();
1993    });
1994    
1995    header.appendChild(headerIcon);
1996    header.appendChild(headerTitle);
1997    header.appendChild(generateButton);
1998    header.appendChild(mapButton);
1999    header.appendChild(closeBtn);
2000    storySection.appendChild(header);
2001    
2002    // Create content area
2003    contentArea = document.createElement('div');
2004    contentArea.className = 'story-content';
2005    contentArea.style.cssText = `
2006        min-height: 200px;
2007        color: rgba(255,255,255,0.9);
2008        line-height: 1.6;
2009        margin-bottom: 15px;
2010        font-size: 16px;
2011    `;
2012    
2013    // Initial content
2014    contentArea.innerHTML = `
2015        <div style="text-align: center; padding: 40px 20px; color: rgba(255,255,255,0.6);">
2016            <div style="font-size: 48px; margin-bottom: 20px;">📚</div>
2017            <p style="font-size: 18px; margin: 0;">Click "Generate Story" to create a Wikipedia-like narrative</p>
2018            <p style="font-size: 14px; margin: 10px 0 0 0;">Transform ${data.totalResults} entities into an engaging story</p>
2019        </div>
2020    `;
2021    
2022    storySection.appendChild(contentArea);
2023    
2024    return storySection;
2025}
2026
2027// Function to generate Wikipedia story using OpenAI with chunking
2028async function generateWikipediaStory(data, contentArea, button) {
2029    console.log('📖 [WikipediaStory] Generating chunked story for:', data.searchTerm);
2030    
2031    // Show loading state
2032    button.textContent = '⏳ Generating...';
2033    button.disabled = true;
2034    button.style.opacity = '0.6';
2035    
2036    contentArea.innerHTML = `
2037        <div style="text-align: center; padding: 40px 20px;">
2038            <div class="loading-spinner" style="
2039                width: 40px;
2040                height: 40px;
2041                border: 3px solid rgba(255,255,255,0.3);
2042                border-top: 3px solid white;
2043                border-radius: 50%;
2044                animation: spin 1s linear infinite;
2045                margin: 0 auto 20px;
2046            "></div>
2047            <p style="color: rgba(255,255,255,0.8); margin: 0;">Initializing story generation...</p>
2048            <p style="color: rgba(255,255,255,0.6); font-size: 14px; margin: 10px 0 0 0;" id="story-progress">
2049                Analyzing ${data.totalResults} entities
2050            </p>
2051        </div>
2052        <style>
2053            @keyframes spin {
2054                0% { transform: rotate(0deg); }
2055                100% { transform: rotate(360deg); }
2056            }
2057        </style>
2058    `;
2059    
2060    try {
2061        // Step 1: Initialize chunked story generation
2062        const initResponse = await fetch('/.netlify/functions/generate-wikipedia-story', {
2063            method: 'POST',
2064            headers: { 'Content-Type': 'application/json' },
2065            body: JSON.stringify({
2066                infoboxData: data.infoboxes,
2067                searchTerm: data.searchTerm,
2068                totalResults: data.totalResults
2069            })
2070        });
2071        
2072        if (!initResponse.ok) {
2073            throw new Error(`Initialization failed: ${initResponse.status}`);
2074        }
2075        
2076        const initResult = await initResponse.json();
2077        console.log('📖 [WikipediaStory] Initialization result:', initResult);
2078        
2079        if (initResult.type !== 'chunked_story_init') {
2080            throw new Error('Invalid initialization response');
2081        }
2082        
2083        const totalChunks = initResult.totalChunks;
2084        console.log(`📖 [WikipediaStory] Will generate ${totalChunks} sections`);
2085        
2086        // Initialize story buffer
2087        const storyBuffer = {
2088            chunks: new Array(totalChunks).fill(null),
2089            totalChunks: totalChunks,
2090            receivedChunks: 0,
2091            searchTerm: data.searchTerm
2092        };
2093        
2094        // Update progress
2095        document.getElementById('story-progress').textContent = `Generating ${totalChunks} story sections...`;
2096        
2097        // Step 2: Generate chunks with sequential batched processing (prevents 502s)
2098        console.log(`📖 [WikipediaStory] Using sequential batched processing for ${totalChunks} sections`);
2099        await generateChunksSequentially(data, totalChunks, storyBuffer);
2100        
2101        // Step 3: Assemble and display complete story
2102        console.log('📖 [WikipediaStory] All sections received, assembling story');
2103        const completeStory = assembleCompleteStory(storyBuffer);
2104        displayGeneratedStory(completeStory, contentArea, button);
2105        
2106    } catch (error) {
2107        console.error('📖 [WikipediaStory] Error generating story:', error);
2108        
2109        // Show error state with fallback
2110        contentArea.innerHTML = `
2111            <div style="text-align: center; padding: 40px 20px;">
2112                <div style="font-size: 48px; margin-bottom: 20px;">⚠️</div>
2113                <p style="color: rgba(255,255,255,0.8); margin: 0;">Story generation temporarily unavailable</p>
2114                <div style="background: rgba(255,255,255,0.1); padding: 20px; border-radius: 10px; text-align: left;">
2115                    <h4 style="color: white; margin: 0 0 15px 0;">📚 Quick Summary</h4>
2116                    <p style="color: rgba(255,255,255,0.8); margin: 0 0 15px 0;">
2117                        We found ${data.totalResults} related Wikipedia articles about "${data.searchTerm}". 
2118                        While the AI story generator is temporarily unavailable, you can explore the individual 
2119                        infoboxes above to learn more.
2120                    </p>
2121                    <button onclick="copyStoryToClipboard(event)" style="
2122                        background: rgba(255,255,255,0.2);
2123                        border: 1px solid rgba(255,255,255,0.3);
2124                        padding: 8px 16px;
2125                        border-radius: 5px;
2126                        color: white;
2127                        font-size: 14px;
2128                        cursor: pointer;
2129                        transition: all 0.3s ease;
2130                    ">📋 Copy Story</button>
2131                </div>
2132            </div>
2133        `;
2134        
2135        // Reset button
2136        button.textContent = '🗺️ Wikipedia Map';
2137        button.disabled = false;
2138        button.style.opacity = '1';
2139    }
2140}
2141
2142// Function to generate chunks sequentially with retry logic
2143async function generateChunksSequentially(data, totalChunks, storyBuffer) {
2144    const chunkSize = 5; // Process 5 chunks at a time
2145    const maxRetries = 3; // Allow up to 3 retries per chunk
2146    
2147    for (let chunkIndex = 0; chunkIndex < totalChunks; chunkIndex += chunkSize) {
2148        const chunkBatch = [];
2149        
2150        // Create a batch of chunk requests
2151        for (let i = chunkIndex; i < Math.min(chunkIndex + chunkSize, totalChunks); i++) {
2152            chunkBatch.push(generateStoryChunk(data, i, totalChunks, storyBuffer, maxRetries));
2153        }
2154        
2155        // Wait for the batch to complete
2156        await Promise.all(chunkBatch);
2157    }
2158}
2159
2160// Generate individual story chunk with retry logic
2161async function generateStoryChunk(data, chunkIndex, totalChunks, storyBuffer, maxRetries) {
2162    let retries = 0;
2163    
2164    while (retries <= maxRetries) {
2165        try {
2166            console.log(`📖 [WikipediaStory] Generating section ${chunkIndex + 1}/${totalChunks}`);
2167            
2168            const response = await fetch('/.netlify/functions/generate-wikipedia-story', {
2169                method: 'POST',
2170                headers: { 'Content-Type': 'application/json' },
2171                body: JSON.stringify({
2172                    infoboxData: data.infoboxes,
2173                    searchTerm: data.searchTerm,
2174                    totalResults: data.totalResults,
2175                    chunkIndex: chunkIndex,
2176                    totalChunks: totalChunks
2177                })
2178            });
2179            
2180            if (!response.ok) {
2181                throw new Error(`Section ${chunkIndex + 1} failed: ${response.status}`);
2182            }
2183            
2184            const result = await response.json();
2185            console.log(`📖 [WikipediaStory] Section ${chunkIndex + 1} received:`, result.storyChunk?.length, 'characters');
2186            
2187            if (result.type === 'chunked_story_part') {
2188                storyBuffer.chunks[chunkIndex] = result.storyChunk;
2189                storyBuffer.receivedChunks++;
2190                
2191                // Update progress
2192                const progressElement = document.getElementById('story-progress');
2193                if (progressElement) {
2194                    progressElement.textContent = `Generated ${storyBuffer.receivedChunks}/${totalChunks} sections...`;
2195                }
2196                
2197                return; // Chunk generated successfully
2198            }
2199            
2200            // If we reach this point, the chunk generation failed
2201            throw new Error(`Invalid section response for section ${chunkIndex + 1}`);
2202            
2203        } catch (error) {
2204            console.error(`📖 [WikipediaStory] Error generating section ${chunkIndex + 1}:`, error);
2205            
2206            // If we've exceeded the max retries, give up on this chunk
2207            if (retries >= maxRetries) {
2208                console.log(`📖 [WikipediaStory] Giving up on section ${chunkIndex + 1} after ${maxRetries} retries`);
2209                storyBuffer.chunks[chunkIndex] = `
2210                    <h3>📖 Section ${chunkIndex + 1}</h3>
2211                    <p style="color: rgba(255,255,255,0.7);">
2212                        <em>This section is temporarily unavailable. Please try regenerating the story.</em>
2213                    </p>
2214                `;
2215                storyBuffer.receivedChunks++;
2216                return;
2217            }
2218            
2219            // Otherwise, retry the chunk
2220            retries++;
2221            console.log(`📖 [WikipediaStory] Retrying section ${chunkIndex + 1} (attempt ${retries + 1}/${maxRetries})`);
2222            await new Promise(resolve => setTimeout(resolve, 1000)); // Wait 1 second before retrying
2223        }
2224    }
2225}
2226
2227// Assemble complete story from chunks
2228function assembleCompleteStory(storyBuffer) {
2229    // Clean up chunks: remove AI-generated section headers like "### Section 2:" or "## Section 1:"
2230    const cleanedChunks = storyBuffer.chunks.map(chunk => {
2231        if (!chunk) return '';
2232        // Remove markdown section headers (###, ##, #) followed by "Section X:"
2233        return chunk.replace(/^#{1,6}\s*Section\s+\d+:?\s*/gim, '').trim();
2234    });
2235    
2236    const completeStoryContent = cleanedChunks.join('\n\n');
2237    
2238    return {
2239        story: `
2240            <h2>📖 ${storyBuffer.searchTerm} - Wikipedia Story</h2>
2241            ${completeStoryContent}
2242        `,
2243        searchTerm: storyBuffer.searchTerm,
2244        totalChunks: storyBuffer.totalChunks,
2245        storyLength: completeStoryContent.length,
2246        generatedAt: new Date().toISOString(),
2247        metadata: {
2248            chunksGenerated: storyBuffer.receivedChunks,
2249            totalChunks: storyBuffer.totalChunks,
2250            generationMethod: 'chunked'
2251        }
2252    };
2253}
2254
2255// Function to display the generated story
2256function displayGeneratedStory(result, contentArea, button) {
2257    console.log('📖 [WikipediaStory] Displaying generated story');
2258    
2259    // Create story content with metadata
2260    const storyHTML = `
2261        <div class="wikipedia-story-content">
2262            <div class="story-metadata" style="
2263                background: rgba(255,255,255,0.1);
2264                padding: 15px;
2265                border-radius: 10px;
2266                margin-bottom: 20px;
2267                display: flex;
2268                justify-content: space-between;
2269                align-items: center;
2270                flex-wrap: wrap;
2271                gap: 10px;
2272            ">
2273                <div style="color: rgba(255,255,255,0.8); font-size: 14px;">
2274                    📊 ${result.totalChunks} sections • ${result.storyLength} characters • Generated ${new Date(result.generatedAt).toLocaleTimeString()}
2275                </div>
2276                ${result.metadata.generationMethod === 'chunked' ? '<span style="color: #4ecdc4; font-size: 12px;">✨ Chunked Generation</span>' : '<span style="color: #ff6b6b; font-size: 12px;">⚠️ Fallback Story</span>'}
2277            </div>
2278            
2279            <div class="story-text" style="
2280                background: rgba(255,255,255,0.05);
2281                padding: 25px;
2282                border-radius: 12px;
2283                border-left: 4px solid #4ecdc4;
2284                line-height: 1.7;
2285                font-size: 16px;
2286            ">
2287                ${result.story}
2288            </div>
2289            
2290            <div class="story-actions" style="
2291                margin-top: 20px;
2292                display: flex;
2293                gap: 10px;
2294                flex-wrap: wrap;
2295            ">
2296                <button onclick="copyStoryToClipboard(event)" style="
2297                    background: rgba(255,255,255,0.2);
2298                    border: 1px solid rgba(255,255,255,0.3);
2299                    padding: 8px 16px;
2300                    border-radius: 20px;
2301                    color: white;
2302                    font-size: 14px;
2303                    cursor: pointer;
2304                    transition: all 0.3s ease;
2305                ">📋 Copy Story</button>
2306                
2307                <button id="speakStoryButton" onclick="toggleWikipediaStoryTTS()" style="
2308                    background: rgba(255,255,255,0.2);
2309                    border: 1px solid rgba(255,255,255,0.3);
2310                    padding: 8px 16px;
2311                    border-radius: 20px;
2312                    color: white;
2313                    font-size: 14px;
2314                    cursor: pointer;
2315                    transition: all 0.3s ease;
2316                ">🔊 Speak</button>
2317                
2318                <button onclick="shareStory()" style="
2319                    background: rgba(255,255,255,0.2);
2320                    border: 1px solid rgba(255,255,255,0.3);
2321                    padding: 8px 16px;
2322                    border-radius: 20px;
2323                    color: white;
2324                    font-size: 14px;
2325                    cursor: pointer;
2326                    transition: all 0.3s ease;
2327                ">🔗 Share</button>
2328                
2329                <button onclick="exportStoryToPDF()" style="
2330                    background: rgba(255,255,255,0.2);
2331                    border: 1px solid rgba(255,255,255,0.3);
2332                    padding: 8px 16px;
2333                    border-radius: 20px;
2334                    color: white;
2335                    font-size: 14px;
2336                    cursor: pointer;
2337                    transition: all 0.3s ease;
2338                ">📄 Export PDF</button>
2339            </div>
2340        </div>
2341        
2342        <style>
2343            .wikipedia-story-link {
2344                color: #4ecdc4 !important;
2345                text-decoration: none;
2346                border-bottom: 1px solid rgba(78, 205, 196, 0.3);
2347                transition: all 0.3s ease;
2348            }
2349            
2350            .wikipedia-story-link:hover {
2351                color: #ff6b6b !important;
2352                border-bottom-color: rgba(255, 107, 107, 0.5);
2353                background: rgba(255, 107, 107, 0.1);
2354                padding: 2px 4px;
2355                border-radius: 3px;
2356            }
2357            
2358            .story-entity-image {
2359                width: 100px;
2360                height: 100px;
2361                object-fit: cover;
2362                border-radius: 8px;
2363                margin-right: 12px;
2364                vertical-align: middle;
2365                border: 2px solid #4ecdc4;
2366                transition: all 0.3s ease;
2367                display: inline-block;
2368            }
2369            
2370            .wikipedia-story-link:hover .story-entity-image {
2371                border-color: #ff6b6b;
2372                transform: scale(1.05);
2373                box-shadow: 0 2px 8px rgba(255, 107, 107, 0.3);
2374            }
2375            
2376            .story-actions button:hover {
2377                background: rgba(255,255,255,0.3) !important;
2378                transform: translateY(-1px);
2379            }
2380        </style>
2381    `;
2382    
2383    contentArea.innerHTML = storyHTML;
2384    
2385    // Reset button to allow regeneration
2386    button.textContent = '🗺️ Wikipedia Map';
2387    button.disabled = false;
2388    button.style.opacity = '1';
2389    
2390    // Scroll to story
2391    contentArea.scrollIntoView({ behavior: 'smooth', block: 'start' });
2392    
2393    // Store the current infobox data globally for relationship analysis
2394    storeInfoboxData(result);
2395}
2396
2397// Helper function to copy story to clipboard
2398window.copyStoryToClipboard = function(event) {
2399    const storyText = document.querySelector('.story-text');
2400    if (storyText) {
2401        // Create a temporary element to extract plain text
2402        const tempDiv = document.createElement('div');
2403        tempDiv.innerHTML = storyText.innerHTML;
2404        const plainText = tempDiv.textContent || tempDiv.innerText || '';
2405        
2406        navigator.clipboard.writeText(plainText).then(() => {
2407            // Show feedback - find button element properly
2408            const button = event ? event.target : document.querySelector('[onclick*="copyStoryToClipboard"]');
2409            if (button) {
2410                const originalText = button.textContent;
2411                button.textContent = '✅ Copied!';
2412                setTimeout(() => {
2413                    button.textContent = originalText;
2414                }, 2000);
2415            }
2416        }).catch(err => {
2417            console.error('Failed to copy story:', err);
2418            // Show error feedback
2419            const button = event ? event.target : document.querySelector('[onclick*="copyStoryToClipboard"]');
2420            if (button) {
2421                const originalText = button.textContent;
2422                button.textContent = '❌ Copy failed';
2423                setTimeout(() => {
2424                    button.textContent = originalText;
2425                }, 2000);
2426            }
2427        });
2428    }
2429};
2430
2431// Global variables for TTS state management
2432let wikipediaStoryAudio = null;
2433let wikipediaStoryController = null;
2434
2435// Global variables for chunked TTS playback
2436let ttsAudioQueue = [];
2437let ttsCurrentIndex = 0;
2438let ttsIsPlaying = false;
2439
2440// Function to split text into chunks (OpenAI TTS limit is 4096 characters)
2441function splitTextIntoChunks(text, maxLength = 3500) {
2442    const chunks = [];
2443    const sentences = text.match(/[^.!?]+[.!?]+/g) || [text];
2444    let currentChunk = '';
2445    
2446    for (const sentence of sentences) {
2447        if ((currentChunk + sentence).length > maxLength && currentChunk.length > 0) {
2448            chunks.push(currentChunk.trim());
2449            currentChunk = sentence;
2450        } else {
2451            currentChunk += sentence;
2452        }
2453    }
2454    
2455    if (currentChunk.trim().length > 0) {
2456        chunks.push(currentChunk.trim());
2457    }
2458    
2459    return chunks;
2460}
2461
2462// Function to play next audio chunk in queue
2463function playNextTTSChunk() {
2464    if (!ttsIsPlaying || ttsCurrentIndex >= ttsAudioQueue.length) {
2465        console.log('[TTS] Playback queue completed');
2466        resetTTSButton();
2467        ttsAudioQueue = [];
2468        ttsCurrentIndex = 0;
2469        ttsIsPlaying = false;
2470        return;
2471    }
2472    
2473    const audioUrl = ttsAudioQueue[ttsCurrentIndex];
2474    console.log(`[TTS] Playing chunk ${ttsCurrentIndex + 1}/${ttsAudioQueue.length}`);
2475    
2476    wikipediaStoryAudio = new Audio(audioUrl);
2477    
2478    wikipediaStoryAudio.onended = () => {
2479        URL.revokeObjectURL(audioUrl);
2480        ttsCurrentIndex++;
2481        playNextTTSChunk();
2482    };
2483    
2484    wikipediaStoryAudio.onerror = (error) => {
2485        console.error('[TTS] Audio playback error:', error);
2486        URL.revokeObjectURL(audioUrl);
2487        resetTTSButton();
2488        ttsIsPlaying = false;
2489        alert('Error playing audio');
2490    };
2491    
2492    wikipediaStoryAudio.play();
2493}
2494
2495// Function to speak the Wikipedia Story using OpenAI TTS
2496async function speakWikipediaStory() {
2497    const storyText = document.querySelector('.story-text');
2498    const ttsButton = document.getElementById('speakStoryButton');
2499    
2500    if (!storyText || !ttsButton) {
2501        console.error('[TTS] Story text or button not found');
2502        return;
2503    }
2504    
2505    // Get the text content
2506    const text = storyText.innerText || storyText.textContent;
2507    
2508    if (!text || text.trim().length === 0) {
2509        console.error('[TTS] No text to speak');
2510        alert('No story text available to speak');
2511        return;
2512    }
2513    
2514    console.log(`[TTS] Starting OpenAI TTS for ${text.length} characters`);
2515    
2516    // Split text into chunks (smaller chunks = faster processing, avoid timeouts)
2517    // Using 1000 chars to avoid Netlify 10-second timeout limit
2518    const chunks = splitTextIntoChunks(text, 1000);
2519    console.log(`[TTS] Split into ${chunks.length} chunks`);
2520    
2521    // Update button to show loading state
2522    ttsButton.textContent = `⏳ Generating ${chunks.length} parts...`;
2523    ttsButton.disabled = true;
2524    ttsButton.style.background = 'rgba(255,165,0,0.3)';
2525    
2526    try {
2527        // Create abort controller for cancellation
2528        wikipediaStoryController = new AbortController();
2529        
2530        // Initialize playback state
2531        ttsAudioQueue = [];
2532        ttsIsPlaying = true;
2533        ttsCurrentIndex = 0;
2534        let isFirstChunk = true;
2535        
2536        // Update button to show playing state immediately
2537        ttsButton.textContent = '⏸️ Stop Speaking';
2538        ttsButton.disabled = false;
2539        ttsButton.style.background = 'rgba(255,0,0,0.3)';
2540        ttsButton.style.borderColor = 'rgba(255,0,0,0.5)';
2541        
2542        // Generate and play audio chunks progressively
2543        for (let i = 0; i < chunks.length; i++) {
2544            console.log(`[TTS] Generating chunk ${i + 1}/${chunks.length} (${chunks[i].length} chars)`);
2545            
2546            const response = await fetch('/.netlify/functions/text-to-speech', {
2547                method: 'POST',
2548                headers: {
2549                    'Content-Type': 'application/json'
2550                },
2551                body: JSON.stringify({ text: chunks[i] }),
2552                signal: wikipediaStoryController.signal
2553            });
2554            
2555            if (!response.ok) {
2556                const error = await response.json();
2557                throw new Error(error.error || 'Failed to generate speech');
2558            }
2559            
2560            const audioBlob = await response.blob();
2561            const audioUrl = URL.createObjectURL(audioBlob);
2562            ttsAudioQueue.push(audioUrl);
2563            
2564            console.log(`[TTS] Chunk ${i + 1}/${chunks.length} ready`);
2565            
2566            // Start playback immediately after first chunk is ready
2567            if (isFirstChunk) {
2568                isFirstChunk = false;
2569                console.log('[TTS] Starting progressive playback...');
2570                playNextTTSChunk();
2571            }
2572        }
2573        
2574        console.log(`[TTS] All ${ttsAudioQueue.length} chunks generated, playback in progress`);
2575        
2576    } catch (error) {
2577        if (error.name === 'AbortError') {
2578            console.log('[TTS] Request cancelled');
2579        } else {
2580            console.error('[TTS] Error:', error);
2581            alert(`Failed to generate speech: ${error.message}`);
2582        }
2583        resetTTSButton();
2584        ttsAudioQueue = [];
2585        ttsIsPlaying = false;
2586    }
2587}
2588
2589// Function to stop Wikipedia Story TTS
2590function stopWikipediaStoryTTS() {
2591    console.log('Stopping Wikipedia Story TTS');
2592    
2593    // Stop playback flag
2594    ttsIsPlaying = false;
2595    
2596    // Stop audio playback
2597    if (wikipediaStoryAudio) {
2598        wikipediaStoryAudio.pause();
2599        wikipediaStoryAudio.currentTime = 0;
2600        wikipediaStoryAudio = null;
2601    }
2602    
2603    // Cancel any ongoing request
2604    if (wikipediaStoryController) {
2605        wikipediaStoryController.abort();
2606        wikipediaStoryController = null;
2607    }
2608    
2609    // Clean up audio queue
2610    ttsAudioQueue.forEach(url => URL.revokeObjectURL(url));
2611    ttsAudioQueue = [];
2612    ttsCurrentIndex = 0;
2613    
2614    // Reset button state
2615    resetTTSButton();
2616}
2617
2618// Function to toggle Wikipedia Story TTS (start or stop)
2619function toggleWikipediaStoryTTS() {
2620    const ttsButton = document.getElementById('speakStoryButton');
2621    
2622    if (!ttsButton) {
2623        console.error('TTS button not found');
2624        return;
2625    }
2626    
2627    // Check if TTS is currently active (button shows "Stop Speaking")
2628    if (ttsButton.textContent.includes('Stop')) {
2629        // TTS is active, stop it
2630        stopWikipediaStoryTTS();
2631    } else {
2632        // TTS is not active, start it
2633        speakWikipediaStory();
2634    }
2635}
2636
2637// Function to reset TTS button to initial state
2638function resetTTSButton() {
2639    const ttsButton = document.getElementById('speakStoryButton');
2640    if (ttsButton) {
2641        ttsButton.textContent = '🔊 Speak';
2642        ttsButton.style.background = 'rgba(255,255,255,0.2)';
2643        ttsButton.style.borderColor = 'rgba(255,255,255,0.3)';
2644        ttsButton.disabled = false;
2645    }
2646    
2647    // Clean up global state
2648    wikipediaStoryAudio = null;
2649    wikipediaStoryController = null;
2650}
2651
2652// Export story to PDF using server-side Node.js generation
2653window.exportStoryToPDF = async function() {
2654    console.log('📄 Starting Node.js PDF export...');
2655    
2656    try {
2657        // Show loading indicator
2658        const exportButton = document.querySelector('button[onclick="exportStoryToPDF()"]');
2659        if (exportButton) {
2660            const originalText = exportButton.textContent;
2661            exportButton.textContent = '📄 Generating PDF...';
2662            exportButton.disabled = true;
2663            
2664            // Restore button after completion
2665            setTimeout(() => {
2666                exportButton.textContent = originalText;
2667                exportButton.disabled = false;
2668            }, 15000); // 15 second timeout for Node.js PDF generation
2669        }
2670        
2671        // Extract story data
2672        const storyData = extractStoryDataForPDF();
2673        if (!storyData) {
2674            throw new Error('Failed to extract story data');
2675        }
2676        
2677        console.log('📄 Extracted story data:', storyData);
2678        
2679        // Send to Node.js PDF generation endpoint
2680        const response = await fetch('/.netlify/functions/export-wikipedia-pdf', {
2681            method: 'POST',
2682            headers: {
2683                'Content-Type': 'application/json'
2684            },
2685            body: JSON.stringify({
2686                storyData: storyData
2687            })
2688        });
2689        
2690        if (!response.ok) {
2691            let errorMessage = `HTTP ${response.status}: ${response.statusText}`;
2692            try {
2693                const errorData = await response.json();
2694                errorMessage = errorData.error || errorMessage;
2695            } catch (e) {
2696                // If response is not JSON, use the status text
2697                console.warn('Non-JSON error response:', e);
2698            }
2699            throw new Error(errorMessage);
2700        }
2701        
2702        const result = await response.json();
2703        
2704        if (!result.success) {
2705            throw new Error(result.error || 'PDF generation failed');
2706        }
2707        
2708        console.log('📄 PDF generated successfully:', result.filename, `(${result.size} bytes)`);
2709        
2710        // Convert base64 to blob and download
2711        const pdfBytes = atob(result.pdf);
2712        const pdfArray = new Uint8Array(pdfBytes.length);
2713        for (let i = 0; i < pdfBytes.length; i++) {
2714            pdfArray[i] = pdfBytes.charCodeAt(i);
2715        }
2716        
2717        const blob = new Blob([pdfArray], { type: 'application/pdf' });
2718        const url = URL.createObjectURL(blob);
2719        
2720        // Create download link
2721        const downloadLink = document.createElement('a');
2722        downloadLink.href = url;
2723        downloadLink.download = result.filename;
2724        downloadLink.style.display = 'none';
2725        
2726        // Trigger download
2727        document.body.appendChild(downloadLink);
2728        downloadLink.click();
2729        document.body.removeChild(downloadLink);
2730        
2731        // Clean up
2732        URL.revokeObjectURL(url);
2733        
2734        // Show success message
2735        if (exportButton) {
2736            exportButton.textContent = '✅ PDF Downloaded!';
2737            setTimeout(() => {
2738                exportButton.textContent = '📄 Export PDF';
2739                exportButton.disabled = false;
2740            }, 3000);
2741        }
2742        
2743        console.log('📄 Node.js PDF download completed successfully');
2744        
2745    } catch (error) {
2746        console.error('📄 Node.js PDF export failed:', error);
2747        
2748        // Show error message
2749        const exportButton = document.querySelector('button[onclick="exportStoryToPDF()"]');
2750        if (exportButton) {
2751            exportButton.textContent = '❌ Export Failed';
2752            exportButton.disabled = false;
2753            setTimeout(() => {
2754                exportButton.textContent = '📄 Export PDF';
2755            }, 3000);
2756        }
2757        
2758        // Show user-friendly error with more details
2759        alert(`PDF export failed: ${error.message}\n\nThis may be due to:\n• Server processing timeout\n• Large content size\n• Network connectivity\n\nPlease try again or c
2759ontact support if the issue persists.`);
2760    }
2761};
2762
2763// Enhanced text cleaning function for PDF export
2764function cleanTextForPDF(text) {
2765    if (!text || typeof text !== 'string') return '';
2766    
2767    // Remove encoding artifacts that cause PDF issues
2768    text = text.replace(/Ø=Ü[ÖÝ÷Ê]/g, ''); // Remove specific encoding artifacts
2769    text = text.replace(/Ø=Ü./g, ''); // Remove any Ø=Ü followed by any character
2770    text = text.replace(/[ØÜÖÝÊ÷]/g, ''); // Remove individual problematic characters
2771    
2772    // Remove other problematic characters
2773    text = text.replace(/[\u0080-\u00FF]/g, ''); // Remove Latin-1 supplement
2774    text = text.replace(/[\u0100-\u017F]/g, ''); // Remove Latin Extended-A
2775    text = text.replace(/[\u0180-\u024F]/g, ''); // Remove Latin Extended-B
2776    
2777    // Fix spaced-out text ("G o o g l e" -> "Google")
2778    text = text.replace(/\b([a-zA-Z])\s+([a-zA-Z])\s+([a-zA-Z])/g, '$1$2$3');
2779    text = text.replace(/\b([a-zA-Z])\s+([a-zA-Z])/g, '$1$2');
2780    
2781    // Normalize whitespace
2782    text = text.replace(/\s+/g, ' ');
2783    text = text.replace(/^\s+|\s+$/g, '');
2784    
2785    // Remove any remaining non-printable characters
2786    text = text.replace(/[^\x20-\x7E\n\r\t]/g, '');
2787    
2788    return text;
2789}
2790
2791// Function to extract story data for PDF
2792function extractStoryDataForPDF() {
2793    const storyContainer = document.querySelector('.wikipedia-story-content');
2794    const storyText = document.querySelector('.story-text');
2795    const storyMetadata = document.querySelector('.story-metadata');
2796    
2797    if (!storyText || !storyContainer) {
2798        console.error('Story content not found for PDF export');
2799        return null;
2800    }
2801    
2802    console.log('📄 Extracting story data for PDF...');
2803    
2804    // Extract title from the story content
2805    const titleElement = storyText.querySelector('h2');
2806    const title = titleElement ? cleanTextForPDF(titleElement.textContent.trim()) : 'Wikipedia Story';
2807    
2808    // Extract content by processing the HTML properly
2809    const content = [];
2810    
2811    // Get the raw HTML content from the story text div
2812    let htmlContent = storyText.innerHTML;
2813    console.log('📄 Raw HTML content length:', htmlContent.length);
2814    
2815    // Split the content by section markers and process each section
2816    const sections = htmlContent.split(/###\s*Section\s*\d+:/i);
2817    console.log('📄 Text sections found:', sections.length);
2818    
2819    sections.forEach((section, index) => {
2820        if (!section.trim()) return;
2821        
2822        // Create a temporary div to parse this section's HTML
2823        const tempDiv = document.createElement('div');
2824        tempDiv.innerHTML = section.trim();
2825        
2826        // Extract text content from this section
2827        const sectionText = tempDiv.textContent || tempDiv.innerText || '';
2828        const cleanText = cleanTextForPDF(sectionText.trim());
2829        
2830        if (cleanText.length > 50) {
2831            // Extract images from this section with full details
2832            const images = tempDiv.querySelectorAll('img.story-entity-image, img');
2833            const imageData = Array.from(images).map(img => ({
2834                src: img.src,
2835                alt: cleanTextForPDF(img.alt || 'Image'),
2836                width: parseInt(img.width) || 100,
2837                height: parseInt(img.height) || 100
2838            }));
2839            
2840            console.log(`📄 Section ${index} found ${imageData.length} images:`, imageData.map(img => img.src));
2841            
2842            // Extract hyperlinks from this section
2843            const links = tempDiv.querySelectorAll('a[href]');
2844            const linkData = Array.from(links).map(link => ({
2845                text: cleanTextForPDF(link.textContent.trim()),
2846                url: link.href,
2847                title: cleanTextForPDF(link.title || '')
2848            })).filter(link => link.text.length > 0 && link.url.length > 0);
2849            
2850            console.log(`📄 Section ${index} found ${linkData.length} links:`, linkData.map(link => `${link.text} -> ${link.url}`));
2851            
2852            if (index === 0) {
2853                // First section is usually the title
2854                content.push({ 
2855                    type: 'title', 
2856                    text: cleanText, 
2857                    images: imageData,
2858                    links: linkData
2859                });
2860            } else {
2861                // Other sections are content
2862                content.push({ 
2863                    type: 'section', 
2864                    text: `Section ${index}: ${cleanText}`, 
2865                    images: imageData,
2866                    links: linkData
2867                });
2868            }
2869        }
2870    });
2871    
2872    // If section splitting didn't work, try alternative approaches
2873    if (content.length <= 1) {
2874        console.log(`📄 Section splitting failed, trying paragraph-based extraction...`);
2875        
2876        // Clear previous content
2877        content.length = 0;
2878        
2879        // Try to extract by paragraphs and headings
2880        const tempDiv = document.createElement('div');
2881        tempDiv.innerHTML = htmlContent;
2882        
2883        // Look for all text content and split by patterns
2884        const allText = tempDiv.textContent || tempDiv.innerText || '';
2885        console.log('📄 All text length:', allText.length);
2886        
2887        // Split by common section patterns
2888        const textSections = allText.split(/(?=###\s*Section\s*\d+:|(?:\*\*[^*]+\*\*)|(?:\n\s*\n))/i);
2889        console.log('📄 Text sections found:', textSections.length);
2890        
2891        textSections.forEach((section, index) => {
2892            const cleanText = cleanTextForPDF(section.trim());
2893            if (cleanText.length > 100) {
2894                // Extract images and links for this text section
2895                const sectionDiv = document.createElement('div');
2896                sectionDiv.innerHTML = htmlContent; // Use full HTML for extraction
2897                
2898                const images = Array.from(sectionDiv.querySelectorAll('img')).map(img => ({
2899                    src: img.src,
2900                    alt: cleanTextForPDF(img.alt || 'Image'),
2901                    width: parseInt(img.width) || 100,
2902                    height: parseInt(img.height) || 100
2903                }));
2904                
2905                const links = Array.from(sectionDiv.querySelectorAll('a[href]')).map(link => ({
2906                    text: cleanTextForPDF(link.textContent.trim()),
2907                    url: link.href,
2908                    title: cleanTextForPDF(link.title || '')
2909                }));
2910                
2911                if (cleanText.includes('Wikipedia Story') && index === 0) {
2912                    content.push({ type: 'title', text: cleanText, images: images, links: links });
2913                } else if (cleanText.startsWith('###') || cleanText.includes('Section')) {
2914                    content.push({ type: 'section', text: cleanText, images: images, links: links });
2915                } else {
2916                    content.push({ type: 'paragraph', text: cleanText, images: images, links: links });
2917                }
2918            }
2919        });
2920    }
2921    
2922    // Final fallback: extract everything as one large text block and split manually
2923    if (content.length <= 1) {
2924        console.log('📄 All extraction methods failed, using final fallback...');
2925        
2926        content.length = 0;
2927        
2928        const tempDiv = document.createElement('div');
2929        tempDiv.innerHTML = htmlContent;
2930        const fullText = cleanTextForPDF(tempDiv.textContent || tempDiv.innerText || '');
2931        
2932        // Extract all images and links from the full content
2933        const allImages = Array.from(tempDiv.querySelectorAll('img')).map(img => ({
2934            src: img.src,
2935            alt: cleanTextForPDF(img.alt || 'Image'),
2936            width: parseInt(img.width) || 100,
2937            height: parseInt(img.height) || 100
2938        }));
2939        
2940        const allLinks = Array.from(tempDiv.querySelectorAll('a[href]')).map(link => ({
2941            text: cleanTextForPDF(link.textContent.trim()),
2942            url: link.href,
2943            title: cleanTextForPDF(link.title || '')
2944        })).filter(link => link.text.length > 0 && link.url.length > 0);
2945        
2946        // If section splitting didn't work, try alternative approaches
2947        if (fullText.length > 100) {
2948            content.push({ type: 'paragraph', text: fullText, images: allImages, links: allLinks });
2949        }
2950    }
2951    
2952    console.log('📄 Extracted content pieces:', content.length);
2953    content.forEach((item, index) => {
2954        console.log(`📄 Content ${index + 1}: ${item.type} - ${item.text.substring(0, 100)}... (${item.images?.length || 0} images, ${item.links?.length || 0} links)`);
2955    });
2956    
2957    // Extract metadata
2958    let metadata = '';
2959    if (storyMetadata) {
2960        metadata = cleanTextForPDF(storyMetadata.textContent.trim());
2961    }
2962    
2963    return {
2964        title,
2965        content,
2966        metadata,
2967        searchTerm: title.replace('📖 ', '').replace(' - Wikipedia Story', ''),
2968        generatedDate: new Date().toLocaleDateString('en-US', {
2969            year: 'numeric',
2970            month: 'long',
2971            day: 'numeric'
2972        })
2973    };
2974}
2975
2976// Method to open the relationship map
2977function openRelationshipMap() {
2978    console.log('🗺️ [FancyUI] Opening AI-powered relationship map...');
2979    
2980    // FORCE CLEAR ALL CACHED DATA - Critical for preventing stale data
2981    window.lastSearchData = null;
2982    window.relationshipMapInstance = null;
2983    
2984    // ALWAYS get the most current data - prioritize the latest stored data
2985    let currentData = window.currentFancyData;
2986    
2987    // Log what data we're working with
2988    console.log('📊 [FancyUI] Current data check:', {
2989        hasCurrentData: !!currentData,
2990        searchTerm: currentData?.searchTerm,
2991        timestamp: currentData?.timestamp,
2992        totalResults: currentData?.totalResults
2993    });
2994    
2995    // If no current data, try to extract fresh data from the DOM
2996    if (!currentData || !currentData.infoboxes || currentData.infoboxes.length === 0) {
2997        console.log('🔍 [FancyUI] No current data found, attempting to extract fresh data from DOM...');
2998        
2999        // Try to extract fresh data from existing infoboxes
3000        if (typeof extractInfoboxDataAsJSON === 'function') {
3001            try {
3002                // Get the chat container element
3003                const chatContainer = document.getElementById('chat-container') || document.querySelector('.chat-container');
3004                if (chatContainer) {
3005                    currentData = extractInfoboxDataAsJSON(chatContainer);
3006                    console.log('✅ [FancyUI] Successfully extracted fresh data from DOM:', currentData);
3007                } else {
3008                    console.error('❌ [FancyUI] Chat container not found');
3009                }
3010            } catch (error) {
3011                console.error('❌ [FancyUI] Failed to extract fresh data from DOM:', error);
3012            }
3013        }
3014        
3015        // Final fallback - but log warning
3016        if (!currentData || !currentData.infoboxes || currentData.infoboxes.length === 0) {
3017            console.warn('⚠️ [FancyUI] Using fallback data - this may be stale!');
3018            currentData = window.lastSearchData;
3019        }
3020    }
3021    
3022    if (!currentData || !currentData.infoboxes || currentData.infoboxes.length === 0) {
3023        alert('❌ No infobox data available for relationship analysis. Please perform a search first.');
3024        return;
3025    }
3026    
3027    console.log('📊 [FancyUI] Using relationship map data:', {
3028        searchTerm: currentData.searchTerm,
3029        timestamp: currentData.timestamp,
3030        totalResults: currentData.totalResults
3031    });
3032    
3033    // Create full-screen relationship map container
3034    const mapContainer = document.createElement('div');
3035    mapContainer.id = 'relationship-map-container';
3036    mapContainer.style.cssText = `
3037        position: fixed;
3038        top: 0;
3039        left: 0;
3040        width: 100vw;
3041        height: 100vh;
3042        background: rgba(0,0,0,0.95);
3043        z-index: 10000;
3044        display: flex;
3045        flex-direction: column;
3046    `;
3047    
3048    // Create header for the map
3049    const mapHeader = document.createElement('div');
3050    mapHeader.style.cssText = `
3051        padding: 20px;
3052        background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
3053        color: white;
3054        display: flex;
3055        justify-content: space-between;
3056        align-items: center;
3057    `;
3058    
3059    mapHeader.innerHTML = `
3060        <div>
3061            <h1 style="margin: 0; font-size: 24px;">🗺️ WikiLive Search Map</h1>
3062            <p style="margin: 5px 0 0 0; opacity: 0.8;">Visualizing connections for "${currentData.searchTerm}"</p>
3063        </div>
3064        <button id="close-relationship-map" style="
3065            background: rgba(255,255,255,0.2);
3066            border: none;
3067            border-radius: 50%;
3068            width: 40px;
3069            height: 40px;
3070            color: white;
3071            cursor: pointer;
3072            font-size: 20px;
3073            font-weight: bold;
3074        ">✕</button>
3075    `;
3076    
3077    // Create map content area
3078    const mapContent = document.createElement('div');
3079    mapContent.id = 'relationship-map-content';
3080    mapContent.style.cssText = `
3081        flex: 1;
3082        position: relative;
3083        overflow: hidden;
3084    `;
3085    
3086    mapContainer.appendChild(mapHeader);
3087    mapContainer.appendChild(mapContent);
3088    document.body.appendChild(mapContainer);
3089    
3090    // Add close functionality
3091    document.getElementById('close-relationship-map').addEventListener('click', () => {
3092        mapContainer.remove();
3093    });
3094    
3095    // Add escape key listener
3096    const escapeHandler = (e) => {
3097        if (e.key === 'Escape') {
3098            mapContainer.remove();
3099            document.removeEventListener('keydown', escapeHandler);
3100        }
3101    };
3102    document.addEventListener('keydown', escapeHandler);
3103    
3104    // Load D3.js if not already loaded
3105    if (typeof d3 === 'undefined') {
3106        console.log('📚 [FancyUI] Loading D3.js library...');
3107        const d3Script = document.createElement('script');
3108        d3Script.src = 'https://d3js.org/d3.v7.min.js';
3109        d3Script.onload = () => {
3110            console.log('✅ [FancyUI] D3.js loaded successfully');
3111            initializeRelationshipMap();
3112        };
3113        d3Script.onerror = () => {
3114            console.error('❌ [FancyUI] Failed to load D3.js');
3115            mapContent.innerHTML = `
3116                <div style="display: flex; align-items: center; justify-content: center; height: 100%; color: white; text-align: center;">
3117                    <div>
3118                        <h2>❌ Failed to load visualization library</h2>
3119                        <p>Please check your internet connection and try again.</p>
3120                    </div>
3121                </div>
3122            `;
3123        };
3124        document.head.appendChild(d3Script);
3125    } else {
3126        initializeRelationshipMap();
3127    }
3128    
3129    function initializeRelationshipMap() {
3130        console.log('🚀 [FancyUI] Initializing relationship map with data:', currentData);
3131        
3132        // VALIDATE DATA FRESHNESS - Critical check
3133        if (!currentData || !currentData.searchTerm || !currentData.timestamp) {
3134            console.error('❌ [FancyUI] Invalid or missing data for relationship map:', currentData);
3135            alert('Unable to open relationship map: No valid search data available. Please perform a search first.');
3136            return;
3137        }
3138        
3139        // Log data being used for relationship map
3140        console.log('📋 [FancyUI] Using data for relationship map:', {
3141            searchTerm: currentData.searchTerm,
3142            timestamp: currentData.timestamp,
3143            totalResults: currentData.totalResults,
3144            firstFewTitles: currentData.infoboxes?.slice(0, 3).map(box => box.title)
3145        });
3146        
3147        // Clear any existing relationship map content first - CRITICAL for new searches
3148        const container = document.getElementById('relationship-map-content');
3149        if (container) {
3150            container.innerHTML = '';
3151            console.log('🧹 [FancyUI] Cleared existing relationship map container');
3152        }
3153        
3154        // Also clear any global relationship map instances
3155        if (window.relationshipMapInstance) {
3156            if (typeof window.relationshipMapInstance.clearVisualization === 'function') {
3157                window.relationshipMapInstance.clearVisualization();
3158            }
3159            window.relationshipMapInstance = null;
3160            console.log('🗑️ [FancyUI] Cleared previous relationship map instance');
3161        }
3162        
3163        // Create the relationship map instance
3164        const relationshipMap = new RelationshipMap('relationship-map-content');
3165        
3166        // Store the instance globally for cleanup
3167        window.relationshipMapInstance = relationshipMap;
3168        
3169        // Load and analyze the data
3170        relationshipMap.loadRelationshipData(currentData);
3171    }
3172}
3173
3174// Store the current infobox data globally for relationship analysis
3175function storeInfoboxData(result) {
3176    try {
3177        // Clear any previous data to prevent stale data issues
3178        window.currentInfoboxData = null;
3179        window.lastSearchData = null;
3180        
3181        // Store the fresh data
3182        window.currentInfoboxData = result;
3183        window.lastSearchData = result;
3184        
3185        console.log('💾 [FancyUI] Stored infobox data for relationship analysis:', {
3186            searchTerm: result.searchTerm || 'Unknown',
3187            timestamp: result.timestamp || Date.now(),
3188            totalResults: result.totalResults || (result.infoboxes ? result.infoboxes.length : 0),
3189            hasInfboxes: !!result.infoboxes,
3190            hasRealtimeFacts: !!result.realtime_facts,
3191            hasWikipediaImages: !!result.wikipediaImages
3192        });
3193    } catch (error) {
3194        console.error('💾 [FancyUI] Error in storeInfoboxData:', error);
3195        // Still store the data even if logging fails
3196        window.currentInfoboxData = result;
3197        window.lastSearchData = result;
3198    }
3199}
3200
3201// Category cache for fancy UI (5 minutes duration)
3202const fancyUICategoryCache = new Map();
3203const FANCY_UI_CATEGORY_CACHE_DURATION = 5 * 60 * 1000; // 5 minutes
3204
3205// Helper functions for Wikipedia category API calls in fancy UI
3206async function fetchWikipediaCategoryMembersFancyUI(pageTitle, limit = 20) {
3207    const cacheKey = `members_${pageTitle}_${limit}`;
3208    const cached = fancyUICategoryCache.get(cacheKey);
3209    
3210    // Return cached result if still valid
3211    if (cached && Date.now() - cached.timestamp < FANCY_UI_CATEGORY_CACHE_DURATION) {
3212        console.log(`[FancyUI] Using cached category members for: ${pageTitle}`);
3213        return cached.data;
3214    }
3215    
3216    try {
3217        console.log(`[FancyUI] Fetching fresh category members for: ${pageTitle}`);
3218        const response = await fetch('/.netlify/functions/api/wikipedia-category-members', {
3219            method: 'POST',
3220            headers: {
3221                'Content-Type': 'application/json'
3222            },
3223            body: JSON.stringify({ pageTitle, limit })
3224        });
3225        
3226        if (!response.ok) {
3227            throw new Error(`HTTP error! status: ${response.status}`);
3228        }
3229        
3230        const data = await response.json();
3231        
3232        // Cache the result
3233        fancyUICategoryCache.set(cacheKey, {
3234            data: data,
3235            timestamp: Date.now()
3236        });
3237        
3238        return data;
3239    } catch (error) {
3240        console.error(`[FancyUI] Error fetching category members for ${pageTitle}:`, error);
3241        return { success: false, error: error.message };
3242    }
3243}
3244
3245async function fetchWikipediaPageCategoriesFancyUI(pageTitle) {
3246    const cacheKey = `categories_${pageTitle}`;
3247    const cached = fancyUICategoryCache.get(cacheKey);
3248    
3249    // Return cached result if still valid
3250    if (cached && Date.now() - cached.timestamp < FANCY_UI_CATEGORY_CACHE_DURATION) {
3251        console.log(`[FancyUI] Using cached page categories for: ${pageTitle}`);
3252        return cached.data;
3253    }
3254    
3255    try {
3256        console.log(`[FancyUI] Fetching fresh page categories for: ${pageTitle}`);
3257        const response = await fetch('/.netlify/functions/api/wikipedia-page-categories', {
3258            method: 'POST',
3259            headers: {
3260                'Content-Type': 'application/json'
3261            },
3262            body: JSON.stringify({ pageTitle })
3263        });
3264        
3265        if (!response.ok) {
3266            throw new Error(`HTTP error! status: ${response.status}`);
3267        }
3268        
3269        const data = await response.json();
3270        
3271        // Cache the result
3272        fancyUICategoryCache.set(cacheKey, {
3273            data: data,
3274            timestamp: Date.now()
3275        });
3276        
3277        return data;
3278    } catch (error) {
3279        console.error(`[FancyUI] Error fetching page categories for ${pageTitle}:`, error);
3280        return { success: false, error: error.message };
3281    }
3282}
3283
3284// 🌐 CRITICAL: Expose createFancyUIPreview to global scope for main.js access
3285if (typeof window !== 'undefined') {
3286    window.createFancyUIPreview = createFancyUIPreview;
3287    console.log('🎯 [Global] createFancyUIPreview function exposed to window scope');
3288} else if (typeof global !== 'undefined') {
3289    global.createFancyUIPreview = createFancyUIPreview;
3290    console.log('🎯 [Global] createFancyUIPreview function exposed to global scope');
3291}

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.