diff --git a/new_theme/static/js/navbar.js b/new_theme/static/js/navbar.js index 8690c24..eb44360 100644 --- a/new_theme/static/js/navbar.js +++ b/new_theme/static/js/navbar.js @@ -6,18 +6,29 @@ document.addEventListener('DOMContentLoaded', function() { const languageOptions = document.getElementById('language-options'); const isMobile = () => window.innerWidth < 768; // matches Tailwind's md breakpoint + const reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches; + const ANIM_MS = reduceMotion ? 0 : 320; + + function isOpen(dropdown) { + return dropdown ? dropdown.classList.contains('menu-open') : false; + } function openDropdown(dropdown) { - dropdown.classList.remove('hidden'); + dropdown.classList.add('menu-open'); if (isMobile()) { document.body.style.overflow = 'hidden'; } } function closeDropdown(dropdown) { - dropdown.classList.add('hidden'); + dropdown.classList.remove('menu-open'); if (isMobile()) { - document.body.style.overflow = ''; + // Release body scroll only after the slide-up animation finishes + setTimeout(() => { + if (!isOpen(moreMenuDropdown) && !isOpen(languageOptions)) { + document.body.style.overflow = ''; + } + }, ANIM_MS); } } @@ -25,12 +36,12 @@ document.addEventListener('DOMContentLoaded', function() { button.addEventListener('click', function(e) { e.preventDefault(); e.stopPropagation(); - if (dropdown.classList.contains('hidden')) { + if (!isOpen(dropdown)) { // Close other dropdown first - if (dropdown === moreMenuDropdown && languageOptions && !languageOptions.classList.contains('hidden')) { + if (dropdown === moreMenuDropdown && isOpen(languageOptions)) { closeDropdown(languageOptions); } - if (dropdown === languageOptions && moreMenuDropdown && !moreMenuDropdown.classList.contains('hidden')) { + if (dropdown === languageOptions && isOpen(moreMenuDropdown)) { closeDropdown(moreMenuDropdown); } openDropdown(dropdown); @@ -58,31 +69,25 @@ document.addEventListener('DOMContentLoaded', function() { // Close dropdowns when clicking outside document.addEventListener('click', function(e) { if (moreMenuDropdown && !moreMenuButton.contains(e.target) && !moreMenuDropdown.contains(e.target)) { - closeDropdown(moreMenuDropdown); + if (isOpen(moreMenuDropdown)) closeDropdown(moreMenuDropdown); } if (languageOptions && !languageSelectorButton.contains(e.target) && !languageOptions.contains(e.target)) { - closeDropdown(languageOptions); + if (isOpen(languageOptions)) closeDropdown(languageOptions); } }); // Close dropdowns on Escape key document.addEventListener('keydown', function(e) { if (e.key === 'Escape') { - if (moreMenuDropdown && !moreMenuDropdown.classList.contains('hidden')) { - closeDropdown(moreMenuDropdown); - } - if (languageOptions && !languageOptions.classList.contains('hidden')) { - closeDropdown(languageOptions); - } + if (isOpen(moreMenuDropdown)) closeDropdown(moreMenuDropdown); + if (isOpen(languageOptions)) closeDropdown(languageOptions); } }); // Handle window resize: close mobile overlay if resizing to desktop window.addEventListener('resize', function() { if (!isMobile()) { - if (moreMenuDropdown && !moreMenuDropdown.classList.contains('hidden')) { - document.body.style.overflow = ''; - } + document.body.style.overflow = ''; } }); @@ -112,4 +117,4 @@ document.addEventListener('DOMContentLoaded', function() { lastScrollTop = scrollTop; }); } -}); +}); \ No newline at end of file diff --git a/templates/base.html b/templates/base.html index 49cc125..4a4eb91 100644 --- a/templates/base.html +++ b/templates/base.html @@ -46,6 +46,39 @@ transition: top 0.3s ease-in-out; } + /* Animated nav dropdowns. + We toggle a `.menu-open` class instead of `hidden` so that CSS + opacity/transform transitions run on open and close. */ + #more-menu-dropdown, + #language-options { + opacity: 0; + pointer-events: none; + transform: translateY(-10px); + transition: opacity 0.22s ease, transform 0.22s cubic-bezier(0.28, 0.11, 0.32, 1); + } + #more-menu-dropdown.menu-open, + #language-options.menu-open { + opacity: 1; + pointer-events: auto; + transform: translateY(0); + } + /* Mobile: full-screen overlay slides down from the top */ + @media (max-width: 767px) { + #more-menu-dropdown { + transform: translateY(-12px); + transition: opacity 0.28s ease, transform 0.32s cubic-bezier(0.28, 0.11, 0.32, 1); + } + } + @media (prefers-reduced-motion: reduce) { + #more-menu-dropdown, + #language-options, + #more-menu-dropdown.menu-open, + #language-options.menu-open { + transition: none; + transform: none; + } + } + /* Line clamp utilities for truncating text */ .line-clamp-2 { display: -webkit-box; @@ -71,7 +104,7 @@ {% trans "Menu" %} -