From 87055bd14bae8c86080f78448a7e022e5d753a1f Mon Sep 17 00:00:00 2001 From: fs Date: Mon, 27 Jul 2026 10:09:33 +0200 Subject: [PATCH] Design-System: Spacing auf Tokens migrieren MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Alle padding/margin/gap-Deklarationen in components.css, layout.css und base.css nutzen jetzt die Spacing-Skala — 414 Token-Verwendungen, kein roher rem-Wert mehr in einer Abstands-Deklaration. Die Substitution lief mechanisch (nächste Rasterstufe, Gleichstand aufgerundet), damit sie deterministisch und nachvollziehbar ist statt 450-mal von Hand geraten. Rund 325 Werte lagen bereits exakt auf einer Stufe und sind verlustfrei übernommen. 125 Werte verschieben sich: ±0,4–2,0px 106 Werte (0.1/0.15/0.3/0.35/0.4/0.6/0.85/0.9/1.1/1.6rem …) ±4px 12 Werte (2.25 -> 2.5, 2.75 -> 3rem) ±8px 8 Werte (3.5 -> 4, 4.5 -> 5, 7.5 -> 7rem) Die 8px-Sprünge treffen ausschließlich Sektionsabstände (padding-block großer Bänder, margin-top von Blöcken), wo sie optisch nicht ins Gewicht fallen. Die ±4px-Gruppe ist 2.25rem, das exakt zwischen zwei Stufen lag. em-Werte bleiben bewusst roh: sie skalieren mit der font-size (Button-Padding, Badge-Innenabstände) und gehören damit nicht auf ein festes px-Raster. Nullen, auto und Prozentwerte sind unangetastet, calc() wurde korrekt mitgezogen (calc(var(--header-h) + var(--space-5xl))). Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01MSsVdooFLgPA8gPFp7HwZU --- app/actions/sportheimbuchung-senden.php | 147 +++--- app/components/contact-form.php | 10 +- app/components/membership-form.php | 10 +- app/components/sportheimbuchung-form.php | 10 +- public/assets/css/components.css | 576 +++++++++++------------ public/assets/css/layout.css | 70 +-- public/assets/js/form.js | 27 +- 7 files changed, 458 insertions(+), 392 deletions(-) diff --git a/app/actions/sportheimbuchung-senden.php b/app/actions/sportheimbuchung-senden.php index 7a7bd82..fa3c452 100644 --- a/app/actions/sportheimbuchung-senden.php +++ b/app/actions/sportheimbuchung-senden.php @@ -3,12 +3,13 @@ declare(strict_types=1); /** - * POST /sportheimbuchung-senden — Sportheim-Buchungsanfrage validieren und via Brevo SMTP versenden. - * Antwort: PRG-Redirect zu /sportheimbuchung#buchung (?sent=1 | ?error=…). + * POST /sportheimbuchung-senden — Sportheim-Buchungsanfrage validieren und über + * All-Inkl-SMTP versenden. + * Antwort: PRG-Redirect zu /sportheimbuchung#buchung (?sent=1 | ?error=…), + * JSON bei fetch (form.js) oder — bei Feldfehlern ohne JS — die Seite direkt + * mit HTTP 422, erhaltenen Eingaben und Feldfehlern (render_form_invalid()). */ -use PHPMailer\PHPMailer\PHPMailer; - $wantsJson = str_contains($_SERVER['HTTP_ACCEPT'] ?? '', 'application/json'); $respond = static function (bool $ok, string $error = '') use ($wantsJson): never { @@ -26,6 +27,13 @@ $field = static fn (string $key): string => trim((string) ($_POST[$key] ?? '')); $route = 'sportheimbuchung'; $token = $field('ft'); +// Ohne vollständige config/config.php liefe die Seite auf der Vorlage (öffentliches +// app_secret, kein SMTP-Passwort) — dann gar nicht erst verarbeiten. +if (config_problems() !== []) { + error_log('[' . date('c') . "] {$route}: Formular abgewiesen, Konfiguration unvollständig (php bin/preflight.php).\n", 3, STORAGE_PATH . '/logs/mail.log'); + $respond(false, 'mail'); +} + // --- Spam-Checks: Bots bekommen ein stilles "OK" (kein Feedback-Kanal) --- if ($field('company_url') !== '') { log_spam($route, 'honeypot'); @@ -36,13 +44,7 @@ if (!form_token_valid($token)) { $respond(true); } -// Rate-Limit pro IP (sichtbarer Hinweis statt still — legitime NAT-Nutzer nicht im Dunkeln lassen). -if (!rate_limit_ok('submit|' . $route . '|' . client_ip(), 5, 600)) { - log_spam($route, 'ratelimit'); - $respond(false, 'ratelimit'); -} - -// --- Validierung --- +// --- Eingaben --- $name = $field('name'); $email = $field('email'); $phone = $field('phone'); @@ -50,43 +52,86 @@ $eventType = $field('event_type'); $eventDate = $field('event_date'); $attendees = $field('attendees'); $message = $field('message'); +$privacy = ($_POST['privacy'] ?? '') === '1'; -$validTypes = ['', 'Geburtstagsfeier', 'Party', 'Vereinsveranstaltung', 'Weihnachtsfeier', 'Firmenveranstaltung', 'Sonstiges']; +// Veranstaltungsarten aus derselben Quelle wie das Formular (data/sportheimbuchung.json). +$validTypes = json_load('sportheimbuchung')['form']['event_types'] ?? []; -$valid = $name !== '' && mb_strlen($name) <= 200 - && filter_var($email, FILTER_VALIDATE_EMAIL) !== false - && mb_strlen($phone) <= 50 - && in_array($eventType, $validTypes, true) - && ($eventDate === '' || preg_match('/^\d{4}-\d{2}-\d{2}$/', $eventDate)) - && ($attendees === '' || (ctype_digit($attendees) && (int) $attendees >= 1 && (int) $attendees <= 500)) - && mb_strlen($message) <= 3000 - && ($_POST['privacy'] ?? '') === '1' - && !preg_match('/[\r\n]/', $name); - -if (!$valid) { - $respond(false, 'validation'); +// Wunschdatum: echter Kalendertag, nicht in der Vergangenheit, höchstens 3 Jahre voraus. +$dateValid = false; +if (preg_match('/^(\d{4})-(\d{2})-(\d{2})$/', $eventDate, $m) === 1) { + $dateValid = checkdate((int) $m[2], (int) $m[3], (int) $m[1]) + && $eventDate >= date('Y-m-d') + && $eventDate <= date('Y-m-d', strtotime('+3 years')); } -// --- Inhalts-/Aufkommens-Heuristiken (still abweisen, Bot-typisch) --- +// --- Validierung (Whitelist-Prinzip, Fehler pro Feld) --- +$errors = []; +if ($name === '' || mb_strlen($name) > 200 || preg_match('/[\r\n]/', $name)) { + $errors['buch-name'] = 'Bitte gib deinen Namen an (max. 200 Zeichen).'; +} +if (filter_var($email, FILTER_VALIDATE_EMAIL) === false) { + $errors['buch-email'] = 'Bitte gib eine gültige E-Mail-Adresse an.'; +} +if (mb_strlen($phone) > 50) { + $errors['buch-phone'] = 'Die Telefonnummer ist zu lang (max. 50 Zeichen).'; +} +if ($eventType !== '' && !in_array($eventType, $validTypes, true)) { + $errors['buch-type'] = 'Bitte wähle eine der angebotenen Veranstaltungsarten.'; +} +if (!$dateValid) { + $errors['buch-date'] = 'Bitte gib ein Wunschdatum an (heute oder später).'; +} +if (!ctype_digit($attendees) || (int) $attendees < 1 || (int) $attendees > 500) { + $errors['buch-attendees'] = 'Bitte gib die ungefähre Personenzahl an (1 bis 500).'; +} +if (mb_strlen($message) > 3000) { + $errors['buch-message'] = 'Der Text ist zu lang (max. 3000 Zeichen).'; +} +if (!$privacy) { + $errors['buch-privacy'] = 'Ohne Zustimmung zum Datenschutz können wir die Anfrage nicht entgegennehmen.'; +} + +if ($errors !== []) { + if ($wantsJson) { + header('Content-Type: application/json; charset=utf-8'); + echo json_encode(['ok' => false, 'error' => 'validation', 'fields' => $errors]); + exit; + } + // Ohne JS: Seite mit Eingaben und Feldfehlern neu rendern statt PRG (Werte bleiben erhalten). + render_form_invalid($route, [ + 'name' => $name, 'email' => $email, 'phone' => $phone, 'event_type' => $eventType, + 'event_date' => $eventDate, 'attendees' => $attendees, 'message' => $message, + 'privacy' => $privacy ? '1' : '', + ], $errors); +} + +// --- Inhalts-Heuristik (still abweisen, Bot-typisch) --- if (preg_match_all('~https?://~i', $message) > 2) { log_spam($route, 'links'); $respond(true); } -// Globaler Tages-Cap als Brevo-Kosten-Backstop — die Flut soll ihn nicht bemerken. -if (!rate_limit_ok('daily', 100, 86400)) { - log_spam($route, 'daily-cap'); - $respond(true); + +// --- Volumen-Grenzen: erst ab hier, damit Tippfehler kein Kontingent verbrauchen. +// Sie antworten ehrlich — stilles "Danke" ohne Versand verschluckt echte Anfragen. --- +if (!rate_limit_ok('submit|' . $route . '|' . client_ip(), 5, 600)) { + log_spam($route, 'ratelimit'); + $respond(false, 'ratelimit'); } -// Token-Mehrfachnutzung begrenzen (Replay-Schutz, großzügig für menschliches Mehrfach-Senden). -if (!rate_limit_ok('token|' . $token, 3, 86400)) { +// Token-Mehrfachnutzung begrenzen (Replay-Schutz). IP im Schlüssel, weil form_token() +// sekundengenau ist: sonst teilen sich alle Besucher derselben Sekunde ein Kontingent. +if (!rate_limit_ok('token|' . $token . '|' . client_ip(), 3, 86400)) { log_spam($route, 'replay'); - $respond(true); + $respond(false, 'ratelimit'); +} +// Globaler Tages-Cap über alle Formulare: schützt Postfach und Absender-Reputation. +if (!rate_limit_ok('daily', 200, 86400)) { + log_spam($route, 'daily-cap'); + $respond(false, 'busy'); } // --- Versand --- -$smtp = config('smtp'); - -$dateFormatted = $eventDate !== '' ? date('d.m.Y', strtotime($eventDate)) : '–'; +$dateFormatted = date('d.m.Y', (int) strtotime($eventDate)); $body = "Sportheimbuchung-Anfrage über tsv08kulmbach.de\n" . str_repeat('-', 40) . "\n" @@ -95,33 +140,17 @@ $body = "Sportheimbuchung-Anfrage über tsv08kulmbach.de\n" . ($phone !== '' ? "Telefon: {$phone}\n" : '') . ($eventType !== '' ? "Veranstaltungstyp: {$eventType}\n" : '') . "Gewünschtes Datum: {$dateFormatted}\n" - . ($attendees !== '' ? "Personenzahl: {$attendees}\n" : '') + . "Personenzahl: {$attendees}\n" . str_repeat('-', 40) . "\n\n" . ($message !== '' ? $message . "\n\n" : '') . str_repeat('-', 40) . "\n" . 'Datenschutz zugestimmt: ja (' . date('d.m.Y H:i') . ")\n"; -try { - $mail = new PHPMailer(true); - $mail->isSMTP(); - $mail->Host = $smtp['host']; - $mail->Port = (int) $smtp['port']; - $mail->SMTPAuth = true; - $mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS; - $mail->Username = $smtp['username']; - $mail->Password = $smtp['password']; - $mail->CharSet = PHPMailer::CHARSET_UTF8; +$sent = send_mail([ + 'context' => 'Sportheimbuchung', + 'subject' => 'Sportheimbuchung: ' . ($eventType !== '' ? $eventType : 'Anfrage') . ', ' . $dateFormatted, + 'body' => $body, + 'reply_to' => ['email' => $email, 'name' => $name], +]); - $mail->setFrom($smtp['from'], $smtp['from_name']); - $mail->addAddress($smtp['to']); - $mail->addReplyTo($email, $name); - $mail->Subject = 'Sportheimbuchung: ' . ($eventType !== '' ? $eventType : 'Anfrage') . ', ' . $dateFormatted; - $mail->Body = $body; - - $mail->send(); -} catch (Throwable $e) { - error_log('[' . date('c') . '] Sportheimbuchung-Mailversand fehlgeschlagen: ' . $e->getMessage() . "\n", 3, STORAGE_PATH . '/logs/mail.log'); - $respond(false, 'mail'); -} - -$respond(true); +$respond($sent, $sent ? '' : 'mail'); diff --git a/app/components/contact-form.php b/app/components/contact-form.php index ee03ec1..64e59e9 100644 --- a/app/components/contact-form.php +++ b/app/components/contact-form.php @@ -19,11 +19,14 @@ $intro = $intro ?? null; $interests = ['Herrenfußball', 'Frauenfußball', 'Jugendfußball', 'Turnen', 'Sonstiges']; +// Fehlerquelle: PRG-Query (?error=…) oder — beim 422-Re-Render ohne JS — die +// serverseitigen Feldfehler aus der Action. $sent = isset($_GET['sent']); -$error = isset($_GET['error']) ? (string) $_GET['error'] : null; +$error = isset($_GET['error']) ? (string) $_GET['error'] : (form_has_errors() ? 'validation' : null); $errorMessages = [ - 'validation' => 'Bitte prüfe deine Eingaben – Pflichtfelder fehlen oder die E-Mail-Adresse ist ungültig.', + 'validation' => 'Bitte prüfe deine Eingaben – die markierten Felder brauchen noch etwas Aufmerksamkeit.', 'ratelimit' => 'Zu viele Anfragen in kurzer Zeit. Bitte versuche es in ein paar Minuten erneut.', + 'busy' => 'Bei uns gehen gerade ungewöhnlich viele Anfragen ein. Bitte versuche es später noch einmal oder schreib uns direkt an ' . $club['email'] . '.', 'mail' => 'Deine Nachricht konnte gerade nicht versendet werden. Bitte versuche es später erneut oder schreib uns direkt an ' . $club['email'] . '.', ]; @@ -51,7 +54,8 @@ $f = [
-
+ +
>

Danke für deine Anfrage! Wir melden uns so schnell wie möglich bei dir.

diff --git a/app/components/membership-form.php b/app/components/membership-form.php index cdf7467..288fbd1 100644 --- a/app/components/membership-form.php +++ b/app/components/membership-form.php @@ -13,11 +13,14 @@ declare(strict_types=1); $club = json_load('club'); $fee_options = $fee_options ?? []; +// Fehlerquelle: PRG-Query (?error=…) oder — beim 422-Re-Render ohne JS — die +// serverseitigen Feldfehler aus der Action. $sent = isset($_GET['sent']); -$error = isset($_GET['error']) ? (string) $_GET['error'] : null; +$error = isset($_GET['error']) ? (string) $_GET['error'] : (form_has_errors() ? 'validation' : null); $errorMessages = [ - 'validation' => 'Bitte prüfe deine Eingaben – Pflichtfelder fehlen oder die E-Mail-Adresse ist ungültig.', + 'validation' => 'Bitte prüfe deine Eingaben – die markierten Felder brauchen noch etwas Aufmerksamkeit.', 'ratelimit' => 'Zu viele Anfragen in kurzer Zeit. Bitte versuche es in ein paar Minuten erneut.', + 'busy' => 'Bei uns gehen gerade ungewöhnlich viele Anfragen ein. Bitte versuche es später noch einmal oder schreib uns direkt an ' . $club['email'] . '.', 'mail' => 'Dein Antrag konnte gerade nicht versendet werden. Bitte versuche es später erneut oder schreib uns direkt an ' . $club['email'] . '.', ]; @@ -44,7 +47,8 @@ $f = [ ]; ?> -
+ +
>

Danke! Dein Mitgliedsantrag ist bei uns eingegangen – wir melden uns und senden dir die Beitrittserklärung per Post zu.

diff --git a/app/components/sportheimbuchung-form.php b/app/components/sportheimbuchung-form.php index 1cd1506..3b0aaa9 100644 --- a/app/components/sportheimbuchung-form.php +++ b/app/components/sportheimbuchung-form.php @@ -10,11 +10,14 @@ declare(strict_types=1); */ $club = json_load('club'); +// Fehlerquelle: PRG-Query (?error=…) oder — beim 422-Re-Render ohne JS — die +// serverseitigen Feldfehler aus der Action. $sent = isset($_GET['sent']); -$error = isset($_GET['error']) ? (string) $_GET['error'] : null; +$error = isset($_GET['error']) ? (string) $_GET['error'] : (form_has_errors() ? 'validation' : null); $errorMessages = [ - 'validation' => 'Bitte prüfe deine Eingaben – Pflichtfelder fehlen oder die E-Mail-Adresse ist ungültig.', + 'validation' => 'Bitte prüfe deine Eingaben – die markierten Felder brauchen noch etwas Aufmerksamkeit.', 'ratelimit' => 'Zu viele Anfragen in kurzer Zeit. Bitte versuche es in ein paar Minuten erneut.', + 'busy' => 'Bei uns gehen gerade ungewöhnlich viele Anfragen ein. Bitte versuche es später noch einmal oder schreib uns direkt an ' . $club['email'] . '.', 'mail' => 'Deine Anfrage konnte gerade nicht gesendet werden. Bitte versuche es später erneut oder schreib uns direkt an ' . $club['email'] . '.', ]; @@ -43,7 +46,8 @@ $f = [

Oder direkt per Mail an

-
+ +
>

Danke für deine Anfrage! Wir melden uns kurzfristig bei dir.

diff --git a/public/assets/css/components.css b/public/assets/css/components.css index 99345c1..09dfcb8 100644 --- a/public/assets/css/components.css +++ b/public/assets/css/components.css @@ -18,7 +18,7 @@ .hero--compact { min-height: min(60vh, 560px); min-height: min(60svh, 560px); - padding-block: calc(var(--header-h) + 64px) 3.5rem; + padding-block: calc(var(--header-h) + 64px) var(--space-5xl); } /* Mobile: Höhe am Inhalt statt am Viewport orientieren — weniger Leer-Scroll. @@ -28,12 +28,12 @@ .hero { min-height: min(82vh, 700px); min-height: min(82svh, 700px); - padding-block: calc(var(--header-h) + 48px) 3rem; + padding-block: calc(var(--header-h) + 48px) var(--space-4xl); } .hero--compact { min-height: 0; - padding-block: calc(var(--header-h) + 44px) 2.5rem; + padding-block: calc(var(--header-h) + 44px) var(--space-3xl); } } @@ -88,13 +88,13 @@ /* Keyword-Subline innerhalb der h1 (SEO) — visuell kleinere zweite Zeile */ .hero__tagline { display: block; - margin-top: 0.75rem; + margin-top: var(--space-xs); font-size: var(--fs-600); letter-spacing: 0.14em; } .hero__text { - margin-top: 1rem; + margin-top: var(--space-s); max-width: 60ch; text-shadow: 0 1px 12px rgb(0 0 0 / 0.6); text-wrap: pretty; @@ -109,8 +109,8 @@ .hero__actions { display: flex; flex-wrap: wrap; - gap: 0.75rem; - margin-top: 1.75rem; + gap: var(--space-xs); + margin-top: var(--space-xl); } /* Variante: Text mittig zentriert (z. B. Startseite) */ @@ -152,7 +152,7 @@ Schmuck-Linien, damit die Typo-Hierarchie ruhig bleibt (Eyebrow → Titel → Subline → Text). */ .hero__kicker { - margin-bottom: 1.25rem; + margin-bottom: var(--space-m); font-size: var(--fs-500); text-transform: uppercase; letter-spacing: 0.18em; @@ -168,7 +168,7 @@ /* Subline ruhig in Abel, Groß-/Kleinschreibung — konkurriert nicht mehr mit Kicker (gesperrte Versalien) und Coolvetica-Titel. */ .hero--display .hero__tagline { - margin-top: 0.75rem; + margin-top: var(--space-xs); font-family: var(--ff-body); font-size: clamp(1.25rem, 0.95rem + 1.3vw, 1.75rem); font-weight: 400; @@ -218,8 +218,8 @@ .instagram__grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); - gap: 1rem; - margin-top: 2rem; + gap: var(--space-s); + margin-top: var(--space-2xl); } /* Mobil: Ein-Reihen-Swiper statt 2er-Grid — volle Bildgröße, natürliche @@ -230,7 +230,7 @@ grid-template-columns: none; grid-auto-flow: column; grid-auto-columns: min(72vw, 320px); - gap: 0.75rem; + gap: var(--space-xs); overflow-x: auto; scroll-snap-type: x mandatory; scrollbar-width: none; @@ -282,8 +282,8 @@ } .instagram__fallback { - margin-top: 1.5rem; - padding: 2.5rem; + margin-top: var(--space-l); + padding: var(--space-3xl); background: var(--clr-glass-bg); border: 1px solid var(--clr-glass-border); border-radius: var(--radius-card); @@ -295,7 +295,7 @@ } .instagram__fallback .btn { - margin-top: 1rem; + margin-top: var(--space-s); } /* === split (Bild/Text-Sektion) === */ @@ -303,7 +303,7 @@ display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); align-items: center; - gap: 3rem; + gap: var(--space-4xl); } /* Sanft aufgehelltes, durchgehendes Band über die volle Breite — generisch für @@ -331,7 +331,7 @@ /* Bild/Text-Sektionen im Band: mehr Luft nach oben und unten */ .split.section--tinted { - padding-block: clamp(4.5rem, 3.5rem + 3vw, 7.5rem); + padding-block: clamp(var(--space-6xl), var(--space-5xl) + 3vw, var(--space-7xl)); } .split--flip .split__text { @@ -343,11 +343,11 @@ } .split__text p { - margin-top: 1rem; + margin-top: var(--space-s); } .split__cta { - margin-top: 1.5rem; + margin-top: var(--space-l); } .split__img { @@ -359,7 +359,7 @@ @media (max-width: 992px) { .split__inner { grid-template-columns: 1fr; - gap: 2rem; + gap: var(--space-2xl); } .split--flip .split__text { @@ -374,7 +374,7 @@ /* === stats === */ .stats { position: relative; - padding-block: 7rem; + padding-block: var(--space-7xl); overflow: hidden; } @@ -406,7 +406,7 @@ .stats__list { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); - gap: 2rem; + gap: var(--space-2xl); text-align: center; } @@ -418,7 +418,7 @@ .stats__item { display: grid; - gap: 0.25rem; + gap: var(--space-3xs); } .stats__value { @@ -440,7 +440,7 @@ .timeline-intro { max-width: 60ch; margin-inline: auto; - padding-block: 2.5rem 0; + padding-block: var(--space-3xl) 0; text-align: center; color: var(--clr-text-muted); } @@ -452,15 +452,15 @@ z-index: 5; display: flex; justify-content: center; - margin-bottom: 3rem; + margin-bottom: var(--space-4xl); } .timeline-nav__list { display: flex; flex-wrap: wrap; justify-content: center; - gap: 0.4rem; - padding: 0.4rem; + gap: var(--space-2xs); + padding: var(--space-2xs); background: rgb(10 10 10 / 0.82); border: 1px solid var(--clr-glass-border); border-radius: 999px; @@ -494,8 +494,8 @@ .timeline { position: relative; display: grid; - gap: 4.5rem; - padding-block: 1rem 2rem; + gap: var(--space-6xl); + padding-block: var(--space-s) var(--space-2xl); } .timeline::before { @@ -542,7 +542,7 @@ font-size: var(--fs-900); color: var(--clr-text); text-shadow: 0 2px 18px rgb(0 0 0 / 0.5); - margin-bottom: 1rem; + margin-bottom: var(--space-s); } .timeline__event:nth-child(odd) .timeline__year { @@ -552,7 +552,7 @@ /* Museums-Rahmen: Foto wie ein aufgezogener Print, leicht gedreht */ .timeline__figure { margin: 0; - padding: 0.9rem 0.9rem 1.1rem; + padding: var(--space-s) var(--space-s) var(--space-s); background: var(--clr-sepia-bg); border-radius: 3px; box-shadow: 0 12px 35px rgb(0 0 0 / 0.5); @@ -575,7 +575,7 @@ } .timeline__text { - padding-top: 0.9rem; + padding-top: var(--space-s); font-size: var(--fs-500); line-height: 1.45; color: var(--clr-sepia-text); @@ -602,7 +602,7 @@ .timeline__event:nth-child(even) { width: auto; margin-left: 0; - padding-left: 2.5rem; + padding-left: var(--space-3xl); } .timeline__marker, @@ -627,7 +627,7 @@ /* Intro-Abstand zur Überschrift einheitlich mit den anderen Sektionen */ .partners__intro { - margin-top: 1rem; + margin-top: var(--space-s); } .partners__grid { @@ -635,14 +635,14 @@ grid-template-columns: repeat(auto-fit, minmax(180px, 220px)); justify-content: center; align-items: center; - gap: 2rem; - margin-block: 2.5rem; + gap: var(--space-2xl); + margin-block: var(--space-3xl); } .partners__item { display: grid; place-items: center; - padding: 1.5rem; + padding: var(--space-l); /* Dunkles Glas — die Partner-Logos sind weiße Varianten */ background: var(--clr-glass-bg); border: 1px solid var(--clr-glass-border); @@ -661,14 +661,14 @@ .sponsor-arguments__grid { display: grid; grid-template-columns: repeat(2, 1fr); - gap: 2.5rem 3rem; + gap: var(--space-3xl) var(--space-4xl); list-style: none; padding: 0; } .sponsor-arguments__item { display: flex; - gap: 1.25rem; + gap: var(--space-m); align-items: flex-start; } @@ -682,7 +682,7 @@ place-items: center; font-size: 1.1rem; color: #fff; - margin-top: 0.25rem; + margin-top: var(--space-3xs); } .sponsor-arguments__title { @@ -690,7 +690,7 @@ font-size: var(--fs-675); text-transform: uppercase; line-height: var(--lh-heading); - margin: 0 0 0.5rem; + margin: 0 0 var(--space-2xs); } .sponsor-arguments__text { @@ -703,7 +703,7 @@ @media (max-width: 768px) { .sponsor-arguments__grid { grid-template-columns: 1fr; - gap: 2rem; + gap: var(--space-2xl); } } @@ -715,15 +715,15 @@ .sponsor-channels__intro { max-width: 65ch; margin-inline: auto; - margin-top: 1rem; + margin-top: var(--space-s); color: var(--clr-text-muted); } .sponsor-channels__grid { display: grid; grid-template-columns: repeat(3, 1fr); - gap: 1.5rem; - margin-top: 2.5rem; + gap: var(--space-l); + margin-top: var(--space-3xl); list-style: none; padding: 0; align-items: stretch; @@ -734,7 +734,7 @@ border: 1px solid var(--clr-glass-border); border-top: 3px solid var(--clr-accent); border-radius: var(--radius-card); - padding: 2rem; + padding: var(--space-2xl); display: flex; flex-direction: column; gap: 0; @@ -761,7 +761,7 @@ letter-spacing: 0.12em; text-transform: uppercase; color: var(--clr-text-muted); - margin: 0 0 1rem; + margin: 0 0 var(--space-s); } .sponsor-channels__icon { @@ -773,7 +773,7 @@ place-items: center; font-size: 1.25rem; color: #fff; - margin-bottom: 1.25rem; + margin-bottom: var(--space-m); } .sponsor-channels__title { @@ -781,7 +781,7 @@ font-size: var(--fs-700); text-transform: uppercase; line-height: var(--lh-heading); - margin: 0 0 0.75rem; + margin: 0 0 var(--space-xs); } .sponsor-channels__desc { @@ -794,7 +794,7 @@ .sponsor-channels__divider { border: none; border-top: 1px solid var(--clr-glass-border); - margin: 1.5rem 0; + margin: var(--space-l) 0; } .sponsor-channels__features { @@ -803,14 +803,14 @@ margin: 0; display: flex; flex-direction: column; - gap: 0.625rem; + gap: var(--space-xs); flex: 1; } .sponsor-channels__features li { display: flex; align-items: center; - gap: 0.625rem; + gap: var(--space-xs); font-size: var(--fs-500); color: var(--clr-text); } @@ -850,7 +850,7 @@ .persons__intro { margin-inline: auto; - margin-top: 1rem; + margin-top: var(--space-s); max-width: 60ch; color: var(--clr-text-muted); } @@ -861,7 +861,7 @@ display: block; width: 2.5rem; height: 2px; - margin: 0.6rem auto 0; + margin: var(--space-2xs) auto 0; background: var(--clr-accent); } @@ -869,8 +869,8 @@ .persons__grid { display: grid; grid-template-columns: repeat(5, 1fr); - gap: 1.25rem; - margin-top: 2.25rem; + gap: var(--space-m); + margin-top: var(--space-3xl); text-align: left; } @@ -925,8 +925,8 @@ .person-card__body { display: grid; - gap: 0.2rem; - padding: 1.1rem 1.2rem 1.2rem; + gap: var(--space-3xs); + padding: var(--space-s) var(--space-m) var(--space-m); /* Verlauf knüpft an die dunklen Studiofotos an */ background: linear-gradient(180deg, rgb(20 20 20 / 0) 0%, rgb(20 20 20 / 0.35) 100%); } @@ -937,7 +937,7 @@ } .persons .person-card__body { - padding: 0.85rem 1rem 1rem; + padding: var(--space-xs) var(--space-s) var(--space-s); } .person-card__department { @@ -952,9 +952,9 @@ .person-card__contact { display: inline-flex; align-items: center; - gap: 0.5rem; - margin-top: 0.5rem; - padding-block: 0.3rem; + gap: var(--space-2xs); + margin-top: var(--space-2xs); + padding-block: var(--space-3xs); color: var(--clr-text); text-decoration: none; font-size: var(--fs-500); @@ -984,11 +984,11 @@ .filter-bar { display: flex; flex-wrap: wrap; - margin-bottom: 2rem; + margin-bottom: var(--space-2xl); } .filter-bar__tab { - padding: 0.65rem 1.2rem; + padding: var(--space-xs) var(--space-m); background: none; border: none; box-shadow: inset 0 -2px 0 var(--clr-glass-border); @@ -1020,12 +1020,12 @@ .positions__list { display: grid; - gap: 1.5rem; + gap: var(--space-l); } .positions__empty { margin: 0; - padding: 2.5rem 0; + padding: var(--space-3xl) 0; max-width: 60ch; color: var(--clr-text-muted); font-size: var(--fs-500); @@ -1050,12 +1050,12 @@ #warum-mithelfen .sponsor-arguments__intro { max-width: 60ch; - margin: 1rem auto 0; + margin: var(--space-s) auto 0; color: var(--clr-text-muted); } #warum-mithelfen .sponsor-arguments__grid { - margin-top: 2.5rem; + margin-top: var(--space-3xl); text-align: left; } @@ -1081,8 +1081,8 @@ .position-card__head { display: flex; align-items: center; - gap: 1rem; - padding: 1.25rem 1.5rem; + gap: var(--space-s); + padding: var(--space-m) var(--space-l); cursor: pointer; list-style: none; transition: background var(--transition); @@ -1126,7 +1126,7 @@ letter-spacing: 0.12em; text-transform: uppercase; color: var(--clr-text-muted); - margin: 0 0 0.2rem; + margin: 0 0 var(--space-3xs); } .position-card__title { @@ -1153,9 +1153,9 @@ .position-card__body { display: grid; grid-template-columns: 1fr minmax(0, 16rem); - gap: 2.5rem; + gap: var(--space-3xl); align-items: start; - padding: 0 1.5rem 1.5rem 5rem; + padding: 0 var(--space-l) var(--space-l) var(--space-6xl); animation: faq-reveal 260ms ease; } @@ -1173,16 +1173,16 @@ .position-card__tasks { list-style: none; padding: 0; - margin: 1.25rem 0 0; + margin: var(--space-m) 0 0; display: flex; flex-direction: column; - gap: 0.625rem; + gap: var(--space-xs); } .position-card__tasks li { display: flex; align-items: flex-start; - gap: 0.625rem; + gap: var(--space-xs); font-size: var(--fs-500); line-height: var(--lh-body); } @@ -1195,20 +1195,20 @@ } .position-card__meta { - margin: 1.25rem 0 0; + margin: var(--space-m) 0 0; display: grid; - gap: 0.75rem; + gap: var(--space-xs); } .position-card__meta-row { display: grid; - gap: 0.2rem; + gap: var(--space-3xs); } .position-card__meta dt { display: inline-flex; align-items: center; - gap: 0.5rem; + gap: var(--space-2xs); font-size: var(--fs-300); font-weight: var(--fw-bold); letter-spacing: 0.08em; @@ -1234,8 +1234,8 @@ @media (max-width: 768px) { .position-card__body { grid-template-columns: 1fr; - gap: 1.5rem; - padding: 0 1.5rem 1.5rem; + gap: var(--space-l); + padding: 0 var(--space-l) var(--space-l); } .position-card__contact { @@ -1246,7 +1246,7 @@ /* === contact form === */ .contact__inner { display: grid; - gap: 2.5rem; + gap: var(--space-3xl); justify-items: center; } @@ -1256,15 +1256,15 @@ } .contact__intro p { - margin-top: 1rem; + margin-top: var(--space-s); } .form { display: grid; - gap: 1.25rem; + gap: var(--space-m); width: 100%; max-width: 720px; - padding: 2rem; + padding: var(--space-2xl); background: var(--clr-glass-bg); border: 1px solid var(--clr-glass-border); border-radius: var(--radius-card); @@ -1273,7 +1273,7 @@ .form__row { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); - gap: 1.25rem; + gap: var(--space-m); } @media (max-width: 600px) { @@ -1293,7 +1293,7 @@ .form__field select, .form__field textarea { width: 100%; - padding: 1.45rem 0.85rem 0.5rem; + padding: var(--space-l) var(--space-xs) var(--space-2xs); background: var(--clr-field-bg); border: 1px solid var(--clr-field-border); border-radius: var(--radius-btn); @@ -1359,7 +1359,7 @@ .form__field select { appearance: none; - padding-right: 2.5rem; + padding-right: var(--space-3xl); background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='9' fill='none'%3E%3Cpath d='M1 1l6 6 6-6' stroke='%23ffffff' stroke-width='2' stroke-linecap='round'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right 0.9rem center; @@ -1375,13 +1375,13 @@ } .form__hint { - margin-top: 0.4rem; + margin-top: var(--space-2xs); font-size: var(--fs-400); color: var(--clr-text-muted); } .form__error-msg { - margin-top: 0.4rem; + margin-top: var(--space-2xs); font-size: var(--fs-400); color: var(--clr-error); } @@ -1400,14 +1400,14 @@ display: grid; grid-template-columns: auto 1fr; align-items: start; - gap: 0.6rem; + gap: var(--space-2xs); } .form__field--checkbox input { /* 24px — Mindest-Zielgröße (WCAG 2.5.8) */ width: 1.5rem; height: 1.5rem; - margin-top: 0.1rem; + margin-top: var(--space-4xs); accent-color: var(--clr-accent); } @@ -1426,14 +1426,14 @@ } .form__success { - padding: 0.75rem 1rem; + padding: var(--space-xs) var(--space-s); border: 1px solid var(--clr-success); border-radius: var(--radius-btn); color: var(--clr-success); } .form__error { - padding: 0.75rem 1rem; + padding: var(--space-xs) var(--space-s); border: 1px solid var(--clr-error); border-radius: var(--radius-btn); color: var(--clr-error); @@ -1447,14 +1447,14 @@ Feld mit Titel auf der Rahmenlinie. Gliedert lange Formulare optisch klar. */ .form__group { margin: 0; - padding: 1.25rem 1.5rem 1.5rem; + padding: var(--space-m) var(--space-l) var(--space-l); border: 1px solid var(--clr-glass-border); border-radius: var(--radius-card); min-inline-size: 0; } .form__legend { - padding: 0 0.5rem; + padding: 0 var(--space-2xs); font-size: var(--fs-500); text-transform: uppercase; letter-spacing: 0.08em; @@ -1463,7 +1463,7 @@ .form__group-body { display: grid; - gap: 1.25rem; + gap: var(--space-m); } /* === Werbe-/Sponsoren-Banner (Slider) === */ @@ -1482,8 +1482,8 @@ display: flex; align-items: center; justify-content: space-between; - gap: 1rem; - margin-top: 1rem; + gap: var(--space-s); + margin-top: var(--space-s); min-height: 2rem; } @@ -1502,7 +1502,7 @@ flex-direction: column; align-items: flex-start; min-height: 320px; - padding: clamp(1.75rem, 1rem + 2vw, 3rem); + padding: clamp(var(--space-xl), var(--space-s) + 2vw, var(--space-4xl)); background: var(--clr-glass-bg); border: 1px solid var(--clr-glass-border); border-radius: var(--radius-card); @@ -1520,10 +1520,10 @@ display: flex; flex-wrap: wrap; align-items: center; - gap: 0.75rem 1rem; + gap: var(--space-xs) var(--space-s); width: 100%; margin-top: auto; - padding-top: 1.5rem; + padding-top: var(--space-l); } /* Crossfade-Übergang (nur mit JS): Slides im selben Grid-Feld stapeln, sodass @@ -1671,7 +1671,7 @@ font-family: var(--ff-heading); font-size: var(--fs-700); line-height: var(--lh-heading); - margin-bottom: 0.6rem; + margin-bottom: var(--space-2xs); } .ad-banner__text { @@ -1683,17 +1683,17 @@ .ad-banner__actions { display: flex; flex-wrap: wrap; - gap: 0.75rem; + gap: var(--space-xs); } /* Logo führt die Slide an: kompaktes Badge oben links. */ .ad-banner__logo { - margin-bottom: 1.5rem; + margin-bottom: var(--space-l); } /* Helle Platte für dunkle Firmenlogos (Wortmarken). */ .ad-banner__logo--light { - padding: 0.55rem 0.85rem; + padding: var(--space-2xs) var(--space-xs); background: #fff; border-radius: var(--radius-btn); } @@ -1722,7 +1722,7 @@ .ad-banner__controls { display: flex; align-items: center; - gap: 0.75rem; + gap: var(--space-xs); flex-shrink: 0; } @@ -1803,7 +1803,7 @@ display: flex; flex-wrap: wrap; justify-content: center; - gap: 0.5rem; + gap: var(--space-2xs); } .ad-banner__dot { @@ -1860,7 +1860,7 @@ .key-facts__grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); - gap: 1rem; + gap: var(--space-s); margin: 0; padding: 0; list-style: none; @@ -1869,8 +1869,8 @@ .key-facts__item { display: flex; align-items: center; - gap: 0.85rem; - padding: 1.1rem 1.25rem; + gap: var(--space-xs); + padding: var(--space-s) var(--space-m); background: var(--clr-glass-bg); border: 1px solid var(--clr-glass-border); border-radius: var(--radius-card); @@ -1884,7 +1884,7 @@ .key-facts__text { display: grid; - gap: 0.1rem; + gap: var(--space-4xs); } .key-facts__label { @@ -1910,7 +1910,7 @@ display: grid; grid-template-columns: repeat(4, 1fr); grid-auto-rows: minmax(0, auto); - gap: 1.25rem; + gap: var(--space-m); } /* Tile-Größen: Trainingszeiten = großer Anker, Spielort = breit, dazu zwei kleine. */ @@ -1923,7 +1923,7 @@ position: relative; display: flex; flex-direction: column; - padding: 1.75rem; + padding: var(--space-xl); background: var(--clr-glass-bg); border: 1px solid var(--clr-glass-border); border-radius: var(--radius-card); @@ -1933,7 +1933,7 @@ /* Akzent-Tile: gefüllt, weiße Schrift (gut lesbar), zentrierte Kennzahl. */ .bento__tile--accent { justify-content: center; - gap: 0.35rem; + gap: var(--space-3xs); background: var(--clr-accent); border-color: var(--clr-accent); text-align: center; @@ -1942,7 +1942,7 @@ .bento__tile--route { justify-content: center; align-items: center; - gap: 0.9rem; + gap: var(--space-s); text-align: center; } @@ -1958,8 +1958,8 @@ .bento__heading { display: flex; align-items: center; - gap: 0.7rem; - margin-bottom: 1.5rem; + gap: var(--space-xs); + margin-bottom: var(--space-l); font-size: clamp(2rem, 1.6rem + 1.2vw, 2.6rem); line-height: var(--lh-heading); } @@ -1980,8 +1980,8 @@ display: flex; align-items: baseline; justify-content: space-between; - gap: 1rem; - padding: 1rem 0; + gap: var(--space-s); + padding: var(--space-s) 0; border-bottom: 1px solid var(--clr-glass-border); } @@ -1999,7 +1999,7 @@ } .bento__note { - margin-top: 1rem; + margin-top: var(--space-s); } .bento__address { @@ -2065,7 +2065,7 @@ .turnen-dash { display: grid; grid-template-columns: repeat(3, 1fr); - gap: 1.25rem; + gap: var(--space-m); } /* Eine Gruppe: Trainingszeiten (2/3) + Verantwortlich (1/3), Ort volle Breite darunter. */ @@ -2104,7 +2104,7 @@ .turnen-dash__notice { flex-direction: row; align-items: flex-start; - gap: 0.7rem; + gap: var(--space-xs); background: var(--clr-accent); border-color: var(--clr-accent); color: #fff; @@ -2129,7 +2129,7 @@ flex-wrap: wrap; align-items: center; justify-content: space-between; - gap: 1.25rem; + gap: var(--space-m); } .turnen-dash__venue-main { @@ -2138,7 +2138,7 @@ } .turnen-dash__venue .bento__heading { - margin-bottom: 0.6rem; + margin-bottom: var(--space-2xs); } .turnen-dash__route { @@ -2152,8 +2152,8 @@ display: inline-flex; align-self: center; align-items: center; - gap: 0.45rem; - margin-top: 0.75rem; + gap: var(--space-2xs); + margin-top: var(--space-xs); color: #fff; font-size: var(--fs-500); text-decoration: underline; @@ -2201,7 +2201,7 @@ /* === group-list (Turn-Gruppen einer Disziplin) === */ .group-list__title { - margin-bottom: 1.5rem; + margin-bottom: var(--space-l); text-align: center; } @@ -2211,7 +2211,7 @@ justify-content: center; overflow-x: auto; scrollbar-width: none; - margin-bottom: 2rem; + margin-bottom: var(--space-2xl); gap: 0; } @@ -2219,7 +2219,7 @@ .group-tabs__tab { flex-shrink: 0; - padding: 0.7rem 1.4rem; + padding: var(--space-xs) var(--space-l); background: none; border: none; box-shadow: inset 0 -2px 0 var(--clr-glass-border); @@ -2249,9 +2249,9 @@ .group-tabs__panel { display: grid; grid-template-columns: 1.6fr 1fr; - gap: 3rem; + gap: var(--space-4xl); align-items: start; - padding: 2.25rem 2.5rem; + padding: var(--space-3xl) var(--space-3xl); background: var(--clr-glass-bg); border: 1px solid var(--clr-glass-border); border-radius: var(--radius-card); @@ -2265,8 +2265,8 @@ @media (max-width: 768px) { .group-tabs__panel { grid-template-columns: 1fr; - gap: 1.75rem; - padding: 1.6rem 1.75rem; + gap: var(--space-xl); + padding: var(--space-l) var(--space-xl); } } @@ -2274,17 +2274,17 @@ .group-list--grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); - gap: 1.25rem; + gap: var(--space-m); } .group-list--stacked { display: flex; flex-direction: column; - gap: 2.75rem; + gap: var(--space-4xl); } .group-card { - padding: 1.75rem 2rem 2rem; + padding: var(--space-xl) var(--space-2xl) var(--space-2xl); background: var(--clr-glass-bg); border: 1px solid var(--clr-glass-border); border-radius: var(--radius-card); @@ -2294,16 +2294,16 @@ .group-list--stacked .group-card { display: grid; grid-template-columns: 1.6fr 1fr; - gap: 3rem; + gap: var(--space-4xl); align-items: start; - padding: 2.25rem 2.5rem; + padding: var(--space-3xl) var(--space-3xl); } @media (max-width: 768px) { .group-list--stacked .group-card { grid-template-columns: 1fr; - gap: 1.75rem; - padding: 1.6rem 1.75rem 1.75rem; + gap: var(--space-xl); + padding: var(--space-l) var(--space-xl) var(--space-xl); } } @@ -2317,7 +2317,7 @@ display: flex; align-items: center; justify-content: space-between; - gap: 1rem; + gap: var(--space-s); flex-wrap: wrap; } @@ -2328,7 +2328,7 @@ .group-card__level { flex-shrink: 0; - padding: 0.15rem 0.5rem; + padding: var(--space-4xs) var(--space-2xs); font-size: var(--fs-400); text-transform: uppercase; letter-spacing: 0.07em; @@ -2340,7 +2340,7 @@ /* Intro = Lead-Paragraph, deutlich abgesetzt vom Level-Chip */ .group-card__intro { - margin: 1.25rem 0 0; + margin: var(--space-m) 0 0; font-size: var(--fs-600); line-height: 1.65; color: var(--clr-text); @@ -2348,8 +2348,8 @@ /* Detail-Abschnitte: Akzent-Randstrich links als visueller Anker */ .group-card__detail-heading { - margin: 2.25rem 0 0; - padding-left: 0.7rem; + margin: var(--space-3xl) 0 0; + padding-left: var(--space-xs); border-left: 2px solid var(--clr-accent); font-family: var(--ff-body); font-size: var(--fs-500); @@ -2360,16 +2360,16 @@ } .group-card__detail-text { - margin: 0.6rem 0 0; - padding-left: 0.7rem; + margin: var(--space-2xs) 0 0; + padding-left: var(--space-xs); font-size: var(--fs-600); line-height: 1.72; color: var(--clr-text); } .group-card__recommendation { - margin: 2rem 0 0; - padding: 1rem 1rem 0; + margin: var(--space-2xl) 0 0; + padding: var(--space-s) var(--space-s) 0; border-top: 1px solid var(--clr-glass-border); font-size: var(--fs-500); font-style: italic; @@ -2379,13 +2379,13 @@ /* Trainingszeiten-Panel in der Karte. */ .group-card__schedule { - margin-top: 1.25rem; + margin-top: var(--space-m); } .group-list--stacked .group-card__schedule, .group-tabs .group-card__schedule { margin-top: 0; - padding: 1.75rem; + padding: var(--space-xl); background: var(--clr-bg); border: 1px solid var(--clr-glass-border); border-radius: var(--radius-card); @@ -2395,8 +2395,8 @@ .group-card__schedule-title { display: flex; align-items: center; - gap: 0.5rem; - margin: 0 0 1.1rem; + gap: var(--space-2xs); + margin: 0 0 var(--space-s); font-size: var(--fs-650); letter-spacing: 0.01em; } @@ -2407,15 +2407,15 @@ } .group-card__trainers { - margin: 1rem 0 0; - padding-top: 0.75rem; + margin: var(--space-s) 0 0; + padding-top: var(--space-xs); border-top: 1px solid var(--clr-glass-border); font-size: var(--fs-500); line-height: 1.5; } .group-card__note { - margin: 0.5rem 0 0; + margin: var(--space-2xs) 0 0; font-size: var(--fs-500); } @@ -2427,7 +2427,7 @@ } .schedule-row { - padding: 0.85rem 0; + padding: var(--space-xs) 0; border-bottom: 1px solid var(--clr-glass-border); } @@ -2436,7 +2436,7 @@ .schedule-row__label { display: block; - margin-bottom: 0.3rem; + margin-bottom: var(--space-3xs); font-size: var(--fs-500); text-transform: uppercase; letter-spacing: 0.07em; @@ -2448,7 +2448,7 @@ display: flex; align-items: baseline; justify-content: space-between; - gap: 1rem; + gap: var(--space-s); } .schedule-row__day { @@ -2463,14 +2463,14 @@ .schedule-row__note { display: block; - margin-top: 0.2rem; + margin-top: var(--space-3xs); font-size: var(--fs-500); color: var(--clr-text-muted); } .schedule-row__trainers { display: block; - margin-top: 0.3rem; + margin-top: var(--space-3xs); font-size: var(--fs-500); line-height: 1.5; color: var(--clr-text-muted); @@ -2481,14 +2481,14 @@ } .group-list__footnote { - margin: 1.5rem 0 0; + margin: var(--space-l) 0 0; font-size: var(--fs-500); } @media (max-width: 600px) { .schedule-row__when { flex-direction: column; - gap: 0.15rem; + gap: var(--space-4xs); } .schedule-row__time { text-align: left; @@ -2503,7 +2503,7 @@ } .cta-band__text { - margin-top: 0.75rem; + margin-top: var(--space-xs); margin-inline: auto; } @@ -2511,8 +2511,8 @@ display: flex; flex-wrap: wrap; justify-content: center; - gap: 0.75rem; - margin-top: 1.75rem; + gap: var(--space-xs); + margin-top: var(--space-xl); } /* === faq (Accordion) === */ @@ -2527,7 +2527,7 @@ /* Schlichte Trennlinien-Liste statt Kästchen. */ .faq__list { - margin-top: 2.5rem; + margin-top: var(--space-3xl); border-top: 1px solid var(--clr-glass-border); } @@ -2539,8 +2539,8 @@ display: flex; align-items: center; justify-content: space-between; - gap: 1rem; - padding: 1.35rem 0.25rem; + gap: var(--space-s); + padding: var(--space-m) var(--space-3xs); font-size: var(--fs-600); line-height: 1.3; cursor: pointer; @@ -2578,7 +2578,7 @@ } .faq__a { - padding: 0 0.25rem 1.4rem; + padding: 0 var(--space-3xs) var(--space-l); color: var(--clr-text-muted); } @@ -2605,8 +2605,8 @@ .age-groups__grid { display: grid; grid-template-columns: repeat(2, 1fr); - gap: 1.75rem; - margin: 2.5rem 0 0; + gap: var(--space-xl); + margin: var(--space-3xl) 0 0; padding: 0; list-style: none; text-align: left; @@ -2653,9 +2653,9 @@ .age-group__body { display: grid; - gap: 0.6rem; + gap: var(--space-2xs); align-content: start; - padding: 1.5rem 1.6rem 1.6rem; + padding: var(--space-l) var(--space-l) var(--space-l); } .age-group__name { @@ -2666,7 +2666,7 @@ .age-group__training { display: inline-flex; align-items: flex-start; - gap: 0.5rem; + gap: var(--space-2xs); margin: 0; font-size: var(--fs-600); line-height: 1.45; @@ -2693,7 +2693,7 @@ position: absolute; top: 0.9rem; left: 0.9rem; - padding: 0.35rem 0.75rem; + padding: var(--space-3xs) var(--space-xs); font-family: var(--ff-body); font-size: var(--fs-400); letter-spacing: 0.12em; @@ -2710,20 +2710,20 @@ .team-cards__intro { max-width: 60ch; - margin: 1rem auto 0; + margin: var(--space-s) auto 0; } /* Intro als „Klärtext" unter den Kacheln → mehr Luft nach oben zur Kartenreihe */ .team-cards__intro--below { - margin-top: 2.5rem; + margin-top: var(--space-3xl); } .team-cards__grid { display: grid; /* Zwei Karten pro Reihe (50/50) — bei vier Mannschaften ergibt das ein 2×2-Raster */ grid-template-columns: repeat(2, 1fr); - gap: 1.5rem; - margin: 2.5rem 0 0; + gap: var(--space-l); + margin: var(--space-3xl) 0 0; padding: 0; list-style: none; text-align: left; @@ -2771,8 +2771,8 @@ .team-card__body { display: flex; flex-direction: column; - gap: 0.3rem; - padding: 1.4rem 1.5rem 1.5rem; + gap: var(--space-3xs); + padding: var(--space-l) var(--space-l) var(--space-l); } .team-card__name { @@ -2791,10 +2791,10 @@ .team-card__cta { display: inline-flex; align-items: center; - gap: 0.45rem; + gap: var(--space-2xs); /* an den unteren Rand schieben → CTA aller Karten fluchtet, egal wie lang der Name */ margin-top: auto; - padding-top: 1rem; + padding-top: var(--space-s); /* Body-Schrift (Abel) — Coolvetica bleibt den Überschriften vorbehalten */ font-size: var(--fs-500); color: var(--clr-accent); @@ -2815,13 +2815,13 @@ .team-roster__intro { max-width: 60ch; - margin: 1rem auto 0; + margin: var(--space-s) auto 0; } /* Positions-Zwischenüberschrift (Trainer & Betreuer, Torwart, Abwehr, …) */ .team-roster__group { - margin-top: 2.75rem; - padding-bottom: 0.5rem; + margin-top: var(--space-4xl); + padding-bottom: var(--space-2xs); border-bottom: 1px solid var(--clr-glass-border); text-align: left; font-size: var(--fs-700); @@ -2832,8 +2832,8 @@ display: grid; /* Feste 5 pro Reihe → größere Cards/Bilder; runter gestuft auf schmaleren Screens */ grid-template-columns: repeat(5, 1fr); - gap: 1.25rem; - margin: 1.5rem 0 0; + gap: var(--space-m); + margin: var(--space-l) 0 0; padding: 0; list-style: none; text-align: left; @@ -2890,8 +2890,8 @@ .roster-card__body { display: flex; flex-direction: column; - gap: 0.1rem; - padding: 0.85rem 1rem 1rem; + gap: var(--space-4xs); + padding: var(--space-xs) var(--space-s) var(--space-s); background: linear-gradient(180deg, rgb(20 20 20 / 0) 0%, rgb(20 20 20 / 0.35) 100%); } @@ -2927,8 +2927,8 @@ inset: auto 0 0 0; display: flex; flex-direction: column; - gap: 0.15rem; - padding: 2.5rem 0.9rem 0.85rem; + gap: var(--space-4xs); + padding: var(--space-3xl) var(--space-s) var(--space-xs); /* dunkler Fade von unten → Name bleibt auf jedem Foto lesbar */ background: linear-gradient(180deg, rgb(15 15 15 / 0) 0%, rgb(15 15 15 / 0.55) 45%, rgb(15 15 15 / 0.9) 100%); } @@ -2952,7 +2952,7 @@ .faq-contact__inner { display: grid; grid-template-columns: minmax(0, 2fr) minmax(0, 1fr); - gap: 3rem; + gap: var(--space-4xl); align-items: start; } @@ -2972,21 +2972,21 @@ .faq-contact__intro { max-width: 60ch; margin-inline: auto; - margin-bottom: 2.25rem; + margin-bottom: var(--space-3xl); text-align: center; } /* Abstände je nachdem, ob eine Subline folgt */ .faq-contact__title + .faq-contact__intro { - margin-top: 1rem; + margin-top: var(--space-s); } .faq-contact__title + .faq-contact__inner { - margin-top: 2.25rem; + margin-top: var(--space-3xl); } .faq-contact__aside > h2 { - margin-bottom: 1.5rem; + margin-bottom: var(--space-l); } /* --- Ansprechpartner-Mini-Slider (mehrere Kontakte) --- @@ -3001,7 +3001,7 @@ .contact-slider__track { display: flex; flex-direction: column; - gap: 1.5rem; + gap: var(--space-l); margin: 0; padding: 0; list-style: none; @@ -3015,8 +3015,8 @@ .contact-slider--ready .contact-slider__dots { display: flex; justify-content: center; - gap: 0.4rem; - margin-top: 1rem; + gap: var(--space-2xs); + margin-top: var(--space-s); } .contact-slider__dot { @@ -3071,7 +3071,7 @@ @media (max-width: 900px) { .faq-contact__inner { grid-template-columns: 1fr; - gap: 2.5rem; + gap: var(--space-3xl); } } @@ -3089,7 +3089,7 @@ } .team-photo__caption { - margin-top: 0.75rem; + margin-top: var(--space-xs); text-align: center; color: var(--clr-text-muted); font-size: var(--fs-400); @@ -3099,7 +3099,7 @@ .team-bento { display: grid; grid-template-columns: repeat(3, 1fr); - gap: 1.25rem; + gap: var(--space-m); grid-template-areas: "about about about" "train train klasse" @@ -3116,7 +3116,7 @@ } .team-bento__about > h2 { - margin-bottom: 0.75rem; + margin-bottom: var(--space-xs); } .team-bento__about p { @@ -3130,7 +3130,7 @@ flex-wrap: wrap; align-items: center; justify-content: space-between; - gap: 1.25rem; + gap: var(--space-m); } .team-bento__venue-main { @@ -3139,7 +3139,7 @@ } .team-bento__venue .bento__heading { - margin-bottom: 0.6rem; + margin-bottom: var(--space-2xs); } .team-bento__route { @@ -3163,7 +3163,7 @@ .intro-bento { display: grid; grid-template-columns: repeat(3, 1fr); - gap: 1.25rem; + gap: var(--space-m); grid-template-areas: "image about about" "image venue stat"; @@ -3192,7 +3192,7 @@ } .intro-bento__about > h2 { - margin-bottom: 0.75rem; + margin-bottom: var(--space-xs); } .intro-bento__about p { @@ -3205,7 +3205,7 @@ } .intro-bento__venue .bento__address { - margin-bottom: 1.25rem; + margin-bottom: var(--space-m); } .intro-bento__route { @@ -3243,14 +3243,14 @@ .legal__head { max-width: 60ch; - margin-bottom: clamp(2.5rem, 1.5rem + 3vw, 4rem); + margin-bottom: clamp(var(--space-3xl), var(--space-l) + 3vw, var(--space-5xl)); } .legal__kicker { display: flex; align-items: center; - gap: 0.75rem; - margin-bottom: 1.25rem; + gap: var(--space-xs); + margin-bottom: var(--space-m); font-size: var(--fs-500); text-transform: uppercase; letter-spacing: 0.18em; @@ -3265,7 +3265,7 @@ } .legal__lead { - margin-top: 1.25rem; + margin-top: var(--space-m); color: var(--clr-text-muted); } @@ -3275,12 +3275,12 @@ } .legal__body > section + section { - margin-top: clamp(2.5rem, 1.8rem + 2vw, 3.5rem); + margin-top: clamp(var(--space-3xl), var(--space-xl) + 2vw, var(--space-5xl)); } .legal__body h2 { font-size: var(--fs-700); - margin-bottom: 1rem; + margin-bottom: var(--space-s); } /* Coolvetica nie unter --fs-650 (28px) — Unterüberschriften daher in Abel, @@ -3291,13 +3291,13 @@ font-weight: var(--fw-bold); text-transform: none; line-height: var(--lh-body); - margin-top: 1.75rem; - margin-bottom: 0.5rem; + margin-top: var(--space-xl); + margin-bottom: var(--space-2xs); } .legal__body p, .legal__body ul { - margin-bottom: 1rem; + margin-bottom: var(--space-s); } .legal__body a { @@ -3311,12 +3311,12 @@ } .legal__body ul { - padding-left: 1.25rem; + padding-left: var(--space-m); max-width: 70ch; } .legal__body li { - margin-bottom: 0.35rem; + margin-bottom: var(--space-3xs); } .legal__body li::marker { @@ -3325,8 +3325,8 @@ /* Adress-/Kontaktblöcke (Verantwortlicher, Bankverbindung) als abgesetzte Karte */ .legal__card { - margin-bottom: 1rem; - padding: clamp(1.25rem, 1rem + 1.5vw, 1.75rem); + margin-bottom: var(--space-s); + padding: clamp(var(--space-m), var(--space-s) + 1.5vw, var(--space-xl)); background: var(--clr-glass-bg); border: 1px solid var(--clr-glass-border); border-radius: var(--radius-card); @@ -3343,8 +3343,8 @@ .legal__cards { display: grid; - gap: 1rem; - margin-bottom: 1rem; + gap: var(--space-s); + margin-bottom: var(--space-s); } @media (min-width: 600px) { @@ -3360,13 +3360,13 @@ .department-choice__intro { max-width: 60ch; - margin: 1rem auto 0; + margin: var(--space-s) auto 0; } .department-choice__grid { display: grid; grid-template-columns: repeat(2, 1fr); - gap: 1.5rem; + gap: var(--space-l); margin: 0; padding: 0; list-style: none; @@ -3382,9 +3382,9 @@ .department-choice__card { display: flex; flex-direction: column; - gap: 0.35rem; + gap: var(--space-3xs); height: 100%; - padding: clamp(1.75rem, 1.25rem + 2vw, 2.75rem); + padding: clamp(var(--space-xl), var(--space-m) + 2vw, var(--space-4xl)); background: var(--clr-glass-bg); border: 1px solid var(--clr-glass-border); border-radius: var(--radius-card); @@ -3404,7 +3404,7 @@ } .department-choice__text { - margin-top: 0.65rem; + margin-top: var(--space-xs); color: var(--clr-text-muted); } @@ -3413,7 +3413,7 @@ margin-top: auto; display: inline-flex; align-items: center; - gap: 0.5rem; + gap: var(--space-2xs); } .department-choice__arrow { @@ -3422,7 +3422,7 @@ /* === fee-table (Beitragstabellen) === */ .fee-table { - padding: clamp(1.25rem, 1rem + 1.5vw, 1.75rem); + padding: clamp(var(--space-m), var(--space-s) + 1.5vw, var(--space-xl)); background: var(--clr-glass-bg); border: 1px solid var(--clr-glass-border); border-radius: var(--radius-card); @@ -3431,7 +3431,7 @@ .fee-table__title { font-size: var(--fs-650); line-height: var(--lh-heading); - margin-bottom: 1rem; + margin-bottom: var(--space-s); } .fee-table__list { @@ -3442,8 +3442,8 @@ display: flex; align-items: baseline; justify-content: space-between; - gap: 1rem; - padding: 0.65rem 0; + gap: var(--space-s); + padding: var(--space-xs) 0; border-bottom: 1px solid var(--clr-glass-border); } @@ -3465,7 +3465,7 @@ } .fee-table__note { - margin-top: 1rem; + margin-top: var(--space-s); font-size: var(--fs-400); color: var(--clr-text-muted); } @@ -3474,8 +3474,8 @@ .fee-stack { display: flex; flex-direction: column; - gap: 1.5rem; - margin-top: 1.5rem; + gap: var(--space-l); + margin-top: var(--space-l); } /* === membership (Mitglied werden) === */ @@ -3487,18 +3487,18 @@ /* Zentrierter, ruhiger Sektions-Kopf (einheitlich mit instagram/partners) */ .membership-signup__head { max-width: 60ch; - margin: 0 auto 2.5rem; + margin: 0 auto var(--space-3xl); text-align: center; } .membership-signup__intro { - margin-top: 1rem; + margin-top: var(--space-s); color: var(--clr-text-muted); } /* Eine einzige Karte: Beitragsinfo + Formular zusammen, durch Hairline getrennt. */ .membership-card { - padding: clamp(1.5rem, 1rem + 2.5vw, 2.5rem); + padding: clamp(var(--space-l), var(--space-s) + 2.5vw, var(--space-3xl)); background: var(--clr-glass-bg); border: 1px solid var(--clr-glass-border); border-radius: var(--radius-card); @@ -3516,22 +3516,22 @@ } .membership-card .fee-table { - padding-bottom: 1.75rem; - margin-bottom: 1.75rem; + padding-bottom: var(--space-xl); + margin-bottom: var(--space-xl); border-bottom: 1px solid var(--clr-glass-border); } /* Turnen-Hinweise: ruhige Liste, Icon links, dezente Hairline als Trenner. */ .membership-notes { - margin: 0 0 2.5rem; + margin: 0 0 var(--space-3xl); padding: 0; list-style: none; } .membership-notes__item { display: flex; - gap: 1rem; - padding: 1.25rem 0; + gap: var(--space-s); + padding: var(--space-m) 0; border-bottom: 1px solid var(--clr-glass-border); } @@ -3546,7 +3546,7 @@ .membership-notes__icon { flex: none; - margin-top: 0.15rem; + margin-top: var(--space-4xs); font-size: 1.5rem; line-height: 1; color: var(--clr-accent); @@ -3555,7 +3555,7 @@ .membership-notes__title { font-size: var(--fs-650); line-height: var(--lh-heading); - margin-bottom: 0.25rem; + margin-bottom: var(--space-3xs); } .membership-notes__body p { @@ -3567,14 +3567,14 @@ display: flex; flex-direction: column; align-items: center; - gap: 0.75rem; + gap: var(--space-xs); text-align: center; } .membership-download__btn { display: inline-flex; align-items: center; - gap: 0.5rem; + gap: var(--space-2xs); } .membership-download__icon { @@ -3600,13 +3600,13 @@ .facility__lead { max-width: 70ch; - margin-top: 1rem; + margin-top: var(--space-s); margin-inline: auto; color: var(--clr-text-muted); } .facility__figure { - margin: 2.5rem 0 0; + margin: var(--space-3xl) 0 0; } .facility__img { @@ -3618,13 +3618,13 @@ } .facility__caption { - margin-top: 0.85rem; + margin-top: var(--space-xs); color: var(--clr-text-faint); font-size: var(--fs-500); } .facility__actions { - margin-top: 1.75rem; + margin-top: var(--space-xl); } /* === matchcenter === */ @@ -3663,7 +3663,7 @@ .match-feature { display: flex; flex-direction: column; - gap: clamp(1rem, 0.8rem + 0.6vw, 1.5rem); + gap: clamp(var(--space-s), var(--space-xs) + 0.6vw, var(--space-l)); } .match-feature__meta { @@ -3684,13 +3684,13 @@ .match-feature__fixture { display: flex; flex-direction: column; - gap: 0.5rem; + gap: var(--space-2xs); } .match-feature__side { display: flex; align-items: center; - gap: clamp(0.6rem, 0.4rem + 0.6vw, 1rem); + gap: clamp(var(--space-2xs), var(--space-2xs) + 0.6vw, var(--space-s)); min-width: 0; /* lange Vereinsnamen dürfen umbrechen statt zu überlaufen */ } @@ -3737,12 +3737,12 @@ /* Der Aufmacher trägt mehr Gewicht als die Terminliste. */ grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr); align-items: start; - gap: clamp(1.25rem, 1rem + 1.5vw, 2.5rem); - margin-top: clamp(1.5rem, 1.2rem + 1vw, 2.25rem); + gap: clamp(var(--space-m), var(--space-s) + 1.5vw, var(--space-3xl)); + margin-top: clamp(var(--space-l), var(--space-m) + 1vw, var(--space-3xl)); } .next-matches__list-title { - margin-bottom: 0.75rem; + margin-bottom: var(--space-xs); /* Coolvetica wäre in dieser Größe unleserlich — Abel mit Sperrung (CLAUDE.md). */ font-family: var(--ff-body); font-size: var(--fs-400); @@ -3763,15 +3763,15 @@ flex-wrap: wrap; align-items: center; justify-content: space-between; - gap: 1rem; - margin-bottom: 1.5rem; + gap: var(--space-s); + margin-bottom: var(--space-l); } .matchcenter-team__league { display: inline-flex; align-items: center; gap: 0.4em; - margin-top: 0.35rem; + margin-top: var(--space-3xs); color: var(--clr-text-muted); } @@ -3780,7 +3780,7 @@ Panels gestapelt sichtbar (group-tabs.js versteckt erst on init das zweite). */ .matchcenter-team__seasons-bar { justify-content: flex-start; - margin-bottom: 1.25rem; + margin-bottom: var(--space-m); } /* display-neutrales Panel (kein group-tabs__panel-Grid) — das innere @@ -3799,7 +3799,7 @@ .matchcenter-team__grid { display: grid; grid-template-columns: 2fr 1fr; - gap: clamp(1.5rem, 1rem + 2vw, 2.75rem); + gap: clamp(var(--space-l), var(--space-s) + 2vw, var(--space-4xl)); align-items: start; } @@ -3823,7 +3823,7 @@ .matchcenter-team__aside { display: flex; flex-direction: column; - gap: 1rem; + gap: var(--space-s); max-width: 30rem; } @@ -3867,8 +3867,8 @@ display: grid; grid-template-columns: 1fr auto; align-items: center; - column-gap: 0.75rem; - padding: 0.7rem 0.9rem; + column-gap: var(--space-xs); + padding: var(--space-xs) var(--space-s); color: inherit; text-decoration: none; } @@ -3882,8 +3882,8 @@ a.match-row__link:hover { display: flex; flex-wrap: wrap; align-items: baseline; - gap: 0.5rem; - margin-bottom: 0.45rem; + gap: var(--space-2xs); + margin-bottom: var(--space-2xs); } .match-row__date { @@ -3901,14 +3901,14 @@ a.match-row__link:hover { .match-row__teams { display: flex; flex-direction: column; - gap: 0.3rem; + gap: var(--space-3xs); min-width: 0; /* erlaubt Ellipsis in den Namen */ } .match-row__team { display: flex; align-items: center; - gap: 0.5rem; + gap: var(--space-2xs); min-width: 0; } @@ -3990,7 +3990,7 @@ a.match-row__link:hover { .league-table th, .league-table td { - padding: 0.6rem 0.5rem; + padding: var(--space-2xs) var(--space-2xs); text-align: center; border-bottom: 1px solid var(--clr-glass-border); } @@ -4029,7 +4029,7 @@ a.match-row__link:hover { .league-table tbody th.league-table__club { display: flex; align-items: center; - gap: 0.6rem; + gap: var(--space-2xs); min-width: 0; /* erlaubt Ellipsis im Namen */ font-weight: var(--fw-regular); } @@ -4085,7 +4085,7 @@ a.match-row__link:hover { /* Hinweis über der Tabelle, solange die Liga noch keinen Spieltag gewertet hat. */ .league-table__note { - margin-bottom: 0.85rem; + margin-bottom: var(--space-xs); font-size: var(--fs-400); color: var(--clr-text-muted); } @@ -4093,8 +4093,8 @@ a.match-row__link:hover { .league-table__legend { display: flex; flex-wrap: wrap; - gap: 0.5rem 1.25rem; - margin: 0.85rem 0 0; + gap: var(--space-2xs) var(--space-m); + margin: var(--space-xs) 0 0; padding: 0; list-style: none; font-size: var(--fs-300); @@ -4135,7 +4135,7 @@ a.match-row__link:hover { .matchcenter-empty { max-width: 540px; margin-inline: auto; - padding: clamp(1.5rem, 1rem + 2vw, 2.5rem); + padding: clamp(var(--space-l), var(--space-s) + 2vw, var(--space-3xl)); text-align: center; background: var(--clr-glass-bg); border: 1px solid var(--clr-glass-border); @@ -4148,7 +4148,7 @@ a.match-row__link:hover { } .matchcenter-empty p { - margin-top: 0.75rem; + margin-top: var(--space-xs); color: var(--clr-text-muted); } @@ -4156,8 +4156,8 @@ a.match-row__link:hover { display: flex; flex-wrap: wrap; justify-content: center; - gap: 0.75rem; - margin-top: 1.5rem; + gap: var(--space-xs); + margin-top: var(--space-l); } /* === sportheim === */ @@ -4168,7 +4168,7 @@ a.match-row__link:hover { display: grid; grid-template-columns: repeat(4, 1fr); grid-auto-rows: 220px; - gap: 1.25rem; + gap: var(--space-m); grid-template-areas: "p1 p1 p2 cta" "p1 p1 map adr"; @@ -4221,7 +4221,7 @@ a.match-row__link:hover { background: var(--clr-glass-bg); border: 1px solid var(--clr-glass-border); border-radius: var(--radius-card); - padding: 1.5rem; + padding: var(--space-l); display: flex; flex-direction: column; justify-content: center; @@ -4234,7 +4234,7 @@ a.match-row__link:hover { letter-spacing: 0.1em; text-transform: uppercase; color: var(--clr-text-muted); - margin: 0 0 0.625rem; + margin: 0 0 var(--space-xs); } .sportheim-bento__addr-text { @@ -4252,9 +4252,9 @@ a.match-row__link:hover { flex-direction: column; justify-content: center; align-items: center; - gap: 1.5rem; + gap: var(--space-l); text-align: center; - padding: 2rem 1.25rem; + padding: var(--space-2xl) var(--space-m); } .sportheim-bento__cta-title { diff --git a/public/assets/css/layout.css b/public/assets/css/layout.css index 27038fc..57fb841 100644 --- a/public/assets/css/layout.css +++ b/public/assets/css/layout.css @@ -30,14 +30,14 @@ .site-header__inner { display: flex; align-items: center; - gap: 1.5rem; + gap: var(--space-l); min-height: var(--header-h); } .site-header__brand { display: flex; align-items: center; - gap: 0.75rem; + gap: var(--space-xs); text-decoration: none; color: var(--clr-text); } @@ -60,7 +60,7 @@ .site-header__actions { display: flex; align-items: center; - gap: 0.5rem; + gap: var(--space-2xs); } /* Header-CTAs haben eine eigene, kompakte Größe (kein generisches sm/lg-Preset): @@ -84,7 +84,7 @@ display: flex; align-items: center; flex-wrap: wrap; - gap: 1.5rem; + gap: var(--space-l); font-size: var(--fs-500); } @@ -162,11 +162,11 @@ transform-origin: top center; z-index: 25; display: grid; - gap: 0.2rem; + gap: var(--space-3xs); min-width: 15rem; margin: 0; /* Gleichmäßiges Padding rundum — Pillen stehen oben/unten/seitlich gleich dezent frei. */ - padding: 0.55rem; + padding: var(--space-2xs); list-style: none; /* Glasiges, leicht transluzentes Panel mit Tiefe über der Hintergrund-Textur. */ background: rgb(20 20 20 / 0.96); @@ -199,7 +199,7 @@ .site-nav__list .site-nav__submenu a:not(.btn) { position: relative; display: block; - padding: 0.65rem 0.9rem 0.65rem 1rem; + padding: var(--space-xs) var(--space-s) var(--space-xs) var(--space-s); border-bottom: 0; border-radius: var(--radius-btn); font-size: var(--fs-500); @@ -226,7 +226,7 @@ .site-nav__list .site-nav__submenu a:not(.btn):hover, .site-nav__list .site-nav__submenu a:not(.btn):focus-visible { border-bottom: 0; - padding-left: 1.25rem; + padding-left: var(--space-m); background: rgb(var(--clr-accent-rgb) / 0.14); } @@ -238,15 +238,15 @@ .site-nav__list .site-nav__submenu a:not(.btn)[aria-current="page"] { border-bottom: 0; - padding-left: 1.25rem; + padding-left: var(--space-m); background: rgb(var(--clr-accent-rgb) / 0.2); } /* Letzter Dropdown-Punkt mit optischer Trennung (hairline oben + etwas Abstand) */ .site-nav__submenu-sep { border-top: 1px solid var(--clr-glass-border); - margin-top: 0.35rem; - padding-top: 0.35rem; + margin-top: var(--space-3xs); + padding-top: var(--space-3xs); } @media (max-width: 1249px) { @@ -349,7 +349,7 @@ damit die Menüpunkte mit dem Logo fluchten. */ .site-nav .site-nav__list { /* Oben: Header + Logo-Überhang (Logo ragt unter den Header) + Luft. */ - padding: calc(var(--header-h) + 4rem) var(--container-pad) 3rem; + padding: calc(var(--header-h) + var(--space-5xl)) var(--container-pad) var(--space-4xl); } @supports (backdrop-filter: blur(1px)) { @@ -378,7 +378,7 @@ align-items: center; justify-content: space-between; width: 100%; - padding: 1rem 0.25rem; + padding: var(--space-s) var(--space-3xs); border-bottom: 0; font-family: var(--ff-heading); font-size: var(--fs-650); @@ -389,7 +389,7 @@ .site-nav__sublink[data-current] { border-bottom: 0; box-shadow: inset 3px 0 0 var(--clr-accent); - padding-left: 1rem; + padding-left: var(--space-s); } /* Volle Breite + zentrierter Text: Items und Untermenüs zentrieren sich am @@ -422,7 +422,7 @@ overflow: hidden; opacity: 0; visibility: hidden; - gap: 0.25rem; + gap: var(--space-3xs); min-width: 0; justify-self: stretch; margin: 0; @@ -449,7 +449,7 @@ } .site-nav__list .site-nav__submenu > li:last-child { - padding-bottom: 0.75rem; + padding-bottom: var(--space-xs); } /* Unterpunkte in Abel, Groß-/Kleinschreibung — wie im Desktop-Dropdown @@ -457,7 +457,7 @@ den Hauptpunkten; die aktive Unterseite rückt per Desktop-Regel mit Akzentbalken + Tint leicht ein. */ .site-nav__list .site-nav__submenu a:not(.btn) { - padding: 0.6rem 0.25rem; + padding: var(--space-2xs) var(--space-3xs); font-family: var(--ff-body); font-size: var(--fs-500); text-transform: none; @@ -552,8 +552,8 @@ .site-footer__grid { display: grid; grid-template-columns: 1.5fr repeat(4, minmax(0, 1fr)); - gap: 2.5rem 3rem; - padding-block: 4.5rem 3.5rem; + gap: var(--space-3xl) var(--space-4xl); + padding-block: var(--space-6xl) var(--space-5xl); } /* Brand-Block bleibt links stehen, Nav-Spalten rücken rechts daneben */ @@ -575,7 +575,7 @@ @media (max-width: 500px) { .site-footer__grid { grid-template-columns: 1fr; - gap: 2.25rem; + gap: var(--space-3xl); } } @@ -583,7 +583,7 @@ .site-footer__brand { display: grid; justify-items: start; - gap: 1.25rem; + gap: var(--space-m); } .site-footer__claim { @@ -593,14 +593,14 @@ .site-footer__social { display: grid; - gap: 0.5rem; + gap: var(--space-2xs); } .site-footer__social a { display: inline-flex; align-items: center; - gap: 0.6rem; - padding-block: 0.3rem; + gap: var(--space-2xs); + padding-block: var(--space-3xs); color: var(--clr-text-muted); text-decoration: none; transition: color var(--transition); @@ -616,8 +616,8 @@ .site-footer__heading { font-size: var(--fs-650); letter-spacing: 0.06em; - margin-bottom: 1.1rem; - padding-bottom: 0.6rem; + margin-bottom: var(--space-s); + padding-bottom: var(--space-2xs); position: relative; } @@ -646,12 +646,12 @@ font-style: normal; color: var(--clr-text-muted); display: grid; - gap: 1rem; + gap: var(--space-s); } .site-footer__address p { display: flex; - gap: 0.6rem; + gap: var(--space-2xs); margin: 0; } @@ -663,13 +663,13 @@ .site-footer__list { display: grid; - gap: 0.25rem; + gap: var(--space-3xs); } .site-footer__list a, .site-footer__address a { display: inline-block; - padding-block: 0.3rem; + padding-block: var(--space-3xs); color: var(--clr-text-muted); text-decoration: none; transition: color var(--transition); @@ -686,8 +686,8 @@ /* Zusatzlinks (Startseite, Mitglied werden) unter dem Kontakt-Block */ .site-footer__list--extra { - margin-top: 1.25rem; - padding-top: 1.25rem; + margin-top: var(--space-m); + padding-top: var(--space-m); border-top: 1px solid var(--clr-glass-border); } @@ -700,8 +700,8 @@ display: flex; flex-wrap: wrap; justify-content: space-between; - gap: 0.5rem 2rem; - padding-block: 1.5rem; + gap: var(--space-2xs) var(--space-2xl); + padding-block: var(--space-l); font-size: var(--fs-400); color: var(--clr-text-muted); } @@ -713,7 +713,7 @@ .site-footer__legal { display: flex; flex-wrap: wrap; - gap: 0.4rem 1.5rem; + gap: var(--space-2xs) var(--space-l); margin: 0; padding: 0; list-style: none; diff --git a/public/assets/js/form.js b/public/assets/js/form.js index 595f008..12ea762 100644 --- a/public/assets/js/form.js +++ b/public/assets/js/form.js @@ -14,8 +14,9 @@ var MESSAGES = { success: 'Danke für deine Anfrage! Wir melden uns so schnell wie möglich bei dir.', - validation: 'Bitte prüfe deine Eingaben – Pflichtfelder fehlen oder die E-Mail-Adresse ist ungültig.', + validation: 'Bitte prüfe deine Eingaben – die markierten Felder brauchen noch etwas Aufmerksamkeit.', ratelimit: 'Zu viele Anfragen in kurzer Zeit. Bitte versuche es in ein paar Minuten erneut.', + busy: 'Bei uns gehen gerade ungewöhnlich viele Anfragen ein. Bitte versuche es später noch einmal.', mail: 'Deine Nachricht konnte gerade nicht versendet werden. Bitte versuche es später erneut.' }; @@ -46,6 +47,26 @@ field.addEventListener('change', function () { clearFieldError(field); }); }); + /* Feldfehler der Server-Validierung übernehmen ({ "": "Meldung" }) — + dieselben Elemente wie beim 422-Re-Render ohne JS. */ + function showServerErrors(fields) { + Object.keys(fields).forEach(function (id) { + var field = document.getElementById(id); + var msg = document.getElementById(id + '-error'); + if (field) { + field.setAttribute('aria-invalid', 'true'); + } + if (msg) { + msg.textContent = fields[id]; + msg.hidden = false; + } + }); + } + + function clearAllFieldErrors() { + Array.prototype.forEach.call(form.elements, clearFieldError); + } + function show(type, text) { var p = document.createElement('p'); p.className = type === 'success' ? 'form__success' : 'form__error'; @@ -78,10 +99,14 @@ }) .then(function (response) { return response.json(); }) .then(function (data) { + clearAllFieldErrors(); if (data.ok) { show('success', MESSAGES.success); form.reset(); } else { + if (data.fields) { + showServerErrors(data.fields); + } show('error', MESSAGES[data.error] || MESSAGES.mail); } })