feat: responsive nav — hamburger menu + wider container (900px breakpoint)

- Widen nav container from 72rem to 80rem (1280px) — matches Zillow's
  nav container width, more breathing room for items on large monitors
- Raise collapse breakpoint from 768px to 899px — links stay visible
  until the screen is actually too narrow
- Add hamburger button (SVG 3-line icon) visible at < 900px
- Add mobile drop-down panel with all nav links grouped under Plan /
  Explore / Account sections; overlay + Escape key close it

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Deeman
2026-02-20 14:04:56 +01:00
parent 32f29c53ec
commit 48587d6436
3 changed files with 153 additions and 7 deletions

View File

@@ -6,6 +6,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
## [Unreleased]
### Added
- Nav: hamburger menu on screens < 900px — clicking opens a full-width mobile panel with all nav links; overlay click and Escape key close it
- Nav: mobile panel groups links under "Plan", "Explore", and "Account" section headers
### Changed
- Nav: widen container from 72rem (1152px) to 80rem (1280px) — matches Zillow's nav container width, more breathing room for nav items on large monitors
- Nav: collapse breakpoint raised from 768px to 899px — nav links no longer hide until the screen is actually too narrow
- Nav: remove redundant inline `style="display:grid;grid-template-columns:1fr auto 1fr"` on `.nav-inner` (already in CSS)
### Fixed
- i18n: improve German nav labels — "Verzeichnis" → "Anbieterverzeichnis", "Planer" → "Kostenrechner"
- CI: add missing env vars to `.env` heredoc — `WAITLIST_MODE`, `LEADS_EMAIL`, `UMAMI_API_URL`; make Paddle vars optional (`:-`) so they don't break deploys when unset

View File

