9a848e8c53
Implement a command palette accessible via CMD+K/Ctrl+K using the ninja-keys web component. Features include: - New /api/cmd-k endpoint serving dynamic CV entries (experiences, projects, courses) - Language-aware responses with 1-hour cache headers - Scroll-to-section functionality for quick navigation - Enhanced keyboard shortcuts modal with CMD+K documentation - Comprehensive test coverage for API and UI interactions Also includes cleanup of deprecated debug test files and various UI polish improvements to contact form, themes, and action bar components.
219 lines
7.5 KiB
Plaintext
219 lines
7.5 KiB
Plaintext
-- ==============================================================================
|
|
-- CV Site - Hyperscript Functions
|
|
-- ==============================================================================
|
|
-- Global hyperscript functions for CV interactive features
|
|
-- Keeps HTML templates clean and behavior organized
|
|
|
|
-- ==============================================================================
|
|
-- PRINT FUNCTIONS
|
|
-- ==============================================================================
|
|
|
|
def printFriendly()
|
|
-- Store current state
|
|
set container to the first .cv-container
|
|
set paper to the first .cv-paper
|
|
set wasClean to container.classList.contains('theme-clean')
|
|
set wasLong to paper.classList.contains('cv-long')
|
|
set currentZoom to localStorage.getItem('cv-zoom') or '100'
|
|
|
|
-- Apply print-friendly settings
|
|
if wasClean is false
|
|
add .theme-clean to container
|
|
end
|
|
remove .cv-long from paper
|
|
add .cv-short to paper
|
|
|
|
-- Reset zoom for consistent printing
|
|
set #zoom-wrapper's *zoom to 1
|
|
|
|
-- Let CSS apply, then print
|
|
wait 50ms
|
|
call window.print()
|
|
|
|
-- Wait for print dialog to close, then restore
|
|
wait 100ms
|
|
|
|
-- Restore original theme
|
|
if wasClean is false
|
|
remove .theme-clean from container
|
|
end
|
|
|
|
-- Restore original length
|
|
if wasLong is true
|
|
remove .cv-short from paper
|
|
add .cv-long to paper
|
|
end
|
|
|
|
-- Restore original zoom by triggering slider input event
|
|
if currentZoom is not '100'
|
|
set #zoom-slider's value to currentZoom
|
|
send input to #zoom-slider
|
|
end
|
|
end
|
|
|
|
-- ==============================================================================
|
|
-- SCROLL BEHAVIOR
|
|
-- ==============================================================================
|
|
|
|
def initScrollBehavior()
|
|
set :lastScroll to 0
|
|
set :scrollThreshold to 100
|
|
set :keepHeaderVisible to false
|
|
end
|
|
|
|
def handleScroll()
|
|
set currentScroll to window.pageYOffset
|
|
set menu to the first .navigation-menu
|
|
set isMenuOpen to menu.classList.contains('menu-open')
|
|
|
|
-- Calculate if at bottom (within 50px)
|
|
set scrollHeight to document.documentElement.scrollHeight
|
|
set clientHeight to document.documentElement.clientHeight
|
|
set isAtBottom to (scrollHeight - currentScroll - clientHeight) < 50
|
|
|
|
-- Reset keepHeaderVisible when scrolling up
|
|
if currentScroll < :lastScroll
|
|
set :keepHeaderVisible to false
|
|
end
|
|
|
|
-- Header visibility: Scrolling down past threshold
|
|
if currentScroll > :scrollThreshold and currentScroll > :lastScroll and :keepHeaderVisible is false
|
|
add .header-hidden to .action-bar
|
|
if isMenuOpen is true
|
|
add .header-hidden to menu
|
|
end
|
|
end
|
|
|
|
-- Header visibility: Scrolling up past threshold
|
|
if currentScroll > :scrollThreshold and (currentScroll <= :lastScroll or :keepHeaderVisible is true)
|
|
remove .header-hidden from .action-bar
|
|
if isMenuOpen is true
|
|
remove .header-hidden from menu
|
|
end
|
|
end
|
|
|
|
-- Header visibility: At top
|
|
if currentScroll <= :scrollThreshold
|
|
remove .header-hidden from .action-bar
|
|
if isMenuOpen is true
|
|
remove .header-hidden from menu
|
|
end
|
|
end
|
|
|
|
-- Back to top button visibility (with null check)
|
|
set backToTop to #back-to-top
|
|
if backToTop is not null
|
|
if currentScroll > 300
|
|
set backToTop's *display to 'flex'
|
|
end
|
|
if currentScroll <= 300
|
|
set backToTop's *display to 'none'
|
|
end
|
|
end
|
|
|
|
-- At-bottom class for fixed buttons (with null checks)
|
|
if isAtBottom
|
|
if backToTop is not null then add .at-bottom to backToTop end
|
|
if #info-button is not null then add .at-bottom to #info-button end
|
|
if #shortcuts-button is not null then add .at-bottom to #shortcuts-button end
|
|
if #download-button is not null then add .at-bottom to #download-button end
|
|
if #print-friendly-button is not null then add .at-bottom to #print-friendly-button end
|
|
if #cmd-k-button is not null then add .at-bottom to #cmd-k-button end
|
|
add .at-bottom to .color-theme-switcher
|
|
if #zoom-toggle-button is not null then add .at-bottom to #zoom-toggle-button end
|
|
end
|
|
|
|
if not isAtBottom
|
|
if backToTop is not null then remove .at-bottom from backToTop end
|
|
if #info-button is not null then remove .at-bottom from #info-button end
|
|
if #shortcuts-button is not null then remove .at-bottom from #shortcuts-button end
|
|
if #download-button is not null then remove .at-bottom from #download-button end
|
|
if #print-friendly-button is not null then remove .at-bottom from #print-friendly-button end
|
|
if #cmd-k-button is not null then remove .at-bottom from #cmd-k-button end
|
|
remove .at-bottom from .color-theme-switcher
|
|
if #zoom-toggle-button is not null then remove .at-bottom from #zoom-toggle-button end
|
|
end
|
|
|
|
-- Update last scroll position
|
|
set :lastScroll to currentScroll
|
|
end
|
|
|
|
-- ==============================================================================
|
|
-- EXPAND/COLLAPSE ALL SECTIONS
|
|
-- ==============================================================================
|
|
-- Expand all <details> elements in the CV
|
|
def expandAllSections(evt)
|
|
if evt then call evt.preventDefault() end
|
|
set details to document.querySelectorAll('details')
|
|
for d in details
|
|
set d's @open to ''
|
|
end
|
|
end
|
|
|
|
-- Collapse all <details> elements in the CV
|
|
def collapseAllSections(evt)
|
|
if evt then call evt.preventDefault() end
|
|
set details to document.querySelectorAll('details')
|
|
for d in details
|
|
call d.removeAttribute('open')
|
|
end
|
|
end
|
|
|
|
-- ==============================================================================
|
|
-- FOOTER HOVER INTERACTION
|
|
-- ==============================================================================
|
|
-- Adds/removes footer-hovered class to fixed buttons when hovering footer
|
|
def setFooterHover(show)
|
|
set buttons to document.querySelectorAll('.download-btn, .print-friendly-btn, .shortcuts-btn, .info-button, .back-to-top, .color-theme-switcher')
|
|
for btn in buttons
|
|
if show
|
|
add .footer-hovered to btn
|
|
else
|
|
remove .footer-hovered from btn
|
|
end
|
|
end
|
|
end
|
|
|
|
-- ==============================================================================
|
|
-- MODAL HELPERS
|
|
-- ==============================================================================
|
|
-- Close modal when clicking backdrop (outside content)
|
|
def closeOnBackdrop(modal, evt)
|
|
if evt.target is modal
|
|
call modal.close()
|
|
end
|
|
end
|
|
|
|
-- ==============================================================================
|
|
-- SCROLL HELPERS
|
|
-- ==============================================================================
|
|
-- Smooth scroll to top of page
|
|
def scrollToTop(evt)
|
|
call evt.preventDefault()
|
|
call window.scrollTo({top: 0, behavior: 'smooth'})
|
|
end
|
|
|
|
-- ==============================================================================
|
|
-- NAVIGATION SCROLL
|
|
-- ==============================================================================
|
|
-- Smooth scroll to a section by ID
|
|
def scrollToSection(evt, sectionId)
|
|
if evt then call evt.preventDefault() end
|
|
set el to document.getElementById(sectionId)
|
|
if el is not null
|
|
call el.scrollIntoView({ behavior: 'smooth' })
|
|
-- Close the menu after navigation
|
|
set menu to document.querySelector('.navigation-menu')
|
|
if menu is not null
|
|
remove .menu-hover from menu
|
|
remove .menu-open from menu
|
|
end
|
|
end
|
|
end
|
|
|
|
-- ==============================================================================
|
|
-- KEYBOARD SHORTCUTS
|
|
-- ==============================================================================
|
|
-- Note: Keyboard event handlers are now defined inline in the body tag
|
|
-- because hyperscript 0.9.12 doesn't support nested event handlers (on ... inside def)
|