diff --git a/static/css/main.css b/static/css/main.css index 804646d..1cca0db 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -1579,24 +1579,38 @@ a:focus { border-radius: 8px; border-left: 4px solid #dc2626; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); - display: flex; + display: none; /* Hidden by default */ align-items: center; gap: 1rem; max-width: 400px; z-index: 1000; - animation: slideIn 0.3s ease-out; font-size: 0.95rem; } -@keyframes slideIn { - from { +/* Show toast with full animation cycle: slide in -> stay -> fade out */ +.error-toast.show { + display: flex; + animation: toastLifecycle 5.5s ease-out forwards; +} + +/* Full lifecycle animation: slide in (0-0.3s), stay (0.3-5s), fade out (5-5.5s) */ +@keyframes toastLifecycle { + 0% { transform: translateX(120%); opacity: 0; } - to { + 5.5% { /* 0.3s / 5.5s = 5.5% */ transform: translateX(0); opacity: 1; } + 90.9% { /* 5s / 5.5s = 90.9% - stay visible */ + transform: translateX(0); + opacity: 1; + } + 100% { /* Final 0.5s - fade out and slide right */ + transform: translateX(120%); + opacity: 0; + } } .error-icon { @@ -2233,8 +2247,10 @@ text-align: center; transition: background-color 0.2s ease; border-radius: 4px; margin: 0 0.5rem; + position: relative; /* For CSS-only hover trigger */ } + .hamburger-btn:hover { background-color: rgba(255, 255, 255, 0.1); } @@ -2259,21 +2275,20 @@ text-align: center; opacity: 0; } -/* Show menu when hovering menu itself OR when it has the hover class */ +/* Pure CSS Menu Activation - Show menu when hovering hamburger OR menu */ +/* Show when hovering the hamburger button (adjacent in DOM after site-title-left) */ +.hamburger-btn:hover ~ .navigation-menu, +.hamburger-btn:focus ~ .navigation-menu, +/* Show when hovering the menu itself */ .navigation-menu:hover, -.navigation-menu.menu-hover { +/* Legacy class for backward compatibility */ +.navigation-menu.menu-hover, +.navigation-menu.menu-open { max-height: calc(100vh - 60px); /* Viewport height minus header + some spacing */ pointer-events: auto; /* Enable pointer events when visible */ opacity: 1; } -/* Legacy class for JS compatibility - keep for backward compatibility */ -.navigation-menu.menu-open { - max-height: calc(100vh - 60px); - pointer-events: auto; - opacity: 1; -} - .menu-content { padding: 1rem 0; } diff --git a/static/js/main.js b/static/js/main.js index 0352e18..cce5175 100644 --- a/static/js/main.js +++ b/static/js/main.js @@ -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(); diff --git a/templates/partials/navigation/action-bar.html b/templates/partials/navigation/action-bar.html index 9f09648..0fd81e5 100644 --- a/templates/partials/navigation/action-bar.html +++ b/templates/partials/navigation/action-bar.html @@ -11,7 +11,7 @@ -