/** * Keyboard Command Palette (Spotlight Search) * Instantiates the command palette UI, binds listeners for hotkeys, * and handles arrow-key navigation. */ (function() { // Generate Command Palette HTML dynamically var html = ` `; var tempDiv = document.createElement('div'); tempDiv.innerHTML = html.trim(); document.body.appendChild(tempDiv.firstChild); var palette = document.getElementById('cmd-palette'); var input = document.getElementById('cmd-palette-input'); var resultsContainer = document.getElementById('cmd-palette-results'); var activeIndex = 0; var filteredItems = []; // Ensure we have a default list of items if window.cmdPaletteItems is missing var items = window.cmdPaletteItems || [ { title: "Home", url: "/", category: "Navigation" }, { title: "All Projects", url: "/projects/", category: "Navigation" }, { title: "About", url: "/about.html", category: "Navigation" }, { title: "Contact", url: "/contact.html", category: "Navigation" } ]; function showPalette() { palette.classList.add('visible'); input.value = ''; input.focus(); activeIndex = 0; renderResults(""); document.body.style.overflow = 'hidden'; } function hidePalette() { palette.classList.remove('visible'); document.body.style.overflow = ''; } function renderResults(query) { resultsContainer.innerHTML = ''; query = query.toLowerCase().trim(); // Filter items filteredItems = items.filter(function(item) { return item.title.toLowerCase().includes(query) || item.category.toLowerCase().includes(query); }); if (filteredItems.length === 0) { resultsContainer.innerHTML = '
No results found
'; return; } // Render filtered items filteredItems.forEach(function(item, idx) { var div = document.createElement('div'); div.className = 'cmd-palette-item' + (idx === activeIndex ? ' active' : ''); div.innerHTML = ` ${item.title} ${item.category} `; div.addEventListener('click', function() { window.location.href = item.url; }); resultsContainer.appendChild(div); }); // Ensure active item is scrolled into view var activeNode = resultsContainer.children[activeIndex]; if (activeNode) { activeNode.scrollIntoView({ block: 'nearest' }); } } input.addEventListener('input', function() { activeIndex = 0; renderResults(input.value); }); // Handle Keyboard Navigation inside Modal document.addEventListener('keydown', function(e) { var isOpen = palette.classList.contains('visible'); // Toggle triggers if ((e.key === 'k' && (e.metaKey || e.ctrlKey)) || (e.key === '/' && !isOpen && !['INPUT', 'TEXTAREA'].includes(document.activeElement.tagName))) { e.preventDefault(); showPalette(); return; } if (!isOpen) return; switch (e.key) { case 'Escape': e.preventDefault(); hidePalette(); break; case 'ArrowDown': e.preventDefault(); activeIndex = (activeIndex + 1) % filteredItems.length; renderResults(input.value); break; case 'ArrowUp': e.preventDefault(); activeIndex = (activeIndex - 1 + filteredItems.length) % filteredItems.length; renderResults(input.value); break; case 'Enter': e.preventDefault(); if (filteredItems[activeIndex]) { window.location.href = filteredItems[activeIndex].url; } break; } }); // Close on backdrop click palette.querySelector('.cmd-palette-backdrop').addEventListener('click', hidePalette); // Bind trigger button if it exists var triggerBtn = document.getElementById('cmd-palette-btn'); if (triggerBtn) { triggerBtn.addEventListener('click', function(e) { e.preventDefault(); showPalette(); }); } })();