@@ -113,9 +113,9 @@
border-bottom: 1px solid #E2E8F0;
}
.nav-inner {
max-width: 72rem;
max-width: 80rem;
margin: 0 auto;
padding: 0 1rem;
padding: 0 1.5rem;
display: grid;
grid-template-columns: 1fr auto 1fr;
align-items: center;
@@ -172,9 +172,84 @@
padding: 0;
display: inline;
}
@media (max-width: 768px) {
/* Hamburger button — hidden on desktop */
.nav-hamburger {
display: none;
align-items: center;
justify-content: center;
width: 36px;
height: 36px;
background: transparent;
border: none;
cursor: pointer;
padding: 6px;
border-radius: 8px;
transition: background 0.15s;
}
.nav-hamburger:hover { background: #F1F5F9; }
.nav-hamburger svg { display: block; }
/* Mobile menu panel — hidden by default */
.nav-mobile {
display: none;
position: absolute;
top: 56px;
left: 0;
right: 0;
background: #fff;
border-bottom: 1px solid #E2E8F0;
box-shadow: 0 8px 24px rgba(0,0,0,0.08);
z-index: 49;
flex-direction: column;
padding: 0.75rem 1.5rem 1rem;
}
.nav-bar[data-navopen="true"] .nav-mobile {
display: flex;
}
.nav-mobile a,
.nav-mobile button.nav-auth-btn,
.nav-mobile a.nav-auth-btn {
display: block;
padding: 0.625rem 0;
border-bottom: 1px solid #F1F5F9;
font-size: 0.9375rem;
font-weight: 500;
color: #475569;
text-decoration: none;
transition: color 0.15s;
}
.nav-mobile a:last-child { border-bottom: none; }
.nav-mobile a:hover { color: #1D4ED8; }
.nav-mobile a.nav-auth-btn,
.nav-mobile button.nav-auth-btn {
display: inline-flex;
margin-top: 0.5rem;
border-bottom: none;
width: auto;
align-self: flex-start;
}
.nav-mobile .nav-mobile-section {
font-size: 0.6875rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.07em;
color: #94A3B8;
padding: 0.75rem 0 0.25rem;
}
/* Overlay behind mobile menu */
.nav-overlay {
display: none;
position: fixed;
inset: 0;
z-index: 48;
}
.nav-bar[data-navopen="true"] ~ .nav-overlay {
display: block;
}
@media (max-width: 899px) {
.nav-links { display: none; }
.nav-inner { justify-content: center; }
.nav-hamburger { display: flex; }
.nav-inner { grid-template-columns: auto 1fr auto; }
.nav-bar { position: sticky; }
}
/* Page container */

View File

@@ -38,8 +38,8 @@
{% block head %}{% endblock %}
</head>
<body>
<nav class="nav-bar">
<div class="nav-inner" style="display:grid;grid-template-columns:1fr auto 1fr">
<nav class="nav-bar" id="main-nav">
<div class="nav-inner">
<!-- Left: demand / buy side -->
<div class="nav-links nav-links--left">
<a href="{{ url_for('planner.index') }}">{{ t.nav_planner }}</a>
@@ -51,7 +51,7 @@
<span style="font-family:'Bricolage Grotesque',sans-serif;font-weight:800;font-size:1.125rem;color:#0F172A;letter-spacing:-0.02em">padelnomics</span>
</a>
<!-- Right: supply side + auth -->
<!-- Right: supply side + auth (desktop only) -->
<div class="nav-links nav-links--right">
<a href="{{ url_for('directory.index') }}">{{ t.nav_directory }}</a>
<a href="{{ url_for('content.markets') }}">{{ t.nav_markets }}</a>
@@ -88,10 +88,72 @@
{% else %}
<a href="{{ url_for('auth.login') }}" class="nav-auth-btn">{{ t.nav_signin }}</a>
{% endif %}
<!-- Hamburger (mobile only) -->
<button type="button" class="nav-hamburger" id="nav-hamburger-btn" aria-label="Open navigation" aria-expanded="false">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<rect y="3" width="20" height="2" rx="1" fill="#0F172A"/>
<rect y="9" width="20" height="2" rx="1" fill="#0F172A"/>
<rect y="15" width="20" height="2" rx="1" fill="#0F172A"/>
</svg>
</button>
</div>
</div>
<!-- Mobile menu panel -->
<div class="nav-mobile" id="nav-mobile-panel">
<span class="nav-mobile-section">Plan</span>
<a href="{{ url_for('planner.index') }}">{{ t.nav_planner }}</a>
<a href="{{ url_for('leads.quote_request') }}">{{ t.nav_quotes }}</a>
<span class="nav-mobile-section">Explore</span>
<a href="{{ url_for('directory.index') }}">{{ t.nav_directory }}</a>
<a href="{{ url_for('content.markets') }}">{{ t.nav_markets }}</a>
<a href="{{ url_for('public.suppliers') }}">{{ t.nav_suppliers }}</a>
<a href="{{ url_for('public.landing') }}#faq">{{ t.nav_help }}</a>
{% if user %}
<span class="nav-mobile-section">Account</span>
<a href="{{ url_for('dashboard.index') }}">{{ t.nav_dashboard }}</a>
{% if is_admin %}
<a href="{{ url_for('admin.index') }}">{{ t.nav_admin }}</a>
{% endif %}
<form method="post" action="{{ url_for('auth.logout') }}" style="margin:0.5rem 0 0;padding:0">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<button type="submit" class="nav-auth-btn">{{ t.nav_signout }}</button>
</form>
{% else %}
<a href="{{ url_for('auth.login') }}" class="nav-auth-btn" style="margin-top:0.5rem">{{ t.nav_signin }}</a>
{% endif %}
</div>
</nav>
<!-- Overlay — closes mobile menu when clicked -->
<div class="nav-overlay" id="nav-overlay"></div>
<script>
(function() {
var nav = document.getElementById('main-nav');
var btn = document.getElementById('nav-hamburger-btn');
var overlay = document.getElementById('nav-overlay');
function open() {
nav.setAttribute('data-navopen', 'true');
btn.setAttribute('aria-expanded', 'true');
}
function close() {
nav.removeAttribute('data-navopen');
btn.setAttribute('aria-expanded', 'false');
}
btn.addEventListener('click', function() {
nav.hasAttribute('data-navopen') ? close() : open();
});
overlay.addEventListener('click', close);
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') close();
});
})();
</script>
<!-- Flash messages -->
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}