Feinschliff: Formulare neu, Glas global dunkler, Hero-Fade sauber

Formulare (form-field.php, 3 Form-Wrapper, components.css, form.js):
- Floating Labels (Label im Feld, wandert bei Fokus/Inhalt hoch; select/date
  dauerhaft oben) via :has() + placeholder=" "-Trick. Echtes <label> bleibt
  im DOM (kein Placeholder-als-Label).
- Globale "Pflichtfelder mit *"-Hinweiszeile entfernt; Stern bleibt am Feld,
  pro Feld als visually-hidden "(Pflichtfeld)" für Screenreader erklärt.
- Markenroter Fokus-Ring statt Weiß.
- Felder als sanfter Füll-Look: weichere Ränder (eigene --clr-field-*-Tokens),
  Erkennbarkeit über Fläche + Rand (WCAG 1.4.11 erfüllt).
- form.js: Submit-Button-Text wird je Formular korrekt wiederhergestellt.

Glas-Look global (tokens.css, components.css):
- Frosted Glass zentral: --glass-blur + --glass-shadow (Glow + Top-Sheen),
  eine Regel statt 28× wiederholt; Leaf-Karten mit Blur, Container nur Glow
  (kein Doppel-Frost). @supports-Fallback.
- Dunkleres Glas (--clr-glass-bg auf Schwarz-Tint), dezenter Card-Rand.

Hero-Fade (components.css):
- Unterer Auslauf nicht mehr mit grauem --clr-bg zugedeckt, sondern .hero__media
  per mask-image ausgeblendet → echter dunkler, texturierter Hintergrund scheint
  durch (schwärzer, durchsichtig, kein graues Band). Abdunklung auf reines
  Schwarz. Gilt für alle drei Hero-Varianten.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HGzc6GhWhmLJt1jC2q1SRZ
This commit is contained in:
2026-06-20 22:18:34 +02:00
parent 08f5a1ae9b
commit 8437f00e06
7 changed files with 134 additions and 38 deletions

View File

