20-02-2026

This commit is contained in:
Kevin Adametz 2026-02-20 17:57:50 +01:00
parent 854ce02bf6
commit 4d6b4930b2
128 changed files with 18247 additions and 2093 deletions

View 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>