refactor: simplify menu and toast interactions with CSS-driven animations

- Moved menu hover logic from JavaScript to CSS selectors, reducing JS to minimal bridge code
- Replaced JavaScript-based toast timing with pure CSS animation lifecycle (slide in → stay → fade out)
- Removed unnecessary event handlers and legacy compatibility code for cleaner implementation
This commit is contained in:
juanatsap
2025-11-12 19:23:46 +00:00
parent fda034ca78
commit 81f8161dd2
3 changed files with 57 additions and 107 deletions
+27 -92
View File
@@ -7,120 +7,58 @@
// NAVIGATION & MENU SYSTEM
// =============================================================================
// Hover-based menu control
// Minimal menu control - CSS handles most logic, JS just bridges hamburger to menu
function initMenuSystem() {
const hamburgerBtn = document.querySelector('.hamburger-btn');
const menu = document.getElementById('navigation-menu');
if (!hamburgerBtn || !menu) return;
// Show menu on hamburger hover
hamburgerBtn.addEventListener('mouseenter', function() {
menu.classList.add('menu-hover');
hamburgerBtn.setAttribute('aria-expanded', 'true');
});
// Show menu on hamburger hover - CSS handles the rest
hamburgerBtn.addEventListener('mouseenter', () => menu.classList.add('menu-hover'));
// Hide menu when leaving hamburger (only if not hovering menu)
hamburgerBtn.addEventListener('mouseleave', function() {
// Hide menu when leaving hamburger if not hovering menu
hamburgerBtn.addEventListener('mouseleave', () => {
setTimeout(() => {
if (!menu.matches(':hover')) {
menu.classList.remove('menu-hover');
hamburgerBtn.setAttribute('aria-expanded', 'false');
}
if (!menu.matches(':hover')) menu.classList.remove('menu-hover');
}, 100);
});
// Hide menu when leaving menu itself
menu.addEventListener('mouseleave', function() {
menu.classList.remove('menu-hover');
hamburgerBtn.setAttribute('aria-expanded', 'false');
});
// Hide menu when leaving menu
menu.addEventListener('mouseleave', () => menu.classList.remove('menu-hover'));
// Position submenu dynamically
// Position submenu dynamically (needed because fixed positioning)
const submenuTrigger = document.querySelector('.menu-item-submenu');
const submenuContent = document.querySelector('.submenu-content');
if (submenuTrigger && submenuContent) {
submenuTrigger.addEventListener('mouseenter', function() {
const triggerRect = submenuTrigger.getBoundingClientRect();
submenuContent.style.top = `${triggerRect.top}px`;
submenuContent.style.top = `${this.getBoundingClientRect().top}px`;
});
}
}
// Legacy toggle function - kept for compatibility
window.toggleMenu = function() {
const menu = document.getElementById('navigation-menu');
const btn = document.querySelector('.hamburger-btn');
if (menu.classList.contains('menu-open')) {
menu.classList.remove('menu-open');
btn.setAttribute('aria-expanded', 'false');
} else {
menu.classList.add('menu-open');
btn.setAttribute('aria-expanded', 'true');
}
};
// Flag to keep header visible after navigation
let keepHeaderVisible = false;
// Expand all sections
// Expand/collapse all sections utility functions
window.expandAllSections = function(event) {
event.preventDefault();
const allDetails = document.querySelectorAll('details');
allDetails.forEach(detail => {
detail.setAttribute('open', '');
});
document.querySelectorAll('details').forEach(d => d.setAttribute('open', ''));
};
// Collapse all sections
window.collapseAllSections = function(event) {
event.preventDefault();
const allDetails = document.querySelectorAll('details');
allDetails.forEach(detail => {
detail.removeAttribute('open');
});
document.querySelectorAll('details').forEach(d => d.removeAttribute('open'));
};
// Toggle submenu - no longer needed for hover, but kept for compatibility
window.toggleSubmenu = function(event) {
event.preventDefault();
const submenuContainer = event.currentTarget.parentElement;
submenuContainer.classList.toggle('submenu-open');
};
// Close menu when navigation links are clicked (native smooth scroll handles scrolling)
// CSS scroll-padding-top handles the header offset automatically
document.addEventListener('click', function(e) {
// Check if clicked element is a navigation link inside the menu
// Close menu when navigation links clicked - CSS handles scrolling
document.addEventListener('click', (e) => {
const navLink = e.target.closest('.submenu-content a[href^="#"]');
if (navLink) {
const navMenu = document.querySelector('.navigation-menu');
const hamburgerBtn = document.querySelector('.hamburger-btn');
if (navMenu && hamburgerBtn) {
navMenu.classList.remove('menu-open');
hamburgerBtn.setAttribute('aria-expanded', 'false');
}
document.querySelector('.navigation-menu')?.classList.remove('menu-hover', 'menu-open');
}
});
// Close menu when clicking outside (only for legacy click-opened menus)
function initClickOutsideHandler() {
document.addEventListener('click', function(event) {
const menu = document.getElementById('navigation-menu');
const btn = document.querySelector('.hamburger-btn');
if (menu && btn && menu.classList.contains('menu-open')) {
if (!menu.contains(event.target) && !btn.contains(event.target)) {
menu.classList.remove('menu-open');
btn.setAttribute('aria-expanded', 'false');
}
}
});
}
// =============================================================================
// LANGUAGE & PREFERENCES
// =============================================================================
@@ -657,26 +595,24 @@
// ERROR HANDLING
// =============================================================================
// Error handling utility
// Error handling utility - CSS handles auto-hide animation
window.showError = function(message) {
const errorToast = document.getElementById('error-toast');
const errorMessage = document.getElementById('error-message');
errorMessage.textContent = message;
errorToast.style.display = 'flex';
// Auto-hide after 5 seconds
setTimeout(() => {
errorToast.style.display = 'none';
}, 5000);
errorMessage.textContent = message;
errorToast.classList.remove('show'); // Reset if already showing
// Trigger reflow to restart animation
void errorToast.offsetWidth;
errorToast.classList.add('show'); // CSS animation handles lifecycle
};
// Add close button handler for error toast
document.addEventListener('click', function(e) {
// Close button handler for error toast - removes class to trigger hide
document.addEventListener('click', (e) => {
if (e.target.closest('.error-close')) {
const errorToast = document.getElementById('error-toast');
if (errorToast) {
errorToast.style.display = 'none';
}
document.getElementById('error-toast')?.classList.remove('show');
}
});
@@ -744,7 +680,6 @@
// Initialize everything when DOM is ready
document.addEventListener('DOMContentLoaded', function() {
initMenuSystem();
initClickOutsideHandler();
initPreferences();
initScrollBehavior();
initHTMXHandlers();