feat(nav): add hamburger menu for mobile navigation (Fix #27)
All checks were successful
Deploy Feature Branch to Test / deploy (push) Successful in 24s
All checks were successful
Deploy Feature Branch to Test / deploy (push) Successful in 24s
- Hamburger button with animated X toggle (CSS-only icon) - Slide-down mobile nav on ≤900px with 44px+ tap targets - Semi-transparent overlay when menu is open - Escape key + outside click + link click closes menu - Auto-close on resize to desktop - Desktop navigation unchanged - Pure vanilla JS toggle, no jQuery dependency
This commit is contained in:
@@ -115,6 +115,84 @@ nav.scrolled .nav-links a:hover {
|
|||||||
box-shadow: 0 4px 20px rgb(139 105 20 / 50%);
|
box-shadow: 0 4px 20px rgb(139 105 20 / 50%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* HAMBURGER */
|
||||||
|
.nav-hamburger {
|
||||||
|
display: none;
|
||||||
|
width: 44px;
|
||||||
|
height: 44px;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
z-index: 110;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-hamburger span,
|
||||||
|
.nav-hamburger span::before,
|
||||||
|
.nav-hamburger span::after {
|
||||||
|
display: block;
|
||||||
|
width: 22px;
|
||||||
|
height: 2px;
|
||||||
|
background: var(--white);
|
||||||
|
border-radius: 1px;
|
||||||
|
transition:
|
||||||
|
transform 0.3s ease,
|
||||||
|
opacity 0.3s ease,
|
||||||
|
background 0.4s;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-hamburger span::before,
|
||||||
|
.nav-hamburger span::after {
|
||||||
|
content: "";
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-hamburger span::before {
|
||||||
|
transform: translateY(-7px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-hamburger span::after {
|
||||||
|
transform: translateY(7px);
|
||||||
|
}
|
||||||
|
|
||||||
|
nav.scrolled .nav-hamburger span,
|
||||||
|
nav.scrolled .nav-hamburger span::before,
|
||||||
|
nav.scrolled .nav-hamburger span::after {
|
||||||
|
background: var(--dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-hamburger.active span {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-hamburger.active span::before {
|
||||||
|
transform: rotate(45deg);
|
||||||
|
background: var(--dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-hamburger.active span::after {
|
||||||
|
transform: rotate(-45deg);
|
||||||
|
background: var(--dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-mobile-overlay {
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
background: rgb(28 26 23 / 50%);
|
||||||
|
z-index: 90;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-mobile-overlay.active {
|
||||||
|
display: block;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
/* HERO */
|
/* HERO */
|
||||||
.hero {
|
.hero {
|
||||||
position: relative;
|
position: relative;
|
||||||
@@ -1042,6 +1120,59 @@ footer {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.nav-hamburger {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mobile slide-down nav */
|
||||||
|
nav.mobile-open .nav-links {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
background: rgb(253 252 250 / 98%);
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
|
padding: 5rem 1.5rem 2rem;
|
||||||
|
gap: 0;
|
||||||
|
z-index: 95;
|
||||||
|
border-bottom: 1px solid var(--warm);
|
||||||
|
animation: slideDown 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav.mobile-open .nav-links a {
|
||||||
|
color: var(--charcoal);
|
||||||
|
font-size: 1rem;
|
||||||
|
padding: 1rem 0;
|
||||||
|
border-bottom: 1px solid var(--warm);
|
||||||
|
display: block;
|
||||||
|
min-height: 44px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav.mobile-open .nav-hamburger span,
|
||||||
|
nav.mobile-open .nav-hamburger span::before,
|
||||||
|
nav.mobile-open .nav-hamburger span::after {
|
||||||
|
background: var(--dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
nav.mobile-open .nav-hamburger.active span {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideDown {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-10px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.hero-content {
|
.hero-content {
|
||||||
padding: 0 1.5rem 4rem;
|
padding: 0 1.5rem 4rem;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,6 +64,9 @@
|
|||||||
<body>
|
<body>
|
||||||
<nav id="navbar">
|
<nav id="navbar">
|
||||||
<div class="nav-logo">Bahnhofstraße 10</div>
|
<div class="nav-logo">Bahnhofstraße 10</div>
|
||||||
|
<button class="nav-hamburger" aria-label="Navigation öffnen" aria-expanded="false">
|
||||||
|
<span></span>
|
||||||
|
</button>
|
||||||
<ul class="nav-links">
|
<ul class="nav-links">
|
||||||
<li><a href="#galerie">Galerie</a></li>
|
<li><a href="#galerie">Galerie</a></li>
|
||||||
<li><a href="#grundriss">Grundriss</a></li>
|
<li><a href="#grundriss">Grundriss</a></li>
|
||||||
@@ -77,6 +80,7 @@
|
|||||||
Jetzt anfragen
|
Jetzt anfragen
|
||||||
</button>
|
</button>
|
||||||
</nav>
|
</nav>
|
||||||
|
<div class="nav-mobile-overlay" aria-hidden="true"></div>
|
||||||
|
|
||||||
<section class="hero" id="hero">
|
<section class="hero" id="hero">
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -101,3 +101,48 @@ $(function () {
|
|||||||
$("#formSuccess").fadeIn(400);
|
$("#formSuccess").fadeIn(400);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Mobile hamburger menu (vanilla JS)
|
||||||
|
(function () {
|
||||||
|
var hamburger = document.querySelector(".nav-hamburger");
|
||||||
|
var nav = document.getElementById("navbar");
|
||||||
|
var overlay = document.querySelector(".nav-mobile-overlay");
|
||||||
|
var links = nav ? nav.querySelectorAll(".nav-links a") : [];
|
||||||
|
|
||||||
|
function toggleMenu() {
|
||||||
|
var isOpen = hamburger.classList.toggle("active");
|
||||||
|
nav.classList.toggle("mobile-open", isOpen);
|
||||||
|
if (overlay) overlay.classList.toggle("active", isOpen);
|
||||||
|
hamburger.setAttribute("aria-expanded", isOpen);
|
||||||
|
document.body.style.overflow = isOpen ? "hidden" : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeMenu() {
|
||||||
|
hamburger.classList.remove("active");
|
||||||
|
nav.classList.remove("mobile-open");
|
||||||
|
if (overlay) overlay.classList.remove("active");
|
||||||
|
hamburger.setAttribute("aria-expanded", "false");
|
||||||
|
document.body.style.overflow = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hamburger) {
|
||||||
|
hamburger.addEventListener("click", toggleMenu);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (overlay) {
|
||||||
|
overlay.addEventListener("click", closeMenu);
|
||||||
|
}
|
||||||
|
|
||||||
|
links.forEach(function (link) {
|
||||||
|
link.addEventListener("click", closeMenu);
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener("keydown", function (e) {
|
||||||
|
if (e.key === "Escape") closeMenu();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Close on resize to desktop
|
||||||
|
window.addEventListener("resize", function () {
|
||||||
|
if (window.innerWidth > 900) closeMenu();
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
|||||||
Reference in New Issue
Block a user