/** * Ninja Keys Initialization * CMD+K command palette for CV site navigation and actions * * Dynamic entries are fetched from the backend API: * GET /api/cmd-k?lang={en|es} * Returns: { experiences: [], projects: [], courses: [] } */ (function() { 'use strict'; // Wait for DOM to be ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initNinjaKeys); } else { initNinjaKeys(); } async function initNinjaKeys() { const ninjaKeys = document.getElementById('cmd-k-bar'); if (!ninjaKeys) { console.warn('ninja-keys element not found'); return; } // Get current language from HTML lang attribute const lang = document.documentElement.lang || 'en'; const currentYear = new Date().getFullYear(); /** * Smooth scroll to section * @param {string} sectionId - The ID of the section to scroll to */ function scrollToSection(sectionId) { const section = document.getElementById(sectionId); if (section) { section.scrollIntoView({ behavior: 'smooth', block: 'start' }); ninjaKeys.close(); } } /** * Open a modal dialog * @param {string} modalId - The ID of the modal to open */ function openModal(modalId) { const modal = document.getElementById(modalId); if (modal && modal.showModal) { modal.showModal(); // Dispatch 'show' event for hyperscript handlers modal.dispatchEvent(new CustomEvent('show')); } ninjaKeys.close(); } /** * Trigger a toggle checkbox click * @param {string} toggleId - The ID of the toggle to click */ function clickToggle(toggleId) { const toggle = document.getElementById(toggleId); if (toggle) { toggle.checked = !toggle.checked; toggle.dispatchEvent(new Event('change', { bubbles: true })); } ninjaKeys.close(); } /** * Download file with specific parameters * @param {string} url - The URL to navigate to or download from * @param {boolean} newTab - Whether to open in new tab */ function downloadFile(url, newTab = false) { if (newTab) { window.open(url, '_blank'); } else { window.location.href = url; } ninjaKeys.close(); } /** * Open external link * @param {string} url - The URL to open */ function openLink(url) { window.open(url, '_blank', 'noopener,noreferrer'); ninjaKeys.close(); } // ======================================================================== // DYNAMIC ENTRIES - Fetched from API // ======================================================================== /** * Fetch dynamic entries from the backend API * @returns {Promise<{experiences: Array, projects: Array, courses: Array}>} */ async function fetchDynamicEntries() { try { const response = await fetch(`/api/cmd-k?lang=${lang}`); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return await response.json(); } catch (error) { console.error('Failed to fetch CMD+K data:', error); return { experiences: [], projects: [], courses: [] }; } } /** Category icon mapping */ const categoryIcons = { cli: 'mdi:console', app: 'mdi:apple', web: 'mdi:web', webapp: 'mdi:web', plugin: 'mdi:puzzle', sdk: 'mdi:package-variant', contrib: 'mdi:source-pull' }; /** Build icon HTML — use project logo if available, fallback to iconify */ function makeIcon(logoFile, folder, fallbackIcon) { if (logoFile) { return ``; } return ``; } function mapExperienceActions(experiences) { return experiences.map(exp => ({ id: exp.id, title: exp.title, section: exp.section, keywords: exp.keywords.toLowerCase(), icon: makeIcon(exp.icon, 'companies', 'mdi:office-building'), handler: () => scrollToSection(exp.id) })); } function mapProjectActions(projects) { return projects.map(proj => ({ id: proj.id, title: proj.title, section: proj.section, keywords: proj.keywords.toLowerCase(), icon: makeIcon(proj.icon, 'projects', categoryIcons[proj.category] || 'mdi:web'), handler: () => scrollToSection(proj.id) })); } function mapCourseActions(courses) { return courses.map(course => ({ id: course.id, title: course.title, section: course.section, keywords: course.keywords.toLowerCase(), icon: makeIcon(course.icon, 'courses', 'mdi:school'), handler: () => scrollToSection(course.id) })); } // ======================================================================== // STATIC ACTIONS - Navigation, Shortcuts, Downloads, etc. // ======================================================================== const staticActions = [ // ============================================ // NAVIGATION SECTION // ============================================ { id: 'nav-top', title: 'Jump to Top', section: 'Navigation', keywords: 'scroll up beginning start', icon: '', handler: () => { window.scrollTo({ top: 0, behavior: 'smooth' }); ninjaKeys.close(); } }, { id: 'nav-experience', title: 'Jump to Experience', section: 'Navigation', keywords: 'work jobs career employment', icon: '', handler: () => scrollToSection('experience') }, { id: 'nav-education', title: 'Jump to Education', section: 'Navigation', keywords: 'university degree school college', icon: '', handler: () => scrollToSection('education') }, { id: 'nav-skills', title: 'Jump to Skills', section: 'Navigation', keywords: 'technologies abilities competencies', icon: '', handler: () => scrollToSection('skills') }, { id: 'nav-projects', title: 'Jump to Projects', section: 'Navigation', keywords: 'portfolio websites apps', icon: '', handler: () => scrollToSection('projects') }, { id: 'nav-courses', title: 'Jump to Courses', section: 'Navigation', keywords: 'training certifications learning', icon: '', handler: () => scrollToSection('courses') }, { id: 'nav-languages', title: 'Jump to Languages', section: 'Navigation', keywords: 'spanish english portuguese', icon: '', handler: () => scrollToSection('languages') }, { id: 'nav-awards', title: 'Jump to Awards', section: 'Navigation', keywords: 'achievements recognition prizes', icon: '', handler: () => scrollToSection('awards') }, { id: 'nav-other', title: 'Jump to Other Info', section: 'Navigation', keywords: 'additional references personal', icon: '', handler: () => scrollToSection('other') }, // ============================================ // SOCIAL LINKS // ============================================ { id: 'social-linkedin', title: 'Open LinkedIn Profile', section: 'Social', keywords: 'linkedin professional network connect', icon: '', handler: () => openLink('https://www.linkedin.com/in/juan-andres-moreno-rubio') }, { id: 'social-github', title: 'Open GitHub Profile', section: 'Social', keywords: 'github code repositories open source', icon: '', handler: () => openLink('https://github.com/juanatsap') }, { id: 'social-domestika', title: 'Open Domestika Portfolio', section: 'Social', keywords: 'domestika portfolio design creative', icon: '', handler: () => openLink('https://www.domestika.org/es/txeo/portfolio') }, { id: 'social-website', title: 'Open Personal Website', section: 'Social', keywords: 'website personal portfolio cv', icon: '', handler: () => openLink('https://juan.andres.morenorub.io') }, // ============================================ // KEYBOARD SHORTCUTS SECTION // ============================================ { id: 'shortcut-length', title: 'Toggle CV Length', hotkey: 'l', section: 'Shortcuts', keywords: 'short long extended compact full', icon: '', handler: () => clickToggle('lengthToggle') }, { id: 'shortcut-icons', title: 'Toggle Icons Visibility', hotkey: 'i', section: 'Shortcuts', keywords: 'icons show hide emoji', icon: '', handler: () => clickToggle('iconToggle') }, { id: 'shortcut-theme', title: 'Toggle Visual Theme', hotkey: 'v', section: 'Shortcuts', keywords: 'theme clean default style visual', icon: '', handler: () => clickToggle('themeToggle') }, { id: 'shortcut-help', title: 'Show Shortcuts Help', hotkey: '?', section: 'Shortcuts', keywords: 'help shortcuts keyboard keys', icon: '', handler: () => openModal('shortcuts-modal') }, { id: 'shortcut-print', title: 'Print CV', hotkey: 'mod+p', section: 'Shortcuts', keywords: 'print pdf paper', icon: '', handler: () => { ninjaKeys.close(); setTimeout(() => window.print(), 100); } }, // ============================================ // PDF DOWNLOADS SECTION // ============================================ { id: 'download-pdf-default', title: 'Download PDF (Default - 5 pages)', section: 'Downloads', keywords: 'pdf download default recommended', icon: '', handler: () => downloadFile(`/cv-jamr-${currentYear}-${lang}.pdf`) }, { id: 'download-pdf-short', title: 'Download PDF (Short - 4 pages)', section: 'Downloads', keywords: 'pdf download short compact brief', icon: '', handler: () => downloadFile(`/export/pdf?lang=${lang}&length=short&icons=show&version=clean`) }, { id: 'download-pdf-extended', title: 'Download PDF (Extended - 9 pages)', section: 'Downloads', keywords: 'pdf download extended long full complete', icon: '', handler: () => downloadFile(`/export/pdf?lang=${lang}&length=long&icons=show&version=with_skills`) }, { id: 'open-pdf-modal', title: 'Open PDF Options', section: 'Downloads', keywords: 'pdf options modal choose select', icon: '', handler: () => openModal('pdf-modal') }, // ============================================ // TEXT CV SECTION // ============================================ { id: 'view-text-cv', title: 'View Text CV (Plain Text)', section: 'Downloads', keywords: 'text plain txt view terminal cli', icon: '', handler: () => downloadFile(`/text?lang=${lang}`, true) }, { id: 'download-text-cv', title: 'Download Text CV (.txt)', section: 'Downloads', keywords: 'text download txt file save', icon: '', handler: () => downloadFile(`/text?lang=${lang}&download=true`) }, // ============================================ // ACTIONS SECTION // ============================================ { id: 'action-contact', title: 'Open Contact Form', section: 'Actions', keywords: 'contact email message send hire', icon: '', handler: () => openModal('contact-modal') }, { id: 'action-info', title: 'Show Site Info', section: 'Actions', keywords: 'info about site technology stack', icon: '', handler: () => openModal('info-modal') }, { id: 'action-zoom', title: 'Toggle Zoom Controls', section: 'Actions', keywords: 'zoom magnify scale size', icon: '', handler: () => { const zoomControl = document.getElementById('zoom-control'); if (zoomControl) { const isVisible = zoomControl.style.display !== 'none'; zoomControl.style.display = isVisible ? 'none' : 'block'; } ninjaKeys.close(); } }, { id: 'action-language-en', title: 'Switch to English', section: 'Actions', keywords: 'english language en switch', icon: '', handler: () => { if (lang !== 'en') { window.location.href = '/?lang=en'; } ninjaKeys.close(); } }, { id: 'action-language-es', title: 'Switch to Spanish', section: 'Actions', keywords: 'spanish espanol language es switch cambiar idioma', icon: '', handler: () => { if (lang !== 'es') { window.location.href = '/?lang=es'; } ninjaKeys.close(); } }, { id: 'action-color-theme', title: 'Change Color Theme', section: 'Actions', keywords: 'dark light color theme mode', icon: '', handler: () => { const colorSwitcher = document.querySelector('.color-theme-switcher'); if (colorSwitcher) { colorSwitcher.click(); } ninjaKeys.close(); } } ]; // ======================================================================== // BUILD FINAL ACTIONS ARRAY // ======================================================================== // Fetch dynamic entries from API and combine with static actions const dynamicData = await fetchDynamicEntries(); const actions = [ ...staticActions, ...mapExperienceActions(dynamicData.experiences || []), ...mapProjectActions(dynamicData.projects || []), ...mapCourseActions(dynamicData.courses || []) ]; // Assign actions to ninja-keys ninjaKeys.data = actions; // Listen for ninja-keys events ninjaKeys.addEventListener('selected', (event) => { console.log('Ninja Keys: Selected action', event.detail); }); // Apply custom styling ninjaKeys.style.setProperty('--ninja-z-index', '10000'); ninjaKeys.style.setProperty('--ninja-accent-color', '#667eea'); ninjaKeys.style.setProperty('--ninja-font-family', 'Quicksand, sans-serif'); // Log counts for debugging const expCount = (dynamicData.experiences || []).length; const projCount = (dynamicData.projects || []).length; const courseCount = (dynamicData.courses || []).length; console.log(`Ninja Keys initialized with ${actions.length} actions (${expCount} experiences, ${projCount} projects, ${courseCount} courses)`); } })();