@@ -59,8 +59,6 @@ $f = [
<?php endif; ?> <?php endif; ?>
</div> </div>
<p class="form__note">Pflichtfelder sind mit <span class="form__required" aria-hidden="true">*</span><span class="visually-hidden">Stern</span> markiert.</p>
<div class="form__row"> <div class="form__row">
<?php component('form-field', ['field' => $f['name']]); ?> <?php component('form-field', ['field' => $f['name']]); ?>
<?php component('form-field', ['field' => $f['email']]); ?> <?php component('form-field', ['field' => $f['email']]); ?>

View File

@@ -27,8 +27,10 @@ $errorId = $id . '-error';
$hintId = $id . '-hint'; $hintId = $id . '-hint';
$describedBy = (!empty($field['hint']) ? $hintId . ' ' : '') . $errorId; $describedBy = (!empty($field['hint']) ? $hintId . ' ' : '') . $errorId;
// Sichtbarer roter Stern (aria-hidden) + pro Feld erklärendes Pflicht-Wort für
// Screenreader — ersetzt die frühere globale „Pflichtfelder mit *"-Hinweiszeile.
$requiredMark = $required $requiredMark = $required
? ' <span class="form__required" aria-hidden="true">*</span>' ? ' <span class="form__required" aria-hidden="true">*</span><span class="visually-hidden"> (Pflichtfeld)</span>'
: ''; : '';
$commonAttrs = 'name="' . e($name) . '" id="' . e($id) . '"' $commonAttrs = 'name="' . e($name) . '" id="' . e($id) . '"'
@@ -56,9 +58,9 @@ $commonAttrs = 'name="' . e($name) . '" id="' . e($id) . '"'
<?php endforeach; ?> <?php endforeach; ?>
</select> </select>
<?php elseif ($type === 'textarea'): ?> <?php elseif ($type === 'textarea'): ?>
<textarea <?= $commonAttrs ?> rows="<?= e($field['rows'] ?? 6) ?>"></textarea> <textarea <?= $commonAttrs ?> rows="<?= e($field['rows'] ?? 6) ?>" placeholder=" "></textarea>
<?php else: ?> <?php else: ?>
<input type="<?= e($type) ?>" <?= $commonAttrs ?>> <input type="<?= e($type) ?>" <?= $commonAttrs ?> placeholder=" ">
<?php endif; ?> <?php endif; ?>
<?php if (!empty($field['hint'])): ?> <?php if (!empty($field['hint'])): ?>
<p class="form__hint" id="<?= e($hintId) ?>"><?= e($field['hint']) ?></p> <p class="form__hint" id="<?= e($hintId) ?>"><?= e($field['hint']) ?></p>

View File

@@ -52,8 +52,6 @@ $f = [
<?php endif; ?> <?php endif; ?>
</div> </div>
<p class="form__note">Pflichtfelder sind mit <span class="form__required" aria-hidden="true">*</span><span class="visually-hidden">Stern</span> markiert.</p>
<fieldset class="form__group"> <fieldset class="form__group">
<legend class="form__legend">Zur Person</legend> <legend class="form__legend">Zur Person</legend>
<div class="form__group-body"> <div class="form__group-body">

View File

@@ -51,8 +51,6 @@ $f = [
<?php endif; ?> <?php endif; ?>
</div> </div>
<p class="form__note">Pflichtfelder sind mit <span class="form__required" aria-hidden="true">*</span><span class="visually-hidden">Stern</span> markiert.</p>
<div class="form__row"> <div class="form__row">
<?php component('form-field', ['field' => $f['name']]); ?> <?php component('form-field', ['field' => $f['name']]); ?>
<?php component('form-field', ['field' => $f['email']]); ?> <?php component('form-field', ['field' => $f['email']]); ?>

View File

@@ -22,6 +22,11 @@
position: absolute; position: absolute;
inset: 0; inset: 0;
z-index: -1; z-index: -1;
/* Unten ausblenden statt mit grauem BG zudecken: der echte, dunkle
texturierte Seiten-Hintergrund scheint durch → transparent & matcht
den Grund exakt (kein graues, opakes Band mehr). */
-webkit-mask-image: linear-gradient(180deg, #000 0%, #000 58%, transparent 92%);
mask-image: linear-gradient(180deg, #000 0%, #000 58%, transparent 92%);
} }
.hero__img { .hero__img {
@@ -44,12 +49,13 @@
} }
} }
/* Dunkler Verlauf wie auf der alten Seite: oben transparent → unten Seitenhintergrund */ /* Reine Schwarz-Abdunklung (kein grauer Fill) — der Auslauf zum Grund kommt
aus der Mask auf .hero__media, hier wird nur schwärzer getönt. */
.hero__media::after { .hero__media::after {
content: ''; content: '';
position: absolute; position: absolute;
inset: 0; inset: 0;
background: linear-gradient(180deg, rgb(20 20 20 / 0.25) 0%, rgb(20 20 20 / 0.55) 55%, var(--clr-bg) 100%); background: linear-gradient(180deg, rgb(0 0 0 / 0.45) 0%, rgb(0 0 0 / 0.15) 45%, rgb(0 0 0 / 0.5) 100%);
} }
.hero__content { .hero__content {
@@ -110,14 +116,14 @@
/* zusätzlicher Verlauf von links für Textlesbarkeit über dem Video */ /* zusätzlicher Verlauf von links für Textlesbarkeit über dem Video */
background: background:
linear-gradient(90deg, rgb(10 10 10 / 0.62) 0%, rgb(10 10 10 / 0.25) 45%, transparent 70%), linear-gradient(90deg, rgb(10 10 10 / 0.62) 0%, rgb(10 10 10 / 0.25) 45%, transparent 70%),
linear-gradient(180deg, rgb(20 20 20 / 0.2) 0%, rgb(20 20 20 / 0.45) 55%, var(--clr-bg) 100%); linear-gradient(180deg, rgb(0 0 0 / 0.42) 0%, rgb(0 0 0 / 0.15) 45%, rgb(0 0 0 / 0.5) 100%);
} }
/* Center+Display: kein Seitenverlauf, nur zentrales Abdunkeln für Lesbarkeit */ /* Center+Display: kein Seitenverlauf, nur zentrales Abdunkeln für Lesbarkeit */
.hero--center.hero--display .hero__media::after { .hero--center.hero--display .hero__media::after {
background: background:
radial-gradient(ellipse 80% 60% at 50% 40%, rgb(10 10 10 / 0.5) 0%, transparent 100%), radial-gradient(ellipse 80% 60% at 50% 40%, rgb(10 10 10 / 0.5) 0%, transparent 100%),
linear-gradient(180deg, rgb(20 20 20 / 0.2) 0%, rgb(20 20 20 / 0.5) 55%, var(--clr-bg) 100%); linear-gradient(180deg, rgb(0 0 0 / 0.4) 0%, rgb(0 0 0 / 0.14) 45%, rgb(0 0 0 / 0.5) 100%);
} }
.hero__kicker { .hero__kicker {
@@ -1243,44 +1249,69 @@
} }
} }
/* Floating Label: Label liegt im leeren Feld wie ein Platzhalter und wandert bei
Fokus oder Inhalt nach oben. Rein visuell — das echte <label for> bleibt im DOM
(kein Placeholder-als-Label), darum auch für Screenreader voll nutzbar. */
.form__field { .form__field {
display: grid; position: relative;
gap: 0.4rem;
}
.form__field label {
font-size: var(--fs-500);
} }
.form__field input, .form__field input,
.form__field select, .form__field select,
.form__field textarea { .form__field textarea {
width: 100%; width: 100%;
padding: 10px 12px; padding: 1.45rem 0.85rem 0.5rem;
background: var(--clr-glass-bg); background: var(--clr-field-bg);
border: 1px solid var(--clr-glass-border-strong); border: 1px solid var(--clr-field-border);
border-radius: var(--radius-btn); border-radius: var(--radius-btn);
color: var(--clr-text); color: var(--clr-text);
transition: border-color var(--transition), background var(--transition), box-shadow var(--transition); transition: border-color var(--transition), background var(--transition), box-shadow var(--transition);
} }
/* Label im Ruhezustand — sitzt auf der Eingabezeile (deckt das " "-Placeholder) */
.form__field:not(.form__field--checkbox) > label {
position: absolute;
left: 0.9rem;
top: 1.4rem;
font-size: var(--fs-500);
line-height: 1;
color: var(--clr-text-muted);
pointer-events: none;
transform-origin: left top;
transition: transform var(--transition), color var(--transition);
}
/* Geschwebt: bei Fokus, bei vorhandenem Inhalt (placeholder=" "-Trick) sowie
dauerhaft für Select und Datumsfelder (haben keinen :placeholder-shown-Zustand). */
.form__field:not(.form__field--checkbox):has(input:focus) > label,
.form__field:not(.form__field--checkbox):has(textarea:focus) > label,
.form__field:not(.form__field--checkbox):has(select:focus) > label,
.form__field:not(.form__field--checkbox):has(input:not(:placeholder-shown)) > label,
.form__field:not(.form__field--checkbox):has(textarea:not(:placeholder-shown)) > label,
.form__field:not(.form__field--checkbox):has(select) > label,
.form__field:not(.form__field--checkbox):has(input[type="date"]) > label {
transform: translateY(-0.95rem) scale(0.78);
color: var(--clr-text);
}
.form__field input:hover, .form__field input:hover,
.form__field select:hover, .form__field select:hover,
.form__field textarea:hover { .form__field textarea:hover {
border-color: rgb(255 255 255 / 0.55); border-color: var(--clr-field-border-hover);
} }
/* Eigener, deutlich sichtbarer Fokus-Ring statt des globalen Outlines */ /* Markenroter Fokus-Ring (Akzent nur als Rahmen/Glow, nie als Text). */
.form__field input:focus-visible, .form__field input:focus-visible,
.form__field select:focus-visible, .form__field select:focus-visible,
.form__field textarea:focus-visible { .form__field textarea:focus-visible {
outline: none; outline: none;
border-color: rgb(255 255 255 / 0.75); border-color: var(--clr-accent);
box-shadow: 0 0 0 3px rgb(255 255 255 / 0.18); box-shadow: 0 0 0 3px rgb(var(--clr-accent-rgb) / 0.3);
} }
/* Der " "-Platzhalter aus dem Floating-Trick bleibt unsichtbar */
.form__field ::placeholder { .form__field ::placeholder {
color: rgb(255 255 255 / 0.45); color: transparent;
} }
/* Fehlerzustand: per JS (aria-invalid) und nativ nach Nutzereingabe (:user-invalid) */ /* Fehlerzustand: per JS (aria-invalid) und nativ nach Nutzereingabe (:user-invalid) */
@@ -1306,22 +1337,18 @@
color: var(--clr-text); color: var(--clr-text);
} }
.form__note {
margin: 0;
font-size: var(--fs-400);
color: var(--clr-text-muted);
}
.form__required { .form__required {
color: var(--clr-accent); color: var(--clr-accent);
} }
.form__hint { .form__hint {
margin-top: 0.4rem;
font-size: var(--fs-400); font-size: var(--fs-400);
color: var(--clr-text-muted); color: var(--clr-text-muted);
} }
.form__error-msg { .form__error-msg {
margin-top: 0.4rem;
font-size: var(--fs-400); font-size: var(--fs-400);
color: var(--clr-error); color: var(--clr-error);
} }
@@ -1337,6 +1364,7 @@
} }
.form__field--checkbox { .form__field--checkbox {
display: grid;
grid-template-columns: auto 1fr; grid-template-columns: auto 1fr;
align-items: start; align-items: start;
gap: 0.6rem; gap: 0.6rem;
@@ -4315,3 +4343,67 @@
min-height: 140px; min-height: 140px;
} }
} }
/* ============================================================
FROSTED GLASS — globaler Look für alle Glas-Oberflächen.
EINZIGE Stelle für Blur + Ruhe-Glow (DRY statt 28× wiederholt).
Neue Glas-Karte? Hier zur passenden Liste ergänzen:
• Leaf = eigenständige Karte → bekommt Blur + Glow
• Container = umschließt selbst Glas-Karten → nur Glow
(kein Blur, sonst Doppel-Frost bei verschachtelten Karten)
Bewusst ausgenommen: Form-Inputs, Wappen-Initialen, Banner-/Slider-Controls.
Glas-Bg/-Border kommen weiterhin aus den Komponenten (var(--clr-glass-*)).
============================================================ */
/* Ruhe-Glow + Top-Sheen für ALLE Glas-Flächen (Leaf + Container).
Hover-Lift bleibt pro Komponente erhalten (:hover hat höhere Spezifität). */
.instagram__fallback,
.sponsor-channels__card,
.person-card,
.position-card,
.partners__item,
.key-facts__item,
.bento__tile,
.group-card,
.group-tabs__panel,
.age-group,
.match-card,
.match-list,
.matchcenter-empty,
.roster-card,
.team-card,
.department-choice__card,
.legal__card,
.fee-table,
.league-table,
.membership-card,
.sportheim-bento__addr,
.form {
box-shadow: var(--glass-shadow);
}
/* Echtes Frosted-Glass-Blur nur auf Leaf-Karten — Container (.match-list,
.group-tabs__panel, .age-group, Tabellen) bleiben blur-frei, damit darin
liegende Glas-Karten nicht doppelt verschwimmen. */
@supports ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
.instagram__fallback,
.sponsor-channels__card,
.person-card,
.position-card,
.partners__item,
.key-facts__item,
.bento__tile,
.group-card,
.match-card,
.matchcenter-empty,
.roster-card,
.team-card,
.department-choice__card,
.legal__card,
.membership-card,
.sportheim-bento__addr,
.form {
-webkit-backdrop-filter: var(--glass-blur);
backdrop-filter: var(--glass-blur);
}
}

