Vorschau Hero: Video vollstaendig sichtbar + Endcard mit CTA am Ende

Umgebaut zu Video-getriebenem Slider mit Endcard-Phase:
- Video object-fit: contain (komplett sichtbar, Navy-Letterbox-Raender)
- Waehrend Video-Laufzeit: keine Overlays (Video spricht fuer sich)
- ended-Event triggert .ended/.endcard-Klassen -> 3s Endcard
- Endcard zeigt Titel + Tagline (zentriert) + Beispieltext + CTA
- Slide 2 ohne CTA: Endcard zeigt nur Titel + Tagline
- loop-Attribut aus allen 5 Videos entfernt (sonst feuert ended nicht)
- Festes 15s-Autoplay entfernt; Slide-Wechsel ist video-getrieben
- Fallback-Timer 25s falls ended-Event nicht kommt (Ladefehler etc.)
- Manuelle Navigation (Dots/Pfeile/Swipe) cleant alle Timer sofort
- Tab-Wechsel pausiert/resumt korrekt im jeweiligen Zustand

Rollback-Punkt: c693114
Dieser Commit ist enthalten in:
Claude Code
2026-04-21 01:28:42 +02:00
Ursprung c6931142e7
Commit c85be47307
3 geänderte Dateien mit 104 neuen und 71 gelöschten Zeilen

Datei anzeigen

