diff --git a/static/css/app.css b/static/css/app.css index eb6a6e0..495469c 100644 --- a/static/css/app.css +++ b/static/css/app.css @@ -438,6 +438,115 @@ body { white-space: nowrap; /* Prevents text from wrapping */ } +/* Burger / mobile navigation */ +.nav-toggle { + display: none; + align-items: center; + justify-content: center; + gap: 8px; + padding: 8px 12px; + border-radius: var(--r-2); +} + +@media (max-width: 900px) { + .site-nav { + position: sticky; + top: 0; + flex-wrap: wrap; + padding: var(--space-2) var(--gutter); + } + + .brand { display: flex; align-items: center; gap: 12px; } + + /* Put the burger to the right of the brand */ + .nav-toggle { display: inline-flex; margin-left: auto; position: relative; z-index: 1301; } + + /* Off-canvas menu (hidden to the right by default) */ + .navbar { + position: fixed; + top: 0; + right: 0; + height: 100vh; + /* Ensure total box never exceeds the viewport width (padding + border included) */ + box-sizing: border-box; + width: min(85vw, 380px); + max-width: 100vw; + margin: 0; + /* Safe-area aware padding on the right (iOS notch) */ + padding: var(--space-5) max(var(--gutter), env(safe-area-inset-right)) var(--space-5) var(--gutter); + display: flex; + flex-direction: column; + gap: var(--space-3); + background: var(--surface); + border-left: 1px solid var(--border); + box-shadow: var(--shadow-3); + z-index: 1200; + overflow-y: auto; + -webkit-overflow-scrolling: touch; + + transform: translateX(100%); + opacity: 0; + visibility: hidden; + pointer-events: none; + transition: transform var(--transition-2), opacity var(--transition-2), visibility 0s linear 320ms; + } + + .navbar.is-open { + transform: translateX(0); + opacity: 1; + visibility: visible; + pointer-events: auto; + transition: transform var(--transition-2), opacity var(--transition-2), visibility 0s; + } + + /* Dim background when menu is open (requires :has support) */ + body:has(#navToggle[aria-expanded="true"]) { overflow: hidden; } + .site-nav:has(#navToggle[aria-expanded="true"])::after { + content: ""; + position: fixed; + inset: 0; + background: rgba(0,0,0,0.45); + backdrop-filter: blur(2px); + z-index: 1100; + } + + .navbar ul { flex-direction: column; width: 100%; } + .navbar li { margin: 0; } + .navbar a { display: block; padding: 12px 14px; border-radius: var(--r-1); } + + /* Dropdowns behave as inline lists on mobile */ + .navbar ul ul { + position: static; + transform: none; + display: block; + background: transparent; + border: 0; + padding-left: 10px; + } + + .navbar ul ul li { border: 0; margin: 0; padding: 6px 8px; } + + .navend { + width: 100%; + margin-top: auto; /* push to bottom */ + display: flex; + flex-direction: column; + align-items: stretch; + gap: 6px; + } + + .navend ul { width: 100%; } +} + +/* Respect reduced motion preferences */ +@media (max-width: 900px) and (prefers-reduced-motion: reduce) { + .navbar, + .navbar.is-open, + .site-nav:has(#navToggle[aria-expanded="true"])::after { + transition: none !important; + } +} + .brand { display: flex; flex-direction: column; diff --git a/static/js/functions.js b/static/js/functions.js index aedd798..db8ff92 100644 --- a/static/js/functions.js +++ b/static/js/functions.js @@ -58,6 +58,42 @@ document.addEventListener('DOMContentLoaded', function() { }); } } catch(e) {} + + // Mobile nav toggle + try { + var navToggle = document.getElementById('navToggle'); + var primaryNav = document.getElementById('primaryNav'); + if (navToggle && primaryNav) { + function setExpanded(expanded) { + navToggle.setAttribute('aria-expanded', expanded ? 'true' : 'false'); + navToggle.setAttribute('aria-label', expanded ? 'Fermer le menu' : 'Ouvrir le menu'); + var icon = navToggle.querySelector('i'); + if (icon) { + icon.classList.remove('fa-bars','fa-xmark'); + icon.classList.add(expanded ? 'fa-xmark' : 'fa-bars'); + } + primaryNav.classList.toggle('is-open', expanded); + } + + navToggle.addEventListener('click', function() { + var expanded = navToggle.getAttribute('aria-expanded') === 'true'; + setExpanded(!expanded); + }); + + // Close menu when a link is clicked (on small screens) + primaryNav.addEventListener('click', function(e) { + var target = e.target; + if (target.tagName === 'A' || target.closest('a')) { + setExpanded(false); + } + }); + + // Close on Escape + document.addEventListener('keydown', function(e) { + if (e.key === 'Escape') setExpanded(false); + }); + } + } catch(e) {} }); // Fonction pour générer des flocons de neige diff --git a/templates/partials/_header.html b/templates/partials/_header.html index 786961c..7b6107d 100644 --- a/templates/partials/_header.html +++ b/templates/partials/_header.html @@ -6,7 +6,10 @@ /* Anthony Violet */ -