PageSourceSearch

https://pointarena.github.io/script.js

js pointarena.github.io collected 2026-10-03 09:46:25 UTC 31,576 bytes, 723 lines download raw bytes

1document.addEventListener('DOMContentLoaded', function() {
2    // Navbar active link highlighting
3    const navLinks = document.querySelectorAll('.nav-link');
4    navLinks.forEach(link => {
5        link.addEventListener('click', function() {
6            navLinks.forEach(l => l.classList.remove('active'));
7            this.classList.add('active');
8        });
9    });
10
11    // Smooth scrolling for anchor links
12    document.querySelectorAll('a[href^="#"]').forEach(anchor => {
13        anchor.addEventListener('click', function(e) {
14            e.preventDefault();
15            const targetId = this.getAttribute('href');
16            if (targetId === '#') return;
17            
18            const targetElement = document.querySelector(targetId);
19            if (targetElement) {
20                window.scrollTo({
21                    top: targetElement.offsetTop - 80,
22                    behavior: 'smooth'
23                });
24            }
25        });
26    });
27
28    // Model filtering functionality
29    const modelTypeFilter = document.querySelector('.filter-group select:first-of-type');
30    const providerFilter = document.querySelector('.filter-group select:last-of-type');
31    const modelCards = document.querySelectorAll('.model-card');
32
33    if (modelTypeFilter && providerFilter) {
34        const filterModels = () => {
35            const typeValue = modelTypeFilter.value;
36            const providerValue = providerFilter.value;
37            
38            modelCards.forEach(card => {
39                const modelType = card.querySelector('.badge:first-of-type').textContent;
40                const provider = card.querySelector('.badge:last-of-type').textContent;
41                
42                const typeMatch = typeValue === 'All Types' || modelType === typeValue;
43                const providerMatch = providerValue === 'All Providers' || provider.includes(providerValue);
44                
45                card.closest('.col-md-4').style.display = typeMatch && providerMatch ? 'block' : 'none';
46            });
47        };
48        
49        modelTypeFilter.addEventListener('change', filterModels);
50        providerFilter.addEventListener('change', filterModels);
51    }
52
53    // Leaderboard sorting functionality for all ranking tables
54    document.querySelectorAll('.leaderboard-table').forEach(leaderboardTable => {
55        const headers = leaderboardTable.querySelectorAll('thead th');
56        let currentSortCol = 0; // Default sort by rank
57        let sortDirection = 'asc';
58        
59        headers.forEach((header, index) => {
60            // Skip first column (rank) as it's already sorted
61            if (index === 0) return;
62            
63            header.style.cursor = 'pointer';
64            header.addEventListener('click', () => {
65                sortTable(leaderboardTable, index);
66            });
67            
68            // Add sort indicator
69            const sortIndicator = document.createElement('span');
70            sortIndicator.classList.add('sort-indicator');
71            sortIndicator.style.marginLeft = '5px';
72            sortIndicator.innerHTML = '↕';
73            header.appendChild(sortIndicator);
74        });
75        
76        function sortTable(table, columnIndex) {
77            const tbody = table.querySelector('tbody');
78            const rows = Array.from(tbody.querySelectorAll('tr'));
79            
80            // Reset all indicators
81            headers.forEach(h => {
82                const indicator = h.querySelector('.sort-indicator');
83                if (indicator) indicator.innerHTML = '↕';
84            });
85            
86            // Set new indicator
87            const indicator = headers[columnIndex].querySelector('.sort-indicator');
88            
89            // Toggle sort direction if clicking on same column
90            if (currentSortCol === columnIndex) {
91                sortDirection = sortDirection === 'asc' ? 'desc' : 'asc';
92            } else {
93                sortDirection = 'asc';
94                currentSortCol = columnIndex;
95            }
96            
97            // Update indicator
98            indicator.innerHTML = sortDirection === 'asc' ? '↑' : '↓';
99            
100            // Sort the rows
101            rows.sort((a, b) => {
102                let aValue = a.cells[columnIndex].textContent;
103                let bValue = b.cells[columnIndex].textContent;
104                
105                // Handle numeric values
106                if (!isNaN(parseFloat(aValue))) {
107                    aValue = parseFloat(aValue);
108                    bValue = parseFloat(bValue);
109                }
110                
111                if (aValue < bValue) return sortDirection === 'asc' ? -1 : 1;
112                if (aValue > bValue) return sortDirection === 'asc' ? 1 : -1;
113                return 0;
114            });
115            
116            // Re-append rows in new order
117            rows.forEach(row => tbody.appendChild(row));
118        }
119    });
120
121    // Card hover effects enhancement
122    const cards = document.querySelectorAll('.model-card');
123    cards.forEach(card => {
124        card.addEventListener('mouseenter', function() {
125            this.style.transform = 'translateY(-8px)';
126            this.style.boxShadow = '0 12px 20px rgba(0, 0, 0, 0.15)';
127        });
128        
129        card.addEventListener('mouseleave', function() {
130            this.style.transform = 'translateY(0)';
131            this.style.boxShadow = '0 4px 6px rgba(0, 0, 0, 0.1)';
132        });
133    });
134
135    // Add a small animation to hero section
136    const heroContent = document.querySelector('.hero-section .container');
137    if (heroContent) {
138        heroContent.style.opacity = '0';
139        heroContent.style.transform = 'translateY(20px)';
140        heroContent.style.transition = 'opacity 0.8s ease, transform 0.8s ease';
141        
142        setTimeout(() => {
143            heroContent.style.opacity = '1';
144            heroContent.style.transform = 'translateY(0)';
145        }, 300);
146    }
147
148    // Add scroll reveal animation to sections
149    const sections = document.querySelectorAll('section:not(.hero-section)');
150    const revealSection = (entries, observer) => {
151        entries.forEach(entry => {
152            if (entry.isIntersecting) {
153                entry.target.classList.add('revealed');
154                observer.unobserve(entry.target);
155            }
156        });
157    };
158    
159    const sectionObserver = new IntersectionObserver(revealSection, {
160        root: null,
161        threshold: 0.15,
162    });
163    
164    sections.forEach(section => {
165        section.style.opacity = '0';
166        section.style.transform = 'translateY(30px)';
167        section.style.transition = 'opacity 0.8s ease, transform 0.8s ease';
168        
169        sectionObserver.observe(section);
170    });
171    
172    document.addEventListener('scroll', () => {
173        sections.forEach(section => {
174            const sectionTop = section.getBoundingClientRect().top;
175            const windowHeight = window.innerHeight;
176            
177            if (sectionTop < windowHeight * 0.85) {
178                section.style.opacity = '1';
179                section.style.transform = 'translateY(0)';
180            }
181        });
182    });
183
184    // CSV Data Loading functionality
185    const csvDownloadButtons = document.querySelectorAll('.csv-download .btn');
186    csvDownloadButtons.forEach(button => {
187        button.addEventListener('click', function(e) {
188            e.preventDefault();
189            const section = this.closest('section');
190            const sectionId = section.id;
191            
192            // Define CSV file paths based on section ID
193            let csvPath;
194            switch(sectionId) {
195                case 'point-bench':
196                    csvPath = 'data/point-bench.csv';
197                    break;
198                case 'point-battle':
199                    csvPath = 'data/point-battle.csv';
200                    break;
201                case 'point-act':
202                    csvPath = 'data/point-act.csv';
203                    break;
204                default:
205                    console.error('Unknown section ID');
206                    return;
207            }
208            
209            // Download the CSV file
210            downloadCSV(csvPath);
211        });
212    });
213    
214    function downloadCSV(filePath) {
215        fetch(filePath)
216            .then(response => {
217                if (!response.ok) {
218                    throw new Error(`HTTP error! Status: ${response.status}`);
219                }
220                return response.blob();
221            })
222            .then(blob => {
223                // Create a temporary URL for the blob
224                const url = window.URL.createObjectURL(blob);
225                
226                // Create a temporary anchor element and trigger download
227                const a = document.createElement('a');
228                a.style.display = 'none';
229                a.href = url;
230                a.download = filePath.split('/').pop();
231                document.body.appendChild(a);
232                a.click();
233                
234                // Clean up
235                window.URL.revokeObjectURL(url);
236                a.remove();
237            })
238            .catch(error => {
239                console.error('Error downloading CSV:', error);
240                alert('Failed to download CSV file. Please try again later.');
241            });
242    }
243
244    // Function to create directory structure for CSV files if implementing
245    function createDataDirectories() {
246        // This would typically be done on the server side
247        // For GitHub Pages, you would manually create these directories and files
248        console.log('Data directories should be created manually for GitHub Pages');
249    }
250
251    // Function to parse CSV data
252    function parseCSV(csv) {
253        const lines = csv.trim().split('\n');
254        const headers = lines[0].split(',');
255        
256        return lines.slice(1).map(line => {
257            const values = line.split(',');
258            const entry = {};
259            
260            headers.forEach((header, index) => {
261                entry[header.trim()] = values[index]?.trim() || '';
262            });
263            
264            return entry;
265        });
266    }
267
268    // Function to load CSV data
269    async function loadCSV(url) {
270        const response = await fetch(url);
271        const text = await response.text();
272        return parseCSV(text);
273    }
274
275    // Function to safely parse numeric values
276    function safeParseFloat(value, fallback = 0) {
277        if (value === undefined || value === null || value === '') {
278            return fallback;
279        }
280        
281        // Remove any % character if present
282        if (typeof value === 'string' && value.includes('%')) {
283            value = value.replace('%', '');
284        }
285        
286        const parsed = parseFloat(value);
287        return isNaN(parsed) ? fallback : parsed;
288    }
289
290    // Function to update Point-Bench leaderboard
291    async function updatePointBenchTable() {
292        try {
293            const data = await loadCSV('data/point-bench.csv');
294            const tableBody = document.querySelector('#point-bench .leaderboard-table tbody');
295            tableBody.innerHTML = '';
296            
297            // Sort by Average score (descending)
298            data.sort((a, b) => safeParseFloat(b.Average) - safeParseFloat(a.Average));
299            
300            // Model URL mapping
301            const modelUrls = {
302                "gemini-2.0-Flash": "https://cloud.google.com/vertex-ai/generative-ai/docs/models/gemini/2-0-flash",
303                "gemini-2.5-flash-preview-04-17": "https://cloud.google.com/vertex-ai/generative-ai/docs/models/gemini/2-5-flash",
304                "GPT-4o": "https://openai.com/api/",
305                "GPT-4.1": "https://openai.com/api/",
306                "o3": "https://openai.com/api/",
307                "Qwen2.5-VL-7B-Instruct": "https://huggingface.co/Qwen/Qwen2.5-VL-7B-Instruct",
308                "Qwen2.5-VL-32B-Instruct": "https://huggingface.co/Qwen/Qwen2.5-VL-32B-Instruct",
309                "Qwen2.5-VL-72B-Instruct": "https://huggingface.co/Qwen/Qwen2.5-VL-72B-Instruct",
310                "llava-onevision-qwen2-7b-ov-hf": "https://huggingface.co/llava-hf/llava-onevision-qwen2-7b-ov-hf",
311                "llava-onevision-qwen2-72b-ov-hf": "https://huggingface.co/llava-hf/llava-onevision-qwen2-72b-ov-hf",
312                "Molmo-7B-D": "https://huggingface.co/allenai/Molmo-7B-D-0924",
313                "Molmo-7B-O": "https://huggingface.co/allenai/Molmo-7B-O-0924",
314                "Molmo-72B": "https://huggingface.co/allenai/Molmo-72B-0924",
315                "gemini-2.5-pro-preview-05-06": "https://cloud.google.com/vertex-ai/generative-ai/docs/models/gemini/2-5-pro",
316                "grok-2-vision-latest": "https://docs.x.ai/docs/models",
317                "claude-3-7-sonnet-20250219": "https://docs.anthropic.com/en/docs/about-claude/models/all-models",
318                "Gemini-Robotics-ER 1.5": "https://deepmind.google/",
319                "Human": "#"
320            };
321            
322            // Display all results (not just top 5)
323            data.forEach((row, index) => {
324                const tr = document.createElement('tr');
325                const modelName = row[''] || row.Model || '';
326                const modelUrl = modelUrls[modelName] || '#';
327                
328                tr.innerHTML = `
329                    <td>${index + 1}</td>
330                    <td><a href="${modelUrl}" target="_blank" rel="noopener noreferrer">${modelName}</a></td>
331                    <td class="score">${safeParseFloat(row.Affordance).toFixed(2)}</td>
332                    <td class="score">${safeParseFloat(row.Spatial).toFixed(2)}</td>
333                    <td class="score">${safeParseFloat(row.Reasoning).toFixed(2)}</td>
334                    <td class="score">${safeParseFloat(row.Steerability).toFixed(2)}</td>
335                    <td class="score">${safeParseFloat(row.Counting).toFixed(2)}</td>
336                    <td class="score">${safeParseFloat(row.Average).toFixed(3)}</td>
337                `;
338                
339                tableBody.appendChild(tr);
340            });
341            
342            // Hide loading indicator
343            document.querySelector('#point-bench .loading-indicator').style.display = 'none';
344        } catch (error) {
345            console.error('Error loading point-bench.csv:', error);
346            document.querySelector('#point-bench .loading-indicator').innerHTML = 
347                '<p class="text-danger">Error loading data. Please try again later.</p>';
348        }
349    }
350
351    // Function to update Point-Battle leaderboard
352    async function updatePointBattleTable() {
353        try {
354            const data = await loadCSV('data/point-battle.csv');
355            const tableBody = document.querySelector('#point-battle .leaderboard-table tbody');
356            tableBody.innerHTML = '';
357            
358            // Sort by Elo Rating (descending) just to be sure
359            data.sort((a, b) => safeParseFloat(b['Elo Rating']) - safeParseFloat(a['Elo Rating']));
360            
361            // Display all available results
362            data.forEach((row, index) => {
363                const tr = document.createElement('tr');
364                
365                tr.innerHTML = `
366                    <td>${index + 1}</td>
367                    <td>${row.Model || ''}</td>
368                    <td class="score">${safeParseFloat(row['Elo Rating']).toFixed(1)}</td>
369                    <td class="score">${safeParseFloat(row.Wins, 0)}</td>
370                    <td class="score">${safeParseFloat(row.Losses, 0)}</td>
371                    <td class="score">${safeParseFloat(row.Games, 0)}</td>
372                    <td class="score">${row['Win Rate'] || '0%'}</td>
373                    <td class="score">${safeParseFloat(row['Lower CI']).toFixed(1)}</td>
374                    <td class="score">${safeParseFloat(row['Upper CI']).toFixed(1)}</td>
375                `;
376                
377                tableBody.appendChild(tr);
378            });
379            
380            // Hide loading indicator
381            document.querySelector('#point-battle .loading-indicator').style.display = 'none';
382        } catch (error) {
383            console.error('Error loading point-battle.csv:', error);
384            document.querySelector('#point-battle .loading-indicator').innerHTML = 
385                '<p class="text-danger">Error loading data. Please try again later.</p>';
386        }
387    }
388
389    // Function to update Point-Act leaderboard
390    async function updatePointActTable() {
391        try {
392            const data = await loadCSV('data/point-act.csv');
393            const tableBody = document.querySelector('#point-act .leaderboard-table tbody');
394            tableBody.innerHTML = '';
395            
396            // Sort by Success Rate (descending)
397            data.sort((a, b) => safeParseFloat(b['Success Rate']) - safeParseFloat(a['Success Rate']));
398            
399            data.forEach((row) => {
400                const tr = document.createElement('tr');
401                
402                tr.innerHTML = `
403                    <td>${row.Challenge || ''}</td>
404                    <td class="score">${safeParseFloat(row['Success Rate'])}</td>
405                    <td class="score">${safeParseFloat(row['SUS Score'])}</td>
406                `;
407                
408                tableBody.appendChild(tr);
409            });
410            
411            // Hide loading indicator
412            document.querySelector('#point-act .loading-indicator').style.display = 'none';
413        } catch (error) {
414            console.error('Error loading point-act.csv:', error);
415            document.querySelector('#point-act .loading-indicator').innerHTML = 
416                '<p class="text-danger">Error loading data. Please try again later.</p>';
417        }
418    }
419
420    // Gallery functionality
421    let galleryData = [];
422    let currentExampleIndex = 0;
423    let filteredGalleryData = [];
424
425    async function loadGalleryData() {
426        try {
427            const response = await fetch('data/gallery/data.json');
428            if (!response.ok) {
429                throw new Error(`Failed to load gallery data: ${response.status}`);
430            }
431            galleryData = await response.json();
432            
433            // Use the data directly without checking all files first
434            // This avoids multiple HEAD requests that can lead to broken pipes
435            filteredGalleryData = [...galleryData];
436            
437            console.log(`Loaded ${filteredGalleryData.length} gallery examples`);
438            return filteredGalleryData;
439        } catch (error) {
440            console.error('Error loading gallery data:', error);
441            return [];
442        }
443    }
444
445    function displayGalleryExample(index) {
446        if (!filteredGalleryData || !filteredGalleryData.length) {
447            // Handle case where no valid examples exist
448            const carousel = document.querySelector('#exampleCarousel .carousel-inner');
449            if (carousel) {
450                carousel.innerHTML = '<div class="carousel-item active"><div class="text-center p-5">No valid examples found. Please check the image files.</div></div>';
451            }
452            
453            const queryText = document.getElementById('queryText');
454            const categoryBadge = document.getElementById('categoryBadge');
455            const countInfo = document.getElementById('countInfo');
456            
457            if (queryText) queryText.textContent = '';
458            if (categoryBadge) categoryBadge.textContent = '';
459            if (countInfo) countInfo.style.display = 'none';
460            return;
461        }
462        
463        // Get example data
464        const example = filteredGalleryData[index];
465        if (!example) return;
466        
467        // Get DOM elements
468        const carousel = document.querySelector('#exampleCarousel .carousel-inner');
469        const queryText = document.getElementById('queryText');
470        const categoryBadge = document.getElementById('categoryBadge');
471        const countInfo = document.getElementById('countInfo');
472        
473        if (!carousel || !queryText || !categoryBadge || !countInfo) {
474            console.error('Missing required DOM elements for gallery display');
475            return;
476        }
477        
478        // Clear existing items
479        carousel.innerHTML = '';
480        
481        // Create carousel item
482        const carouselItem = document.createElement('div');
483        carouselItem.className = 'carousel-item active';
484        
485        // Create image container with both original and mask
486        const imageContainer = document.createElement('div');
487        imageContainer.className = 'gallery-image-container position-relative';
488        
489        // Original image with error handling
490        const originalImg = document.createElement('img');
491        originalImg.className = 'gallery-image d-block w-100 original-image';
492        originalImg.alt = 'Original image';
493        originalImg.src = `data/gallery/${example.image_filename}`;
494        originalImg.dataset.src = `data/gallery/${example.image_filename}`;
495        originalImg.onerror = function() {
496            this.onerror = null;
497            this.src = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iODAwIiBoZWlnaHQ9IjQwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iODAwIiBoZWlnaHQ9IjQwMCIgZmlsbD0iI2YxZjFmMSIvPjx0ZXh0IHg9IjQwMCIgeT0iMjAwIiBmb250LWZhbWlseT0iQXJpYWwiIGZvbnQtc2l6ZT0iMzZweCIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZmlsbD0iIzk5OTk5OSI+SW1hZ2Ugbm90IGF2YWlsYWJsZTwvdGV4dD48L3N2Zz4=';
498        };
499        
500        // Mask image with error handling
501        const maskImg = document.createElement('img');
502        maskImg.className = 'gallery-image d-block w-100 mask-image';
503        maskImg.alt = 'Mask';
504        maskImg.src = `data/gallery/${example.mask_filename}`;
505        maskImg.dataset.src = `data/gallery/${example.mask_filename}`;
506        maskImg.style.display = 'none';
507        maskImg.onerror = function() {
508            this.onerror = null;
509            this.src = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iODAwIiBoZWlnaHQ9IjQwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iODAwIiBoZWlnaHQ9IjQwMCIgZmlsbD0iI2YxZjFmMSIvPjx0ZXh0IHg9IjQwMCIgeT0iMjAwIiBmb250LWZhbWlseT0iQXJpYWwiIGZvbnQtc2l6ZT0iMzZweCIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZmlsbD0iIzk5OTk5OSI+TWFzayBpbWFnZSBub3QgYXZhaWxhYmxlPC90ZXh0Pjwvc3ZnPg==';
510        };
511        
512        // Add images to container
513        imageContainer.appendChild(originalImg);
514        imageContainer.appendChild(maskImg);
515        
516        // Add image container to carousel item
517        carouselItem.appendChild(imageContainer);
518        
519        // Add carousel item to carousel
520        carousel.appendChild(carouselItem);
521        
522        // Update metadata
523        queryText.textContent = example.user_input || 'No query provided';
524        categoryBadge.textContent = example.category || 'Unknown';
525        
526        // Update count info if available
527        if (example.count) {
528            countInfo.textContent = `Count: ${example.count}`;
529            countInfo.style.display = 'inline-block';
530        } else {
531            countInfo.style.display = 'none';
532        }
533        
534        // Set color based on category
535        const categoryColors = {
536            'spatial': 'bg-primary',
537            'reasoning': 'bg-success',
538            'affordable': 'bg-warning',
539            'steerable': 'bg-info',
540            'counting': 'bg-danger'
541        };
542        
543        // Reset all classes and add the appropriate one
544        categoryBadge.className = 'badge';
545        categoryBadge.classList.add(categoryColors[example.category] || 'bg-secondary');
546        
547        currentExampleIndex = index;
548    }
549
550    function filterGalleryByCategory(category) {
551        if (category === 'all') {
552            filteredGalleryData = [...galleryData];
553        } else {
554            filteredGalleryData = galleryData.filter(item => item.category === category);
555        }
556        
557        if (filteredGalleryData.length > 0) {
558            displayGalleryExample(0);
559        } else {
560            // No examples for this category
561            const carousel = document.querySelector('#exampleCarousel .carousel-inner');
562            carousel.innerHTML = '<div class="carousel-item active"><div class="text-center p-5">No examples found for this category</div></div>';
563            
564            const queryText = document.getElementById('queryText');
565            const categoryBadge = document.getElementById('categoryBadge');
566            const countInfo = document.getElementById('countInfo');
567            
568            queryText.textContent = '';
569            categoryBadge.textContent = '';
570            countInfo.style.display = 'none';
571        }
572    }
573
574    async function initGallery() {
575        await loadGalleryData();
576        
577        if (galleryData.length) {
578            displayGalleryExample(0);
579            
580            // Initialize view mode buttons
581            const showOriginalBtn = document.getElementById('showOriginal');
582            const showMaskBtn = document.getElementById('showMask');
583            const showOverlayBtn = document.getElementById('showOverlay');
584            
585            if (showOriginalBtn && showMaskBtn && showOverlayBtn) {
586                // Show original image
587                showOriginalBtn.addEventListener('click', () => {
588                    // Load original images if they're still showing placeholders
589                    document.querySelectorAll('.original-image').forEach(img => {
590                        if (img.src !== img.dataset.src && img.dataset.src) {
591                            img.src = img.dataset.src;
592                        }
593                        img.style.display = 'block';
594                    });
595                    document.querySelectorAll('.mask-image').forEach(img => img.style.display = 'none');
596                    document.querySelectorAll('.gallery-image-container').forEach(container => container.classList.remove('overlay-mode'));
597                    
598                    // Update button states
599                    showOriginalBtn.classList.add('active');
600                    showMaskBtn.classList.remove('active');
601                    showOverlayBtn.classList.remove('active');
602                });
603                
604                // Show mask only
605                showMaskBtn.addEventListener('click', () => {
606                    document.querySelectorAll('.original-image').forEach(img => img.style.display = 'none');
607                    
608                    // Load mask images if they're still showing placeholders
609                    document.querySelectorAll('.mask-image').forEach(img => {
610                        if (img.src !== img.dataset.src && img.dataset.src) {
611                            img.src = img.dataset.src;
612                        }
613                        img.style.display = 'block';
614                    });
615                    
616                    document.querySelectorAll('.gallery-image-container').forEach(container => container.classList.remove('overlay-mode'));
617                    
618                    // Update button states
619                    showOriginalBtn.classList.remove('active');
620                    showMaskBtn.classList.add('active');
621                    showOverlayBtn.classList.remove('active');
622                });
623                
624                // Show overlay (both original and mask)
625                showOverlayBtn.addEventListener('click', () => {
626                    // Load original images if they're still showing placeholders
627                    document.querySelectorAll('.original-image').forEach(img => {
628                        if (img.src !== img.dataset.src && img.dataset.src) {
629                            img.src = img.dataset.src;
630                        }
631                        img.style.display = 'block';
632                    });
633                    
634                    // Load mask images if they're still showing placeholders
635                    document.querySelectorAll('.mask-image').forEach(img => {
636                        if (img.src !== img.dataset.src && img.dataset.src) {
637                            img.src = img.dataset.src;
638                        }
639                        img.style.display = 'block';
640                        img.style.opacity = '0.7';
641                        img.style.position = 'absolute';
642                        img.style.top = '0';
643                        img.style.left = '0';
644                    });
645                    
646                    document.querySelectorAll('.gallery-image-container').forEach(container => container.classList.add('overlay-mode'));
647                    
648                    // Update button states
649                    showOriginalBtn.classList.remove('active');
650                    showMaskBtn.classList.remove('active');
651                    showOverlayBtn.classList.add('active');
652                });
653            }
654            
655            // Initialize category filter
656            const categoryFilter = document.getElementById('categoryFilter');
657            if (categoryFilter) {
658                categoryFilter.addEventListener('change', function() {
659                    filterGalleryByCategory(this.value);
660                });
661            }
662            
663            // Initialize carousel navigation
664            const carousel = document.getElementById('exampleCarousel');
665            if (carousel) {
666                const prevButton = carousel.querySelector('.carousel-control-prev');
667                const nextButton = carousel.querySelector('.carousel-control-next');
668                
669                prevButton.addEventListener('click', () => {
670                    currentExampleIndex = (currentExampleIndex - 1 + filteredGalleryData.length) % filteredGalleryData.length;
671                    displayGalleryExample(currentExampleIndex);
672                });
673                
674                nextButton.addEventListener('click', () => {
675                    currentExampleIndex = (currentExampleIndex + 1) % filteredGalleryData.length;
676                    displayGalleryExample(currentExampleIndex);
677                });
678            }
679        }
680    }
681
682    // Initialize tables and features
683    async function initializeTables() {
684        try {
685            await Promise.all([
686                updatePointBenchTable(),
687                updatePointBattleTable(),
688                updatePointActTable(),
689                initGallery()
690            ]);
691            
692            // Hide all loading indicators once data is loaded
693            document.querySelectorAll('.loading-indicator').forEach(indicator => {
694                indicator.style.display = 'none';
695            });
696        } catch (error) {
697            console.error('Error initializing tables:', error);
698            document.querySelectorAll('.loading-indicator').forEach(indicator => {
699                indicator.innerHTML = '<p class="text-danger">Error loading data. Please try again later.</p>';
700            });
701        }
702    }
703    
704    // Initialize everything
705    initializeTables();
706
707    // Function to copy BibTeX citation
708    function copyBibtex() {
709        const bibtexText = document.querySelector('.bibtex-container pre').textContent;
710        navigator.clipboard.writeText(bibtexText)
711            .then(() => {
712                const copyBtn = document.querySelector('.copy-button');
713                const originalText = copyBtn.textContent;
714                copyBtn.textContent = 'Copied!';
715                setTimeout(() => {
716                    copyBtn.textContent = originalText;
717                }, 2000);
718            })
719            .catch(err => {
720                console.error('Failed to copy text: ', err);
721            });
722    }
723}); 

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.