refactor header and navigation for improved mobile usability and accessibility

This commit is contained in:
Fabian Schieder 2026-02-27 22:51:59 +01:00
parent 5a055fba70
commit b1a8da315d
4 changed files with 160 additions and 139 deletions

View File

@ -14,41 +14,38 @@
<link rel="icon" href="/assets/images/favicon.ico" sizes="any"> <link rel="icon" href="/assets/images/favicon.ico" sizes="any">
<!-- Prevent tap-highlight on mobile --> <title>Geizkragen</title>
<style> <style>
/* ─── Mobile tap highlight ─── */
* { -webkit-tap-highlight-color: transparent; } * { -webkit-tap-highlight-color: transparent; }
/* Safe area for notch phones */
body { /* ─── Safe areas (notch phones) ─── */
padding-left: env(safe-area-inset-left); .header { padding-left: env(safe-area-inset-left); padding-right: env(safe-area-inset-right); }
padding-right: env(safe-area-inset-right); .footer { padding-bottom: env(safe-area-inset-bottom); }
}
.header { /* ─── Mobile overlay ─── */
padding-left: env(safe-area-inset-left);
padding-right: env(safe-area-inset-right);
}
.footer {
padding-bottom: env(safe-area-inset-bottom);
}
/* Mobile menu overlay */
.nav__overlay { .nav__overlay {
display: none;
position: fixed; position: fixed;
inset: 0; inset: 0;
background: rgba(0,0,0,0.5); z-index: 1000;
background: rgba(0, 0, 0, 0.55);
backdrop-filter: blur(4px); backdrop-filter: blur(4px);
-webkit-backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px);
z-index: 1000; visibility: hidden;
opacity: 0; opacity: 0;
transition: opacity 300ms ease; transition: opacity 300ms ease, visibility 0s 300ms;
}
.nav__overlay.is-visible {
visibility: visible;
opacity: 1;
transition: opacity 300ms ease, visibility 0s 0s;
} }
.nav__overlay.show { display: block; opacity: 1; }
</style> </style>
<title>Geizkragen</title>
</head> </head>
<body> <body>
<!-- Mobile overlay for hamburger menu --> <!-- Overlay (klick = Menü schließen) -->
<div class="nav__overlay" id="nav-overlay"></div> <div class="nav__overlay" id="nav-overlay"></div>
<header class="header" id="header"> <header class="header" id="header">
@ -57,19 +54,24 @@
<img class="nav__logo" src="/assets/images/logoText.png" alt="Geizkragen" width="150"> <img class="nav__logo" src="/assets/images/logoText.png" alt="Geizkragen" width="150">
</a> </a>
<!-- Desktop search (hidden on mobile via CSS) --> <!-- Desktop-Suche (wird per CSS auf ≤900 px ausgeblendet) -->
<form class="nav__searchForm" action="index.php" method="GET" autocomplete="off"> <form class="nav__searchForm" action="index.php" method="GET" autocomplete="off">
<div class="nav__searchField"> <div class="nav__searchField">
<input class="nav__searchInput" type="text" id="search" name="search" placeholder="Produkte suchen…" inputmode="text"> <input class="nav__searchInput" type="text" id="search" name="search"
placeholder="Produkte suchen…" inputmode="text">
</div> </div>
</form> </form>
<div class="nav__inner container"> <div class="nav__inner container">
<!-- ═══ Slide-In-Menü (wird auf Mobile per JS getoggelt) ═══ -->
<div class="nav__menu" id="nav-menu"> <div class="nav__menu" id="nav-menu">
<!-- Mobile search (inside hamburger) -->
<!-- Mobile-Suche (nur im Hamburger sichtbar) -->
<form class="nav__searchForm nav__searchForm--mobile" action="index.php" method="GET" autocomplete="off"> <form class="nav__searchForm nav__searchForm--mobile" action="index.php" method="GET" autocomplete="off">
<div class="nav__searchField"> <div class="nav__searchField">
<input class="nav__searchInput" type="text" name="search" placeholder="Produkte suchen…" inputmode="text"> <input class="nav__searchInput" type="text" name="search"
placeholder="Produkte suchen…" inputmode="text">
</div> </div>
</form> </form>
@ -85,17 +87,22 @@
</li> </li>
</ul> </ul>
<div class="nav__close" id="nav-close" role="button" aria-label="Menü schließen" tabindex="0"> <!-- Schließen-Button -->
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"> <button class="nav__close" id="nav-close" type="button" aria-label="Menü schließen">
<line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/> <svg width="24" height="24" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round">
<line x1="18" y1="6" x2="6" y2="18"/>
<line x1="6" y1="6" x2="18" y2="18"/>
</svg> </svg>
</div> </button>
</div> </div>
<!-- ═══ /Slide-In-Menü ═══ -->
<div class="nav__actions"> <div class="nav__actions">
<a class="nav__login nav__wishlist" href="wunschliste.php" aria-label="Wunschliste"> <a class="nav__login nav__wishlist" href="wunschliste.php" aria-label="Wunschliste">
<svg class="icon icon-user" viewBox="0 0 24 24" aria-hidden="true"> <svg class="icon icon-user" viewBox="0 0 24 24" aria-hidden="true">
<path d="M8 6.00067L21 6.00139M8 12.0007L21 12.0015M8 18.0007L21 18.0015M3.5 6H3.51M3.5 12H3.51M3.5 18H3.51M4 6C4 6.27614 3.77614 6.5 3.5 6.5C3.22386 6.5 3 6.27614 3 6C3 5.72386 3.22386 5.5 3.5 5.5C3.77614 5.5 4 5.72386 4 6ZM4 12C4 12.2761 3.77614 12.5 3.5 12.5C3.22386 12.5 3 12.2761 3 12C3 11.7239 3.22386 11.5 3.5 11.5C3.77614 11.5 4 11.7239 4 12ZM4 18C4 18.2761 3.77614 18.5 3.5 18.5C3.22386 18.5 3 18.2761 3 18C3 17.7239 3.22386 17.5 3.5 17.5C3.77614 17.5 4 17.7239 4 18Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> <path d="M8 6.00067L21 6.00139M8 12.0007L21 12.0015M8 18.0007L21 18.0015M3.5 6H3.51M3.5 12H3.51M3.5 18H3.51M4 6C4 6.27614 3.77614 6.5 3.5 6.5C3.22386 6.5 3 6.27614 3 6C3 5.72386 3.22386 5.5 3.5 5.5C3.77614 5.5 4 5.72386 4 6ZM4 12C4 12.2761 3.77614 12.5 3.5 12.5C3.22386 12.5 3 12.2761 3 12C3 11.7239 3.22386 11.5 3.5 11.5C3.77614 11.5 4 11.7239 4 12ZM4 18C4 18.2761 3.77614 18.5 3.5 18.5C3.22386 18.5 3 18.2761 3 18C3 17.7239 3.22386 17.5 3.5 17.5C3.77614 17.5 4 17.7239 4 18Z"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg> </svg>
</a> </a>
<a class="nav__login" href="account.php" aria-label="Account"> <a class="nav__login" href="account.php" aria-label="Account">
@ -103,47 +110,53 @@
<path d="M4 22C4 17.5817 7.58172 14 12 14C16.4183 14 20 17.5817 20 22H18C18 18.6863 15.3137 16 12 16C8.68629 16 6 18.6863 6 22H4ZM12 13C8.685 13 6 10.315 6 7C6 3.685 8.685 1 12 1C15.315 1 18 3.685 18 7C18 10.315 15.315 13 12 13ZM12 11C14.21 11 16 9.21 16 7C16 4.79 14.21 3 12 3C9.79 3 8 4.79 8 7C8 9.21 9.79 11 12 11Z"/> <path d="M4 22C4 17.5817 7.58172 14 12 14C16.4183 14 20 17.5817 20 22H18C18 18.6863 15.3137 16 12 16C8.68629 16 6 18.6863 6 22H4ZM12 13C8.685 13 6 10.315 6 7C6 3.685 8.685 1 12 1C15.315 1 18 3.685 18 7C18 10.315 15.315 13 12 13ZM12 11C14.21 11 16 9.21 16 7C16 4.79 14.21 3 12 3C9.79 3 8 4.79 8 7C8 9.21 9.79 11 12 11Z"/>
</svg> </svg>
</a> </a>
<div class="nav__toggle" id="nav-toggle" role="button" aria-label="Menü öffnen" tabindex="0">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"> <!-- Hamburger-Toggle -->
<line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/> <button class="nav__toggle" id="nav-toggle" type="button" aria-label="Menü öffnen">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round">
<line x1="3" y1="6" x2="21" y2="6"/>
<line x1="3" y1="12" x2="21" y2="12"/>
<line x1="3" y1="18" x2="21" y2="18"/>
</svg> </svg>
</div> </button>
</div> </div>
</div> </div>
</nav> </nav>
</header> </header>
<!-- Hamburger Menu JS -->
<script> <script>
(function() { (function () {
const toggle = document.getElementById('nav-toggle'); var toggle = document.getElementById('nav-toggle');
const close = document.getElementById('nav-close'); var closeBtn = document.getElementById('nav-close');
const menu = document.getElementById('nav-menu'); var menu = document.getElementById('nav-menu');
const overlay = document.getElementById('nav-overlay'); var overlay = document.getElementById('nav-overlay');
if (!toggle || !closeBtn || !menu || !overlay) return;
function openMenu() { function open() {
menu.classList.add('show-menu'); menu.classList.add('show-menu');
overlay.classList.add('show'); overlay.classList.add('is-visible');
document.body.style.overflow = 'hidden'; document.body.style.overflow = 'hidden';
} }
function closeMenu() {
function close() {
menu.classList.remove('show-menu'); menu.classList.remove('show-menu');
overlay.classList.remove('show'); overlay.classList.remove('is-visible');
document.body.style.overflow = ''; document.body.style.overflow = '';
} }
if (toggle) toggle.addEventListener('click', openMenu); toggle.addEventListener('click', open);
if (close) close.addEventListener('click', closeMenu); closeBtn.addEventListener('click', close);
if (overlay) overlay.addEventListener('click', closeMenu); overlay.addEventListener('click', close);
// Close on Escape document.addEventListener('keydown', function (e) {
document.addEventListener('keydown', function(e) { if (e.key === 'Escape') close();
if (e.key === 'Escape') closeMenu();
}); });
// Close menu on link click (mobile) // Links im Menü → schließen
menu.querySelectorAll('.nav__link').forEach(function(link) { var links = menu.querySelectorAll('a');
link.addEventListener('click', closeMenu); for (var i = 0; i < links.length; i++) {
}); links[i].addEventListener('click', close);
}
})(); })();
</script> </script>

