document.addEventListener('DOMContentLoaded', () => { const searchToggleBtn = document.querySelector('.search-toggle'); if (!searchToggleBtn) return; // Check if elasticlunr is available if (typeof elasticlunr === 'undefined') { console.warn('Elasticlunr not loaded. Search will not work.'); return; } // Modal elements const overlay = document.getElementById('search-overlay'); const input = document.getElementById('search-input'); const resultsContainer = document.getElementById('search-results'); if (!overlay || !input || !resultsContainer) return; let index = null; let isIndexLoading = false; let selectedIndex = -1; let resultItems = []; // Initialize search index (loaded lazily on first open) const initSearch = async () => { if (index || isIndexLoading) return; isIndexLoading = true; try { // Fetch the Zola-generated index const response = await fetch('/search_index.en.js'); const jsText = await response.text(); // The file defines `window.searchIndex = {...}` // We use a safe evaluation approach if it's purely assignment // Or just append it as a script tag const script = document.createElement('script'); script.innerHTML = jsText; document.body.appendChild(script); // Wait for it to parse setTimeout(() => { if (window.searchIndex) { index = elasticlunr.Index.load(window.searchIndex); // Search immediately if there's text if (input.value.trim().length > 0) { performSearch(); } } }, 50); } catch (e) { console.error('Failed to load search index:', e); } }; // Open Modal const openSearch = () => { overlay.classList.add('active'); // Slight delay ensures the browser focuses the input after the display transition starts setTimeout(() => { input.focus(); }, 50); initSearch(); document.body.style.overflow = 'hidden'; // Prevent background scrolling }; // Close Modal const closeSearch = () => { overlay.classList.remove('active'); input.value = ''; resultsContainer.innerHTML = ''; document.body.style.overflow = ''; selectedIndex = -1; resultItems = []; }; // Bind Toggle Button searchToggleBtn.addEventListener('click', (e) => { e.preventDefault(); if (overlay.classList.contains('active')) { closeSearch(); } else { openSearch(); } }); // Close on overlay click overlay.addEventListener('click', (e) => { if (e.target === overlay) closeSearch(); }); // Handle Keyboard Shortcuts document.addEventListener('keydown', (e) => { // Ctrl+K -> Open Search if ((e.ctrlKey || e.metaKey) && e.key === 'k') { e.preventDefault(); openSearch(); } if (!overlay.classList.contains('active')) return; // Escape -> Close Search if (e.key === 'Escape') { closeSearch(); } // Arrow Navigation if (e.key === 'ArrowDown' || e.key === 'ArrowUp') { e.preventDefault(); if (resultItems.length === 0) return; if (e.key === 'ArrowDown') { selectedIndex = (selectedIndex + 1) % resultItems.length; } else { selectedIndex = (selectedIndex - 1 + resultItems.length) % resultItems.length; } updateSelection(); } // Enter -> Navigate to selected if (e.key === 'Enter') { e.preventDefault(); if (selectedIndex >= 0 && resultItems[selectedIndex]) { window.location.href = resultItems[selectedIndex].href; } else if (resultItems.length > 0) { // Default to first item if none selected window.location.href = resultItems[0].href; } } }); const updateSelection = () => { resultItems.forEach((el, i) => { if (i === selectedIndex) { el.classList.add('selected'); el.scrollIntoView({ block: 'nearest' }); } else { el.classList.remove('selected'); } }); }; // Perform the Search const performSearch = () => { if (!index) { resultsContainer.innerHTML = '