var Search = (function() { var dropdownVisible = false; var selectedIndex = -1; var results = []; var debounceTimer = null; var currentInput = null; // Page title mapping var PAGE_TITLES = { 'index.html': 'Home', 'terminology.html': 'Terminology', 'techniques.html': 'Techniques', 'use-cases.html': 'Use Cases', 'model-types.html': 'Model Types', 'prompts.html': 'Prompt Guide', 'math.html': 'Math & Concepts', 'chat.html': 'Chat', 'image-gen.html': 'Image Gen' }; // Content to skip during crawling var SKIP_CONTENT = [ 'AI Cheat Sheet', 'Toggle dark mode', 'Toggle menu', 'Send', 'Ask me anything about AI', 'Ask a Question', 'Ask anything about AI', 'Powered by your configured LLM', 'Try AI right now', 'Full Chat', 'Generate', 'Clear', 'Image Generation', 'Enter image prompt' ]; function init() { var navInner = document.querySelector('.nav-inner'); if (!navInner) return; var brand = navInner.querySelector('.nav-brand'); if (!brand) return; // Create search wrapper var searchWrapper = document.createElement('div'); searchWrapper.className = 'sidebar-search'; searchWrapper.style.cssText = 'position:relative; margin-top:0.5rem; margin-bottom:0.25rem;'; var searchInput = document.createElement('input'); searchInput.type = 'text'; searchInput.className = 'sidebar-search-input'; searchInput.placeholder = 'Search...'; searchInput.setAttribute('aria-label', 'Search all pages'); searchInput.autocomplete = 'off'; searchWrapper.appendChild(searchInput); // Insert after brand brand.parentNode.insertBefore(searchWrapper, brand.nextSibling); // Move results container inside search wrapper for correct absolute positioning var existingContainer = document.getElementById('searchResultsContainer'); if (existingContainer) { searchWrapper.appendChild(existingContainer); } currentInput = searchInput; // Event listeners searchInput.addEventListener('input', function(e) { clearTimeout(debounceTimer); var query = e.target.value.trim(); if (query.length === 0) { hideDropdown(); return; } debounceTimer = setTimeout(function() { performSearch(query); }, 150); }); searchInput.addEventListener('keydown', function(e) { if (!dropdownVisible || results.length === 0) return; if (e.key === 'ArrowDown') { e.preventDefault(); selectedIndex = Math.min(selectedIndex + 1, results.length - 1); updateSelection(); } else if (e.key === 'ArrowUp') { e.preventDefault(); selectedIndex = Math.max(selectedIndex - 1, 0); updateSelection(); } else if (e.key === 'Enter') { e.preventDefault(); if (selectedIndex >= 0 && selectedIndex < results.length) { navigateToResult(results[selectedIndex]); } else if (results.length > 0) { navigateToResult(results[0]); } } else if (e.key === 'Escape') { e.preventDefault(); hideDropdown(); searchInput.blur(); } }); searchInput.addEventListener('focus', function() { if (results.length > 0 && currentInput.value.trim().length > 0) { showDropdown(); } }); // Close dropdown when clicking outside document.addEventListener('click', function(e) { if (!searchWrapper.contains(e.target)) { hideDropdown(); } }); } function crawlPage(url, callback) { fetch(url) .then(function(response) { return response.text(); }) .then(function(html) { var parser = new DOMParser(); var doc = parser.parseFromString(html, 'text/html'); var results = []; // Get page title var pageTitle = PAGE_TITLES[doc.title.replace(' - Cheat Sheet', '').replace('AI Cheat Sheet', '').trim()] || doc.title; // Extract def-cards var defCards = doc.querySelectorAll('.def-card'); defCards.forEach(function(card) { var heading = card.querySelector('h3'); var p = card.querySelector('p'); var example = card.querySelector('.example'); var category = card.querySelector('.category'); if (!heading || !p) return; var headingText = heading.textContent.trim(); var defText = p.textContent.trim(); var catText = category ? category.textContent.trim() : ''; var exText = example ? example.textContent.trim() : ''; var fullText = (catText + ' ' + headingText + ' ' + defText + ' ' + exText).trim(); if (fullText.length < 10) return; // Skip boilerplate content var skip = false; for (var i = 0; i < SKIP_CONTENT.length; i++) { if (fullText.indexOf(SKIP_CONTENT[i]) === 0) { skip = true; break; } } if (skip) return; results.push({ page: pageTitle, url: url, heading: headingText, category: catText, snippet: defText, fullText: fullText.toLowerCase() }); }); // Extract table rows var tables = doc.querySelectorAll('.glossary-table tbody tr'); tables.forEach(function(row) { var tds = row.querySelectorAll('td'); if (tds.length < 2) return; var acronym = tds[0].textContent.trim(); var meaning = tds[1].textContent.trim(); results.push({ page: pageTitle, url: url, heading: acronym, category: 'Acronym', snippet: meaning, fullText: (acronym + ' ' + meaning).toLowerCase() }); }); // Extract section headings for pages without def-cards (like use-cases, model-types) var sections = doc.querySelectorAll('h2.section-title'); sections.forEach(function(h2) { var sectionName = h2.textContent.trim(); // Check if there are cards under this section var next = h2.nextElementSibling; var cards = []; while (next && !next.classList.contains('section-title')) { if (next.classList && (next.classList.contains('card') || next.classList.contains('use-card') || next.classList.contains('prompt-block') || next.classList.contains('def-card'))) { cards.push(next); } next = next.nextElementSibling; } cards.forEach(function(card) { var h3 = card.querySelector('h3'); var p = card.querySelector('p'); if (!h3 || !p) return; var headingText = h3.textContent.trim(); var cardText = p.textContent.trim(); // Skip if already captured as def-card if (card.classList.contains('def-card')) return; results.push({ page: pageTitle, url: url, heading: headingText, category: sectionName, snippet: cardText, fullText: (sectionName + ' ' + headingText + ' ' + cardText).toLowerCase() }); }); }); // For pages with .use-grid, extract use-cards var useGrid = doc.querySelector('.use-grid'); if (useGrid) { var useCards = useGrid.querySelectorAll('.use-card'); useCards.forEach(function(card) { var h3 = card.querySelector('h3'); var p = card.querySelector('p'); if (!h3 || !p) return; var headingText = h3.textContent.trim(); var cardText = p.textContent.trim(); results.push({ page: pageTitle, url: url, heading: headingText, category: 'Use Case', snippet: cardText, fullText: (headingText + ' ' + cardText).toLowerCase() }); }); } // For prompt guide, extract prompt-blocks var promptBlocks = doc.querySelectorAll('.prompt-block'); if (promptBlocks.length > 0) { promptBlocks.forEach(function(block) { var h3 = block.querySelector('h3'); var label = block.querySelector('.label'); var p = block.querySelector('p'); if (!h3) return; var headingText = h3.textContent.trim(); var labelText = label ? label.textContent.trim() : ''; var cardText = p ? p.textContent.trim() : ''; results.push({ page: pageTitle, url: url, heading: headingText, category: labelText || 'Prompt', snippet: cardText, fullText: (headingText + ' ' + labelText + ' ' + cardText).toLowerCase() }); }); } // For math page, extract def-cards (already handled above) // For model-types, also grab comparison table var modelTables = doc.querySelectorAll('.glossary-table'); if (modelTables.length > 0) { modelTables.forEach(function(table) { var rows = table.querySelectorAll('tbody tr'); rows.forEach(function(row) { var tds = row.querySelectorAll('td'); if (tds.length === 0) return; var rowText = ''; for (var i = 0; i < tds.length; i++) { rowText += ' ' + tds[i].textContent.trim(); } if (rowText.length < 5) return; results.push({ page: pageTitle, url: url, heading: tds[0] ? tds[0].textContent.trim() : 'Table Entry', category: 'Comparison', snippet: rowText.trim(), fullText: rowText.toLowerCase() }); }); }); } callback(results); }) .catch(function() { callback([]); }); } function crawlAllPages(callback) { var pages = [ { url: '/index.html', title: 'Home' }, { url: '/pages/terminology.html', title: 'Terminology' }, { url: '/pages/techniques.html', title: 'Techniques' }, { url: '/pages/use-cases.html', title: 'Use Cases' }, { url: '/pages/model-types.html', title: 'Model Types' }, { url: '/pages/prompts.html', title: 'Prompt Guide' }, { url: '/pages/math.html', title: 'Math & Concepts' } ]; var allResults = []; var completed = 0; if (pages.length === 0) { callback(allResults); return; } pages.forEach(function(page) { crawlPage(page.url, function(results) { allResults = allResults.concat(results); completed++; if (completed === pages.length) { callback(allResults); } }); }); } function performSearch(query) { var q = query.toLowerCase().trim(); if (q.length === 0) { hideDropdown(); return; } crawlAllPages(function(allResults) { searchResults(allResults, q); }); } function searchResults(allResults, q) { var qWords = q.split(/\s+/).filter(function(w) { return w.length > 0; }); var scored = []; for (var i = 0; i < allResults.length; i++) { var item = allResults[i]; var text = item.fullText || ''; var score = 0; if (text.indexOf(q) !== -1) score += 100; for (var j = 0; j < qWords.length; j++) { if (text.indexOf(qWords[j]) !== -1) score += 10; if (item.heading && item.heading.toLowerCase().indexOf(qWords[j]) === 0) score += 20; if (item.category && item.category.toLowerCase().indexOf(qWords[j]) !== -1) score += 5; } if (score > 0) { scored.push({ item: item, score: score }); } } scored.sort(function(a, b) { return b.score - a.score; }); var topResults = scored.slice(0, 10).map(function(s) { return s.item; }); displayResults(topResults, q); } function highlightText(text, query) { if (!text || !query) return escapeHTML(text); var escaped = escapeHTML(text); var qWords = query.toLowerCase().split(/\s+/).filter(function(w) { return w.length > 0; }); for (var i = 0; i < qWords.length; i++) { var word = qWords[i]; var regex = new RegExp('(' + escapeRegex(word) + ')', 'gi'); escaped = escaped.replace(regex, '$1'); } return escaped; } function escapeRegex(str) { return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); } function escapeHTML(str) { var div = document.createElement('div'); div.textContent = str; return div.innerHTML; } function displayResults(foundResults, query) { results = foundResults; selectedIndex = -1; if (results.length === 0) { showDropdown(); var container = document.getElementById('searchResultsContainer'); if (container) { container.innerHTML = '