@@ -41,13 +41,21 @@
});
});
/* ==================== HERO SLIDER ==================== */
/* ==================== HERO SLIDER (video-driven mit Endcard) ==================== */
var heroEl = document.querySelector('.hero');
var heroSlides = document.querySelectorAll('.hero-slide');
var heroDots = document.querySelectorAll('.hero-dot');
var heroCurrentSlide = 0;
var heroTimer = null;
var HERO_INTERVAL = 15000;
var heroEndcardTimer = null;
var heroFallbackTimer = null;
var heroIsTransitioning = false;
var HERO_ENDCARD_MS = 3000;
var HERO_FALLBACK_MS = 25000;
function heroClearTimers() {
if (heroEndcardTimer) { clearTimeout(heroEndcardTimer); heroEndcardTimer = null; }
if (heroFallbackTimer) { clearTimeout(heroFallbackTimer); heroFallbackTimer = null; }
}
function heroPlaySlideVideo(slide) {
var v = slide && slide.querySelector('video');
@@ -62,16 +70,43 @@
if (v) v.pause();
}
function heroEnterEndcard() {
if (!heroSlides.length) return;
var slide = heroSlides[heroCurrentSlide];
if (!slide || slide.classList.contains('ended')) return;
slide.classList.add('ended');
if (heroEl) heroEl.classList.add('endcard');
heroClearTimers();
heroEndcardTimer = setTimeout(function () {
heroEndcardTimer = null;
heroNext();
}, HERO_ENDCARD_MS);
}
function heroStartSlide() {
var slide = heroSlides[heroCurrentSlide];
if (!slide) return;
slide.classList.remove('ended');
if (heroEl) heroEl.classList.remove('endcard');
heroPlaySlideVideo(slide);
heroClearTimers();
heroFallbackTimer = setTimeout(function () {
heroFallbackTimer = null;
heroEnterEndcard();
}, HERO_FALLBACK_MS);
}
function heroGoTo(index) {
if (heroIsTransitioning || index === heroCurrentSlide || !heroSlides.length) return;
heroIsTransitioning = true;
heroClearTimers();
var oldIndex = heroCurrentSlide;
heroSlides[oldIndex].classList.add('exiting');
heroSlides[oldIndex].classList.remove('active');
heroSlides[oldIndex].classList.remove('active', 'ended');
if (heroEl) heroEl.classList.remove('endcard');
if (heroDots[oldIndex]) heroDots[oldIndex].classList.remove('active');
// Altes Video sofort pausieren, damit es während des Fade-Outs nicht mehr läuft
heroPauseSlideVideo(heroSlides[oldIndex]);
setTimeout(function () {
@@ -79,10 +114,7 @@
heroCurrentSlide = index;
heroSlides[heroCurrentSlide].classList.add('active');
if (heroDots[heroCurrentSlide]) heroDots[heroCurrentSlide].classList.add('active');
// Neues Video von Anfang starten, sobald der Slide sichtbar wird
heroPlaySlideVideo(heroSlides[heroCurrentSlide]);
heroStartSlide();
heroIsTransitioning = false;
}, 400);
}
@@ -95,55 +127,66 @@
heroGoTo((heroCurrentSlide - 1 + heroSlides.length) % heroSlides.length);
}
function heroStartAutoplay() {
heroStopAutoplay();
heroTimer = setTimeout(function tick() {
heroNext();
heroTimer = setTimeout(tick, HERO_INTERVAL);
}, HERO_INTERVAL);
}
function heroStopAutoplay() {
if (heroTimer) { clearInterval(heroTimer); heroTimer = null; }
}
// Pro Video: 'ended' → Endcard-Phase starten
heroSlides.forEach(function (slide) {
var v = slide.querySelector('video');
if (!v) return;
v.addEventListener('ended', function () {
if (slide.classList.contains('active')) heroEnterEndcard();
});
});
heroDots.forEach(function (dot, i) {
dot.addEventListener('click', function () {
heroGoTo(i);
heroStartAutoplay();
});
dot.addEventListener('click', function () { heroGoTo(i); });
});
var heroPrevBtn = document.querySelector('.hero-arrow-prev');
var heroNextBtn = document.querySelector('.hero-arrow-next');
if (heroPrevBtn) heroPrevBtn.addEventListener('click', function () { heroPrev(); heroStartAutoplay(); });
if (heroNextBtn) heroNextBtn.addEventListener('click', function () { heroNext(); heroStartAutoplay(); });
if (heroPrevBtn) heroPrevBtn.addEventListener('click', heroPrev);
if (heroNextBtn) heroNextBtn.addEventListener('click', heroNext);
var heroSlider = document.querySelector('.hero-slider');
if (heroSlider) {
var heroTouchStartX = 0;
heroSlider.addEventListener('touchstart', function (e) {
heroTouchStartX = e.changedTouches[0].screenX;
heroStopAutoplay();
}, { passive: true });
heroSlider.addEventListener('touchend', function (e) {
var diff = e.changedTouches[0].screenX - heroTouchStartX;
if (Math.abs(diff) > 50) {
if (diff < 0) heroNext(); else heroPrev();
}
heroStartAutoplay();
}, { passive: true });
}
document.addEventListener('visibilitychange', function () {
if (document.hidden) { heroStopAutoplay(); }
else { heroStartAutoplay(); }
var slide = heroSlides[heroCurrentSlide];
if (!slide) return;
if (document.hidden) {
heroClearTimers();
heroPauseSlideVideo(slide);
return;
}
if (slide.classList.contains('ended')) {
heroEndcardTimer = setTimeout(function () {
heroEndcardTimer = null;
heroNext();
}, HERO_ENDCARD_MS);
} else {
var v = slide.querySelector('video');
if (v) {
var p = v.play();
if (p && typeof p.catch === 'function') p.catch(function () {});
}
heroFallbackTimer = setTimeout(function () {
heroFallbackTimer = null;
heroEnterEndcard();
}, HERO_FALLBACK_MS);
}
});
if (heroSlides.length > 1) heroStartAutoplay();
// Initialer Video-Start für Slide 0 (ersetzt das weggefallene autoplay-Attribut)
if (heroSlides.length) heroPlaySlideVideo(heroSlides[0]);
// Initialer Start (Slide 0 ist bereits .active im HTML)
if (heroSlides.length) heroStartSlide();
/* ==================== MAP STATE ==================== */
var mapInstance = null;