b2in/public/_cabinet/offers/player.html
2026-02-20 17:57:50 +01:00

408 lines
9.9 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CABINET Angebote Display</title>
<style>
*, *::before, *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html, body {
height: 100%;
width: 100%;
overflow: hidden;
background: #0a0a0a;
}
/* Container für 9:16 Aspect Ratio */
.player-container {
width: 100vw;
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
.player-frame {
width: 100%;
height: 100%;
max-width: 1080px;
max-height: 1920px;
aspect-ratio: 9 / 16;
position: relative;
background: #fff;
overflow: hidden;
}
@media (min-aspect-ratio: 9/16) {
.player-frame {
width: auto;
height: 100vh;
}
}
@media (max-aspect-ratio: 9/16) {
.player-frame {
width: 100vw;
height: auto;
}
}
/* iFrames für Slides */
.slide-frame {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
border: none;
opacity: 0;
transition: opacity 0.6s ease;
pointer-events: none;
}
.slide-frame.active {
opacity: 1;
z-index: 2;
pointer-events: auto;
}
.slide-frame.preload {
z-index: 1;
}
/* Progress Indicator */
.progress-bar {
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 4px;
background: rgba(0, 0, 0, 0.1);
z-index: 100;
}
.progress-fill {
height: 100%;
width: 0%;
background: #009FE3;
transition: width linear;
}
/* Slide Indicators */
.slide-indicators {
position: absolute;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
display: flex;
gap: 8px;
z-index: 100;
opacity: 0.6;
}
.indicator {
width: 8px;
height: 8px;
border-radius: 50%;
background: rgba(0, 0, 0, 0.3);
transition: all 0.3s ease;
}
.indicator.active {
background: #009FE3;
transform: scale(1.2);
}
/* Loading State */
.loading-overlay {
position: absolute;
inset: 0;
background: #fff;
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
transition: opacity 0.5s ease;
}
.loading-overlay.hidden {
opacity: 0;
pointer-events: none;
}
.loading-text {
font-family: 'IBM Plex Sans', sans-serif;
font-size: 18px;
color: #666;
}
/* Debug Info (optional, ausblendbar) */
.debug-info {
position: absolute;
top: 10px;
right: 10px;
background: rgba(0,0,0,0.7);
color: #fff;
padding: 8px 12px;
border-radius: 6px;
font-family: monospace;
font-size: 12px;
z-index: 1000;
display: none;
}
.debug-info.visible {
display: block;
}
</style>
</head>
<body>
<div class="player-container">
<div class="player-frame" id="player">
<!-- Loading Overlay -->
<div class="loading-overlay" id="loading">
<span class="loading-text">Lädt Angebote...</span>
</div>
<!-- Slides werden hier dynamisch eingefügt -->
<!-- Progress Bar -->
<div class="progress-bar">
<div class="progress-fill" id="progress"></div>
</div>
<!-- Slide Indicators (optional) -->
<div class="slide-indicators" id="indicators"></div>
<!-- Debug Info -->
<div class="debug-info" id="debug">
Slide: <span id="debug-slide">0</span> / <span id="debug-total">0</span>
</div>
</div>
</div>
<script>
/**
* CABINET Offer Slide Player
* Lädt und rotiert durch Slides basierend auf config.json
*/
const CONFIG_URL = 'config.json';
const DEBUG_MODE = false; // Auf true setzen für Debug-Infos
class SlidePlayer {
constructor() {
this.config = null;
this.slides = [];
this.currentIndex = 0;
this.frames = [];
this.timer = null;
this.isPlaying = false;
// DOM Elements
this.player = document.getElementById('player');
this.loading = document.getElementById('loading');
this.progress = document.getElementById('progress');
this.indicators = document.getElementById('indicators');
this.debug = document.getElementById('debug');
this.debugSlide = document.getElementById('debug-slide');
this.debugTotal = document.getElementById('debug-total');
if (DEBUG_MODE) {
this.debug.classList.add('visible');
}
}
async init() {
try {
console.log('[Player] Initializing...');
// Lade Konfiguration
await this.loadConfig();
// Erstelle iFrames für alle Slides
this.createFrames();
// Erstelle Indikatoren
this.createIndicators();
// Warte bis erster Slide geladen ist
await this.preloadSlide(0);
// Verstecke Loading
this.loading.classList.add('hidden');
// Starte Rotation
this.play();
console.log('[Player] Ready!');
} catch (error) {
console.error('[Player] Init error:', error);
this.showError('Fehler beim Laden der Angebote');
}
}
async loadConfig() {
const response = await fetch(CONFIG_URL);
if (!response.ok) {
throw new Error(`Config load failed: ${response.status}`);
}
this.config = await response.json();
this.slides = this.config.slides || [];
console.log(`[Player] Loaded ${this.slides.length} slides`);
this.debugTotal.textContent = this.slides.length;
}
createFrames() {
this.slides.forEach((slide, index) => {
const iframe = document.createElement('iframe');
iframe.className = 'slide-frame';
iframe.id = `frame-${index}`;
iframe.setAttribute('loading', 'lazy');
iframe.setAttribute('data-src', slide.file);
// Füge vor Progress Bar ein
this.player.insertBefore(iframe, this.player.querySelector('.progress-bar'));
this.frames.push(iframe);
});
}
createIndicators() {
this.slides.forEach((_, index) => {
const dot = document.createElement('div');
dot.className = 'indicator';
dot.dataset.index = index;
this.indicators.appendChild(dot);
});
}
async preloadSlide(index) {
const frame = this.frames[index];
if (!frame) return;
// Wenn noch nicht geladen, lade jetzt
if (!frame.src) {
const src = frame.getAttribute('data-src');
return new Promise((resolve) => {
frame.onload = () => {
console.log(`[Player] Slide ${index} loaded`);
resolve();
};
frame.onerror = () => {
console.error(`[Player] Slide ${index} failed to load`);
resolve(); // Trotzdem weiter
};
frame.src = src;
});
}
return Promise.resolve();
}
showSlide(index) {
// Deaktiviere alle Frames
this.frames.forEach((frame, i) => {
frame.classList.remove('active', 'preload');
});
// Aktiviere aktuellen Frame
const currentFrame = this.frames[index];
if (currentFrame) {
currentFrame.classList.add('active');
}
// Update Indikatoren
const dots = this.indicators.querySelectorAll('.indicator');
dots.forEach((dot, i) => {
dot.classList.toggle('active', i === index);
});
// Update Debug
this.debugSlide.textContent = index + 1;
// Preload nächsten Slide
const nextIndex = (index + 1) % this.slides.length;
this.preloadSlide(nextIndex);
console.log(`[Player] Showing slide ${index}: ${this.slides[index].id}`);
}
startProgress(duration) {
// Reset progress
this.progress.style.transition = 'none';
this.progress.style.width = '0%';
// Force reflow
void this.progress.offsetWidth;
// Animate
this.progress.style.transition = `width ${duration}ms linear`;
this.progress.style.width = '100%';
}
play() {
if (this.isPlaying) return;
this.isPlaying = true;
this.showCurrentSlide();
}
showCurrentSlide() {
const slide = this.slides[this.currentIndex];
const duration = slide.duration || 8000;
// Zeige Slide
this.showSlide(this.currentIndex);
// Starte Progress
this.startProgress(duration);
// Timer für nächsten Slide
clearTimeout(this.timer);
this.timer = setTimeout(() => {
this.nextSlide();
}, duration);
}
nextSlide() {
this.currentIndex = (this.currentIndex + 1) % this.slides.length;
this.showCurrentSlide();
}
prevSlide() {
this.currentIndex = (this.currentIndex - 1 + this.slides.length) % this.slides.length;
this.showCurrentSlide();
}
pause() {
this.isPlaying = false;
clearTimeout(this.timer);
}
showError(message) {
this.loading.innerHTML = `<span class="loading-text" style="color: #c00;">${message}</span>`;
}
}
// Initialize Player
document.addEventListener('DOMContentLoaded', () => {
const player = new SlidePlayer();
player.init();
// Optional: Keyboard Controls (für Testing)
document.addEventListener('keydown', (e) => {
if (e.key === 'ArrowRight') player.nextSlide();
if (e.key === 'ArrowLeft') player.prevSlide();
if (e.key === ' ') player.isPlaying ? player.pause() : player.play();
});
});
</script>
</body>
</html>