20-02-2026
This commit is contained in:
parent
854ce02bf6
commit
4d6b4930b2
128 changed files with 18247 additions and 2093 deletions
408
public/_cabinet/offers/player.html
Normal file
408
public/_cabinet/offers/player.html
Normal file
|
|
@ -0,0 +1,408 @@
|
|||
<!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>
|
||||
Loading…
Add table
Add a link
Reference in a new issue