hopefully fixed mobile nav

This commit is contained in:
fynks 2025-10-12 17:42:44 +05:00
parent 92bb628a2b
commit 4cf024c44b
2 changed files with 365 additions and 235 deletions

120
dist/css/styles.css vendored
View file

@ -963,6 +963,8 @@ button:focus-visible {
font-weight: var(--font-medium);
color: var(--text-secondary);
min-height: 44px;
-webkit-tap-highlight-color: transparent;
touch-action: manipulation;
}
.nav-dropdown-trigger:hover {
@ -977,30 +979,34 @@ button:focus-visible {
visibility: visible;
border: none;
box-shadow: none;
padding: var(--space-xs) 0 var(--space-md) var(--space-2xl);
margin-top: 0;
background: rgba(99, 102, 241, 0.02);
border-left: 2px solid var(--border-primary);
margin-left: var(--space-lg);
padding: 0;
margin: 0;
background: transparent;
border-radius: 0;
backdrop-filter: none;
display: none;
max-height: 0;
overflow: hidden;
transition: max-height 0.3s ease;
/* Prevent layout shift and improve performance */
will-change: max-height;
contain: layout;
/* Remove problematic animations */
transition: none;
will-change: auto;
}
[data-theme="dark"] .nav-dropdown-menu {
background: rgba(99, 102, 241, 0.03);
border-left-color: var(--border-secondary);
background: transparent;
}
.nav-dropdown-trigger[aria-expanded="true"] + .nav-dropdown-menu {
display: block;
max-height: 500px;
padding: var(--space-xs) 0 var(--space-md) var(--space-2xl);
margin-top: var(--space-xs);
margin-left: var(--space-lg);
border-left: 2px solid var(--border-primary);
background: rgba(99, 102, 241, 0.02);
}
[data-theme="dark"] .nav-dropdown-trigger[aria-expanded="true"] + .nav-dropdown-menu {
background: rgba(99, 102, 241, 0.03);
border-left-color: var(--border-secondary);
}
.nav-dropdown-item {
@ -1009,80 +1015,91 @@ button:focus-visible {
min-height: 44px;
display: flex;
align-items: center;
-webkit-tap-highlight-color: transparent;
touch-action: manipulation;
transition: transform 0.2s ease, background 0.2s ease;
}
.nav-dropdown-item:hover {
.nav-dropdown-item:hover,
.nav-dropdown-item:active {
transform: translateX(4px);
background: rgba(99, 102, 241, 0.05);
}
}
/* ===== MOBILE NAVIGATION OVERLAY ===== */
.nav-overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, .4);
backdrop-filter: blur(2px);
background: rgba(0, 0, 0, 0.4);
z-index: 90;
opacity: 0;
visibility: hidden;
transition: opacity .3s ease;
pointer-events: none
transition: opacity 0.3s ease, visibility 0.3s ease;
pointer-events: none;
-webkit-tap-highlight-color: transparent;
}
.nav-open #navOverlay {
.nav-open .nav-overlay {
opacity: 1;
visibility: visible;
pointer-events: auto
pointer-events: auto;
}
@media (max-width:900px) {
/* ===== MOBILE NAVIGATION STYLES ===== */
@media (max-width: 900px) {
/* Show nav toggle button */
.nav-toggle {
display: inline-flex;
width: 48px !important;
height: 48px !important
height: 48px !important;
}
/* Hide nav by default on mobile */
.header-nav {
display: none
}
.nav-open .header-nav {
display: flex;
display: none;
position: fixed;
top: var(--header-height);
right: 0;
width: min(340px, 85%);
background: rgba(255, 255, 255, .98);
backdrop-filter: blur(24px) saturate(180%);
-webkit-backdrop-filter: blur(24px) saturate(180%);
height: calc(100vh - var(--header-height));
background: rgba(255, 255, 255, 0.98);
border-left: 1px solid var(--border-primary);
border-bottom: 1px solid var(--border-primary);
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, .08), 0 4px 16px 0 rgba(0, 0, 0, .04);
box-shadow: -4px 0 20px rgba(0, 0, 0, 0.1);
padding: var(--space-2xl) var(--space-xl) var(--space-3xl);
flex-direction: column;
gap: var(--space-sm);
max-height: calc(100vh - var(--header-height));
overflow-y: auto;
overflow-x: hidden;
overscroll-behavior: contain;
transform: translate3d(100%, 0, 0);
transition: transform .4s cubic-bezier(.22, 1, .36, 1);
z-index: 120
}
[data-theme="dark"] .nav-open .header-nav {
background: rgba(15, 23, 42, .98);
border-color: rgba(52, 65, 85, .6);
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, .2), 0 4px 16px 0 rgba(0, 0, 0, .1)
-webkit-overflow-scrolling: touch;
transform: translateX(100%);
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
z-index: 100;
will-change: transform;
}
/* Show nav when open */
.nav-open .header-nav {
transform: translate3d(0, 0, 0)
display: flex;
transform: translateX(0);
}
/* Dark theme mobile nav */
[data-theme="dark"] .header-nav {
background: rgba(15, 23, 42, 0.98);
border-color: rgba(52, 65, 85, 0.6);
box-shadow: -4px 0 20px rgba(0, 0, 0, 0.3);
}
/* Prevent body scroll when nav is open */
body.nav-open {
overflow: hidden
overflow: hidden;
position: fixed;
width: 100%;
}
/* Mobile nav links */
.nav-open .nav-link {
font-size: var(--text-base);
padding: var(--space-lg) var(--space-xl);
@ -1092,16 +1109,19 @@ button:focus-visible {
min-height: 44px;
display: flex;
align-items: center;
-webkit-tap-highlight-color: transparent;
touch-action: manipulation;
}
.nav-open .nav-link::before {
display: none
display: none;
}
.nav-open .nav-link::after {
border-radius: var(--radius-lg)
border-radius: var(--radius-lg);
}
/* Dark mode toggle positioning on mobile */
.dark-mode-toggle {
display: flex;
flex: 0 0 auto;
@ -1109,18 +1129,20 @@ button:focus-visible {
height: 40px !important;
order: 3;
align-self: center;
z-index: 110
z-index: 110;
}
/* Nav toggle sizing */
.nav-toggle {
width: 40px !important;
height: 40px !important
height: 40px !important;
}
/* Disable transitions before JS is ready */
body:not(.js-ready) .header-nav,
body:not(.js-ready) .nav-toggle-bar,
body:not(.js-ready) .nav-overlay {
transition: none !important
transition: none !important;
}
}

480
dist/index.html vendored
View file

@ -1733,187 +1733,233 @@
<!-- JavaScript -->
<script>
// Dark mode toggle
document.addEventListener('DOMContentLoaded', () => {
const btn = document.getElementById('darkModeToggle');
const bodyEl = document.body;
const navToggle = document.getElementById('navToggle');
const nav = document.getElementById('primary-navigation');
const navOverlay = document.getElementById('navOverlay');
const focusableSelectors = 'a[href], button:not([disabled]), select, textarea, input, [tabindex]:not([tabindex="-1"])';
let lastFocusedBeforeMenu = null;
// Mobile Navigation Manager - Lightweight & Performance Optimized
(function() {
'use strict';
// Navigation dropdowns
const dropdownTriggers = document.querySelectorAll('.nav-dropdown-trigger');
const state = {
isOpen: false,
isMobile: false,
activeDropdown: null
};
// Function to close all dropdowns
function closeAllDropdowns() {
dropdownTriggers.forEach(trigger => {
const elements = {
body: document.body,
navToggle: document.getElementById('navToggle'),
nav: document.getElementById('primary-navigation'),
navOverlay: document.getElementById('navOverlay'),
darkModeToggle: document.getElementById('darkModeToggle'),
dropdownTriggers: []
};
// Check if mobile
const checkMobile = () => window.innerWidth <= 900;
// Update state
const updateMobileState = () => {
state.isMobile = checkMobile();
};
// Close all dropdowns
const closeAllDropdowns = () => {
elements.dropdownTriggers.forEach(trigger => {
trigger.setAttribute('aria-expanded', 'false');
});
}
state.activeDropdown = null;
};
dropdownTriggers.forEach(trigger => {
trigger.addEventListener('click', (e) => {
e.preventDefault();
// On mobile, only allow dropdowns when nav menu is open
if (window.innerWidth <= 900 && !bodyEl.classList.contains('nav-open')) {
return;
}
const isExpanded = trigger.getAttribute('aria-expanded') === 'true';
// Close all other dropdowns
dropdownTriggers.forEach(otherTrigger => {
if (otherTrigger !== trigger) {
otherTrigger.setAttribute('aria-expanded', 'false');
}
});
// Toggle current dropdown
trigger.setAttribute('aria-expanded', String(!isExpanded));
}, { passive: false });
});
// Open mobile navigation
const openNav = () => {
if (!elements.nav || state.isOpen) return;
state.isOpen = true;
elements.body.classList.add('nav-open');
if (elements.navToggle) {
elements.navToggle.setAttribute('aria-expanded', 'true');
}
if (elements.navOverlay) {
elements.navOverlay.hidden = false;
}
if (elements.nav) {
elements.nav.setAttribute('aria-hidden', 'false');
elements.nav.setAttribute('data-open', 'true');
}
};
// Close dropdowns when clicking outside (only on desktop)
document.addEventListener('click', (e) => {
// Don't close if clicking on a dropdown trigger or menu
const isDropdownClick = e.target.closest('.nav-dropdown');
if (!isDropdownClick && window.innerWidth > 900) {
closeAllDropdowns();
}
}, { passive: true });
// Close dropdowns on escape key
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
closeAllDropdowns();
}
}, { passive: true });
// Mark JS ready to enable transitions after initial paint (prevents slide-in glitch)
requestAnimationFrame(() => bodyEl.classList.add('js-ready'));
function trapFocus(container) {
if (!container || container.dataset.trapBound === 'true') return;
const focusables = Array.from(container.querySelectorAll(focusableSelectors));
if (!focusables.length) return;
let first = focusables[0];
let last = focusables[focusables.length - 1];
function handle(e) {
if (e.key === 'Tab') {
if (e.shiftKey && document.activeElement === first) { e.preventDefault(); last.focus(); }
else if (!e.shiftKey && document.activeElement === last) { e.preventDefault(); first.focus(); }
}
}
container.addEventListener('keydown', handle);
container.dataset.trapBound = 'true';
}
function updateNavAria() {
if (!nav) return;
if (window.innerWidth <= 900) {
nav.setAttribute('aria-hidden', bodyEl.classList.contains('nav-open') ? 'false' : 'true');
} else {
nav.removeAttribute('aria-hidden');
// Restore scroll when switching to desktop
document.body.style.overflow = '';
if (bodyEl.classList.contains('nav-open')) {
closeMenu({ restoreFocus: false });
} else {
nav.removeAttribute('data-open');
navToggle?.setAttribute('aria-expanded', 'false');
if (navOverlay) navOverlay.hidden = true;
}
}
}
function openMenu() {
if (!nav) return;
lastFocusedBeforeMenu = document.activeElement;
bodyEl.classList.add('nav-open');
navToggle?.setAttribute('aria-expanded', 'true');
if (navOverlay) navOverlay.hidden = false;
nav.setAttribute('data-open', 'true');
// Close mobile navigation
const closeNav = () => {
if (!elements.nav || !state.isOpen) return;
// Prevent scroll on mobile when menu is open
if (window.innerWidth <= 900) {
document.body.style.overflow = 'hidden';
state.isOpen = false;
elements.body.classList.remove('nav-open');
if (elements.navToggle) {
elements.navToggle.setAttribute('aria-expanded', 'false');
}
const firstLink = nav.querySelector('a');
firstLink && firstLink.focus();
trapFocus(nav);
updateNavAria();
}
function closeMenu(options = {}) {
if (!nav) return;
const { restoreFocus = true } = options;
bodyEl.classList.remove('nav-open');
navToggle?.setAttribute('aria-expanded', 'false');
if (navOverlay) navOverlay.hidden = true;
nav.removeAttribute('data-open');
if (elements.navOverlay) {
elements.navOverlay.hidden = true;
}
// Restore scroll
document.body.style.overflow = '';
if (elements.nav) {
elements.nav.setAttribute('aria-hidden', 'true');
elements.nav.removeAttribute('data-open');
}
// Close all dropdowns when closing menu
// Close all dropdowns when closing nav
closeAllDropdowns();
if (restoreFocus && lastFocusedBeforeMenu) {
lastFocusedBeforeMenu.focus();
};
// Toggle navigation
const toggleNav = () => {
if (state.isOpen) {
closeNav();
} else {
openNav();
}
updateNavAria();
}
if (navToggle) {
navToggle.addEventListener('click', () => {
if (bodyEl.classList.contains('nav-open')) closeMenu(); else openMenu();
};
// Handle dropdown toggle
const toggleDropdown = (trigger) => {
if (!trigger) return;
const isExpanded = trigger.getAttribute('aria-expanded') === 'true';
// Close all other dropdowns first
elements.dropdownTriggers.forEach(t => {
if (t !== trigger) {
t.setAttribute('aria-expanded', 'false');
}
});
}
if (navOverlay) {
navOverlay.addEventListener('click', closeMenu);
}
if (nav) {
nav.addEventListener('click', (event) => {
// Only close menu when clicking actual links, not dropdown triggers
const target = event.target instanceof Element ? event.target.closest('a.nav-link, a.nav-dropdown-item') : null;
if (!target) return;
if (window.innerWidth <= 900) closeMenu();
// Toggle current dropdown
trigger.setAttribute('aria-expanded', String(!isExpanded));
state.activeDropdown = !isExpanded ? trigger : null;
};
// Handle window resize
const handleResize = () => {
const wasMobile = state.isMobile;
updateMobileState();
// If switched from mobile to desktop and nav was open, close it
if (wasMobile && !state.isMobile && state.isOpen) {
closeNav();
}
// Update aria-hidden based on viewport
if (elements.nav) {
if (state.isMobile) {
elements.nav.setAttribute('aria-hidden', state.isOpen ? 'false' : 'true');
} else {
elements.nav.removeAttribute('aria-hidden');
}
}
};
// Initialize navigation
const init = () => {
updateMobileState();
// Mark JS ready for animations
requestAnimationFrame(() => {
elements.body.classList.add('js-ready');
});
}
window.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && bodyEl.classList.contains('nav-open')) closeMenu();
});
// Handle resize (debounced via rAF)
let resizeRaf;
window.addEventListener('resize', () => {
if (resizeRaf) cancelAnimationFrame(resizeRaf);
resizeRaf = requestAnimationFrame(updateNavAria);
});
updateNavAria();
// Scrollspy for active nav link
const sectionIds = Array.from(nav?.querySelectorAll('a[href^="#"]') || []).map(a => a.getAttribute('href')).filter(Boolean);
const sections = sectionIds.map(id => document.querySelector(id)).filter(Boolean);
if ('IntersectionObserver' in window && sections.length) {
const observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
const id = '#' + entry.target.id;
const link = nav.querySelector(`a[href="${id}"]`);
if (link) {
if (entry.isIntersecting) {
nav.querySelectorAll('.nav-link.active').forEach(l => { l.classList.remove('active'); l.removeAttribute('aria-current'); });
link.classList.add('active');
link.setAttribute('aria-current', 'page');
// Initialize dropdowns
elements.dropdownTriggers = Array.from(document.querySelectorAll('.nav-dropdown-trigger'));
// Set initial aria-hidden state
if (elements.nav && state.isMobile) {
elements.nav.setAttribute('aria-hidden', 'true');
}
// Nav toggle click handler
if (elements.navToggle) {
elements.navToggle.addEventListener('click', (e) => {
e.preventDefault();
toggleNav();
}, { passive: false });
}
// Overlay click handler
if (elements.navOverlay) {
elements.navOverlay.addEventListener('click', (e) => {
e.preventDefault();
closeNav();
}, { passive: false });
}
// Dropdown click handlers
elements.dropdownTriggers.forEach(trigger => {
trigger.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
toggleDropdown(trigger);
}, { passive: false });
});
// Link click handler - close nav on mobile when clicking links
if (elements.nav) {
const links = elements.nav.querySelectorAll('a.nav-link:not(.nav-dropdown-trigger), a.nav-dropdown-item');
links.forEach(link => {
link.addEventListener('click', () => {
if (state.isMobile && state.isOpen) {
// Small delay to allow navigation to start
setTimeout(() => closeNav(), 100);
}
}
}, { passive: true });
});
}, { rootMargin: '-40% 0px -50% 0px', threshold: [0, 0.25, 0.5, 1] });
sections.forEach(sec => observer.observe(sec));
}
// Escape key handler
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
if (state.activeDropdown) {
closeAllDropdowns();
} else if (state.isOpen) {
closeNav();
}
}
}, { passive: true });
// Close dropdowns when clicking outside (desktop only)
document.addEventListener('click', (e) => {
if (!state.isMobile && state.activeDropdown) {
const clickedDropdown = e.target.closest('.nav-dropdown');
if (!clickedDropdown) {
closeAllDropdowns();
}
}
}, { passive: true });
// Resize handler with debounce
let resizeTimeout;
window.addEventListener('resize', () => {
clearTimeout(resizeTimeout);
resizeTimeout = setTimeout(handleResize, 150);
}, { passive: true });
};
// Initialize on DOM ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
if (btn) {
})();
// Dark Mode Toggle - Separate Module
(function() {
'use strict';
const init = () => {
const btn = document.getElementById('darkModeToggle');
if (!btn) return;
const getTheme = () => document.documentElement.getAttribute('data-theme') || 'light';
const setTheme = (theme) => {
document.documentElement.setAttribute('data-theme', theme);
@ -1925,34 +1971,87 @@
btn.setAttribute('aria-pressed', String(getTheme() === 'dark'));
btn.addEventListener('click', () => {
setTheme(getTheme() === 'dark' ? 'light' : 'dark');
});
}, { passive: true });
};
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();
// Smooth scroll for anchor links
const prefersReduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener('click', (e) => {
const id = anchor.getAttribute('href');
const target = id && document.querySelector(id);
if (!target) return;
// Scrollspy & Smooth Scroll - Separate Module
(function() {
'use strict';
const init = () => {
const nav = document.getElementById('primary-navigation');
if (!nav) return;
// Scrollspy for active nav link
const sectionIds = Array.from(nav.querySelectorAll('a[href^="#"]') || [])
.map(a => a.getAttribute('href'))
.filter(Boolean);
const sections = sectionIds.map(id => document.querySelector(id)).filter(Boolean);
if ('IntersectionObserver' in window && sections.length) {
const observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
const id = '#' + entry.target.id;
const link = nav.querySelector(`a[href="${id}"]`);
if (link && entry.isIntersecting) {
nav.querySelectorAll('.nav-link.active').forEach(l => {
l.classList.remove('active');
l.removeAttribute('aria-current');
});
link.classList.add('active');
link.setAttribute('aria-current', 'page');
}
});
}, { rootMargin: '-40% 0px -50% 0px', threshold: [0, 0.25, 0.5, 1] });
sections.forEach(sec => observer.observe(sec));
}
e.preventDefault();
if (bodyEl.classList.contains('nav-open')) closeMenu();
target.scrollIntoView({
behavior: prefersReduced ? 'auto' : 'smooth',
block: 'start'
});
// Smooth scroll for anchor links
const prefersReduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener('click', (e) => {
const id = anchor.getAttribute('href');
const target = id && document.querySelector(id);
if (!target) return;
const focusEl = target.querySelector('h1,h2,h3,h4,h5,h6') || target;
focusEl.setAttribute('tabindex', '-1');
focusEl.focus({ preventScroll: true });
setTimeout(() => focusEl.removeAttribute('tabindex'), 400);
e.preventDefault();
target.scrollIntoView({
behavior: prefersReduced ? 'auto' : 'smooth',
block: 'start'
});
const focusEl = target.querySelector('h1,h2,h3,h4,h5,h6') || target;
focusEl.setAttribute('tabindex', '-1');
focusEl.focus({ preventScroll: true });
setTimeout(() => focusEl.removeAttribute('tabindex'), 400);
}, { passive: false });
});
});
};
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();
// Back to top button
const backToTop = document.getElementById('backToTop');
if (backToTop) {
// Back to top button
(function() {
'use strict';
const init = () => {
const backToTop = document.getElementById('backToTop');
if (!backToTop) return;
const prefersReduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
window.addEventListener('scroll', () => {
if (window.pageYOffset > 300) {
backToTop.classList.add('visible');
@ -1966,9 +2065,18 @@
top: 0,
behavior: prefersReduced ? 'auto' : 'smooth'
});
});
}, { passive: true });
};
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();
// Remaining initialization code
document.addEventListener('DOMContentLoaded', () => {
// Search functionality for file hosts
const hostSearchInput = document.getElementById('host-search-input');
const clearHostSearch = document.getElementById('clear-host-search');