View File

@@ -18,9 +18,12 @@
--clr-text-faint: #8c8c8c; /* 4.5:1 auf --clr-bg (AA) */ --clr-text-faint: #8c8c8c; /* 4.5:1 auf --clr-bg (AA) */
--clr-sepia-bg: #f3efe4; /* Timeline-Museumsrahmen (Foto-Mount) */ --clr-sepia-bg: #f3efe4; /* Timeline-Museumsrahmen (Foto-Mount) */
--clr-sepia-text: #3a372f; /* Timeline-Text auf hellem Sepia-Grund */ --clr-sepia-text: #3a372f; /* Timeline-Text auf hellem Sepia-Grund */
--clr-glass-bg: rgb(255 255 255 / 0.11); --clr-glass-bg: rgb(0 0 0 / 0.22); /* dunkles Glas (dunkler als der Grund) */
--clr-glass-border: rgb(255 255 255 / 0.22); --clr-glass-border: rgb(255 255 255 / 0.14); /* sehr dezenter Card-Rand — Glow/Sheen tragen die Kontur */
--clr-glass-border-strong: rgb(255 255 255 / 0.4); /* Formularfelder: ≥3:1 Kontrast (WCAG 1.4.11) */ --clr-glass-border-strong: rgb(255 255 255 / 0.45); /* Back-to-Top, position-/group-card etc.: ≥3:1 (WCAG 1.4.11) */
--clr-field-bg: rgb(255 255 255 / 0.07); /* Formularfelder: sanfte Füllfläche (etwas heller als das Glas) */
--clr-field-border: rgb(255 255 255 / 0.22); /* Feld-Rand weich — Fläche + Rand machen das Feld erkennbar */
--clr-field-border-hover: rgb(255 255 255 / 0.4); /* Feld-Hover: dezent */
--clr-error: #ff8a8a; /* Formularfehler, lesbar auf dunkel */ --clr-error: #ff8a8a; /* Formularfehler, lesbar auf dunkel */
--clr-success: #8ce99a; --clr-success: #8ce99a;
@@ -53,7 +56,11 @@
--radius-btn: 8px; --radius-btn: 8px;
--radius-card: 15px; --radius-card: 15px;
--shadow: 0 0 14px rgb(0 0 0 / 0.15); --shadow: 0 0 14px rgb(0 0 0 / 0.15);
--shadow-card: 0 7px 29px rgb(0 0 0 / 0.33); --shadow-card: 0 14px 38px rgb(0 0 0 / 0.42), inset 0 1px 0 rgb(255 255 255 / 0.18); /* Hover-Lift + Top-Sheen */
/* --- Glas-Oberflächen (Frosted Glass) — siehe „Frosted Glass"-Block in components.css --- */
--glass-blur: blur(13px) saturate(1.4);
--glass-shadow: 0 6px 22px rgb(0 0 0 / 0.30), inset 0 1px 0 rgb(255 255 255 / 0.15); /* Ruhe-Glow + Top-Sheen */
/* --- Layout --- */ /* --- Layout --- */
--container-max: 1200px; --container-max: 1200px;

View File

@@ -67,6 +67,7 @@
} }
event.preventDefault(); event.preventDefault();
var buttonLabel = button.textContent;
button.disabled = true; button.disabled = true;
button.textContent = 'Wird gesendet …'; button.textContent = 'Wird gesendet …';
@@ -89,7 +90,7 @@
}) })
.finally(function () { .finally(function () {
button.disabled = false; button.disabled = false;
button.textContent = 'Nachricht senden'; button.textContent = buttonLabel;
}); });
}); });
})(); })();