408 lines
9.9 KiB
HTML
408 lines
9.9 KiB
HTML
<!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>
|