View File

@ -1,7 +1,5 @@
<?php include 'header.php'; ?>
<?php <?php
// login.php // productpage.php
ini_set('display_errors', 1); ini_set('display_errors', 1);
ini_set('display_startup_errors', 1); ini_set('display_startup_errors', 1);
@ -22,12 +20,12 @@ if (!$conn)
http_response_code(500); http_response_code(500);
die("Datenbankfehler"); die("Datenbankfehler");
} }
?>
<?php
$productId = isset($_GET['id']) ? (int)$_GET['id'] : 0; $productId = isset($_GET['id']) ? (int)$_GET['id'] : 0;
?> ?>
<?php include 'header.php'; ?>
<?php if ($productId <= 0): ?> <?php if ($productId <= 0): ?>
<section class="product-section"> <section class="product-section">
<h2>Produkt nicht gefunden</h2> <h2>Produkt nicht gefunden</h2>

View File

@ -306,9 +306,7 @@ a:hover {
} }
.nav__search, .nav__search,
.nav__login, .nav__login {
.nav__toggle,
.nav__close {
font-size: 1.3rem; font-size: 1.3rem;
color: var(--text-secondary); color: var(--text-secondary);
cursor: pointer; cursor: pointer;
@ -340,58 +338,82 @@ a:hover {
} }
/* ========================================================== /* ==========================================================
TOGGLE / CLOSE TOGGLE / CLOSE (sind <button>, also Standard-Styles resetten)
========================================================== */ ========================================================== */
.nav__toggle { display: none; } .nav__toggle,
.nav__close { display: none; } .nav__close {
display: none; /* auf Desktop unsichtbar */
background: none;
border: none;
color: var(--text-secondary);
cursor: pointer;
padding: 0.5rem;
border-radius: var(--radius-md);
transition: color var(--transition-normal),
background var(--transition-normal),
transform var(--transition-normal);
}
.nav__toggle:hover,
.nav__close:hover {
color: var(--text-invert);
background: rgba(255, 255, 255, 0.08);
}
/* Mobile-only search + items: hidden on desktop */ /* Mobile-only search + items: hidden on desktop */
.nav__searchForm--mobile { display: none; } .nav__searchForm--mobile { display: none; }
.nav__item--mobile { display: none; } .nav__item--mobile { display: none; }
/* ========================================================== /* ==========================================================
MOBILE NAV MOBILE NAV ( 900 px)
========================================================== */ ========================================================== */
@media (max-width: 900px) { @media (max-width: 900px) {
/* --- Hamburger-Icon anzeigen --- */
.nav__toggle { .nav__toggle {
display: flex; display: inline-flex;
align-items: center;
justify-content: center;
} }
/* --- Mobile-only Nav-Einträge anzeigen --- */
.nav__item--mobile { .nav__item--mobile {
display: list-item; display: list-item;
} }
/* --- Slide-In-Panel --- */
.nav__menu { .nav__menu {
position: fixed; position: fixed;
top: 0; top: 0;
right: -100%; right: -100%; /* startet außerhalb */
width: min(300px, 85vw); width: min(300px, 85vw);
height: 100%; height: 100%;
background: rgba(13, 17, 23, 0.97); background: rgba(13, 17, 23, 0.97);
backdrop-filter: blur(30px); backdrop-filter: blur(30px);
-webkit-backdrop-filter: blur(30px); -webkit-backdrop-filter: blur(30px);
border-left: 1px solid var(--border-subtle); border-left: 1px solid var(--border-subtle);
padding: 4.5rem 1.5rem 2rem; padding: 5rem 1.5rem 2rem;
transition: right 350ms cubic-bezier(0.4, 0, 0.2, 1); display: flex;
flex-direction: column; flex-direction: column;
align-items: flex-start; align-items: stretch;
gap: 1.25rem; gap: 0.75rem;
z-index: 1001; z-index: 1001;
overflow-y: auto; overflow-y: auto;
-webkit-overflow-scrolling: touch; -webkit-overflow-scrolling: touch;
transition: right 350ms cubic-bezier(0.4, 0, 0.2, 1);
} }
.nav__menu.show-menu { .nav__menu.show-menu {
right: 0; right: 0; /* reingleiten */
} }
/* Show mobile search inside menu */ /* --- Mobile-Suche im Hamburger --- */
.nav__searchForm--mobile { .nav__searchForm--mobile {
display: block; display: block;
width: 100%; width: 100%;
position: static; position: static;
transform: none; transform: none;
margin-bottom: 0.5rem; margin-bottom: 0.25rem;
} }
.nav__searchForm--mobile .nav__searchField { .nav__searchForm--mobile .nav__searchField {
@ -413,7 +435,7 @@ a:hover {
width: 100%; width: 100%;
height: 46px; height: 46px;
padding: 0 14px 0 36px; padding: 0 14px 0 36px;
font-size: 16px; /* Prevents iOS zoom */ font-size: 16px; /* verhindert iOS-Zoom */
border-radius: var(--radius-md); border-radius: var(--radius-md);
background: rgba(255, 255, 255, 0.05); background: rgba(255, 255, 255, 0.05);
border: 1px solid var(--border-default); border: 1px solid var(--border-default);
@ -425,9 +447,10 @@ a:hover {
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.12); box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.12);
} }
/* --- Nav-Links im Panel --- */
.nav__list { .nav__list {
flex-direction: column; flex-direction: column;
gap: 0.25rem; gap: 0.15rem;
width: 100%; width: 100%;
} }
@ -436,7 +459,7 @@ a:hover {
color: var(--text-secondary); color: var(--text-secondary);
display: flex; display: flex;
align-items: center; align-items: center;
min-height: 44px; min-height: 46px;
padding: 0.6rem 1rem; padding: 0.6rem 1rem;
border-radius: var(--radius-md); border-radius: var(--radius-md);
width: 100%; width: 100%;
@ -447,21 +470,19 @@ a:hover {
transform: translateX(4px); transform: translateX(4px);
} }
/* --- Schließen-Button --- */
.nav__close { .nav__close {
display: flex; display: flex;
align-items: center;
justify-content: center;
position: absolute; position: absolute;
top: 1rem; top: 1rem;
right: 1rem; right: 1rem;
color: var(--text-secondary);
padding: 0.5rem;
min-height: 44px; min-height: 44px;
min-width: 44px; min-width: 44px;
align-items: center;
justify-content: center;
} }
.nav__close:hover { .nav__close:hover {
color: var(--text-invert);
transform: rotate(90deg); transform: rotate(90deg);
} }
} }
@ -997,12 +1018,6 @@ a:hover {
height: 20px; height: 20px;
} }
/* Mobile menu full-width */
.nav__menu {
width: 100% !important;
right: -100%;
}
.product-grid { .product-grid {
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
gap: 0.8rem; gap: 0.8rem;

View File

@ -1,7 +1,5 @@
<?php include 'header.php'; ?>
<?php <?php
// login.php // wunschliste.php
ini_set('display_errors', 1); ini_set('display_errors', 1);
ini_set('display_startup_errors', 1); ini_set('display_startup_errors', 1);
@ -22,57 +20,54 @@ if (!$conn)
http_response_code(500); http_response_code(500);
die("Datenbankfehler"); die("Datenbankfehler");
} }
// Login-Check + Redirect MUSS vor jeglicher HTML-Ausgabe passieren
if (!isset($_SESSION['user_id'])) {
header("Location: login.php");
exit();
}
// Daten laden
$stmt = $conn->prepare("
SELECT products.productID, products.model, products.description, products.imagePath
FROM userFavorites INNER JOIN products ON userFavorites.productID = products.productID
WHERE userID = ?
");
$stmt->bind_param("i", $_SESSION['user_id']);
$stmt->execute();
$result = $stmt->get_result();
?> ?>
<?php <?php include 'header.php'; ?>
if (!isset($_SESSION['user_id'])) {
header("Location: login.php");
exit();
}
?>
<?php <?php if ($result->num_rows > 0): ?>
$stmt = $conn->prepare(" <main>
SELECT products.productID, products.model, products.description, products.imagePath <section class="product-section">
FROM userFavorites INNER JOIN products ON userFavorites.productID = products.productID <h2>Deine Wunschliste</h2>
WHERE userID = ? <div class="product-scroll">
"); <?php while ($product = $result->fetch_assoc()): ?>
<?php $productId = (int)$product['productID']; ?>
<a class="product-card" href="productpage.php?id=<?= $productId ?>">
<img
src="<?= isset($product['imagePath']) ? $product['imagePath'] : 'assets/images/placeholder.png' ?>"
alt="<?= htmlspecialchars($product['model']) ?>">
$stmt->bind_param("i", $_SESSION['user_id']); <div class="product-card__content">
$stmt->execute(); <h3><?= htmlspecialchars($product['model']) ?></h3>
<p><?= htmlspecialchars($product['description']) ?></p>
$result = $stmt->get_result(); </div>
?> </a>
<?php endwhile; ?>
<?php if ($result->num_rows > 0): ?> </div>
<main> </section>
<section class="product-section"> </main>
<h2>Deine Wunschliste</h2> <?php else: ?>
<div class="product-scroll"> <main style="padding: 2rem 1rem; text-align: center; animation: fadeInUp 0.5s ease both;">
<?php while ($product = $result->fetch_assoc()): ?> <p style="color: var(--text-secondary); font-size: 1rem;">Deine Wunschliste ist noch leer.</p>
<?php $productId = (int)$product['productID']; ?> </main>
<a class="product-card" href="productpage.php?id=<?= $productId ?>"> <?php endif; ?>
<img
src="<?= isset($product['imagePath']) ? $product['imagePath'] : 'assets/images/placeholder.png' ?>"
alt="<?= htmlspecialchars($product['model']) ?>">
<div class="product-card__content">
<h3><?= htmlspecialchars($product['model']) ?></h3>
<p><?= htmlspecialchars($product['description']) ?></p>
</div>
</a>
<?php endwhile; ?>
</div>
</section>
</main>
<?php else: ?>
<main style="padding: 2rem 1rem; text-align: center; animation: fadeInUp 0.5s ease both;">
<p style="color: var(--text-secondary); font-size: 1rem;">Deine Wunschliste ist noch leer.</p>
</main>
<?php endif; ?>
<?php $stmt->close(); ?>
<?php $stmt->close(); ?>
<?php include 'footer.php'; ?> <?php include 'footer.php'; ?>