Banner-Slider: Crossfade-Übergang + per-Slide-Akzentfarbe
Folienwechsel als sauberes Crossfade (best practice): Slides im selben Grid-Feld gestapelt → konstante Höhe ohne Sprung, ein-/ausgehende Folie blendet über (Opacity + leichtes translateY). JS steuert die Sichtbarkeit über .is-active statt hidden; ohne JS bleibt der hidden-Fallback (erste Folie). Reduced-Motion über den globalen Kill-Switch instant. Neue Slide-Prop "accent" (CSS-Farbe) überschreibt das Vereins-Rot der Buttons pro Slide; Breadcrumb-Slide nutzt #6cbbbc. Button-Text wird aus der Akzentfarbe dunkel abgeleitet (color-mix), da helle Akzente mit Weiß zu wenig Kontrast haben. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KDQZ6FuchNUh2bxSW9PeNv
This commit is contained in:
@@ -9,7 +9,8 @@ declare(strict_types=1);
|
||||
* "tag" (Badge-Text, Default "Anzeige"), "background" (Bild), "cta.internal"
|
||||
* (interner Link ohne target=_blank/rel=sponsored, für eigene Seiten/Anker),
|
||||
* "cta_secondary" (zweiter Button als Outline), "logo_light" (helle Logo-Platte
|
||||
* für dunkle Firmenlogos).
|
||||
* für dunkle Firmenlogos), "accent" (CSS-Farbe; überschreibt das Vereins-Rot
|
||||
* der Buttons für diese Slide, z. B. Sponsor-CI).
|
||||
* Auto-Rotation + Fortschritts-Indikatoren übernimmt assets/js/banner.js
|
||||
* (pausiert bei Hover/Fokus, respektiert prefers-reduced-motion).
|
||||
* Ohne JS: nur der erste Banner ist sichtbar (Graceful Degradation).
|
||||
@@ -35,9 +36,32 @@ $total = count($banners);
|
||||
<?php endif; ?>
|
||||
<ul class="ad-banner__track" id="ad-banner-track" aria-live="off">
|
||||
<?php foreach ($banners as $i => $banner): ?>
|
||||
<?php $bg = $banner['background'] ?? null; ?>
|
||||
<?php $variant = $banner['variant'] ?? null; ?>
|
||||
<li class="ad-banner__slide<?= $bg ? ' ad-banner__slide--bg' : '' ?><?= $variant ? ' ad-banner__slide--' . e($variant) : '' ?>"<?= $bg ? ' style="--banner-bg: url(' . e(asset($bg)) . ')"' : '' ?> data-banner-slide role="group" aria-roledescription="Folie" aria-label="<?= ($i + 1) ?> von <?= $total ?>"<?= $i === 0 ? '' : ' hidden' ?>>
|
||||
<?php
|
||||
$bg = $banner['background'] ?? null;
|
||||
$variant = $banner['variant'] ?? null;
|
||||
$accent = $banner['accent'] ?? null;
|
||||
|
||||
$classes = 'ad-banner__slide';
|
||||
if ($bg) {
|
||||
$classes .= ' ad-banner__slide--bg';
|
||||
}
|
||||
if ($accent) {
|
||||
$classes .= ' ad-banner__slide--accent';
|
||||
}
|
||||
if ($variant) {
|
||||
$classes .= ' ad-banner__slide--' . preg_replace('/[^a-z0-9-]+/', '', strtolower($variant));
|
||||
}
|
||||
|
||||
$styleParts = [];
|
||||
if ($bg) {
|
||||
$styleParts[] = '--banner-bg: url(' . asset($bg) . ')';
|
||||
}
|
||||
if ($accent) {
|
||||
$styleParts[] = '--banner-accent: ' . $accent;
|
||||
}
|
||||
$style = $styleParts !== [] ? ' style="' . e(implode('; ', $styleParts)) . '"' : '';
|
||||
?>
|
||||
<li class="<?= e($classes) ?>"<?= $style ?> data-banner-slide role="group" aria-roledescription="Folie" aria-label="<?= ($i + 1) ?> von <?= $total ?>"<?= $i === 0 ? '' : ' hidden' ?>>
|
||||
<div class="ad-banner__content">
|
||||
<span class="ad-banner__tag"><?= e($banner['tag'] ?? 'Anzeige') ?></span>
|
||||
<p class="ad-banner__title"><?= e($banner['title']) ?></p>
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
"text": "Beratung, Software & Implementierung aus einer Hand. Wir entwickeln Intranets, Kundenportale, Apps und Individualsoftware – inklusive Einführung, sodass sie im Alltag tatsächlich ankommt.",
|
||||
"logo": "img/banners/breadcrumb-solutions.svg",
|
||||
"logo_light": true,
|
||||
"accent": "#6cbbbc",
|
||||
"background": "img/banners/breadcrumb-hero-1600.jpg",
|
||||
"cta": { "label": "Lösungen entdecken", "url": "https://www.breadcrumb-solutions.de/bc/de/loesungen/" },
|
||||
"cta_secondary": { "label": "Über uns", "url": "https://www.breadcrumb-solutions.de/bc/de/unternehmen/ueber-uns/" }
|
||||
|
||||
@@ -907,6 +907,32 @@
|
||||
box-shadow: var(--shadow-card);
|
||||
}
|
||||
|
||||
/* Crossfade-Übergang (nur mit JS): Slides im selben Grid-Feld stapeln, sodass
|
||||
die Höhe konstant bleibt (kein Sprung) und ein- wie ausgehende Folie sauber
|
||||
überblenden. Die ausgehende driftet leicht nach unten, die eingehende steigt
|
||||
auf. Ohne JS greift weiter .ad-banner__slide[hidden] (nur erste Folie).
|
||||
Reduced-Motion: globaler Kill-Switch in reset.css macht den Wechsel instant. */
|
||||
.ad-banner.is-enhanced .ad-banner__track {
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.ad-banner.is-enhanced .ad-banner__slide {
|
||||
grid-area: 1 / 1;
|
||||
opacity: 0;
|
||||
transform: translateY(8px);
|
||||
pointer-events: none;
|
||||
transition: opacity 0.55s ease, transform 0.55s ease, visibility 0s linear 0.55s;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.ad-banner.is-enhanced .ad-banner__slide.is-active {
|
||||
opacity: 1;
|
||||
transform: none;
|
||||
pointer-events: auto;
|
||||
visibility: visible;
|
||||
transition: opacity 0.55s ease, transform 0.55s ease, visibility 0s;
|
||||
}
|
||||
|
||||
/* Optionaler Hintergrund pro Banner (data: "background").
|
||||
Dunkler Verlauf über dem Bild hält Text & CTA links lesbar. */
|
||||
.ad-banner__slide--bg {
|
||||
@@ -918,6 +944,30 @@
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
/* Pro-Slide-Akzentfarbe (data: "accent") — z. B. Sponsor-CI statt Vereins-Rot.
|
||||
--banner-accent kommt als Inline-Variable von der Komponente. */
|
||||
.ad-banner__slide--accent {
|
||||
--clr-accent: var(--banner-accent);
|
||||
--clr-accent-dark: color-mix(in srgb, var(--banner-accent) 82%, #000);
|
||||
}
|
||||
|
||||
/* Solider Button: dunkler, aus der Akzentfarbe abgeleiteter Text — helle
|
||||
Akzentfarben (z. B. Teal) hätten mit Weiß zu wenig Kontrast (WCAG 1.4.3). */
|
||||
.ad-banner__slide--accent .ad-banner__cta.btn:not(.btn--outline) {
|
||||
color: color-mix(in srgb, var(--banner-accent) 22%, #000);
|
||||
}
|
||||
|
||||
.ad-banner__slide--accent .ad-banner__cta.btn--outline {
|
||||
color: var(--banner-accent);
|
||||
border-color: var(--banner-accent);
|
||||
}
|
||||
|
||||
.ad-banner__slide--accent .ad-banner__cta.btn--outline:hover {
|
||||
background: color-mix(in srgb, var(--banner-accent) 16%, transparent);
|
||||
border-color: var(--banner-accent);
|
||||
color: var(--banner-accent);
|
||||
}
|
||||
|
||||
.ad-banner__slide--bg .ad-banner__text {
|
||||
color: var(--clr-text);
|
||||
}
|
||||
@@ -2343,7 +2393,7 @@
|
||||
|
||||
/* === membership (Mitglied werden) === */
|
||||
.membership-signup__inner {
|
||||
max-width: 820px;
|
||||
max-width: 720px;
|
||||
margin-inline: auto;
|
||||
}
|
||||
|
||||
@@ -2359,43 +2409,78 @@
|
||||
color: var(--clr-text-muted);
|
||||
}
|
||||
|
||||
/* Beitragstabelle und Formular mittig, schlanke Spaltenbreite für Lesbarkeit */
|
||||
.membership-signup .fee-table {
|
||||
max-width: 560px;
|
||||
margin-inline: auto;
|
||||
/* Eine einzige Karte: Beitragsinfo + Formular zusammen, durch Hairline getrennt. */
|
||||
.membership-card {
|
||||
padding: clamp(1.5rem, 1rem + 2.5vw, 2.5rem);
|
||||
background: var(--clr-glass-bg);
|
||||
border: 1px solid var(--clr-glass-border);
|
||||
border-radius: var(--radius-card);
|
||||
}
|
||||
|
||||
.membership-signup .form {
|
||||
margin-inline: auto;
|
||||
/* Verschachtelte Bausteine verlieren ihre eigene Karten-Optik (eine Karte, ein Rahmen). */
|
||||
.membership-card .fee-table,
|
||||
.membership-card .form {
|
||||
max-width: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: none;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.membership-signup .fee-table + .form {
|
||||
margin-top: 2rem;
|
||||
.membership-card .fee-table {
|
||||
padding-bottom: 1.75rem;
|
||||
margin-bottom: 1.75rem;
|
||||
border-bottom: 1px solid var(--clr-glass-border);
|
||||
}
|
||||
|
||||
/* Turnen-Hinweise: ruhige Liste, Icon links, dezente Hairline als Trenner. */
|
||||
.membership-notes {
|
||||
display: grid;
|
||||
gap: 1.5rem;
|
||||
margin: 0 0 2.5rem;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.membership-notes {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
.membership-notes__item {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
padding: 1.25rem 0;
|
||||
border-bottom: 1px solid var(--clr-glass-border);
|
||||
}
|
||||
|
||||
.membership-notes__item:first-child {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.membership-notes__item:last-child {
|
||||
padding-bottom: 0;
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.membership-notes__icon {
|
||||
flex: none;
|
||||
margin-top: 0.15rem;
|
||||
font-size: 1.5rem;
|
||||
line-height: 1;
|
||||
color: var(--clr-accent);
|
||||
}
|
||||
|
||||
.membership-notes__title {
|
||||
font-size: var(--fs-650);
|
||||
line-height: var(--lh-heading);
|
||||
margin-bottom: 0.4rem;
|
||||
padding-top: 0.75rem;
|
||||
border-top: 2px solid var(--clr-accent);
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.membership-notes__body p {
|
||||
color: var(--clr-text-muted);
|
||||
}
|
||||
|
||||
/* Download-Block: alles sauber mittig, Texte in lesbarer Breite. */
|
||||
.membership-download {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@@ -2411,7 +2496,8 @@
|
||||
|
||||
.membership-download__hint,
|
||||
.membership-download__contact {
|
||||
margin-top: 0.75rem;
|
||||
margin: 0;
|
||||
max-width: 48ch;
|
||||
font-size: var(--fs-400);
|
||||
color: var(--clr-text-muted);
|
||||
}
|
||||
|
||||
@@ -42,7 +42,8 @@
|
||||
function activate(i) {
|
||||
index = (i % slides.length + slides.length) % slides.length;
|
||||
slides.forEach(function (slide, n) {
|
||||
slide.hidden = n !== index;
|
||||
// Sichtbarkeit per Klasse → CSS blendet über (siehe .is-enhanced).
|
||||
slide.classList.toggle('is-active', n === index);
|
||||
});
|
||||
dots.forEach(function (dot, n) {
|
||||
var on = n === index;
|
||||
@@ -110,6 +111,13 @@
|
||||
});
|
||||
}
|
||||
|
||||
// Crossfade aktivieren: Slides stapeln (CSS) und Sichtbarkeit per Klasse
|
||||
// steuern. Vorher gesetzte hidden-Attribute (No-JS-Fallback) entfernen.
|
||||
root.classList.add('is-enhanced');
|
||||
slides.forEach(function (slide) {
|
||||
slide.hidden = false;
|
||||
});
|
||||
|
||||
activate(0);
|
||||
syncPaused();
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user