diff --git a/app/components/banner-slider.php b/app/components/banner-slider.php
new file mode 100644
index 0000000..89ee303
--- /dev/null
+++ b/app/components/banner-slider.php
@@ -0,0 +1,68 @@
+
+
+
+
+
= e($heading) ?>
+
+
Anzeigen unserer Sponsoren & Partner
+
+
+
= e($subline) ?>
+
+
+ $banner): ?>
+
+ - data-banner-slide role="group" aria-roledescription="Folie" aria-label="= ($i + 1) ?> von = $total ?>"= $i === 0 ? '' : ' hidden' ?>>
+
+
Anzeige
+
= e($banner['title']) ?>
+
= e($banner['text']) ?>
+
+
= e($banner['cta']['label']) ?> (öffnet in neuem Tab)
+
+
+
+
![<?= e($banner['company'] ?? $banner['title']) ?>](<?= e(asset($banner['logo'])) ?>)
+
+
+
+
+ 1): ?>
+
+
+
+
+
+ $banner): ?>
+
+
+
+
+
+
+
diff --git a/app/pages/home.php b/app/pages/home.php
index f624d2b..b9b55ca 100644
--- a/app/pages/home.php
+++ b/app/pages/home.php
@@ -13,10 +13,11 @@ $meta = [
'title' => 'Fußball & Turnen in Kulmbach – TSV 08 Kulmbach',
'title_absolute' => true,
'description' => 'Dein Sportverein für Fußball und Turnen in Kulmbach: Jugend-, Herren- und Damenfußball, Turnen und Trampolin – seit 1908 im Katzbachtal. Jetzt Mitglied werden!',
- 'scripts' => ['form.js'],
+ 'scripts' => ['form.js', 'banner.js'],
];
component('hero', ['hero' => $home['hero']]);
+component('banner-slider');
component('instagram-feed');
foreach ($home['sections'] as $section) {
diff --git a/data/banners.json b/data/banners.json
new file mode 100644
index 0000000..19cf363
--- /dev/null
+++ b/data/banners.json
@@ -0,0 +1,28 @@
+{
+ "heading": "Unsere Werbepartner",
+ "subline": "Diese Unternehmen aus Kulmbach und der Region unterstützen den TSV 08 – ein herzliches Dankeschön!",
+ "banners": [
+ {
+ "company": "Musterfirma GmbH",
+ "title": "Ihre Werbung beim TSV 08 Kulmbach",
+ "text": "Erreichen Sie tausende sportbegeisterte Kulmbacherinnen und Kulmbacher – als Sponsor unseres Vereins direkt auf der Startseite.",
+ "logo": "img/banners/placeholder-1.svg",
+ "cta": { "label": "Jetzt Partner werden", "url": "https://example.com" }
+ },
+ {
+ "company": "Beispiel Handwerk AG",
+ "title": "Regional. Verlässlich. Mittendrin.",
+ "text": "Wir unterstützen den Vereinssport im Katzbachtal – und freuen uns, Teil der TSV-08-Familie zu sein.",
+ "logo": "img/banners/placeholder-2.svg",
+ "background": "img/banners/bg-sample.svg",
+ "cta": { "label": "Mehr erfahren", "url": "https://example.com" }
+ },
+ {
+ "company": "Sponsor Sport & Freizeit",
+ "title": "Ausrüstung für echte Teamplayer",
+ "text": "Vom Trikot bis zum Trainingsplatz – unser Partner für alles rund um den Sport in der Region Kulmbach.",
+ "logo": "img/banners/placeholder-3.svg",
+ "cta": { "label": "Zum Angebot", "url": "https://example.com" }
+ }
+ ]
+}
diff --git a/public/assets/img/banners/bg-sample.svg b/public/assets/img/banners/bg-sample.svg
new file mode 100644
index 0000000..2336a4a
--- /dev/null
+++ b/public/assets/img/banners/bg-sample.svg
@@ -0,0 +1,15 @@
+
diff --git a/public/assets/img/banners/placeholder-1.svg b/public/assets/img/banners/placeholder-1.svg
new file mode 100644
index 0000000..06109e1
--- /dev/null
+++ b/public/assets/img/banners/placeholder-1.svg
@@ -0,0 +1,5 @@
+
diff --git a/public/assets/img/banners/placeholder-2.svg b/public/assets/img/banners/placeholder-2.svg
new file mode 100644
index 0000000..31db74c
--- /dev/null
+++ b/public/assets/img/banners/placeholder-2.svg
@@ -0,0 +1,5 @@
+
diff --git a/public/assets/img/banners/placeholder-3.svg b/public/assets/img/banners/placeholder-3.svg
new file mode 100644
index 0000000..6015dff
--- /dev/null
+++ b/public/assets/img/banners/placeholder-3.svg
@@ -0,0 +1,5 @@
+
diff --git a/public/assets/js/banner.js b/public/assets/js/banner.js
new file mode 100644
index 0000000..6581133
--- /dev/null
+++ b/public/assets/js/banner.js
@@ -0,0 +1,115 @@
+/* Werbe-/Sponsoren-Banner-Slider: Auto-Rotation über die Fortschrittsanimation
+ des aktiven Indikators (animationend → nächste Folie).
+
+ Barrierefreiheit:
+ - explizite Pause/Play-Taste (WCAG 2.2.2 „Pause, Stop, Hide"),
+ - zusätzlich Pause bei Hover/Fokus (Komfort),
+ - aria-live: im Autoplay "off" (kein Vorlese-Spam), bei Pause/manuell "polite",
+ - bei prefers-reduced-motion läuft keine Animation → keine Auto-Rotation;
+ Indikatoren bleiben per Klick bedienbar, die Pause-Taste entfällt. */
+(function () {
+ 'use strict';
+
+ var root = document.querySelector('[data-banner]');
+ if (!root) {
+ return;
+ }
+
+ var track = root.querySelector('#ad-banner-track');
+ var slides = Array.prototype.slice.call(root.querySelectorAll('[data-banner-slide]'));
+ var dots = Array.prototype.slice.call(root.querySelectorAll('[data-banner-dot]'));
+ var controls = root.querySelector('[data-banner-controls]');
+ var toggle = root.querySelector('[data-banner-toggle]');
+ var toggleLabel = root.querySelector('[data-banner-toggle-label]');
+ if (slides.length <= 1) {
+ return;
+ }
+
+ var reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
+ var index = 0;
+ var hoverPaused = false;
+ var userPaused = false;
+
+ function syncPaused() {
+ // Bei reduzierter Bewegung gilt jeder Wechsel als manuell → ansagen.
+ var paused = hoverPaused || userPaused || reduce;
+ root.classList.toggle('is-paused', paused);
+ if (track) {
+ track.setAttribute('aria-live', paused ? 'polite' : 'off');
+ }
+ }
+
+ function activate(i) {
+ index = (i % slides.length + slides.length) % slides.length;
+ slides.forEach(function (slide, n) {
+ slide.hidden = n !== index;
+ });
+ dots.forEach(function (dot, n) {
+ var on = n === index;
+ dot.classList.toggle('is-active', on);
+ if (on) {
+ dot.setAttribute('aria-current', 'true');
+ } else {
+ dot.removeAttribute('aria-current');
+ }
+ });
+ }
+
+ // Sobald die Fortschrittsanimation des aktiven Indikators durchgelaufen ist,
+ // zur nächsten Folie wechseln. (Beim Wechsel wird die alte Animation
+ // abgebrochen → 'animationcancel', nicht 'animationend' → kein Doppelsprung.)
+ root.addEventListener('animationend', function (event) {
+ if (event.animationName === 'ad-progress') {
+ activate(index + 1);
+ }
+ });
+
+ // Pause bei Hover/Fokus.
+ root.addEventListener('mouseenter', function () {
+ hoverPaused = true;
+ syncPaused();
+ });
+ root.addEventListener('mouseleave', function () {
+ hoverPaused = false;
+ syncPaused();
+ });
+ root.addEventListener('focusin', function () {
+ hoverPaused = true;
+ syncPaused();
+ });
+ root.addEventListener('focusout', function (event) {
+ if (!root.contains(event.relatedTarget)) {
+ hoverPaused = false;
+ syncPaused();
+ }
+ });
+
+ dots.forEach(function (dot, n) {
+ dot.addEventListener('click', function () {
+ activate(n);
+ });
+ });
+
+ // Steuerung einblenden (ohne JS bleibt sie verborgen, da funktionslos).
+ if (controls) {
+ controls.hidden = false;
+ }
+
+ // Pause/Play nur anbieten, wenn überhaupt automatisch rotiert wird.
+ if (toggle && !reduce) {
+ toggle.hidden = false;
+ toggle.addEventListener('click', function () {
+ userPaused = !userPaused;
+ toggle.setAttribute('aria-pressed', String(userPaused));
+ if (toggleLabel) {
+ toggleLabel.textContent = userPaused
+ ? 'Automatischen Wechsel fortsetzen'
+ : 'Automatischen Wechsel pausieren';
+ }
+ syncPaused();
+ });
+ }
+
+ activate(0);
+ syncPaused();
+})();