From ac35720c4f186a42702425897343b75986dd5179 Mon Sep 17 00:00:00 2001 From: Junwei Zhao Date: Thu, 30 Jul 2026 16:43:27 +1000 Subject: [PATCH] Update menu --- new_theme/static/js/navbar.js | 86 ++++++++-- templates/base.html | 291 +++++++++++++++------------------- 2 files changed, 204 insertions(+), 173 deletions(-) diff --git a/new_theme/static/js/navbar.js b/new_theme/static/js/navbar.js index 3fe7e1a..13054df 100644 --- a/new_theme/static/js/navbar.js +++ b/new_theme/static/js/navbar.js @@ -5,38 +5,88 @@ document.addEventListener('DOMContentLoaded', function() { const languageSelectorButton = document.getElementById('language-selector-button'); const languageOptions = document.getElementById('language-options'); + const isMobile = () => window.innerWidth < 768; // matches Tailwind's md breakpoint + + function openDropdown(dropdown) { + dropdown.classList.remove('hidden'); + if (isMobile()) { + document.body.style.overflow = 'hidden'; + } + } + + function closeDropdown(dropdown) { + dropdown.classList.add('hidden'); + if (isMobile()) { + document.body.style.overflow = ''; + } + } function toggleDropdown(button, dropdown) { button.addEventListener('click', function(e) { e.preventDefault(); e.stopPropagation(); - dropdown.classList.toggle('hidden'); + if (dropdown.classList.contains('hidden')) { + // Close other dropdown first + if (dropdown === moreMenuDropdown && languageOptions && !languageOptions.classList.contains('hidden')) { + closeDropdown(languageOptions); + } + if (dropdown === languageOptions && moreMenuDropdown && !moreMenuDropdown.classList.contains('hidden')) { + closeDropdown(moreMenuDropdown); + } + openDropdown(dropdown); + } else { + closeDropdown(dropdown); + } }); } if (moreMenuButton && moreMenuDropdown) { toggleDropdown(moreMenuButton, moreMenuDropdown); - } else { - console.error('More menu button or dropdown not found'); + + // Close dropdown when a menu link is clicked (mobile: auto-dismiss on navigate) + moreMenuDropdown.querySelectorAll('a').forEach(link => { + link.addEventListener('click', function() { + closeDropdown(moreMenuDropdown); + }); + }); } if (languageSelectorButton && languageOptions) { toggleDropdown(languageSelectorButton, languageOptions); - } else { - console.error('Language selector button or options not found'); } - // 点击页面其他地方时关闭下拉菜单 + // Close dropdowns when clicking outside document.addEventListener('click', function(e) { if (moreMenuDropdown && !moreMenuButton.contains(e.target) && !moreMenuDropdown.contains(e.target)) { - moreMenuDropdown.classList.add('hidden'); + closeDropdown(moreMenuDropdown); } if (languageOptions && !languageSelectorButton.contains(e.target) && !languageOptions.contains(e.target)) { - languageOptions.classList.add('hidden'); + 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); + } + } + }); + + // 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 = ''; + } + } + }); + + // Language form submission const languageForm = document.getElementById('language-form'); if (languageForm) { const languageButtons = languageForm.querySelectorAll('button[name="language"]'); @@ -45,7 +95,21 @@ document.addEventListener('DOMContentLoaded', function() { languageForm.submit(); }); }); - } else { - console.error('Language form not found'); + } + + // Scroll-responsive navigation: hide on scroll down, show on scroll up + let lastScrollTop = 0; + const navbar = document.querySelector('nav'); + + if (navbar) { + window.addEventListener('scroll', function() { + let scrollTop = window.pageYOffset || document.documentElement.scrollTop; + if (scrollTop > lastScrollTop && scrollTop > 60) { + navbar.style.top = '-80px'; + } else { + navbar.style.top = '0'; + } + lastScrollTop = scrollTop; + }); } }); \ No newline at end of file diff --git a/templates/base.html b/templates/base.html index 0ccc7b2..49cc125 100644 --- a/templates/base.html +++ b/templates/base.html @@ -28,17 +28,22 @@ background-color: #e2e8f0; } - /* Scroll-responsive navigation styles */ + /* Scroll-responsive navigation styles. + IMPORTANT: do NOT animate the nav with `transform`. Any non-`none` + transform on an ancestor makes it the containing block for its + `position: fixed` descendants, which breaks the mobile menu dropdown + (the dropdown uses `fixed inset-0` on mobile to overlay the viewport). + We animate `top` instead. */ .nav-hidden { - transform: translateY(-100%); + top: -80px; } .nav-visible { - transform: translateY(0); + top: 0; } nav { - transition: transform 0.3s ease-in-out; + transition: top 0.3s ease-in-out; } /* Line clamp utilities for truncating text */ @@ -66,196 +71,158 @@ {% trans "Menu" %} -