10-04-2026
This commit is contained in:
parent
4d6b4930b2
commit
4bb89aad8c
836 changed files with 52961 additions and 5950 deletions
412
public/_cabinet/_old/index copy.html
Normal file
412
public/_cabinet/_old/index copy.html
Normal file
|
|
@ -0,0 +1,412 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Cabinet Digital Signage Bielefeld</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@300;400;600;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
/* --- GRUNDGERÜST --- */
|
||||
body, html {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
|
||||
font-family: 'IBM Plex Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
background-color: #000;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#main-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-width: 100vw;
|
||||
max-height: 100vh;
|
||||
/* Seitenverhältnis 9:16 (1080:1920) beibehalten */
|
||||
aspect-ratio: 9 / 16;
|
||||
}
|
||||
|
||||
/* Wenn Bildschirm breiter als 9:16 ist, nach Höhe skalieren */
|
||||
@media (min-aspect-ratio: 9/16) {
|
||||
#main-container {
|
||||
width: auto;
|
||||
height: 100vh;
|
||||
}
|
||||
}
|
||||
|
||||
/* Wenn Bildschirm höher als 9:16 ist, nach Breite skalieren */
|
||||
@media (max-aspect-ratio: 9/16) {
|
||||
#main-container {
|
||||
width: 100vw;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
/* --- VIDEO BEREICH (Oben) --- */
|
||||
#video-wrapper {
|
||||
flex-grow: 1;
|
||||
position: relative;
|
||||
background: #000;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#video-player {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover; /* Video füllt den Bereich randlos */
|
||||
object-position: center 15%; /* Fallback-Position, wird per JavaScript überschrieben */
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* --- FOOTER BEREICH (Unten - ca. 16.67% der Höhe = 320px bei 1920px) --- */
|
||||
#footer {
|
||||
height: 9.67vh;
|
||||
min-height: 100px;
|
||||
background-color: #1a1a1a; /* Dunkelgrau */
|
||||
color: #ffffff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 20px; /* 60px bei 1080px Breite */
|
||||
box-sizing: border-box;
|
||||
font-size: 10px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Progress Bar am oberen Rand des Footers */
|
||||
#progress-bar {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 3px;
|
||||
background-color: #009FE3; /* Cabinet Blau */
|
||||
width: 0%;
|
||||
transition: none;
|
||||
}
|
||||
|
||||
#progress-bar.animate {
|
||||
animation: progressAnimation 30s linear;
|
||||
}
|
||||
|
||||
@keyframes progressAnimation {
|
||||
from {
|
||||
width: 0%;
|
||||
}
|
||||
to {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
/* --- INHALTE IM FOOTER --- */
|
||||
.cta-text-container {
|
||||
width: 75%;
|
||||
opacity: 1;
|
||||
transition: opacity 1s ease-in-out;
|
||||
}
|
||||
|
||||
.cta-headline {
|
||||
font-size: 2.0em; /* Relativ zur Footer-Schriftgröße */
|
||||
font-weight: 300;
|
||||
margin-bottom: 0.3em;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
color: #bbb;
|
||||
}
|
||||
|
||||
.cta-subline {
|
||||
font-size: 2.4em; /* Relativ zur Footer-Schriftgröße */
|
||||
font-weight: 700;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.qr-container {
|
||||
width: 25%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
opacity: 1;
|
||||
transition: opacity 1s ease-in-out;
|
||||
}
|
||||
|
||||
.qr-code-img {
|
||||
width: 8em; /* Relativ zur Footer-Schriftgröße */
|
||||
height: auto;
|
||||
max-width: 100px;
|
||||
aspect-ratio: 1 / 1; /* Erzwingt quadratische Form */
|
||||
object-fit: contain;
|
||||
background-color: white; /* Weißer Hintergrund für Lesbarkeit */
|
||||
padding: 0.4em;
|
||||
border-radius: 0.6em;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.scan-hint {
|
||||
margin-top: 0.8em;
|
||||
font-size: 1.3em; /* Relativ zur Footer-Schriftgröße */
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
font-weight: 600;
|
||||
color: #009FE3; /* Akzentfarbe */
|
||||
}
|
||||
|
||||
/* Hilfsklasse für den Überblend-Effekt */
|
||||
.fade-out {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
/* Loading Indicator */
|
||||
.loading {
|
||||
text-align: center;
|
||||
padding: 2em;
|
||||
color: #fff;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="main-container">
|
||||
<!-- VIDEO BEREICH -->
|
||||
<div id="video-wrapper">
|
||||
<!-- Videos werden hier geladen. 'muted' ist oft nötig für Autoplay -->
|
||||
<video id="video-player" autoplay muted playsinline></video>
|
||||
</div>
|
||||
|
||||
<!-- FOOTER BEREICH -->
|
||||
<div id="footer">
|
||||
<div id="progress-bar"></div>
|
||||
<div class="cta-text-container" id="text-area">
|
||||
<div class="cta-headline" id="headline">LADEN...</div>
|
||||
<div class="cta-subline" id="subline">Inhalte werden geladen</div>
|
||||
</div>
|
||||
|
||||
<div class="qr-container" id="qr-area">
|
||||
<img src="" id="qr-image" class="qr-code-img" alt="QR Code">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
/* ==============================================
|
||||
KONFIGURATION WIRD DYNAMISCH GELADEN
|
||||
============================================== */
|
||||
|
||||
let videoPlaylist = [];
|
||||
let footerContent = [];
|
||||
let footerContentLength = 0;
|
||||
|
||||
// Basis-URL für Assets und API (b2in.eu Server)
|
||||
const BASE_URL = 'https://b2in.eu';
|
||||
|
||||
// API-URL für die Konfiguration (CORS ist aktiviert für cabinet.b2in.eu)
|
||||
const API_URL = BASE_URL + '/api/display/config';
|
||||
|
||||
/* ==============================================
|
||||
KONFIGURATION LADEN
|
||||
============================================== */
|
||||
|
||||
async function loadConfiguration() {
|
||||
try {
|
||||
const response = await fetch(API_URL);
|
||||
if (!response.ok) {
|
||||
throw new Error('Fehler beim Laden der Konfiguration');
|
||||
}
|
||||
|
||||
const config = await response.json();
|
||||
videoPlaylist = config.videoPlaylist || [];
|
||||
footerContent = config.footerContent || [];
|
||||
|
||||
console.log('Konfiguration geladen:', config);
|
||||
|
||||
// Überprüfe, ob Videos vorhanden sind
|
||||
if (videoPlaylist.length === 0) {
|
||||
console.warn('Keine Videos in der Playlist vorhanden');
|
||||
document.getElementById('headline').innerText = 'KEINE VIDEOS';
|
||||
document.getElementById('subline').innerText = 'Bitte fügen Sie Videos im CMS hinzu';
|
||||
return false;
|
||||
}
|
||||
|
||||
// Überprüfe, ob Footer-Inhalte vorhanden sind
|
||||
if (footerContent.length === 0) {
|
||||
console.warn('Keine Footer-Inhalte vorhanden - Footer wird ausgeblendet');
|
||||
footerContentLength = 0;
|
||||
// Footer ausblenden
|
||||
const footer = document.getElementById('footer');
|
||||
if (footer) {
|
||||
footer.style.display = 'none';
|
||||
}
|
||||
// Video-Wrapper auf 100% Höhe setzen
|
||||
const videoWrapper = document.getElementById('video-wrapper');
|
||||
if (videoWrapper) {
|
||||
videoWrapper.style.flexGrow = '1';
|
||||
videoWrapper.style.height = '100%';
|
||||
}
|
||||
} else {
|
||||
// Footer anzeigen, falls er zuvor ausgeblendet wurde
|
||||
const footer = document.getElementById('footer');
|
||||
if (footer) {
|
||||
footer.style.display = 'flex';
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Laden der Konfiguration:', error);
|
||||
document.getElementById('headline').innerText = 'FEHLER';
|
||||
document.getElementById('subline').innerText = 'Konfiguration konnte nicht geladen werden';
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* ==============================================
|
||||
PROGRAMM-LOGIK
|
||||
============================================== */
|
||||
|
||||
// --- VIDEO PLAYER LOGIC ---
|
||||
const videoElement = document.getElementById('video-player');
|
||||
let currentVideoIndex = 0;
|
||||
|
||||
function playNextVideo() {
|
||||
if (videoPlaylist.length === 0) return;
|
||||
|
||||
const video = videoPlaylist[currentVideoIndex];
|
||||
// Videos von b2in.eu laden (absolute URL)
|
||||
videoElement.src = BASE_URL + "/_cabinet/" + video.src;
|
||||
if(footerContentLength !== 0) {
|
||||
videoElement.style.objectPosition = `center ${video.position}%`;
|
||||
}
|
||||
videoElement.play().catch(e => console.log("Autoplay blocked/failed", e));
|
||||
|
||||
currentVideoIndex++;
|
||||
if (currentVideoIndex >= videoPlaylist.length) {
|
||||
currentVideoIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
videoElement.addEventListener('ended', playNextVideo);
|
||||
|
||||
// --- FOOTER ROTATION LOGIC ---
|
||||
let currentFooterIndex = 0;
|
||||
const textArea = document.getElementById('text-area');
|
||||
const qrArea = document.getElementById('qr-area');
|
||||
const headlineEl = document.getElementById('headline');
|
||||
const sublineEl = document.getElementById('subline');
|
||||
const qrImageEl = document.getElementById('qr-image');
|
||||
const progressBar = document.getElementById('progress-bar');
|
||||
|
||||
function restartProgressBar() {
|
||||
// Animation zurücksetzen und neu starten
|
||||
progressBar.classList.remove('animate');
|
||||
void progressBar.offsetWidth; // Force reflow
|
||||
progressBar.classList.add('animate');
|
||||
}
|
||||
|
||||
function updateFooter() {
|
||||
if (footerContent.length === 0) return;
|
||||
|
||||
// 1. Ausblenden
|
||||
textArea.classList.add('fade-out');
|
||||
qrArea.classList.add('fade-out');
|
||||
|
||||
// Progress Bar neu starten
|
||||
restartProgressBar();
|
||||
|
||||
// 2. Warten, Inhalt tauschen, Einblenden
|
||||
setTimeout(() => {
|
||||
const content = footerContent[currentFooterIndex];
|
||||
|
||||
// Text setzen
|
||||
headlineEl.innerText = content.headline;
|
||||
sublineEl.innerText = content.subline;
|
||||
|
||||
// QR Code nur generieren wenn URL vorhanden
|
||||
if (content.url) {
|
||||
// QR Code generieren (API Aufruf)
|
||||
const qrSize = "300x300";
|
||||
const qrColor = "000000"; // Schwarz
|
||||
const qrBg = "ffffff"; // Weiß
|
||||
const qrUrl = `https://api.qrserver.com/v1/create-qr-code/?size=${qrSize}&color=${qrColor}&bgcolor=${qrBg}&margin=10&data=${encodeURIComponent(content.url)}`;
|
||||
|
||||
qrImageEl.src = qrUrl;
|
||||
qrArea.style.display = 'flex'; // QR-Bereich anzeigen
|
||||
} else {
|
||||
// Kein QR-Code - QR-Bereich ausblenden
|
||||
qrArea.style.display = 'none';
|
||||
// Text-Container auf volle Breite
|
||||
textArea.style.width = '100%';
|
||||
}
|
||||
|
||||
// Index weiterschalten
|
||||
currentFooterIndex++;
|
||||
if (currentFooterIndex >= footerContent.length) {
|
||||
currentFooterIndex = 0;
|
||||
}
|
||||
|
||||
// 3. Einblenden
|
||||
if (content.url) {
|
||||
qrImageEl.onload = () => {
|
||||
textArea.classList.remove('fade-out');
|
||||
qrArea.classList.remove('fade-out');
|
||||
};
|
||||
}
|
||||
// Fallback falls Bild sofort da ist (Cache) oder kein QR-Code
|
||||
setTimeout(() => {
|
||||
textArea.classList.remove('fade-out');
|
||||
if (content.url) {
|
||||
qrArea.classList.remove('fade-out');
|
||||
}
|
||||
}, 100);
|
||||
|
||||
}, 1000); // 1 Sekunde für Fade-Out Animation
|
||||
}
|
||||
|
||||
/* ==============================================
|
||||
INITIALISIERUNG
|
||||
============================================== */
|
||||
|
||||
async function initialize() {
|
||||
const success = await loadConfiguration();
|
||||
|
||||
if (success && videoPlaylist.length > 0) {
|
||||
// Start Video
|
||||
playNextVideo();
|
||||
|
||||
// Start Footer Loop
|
||||
if (footerContent.length > 0) {
|
||||
updateFooter();
|
||||
setInterval(updateFooter, 30000); // Alle 30.000 ms (30 sek) wechseln
|
||||
|
||||
// Progress Bar initial starten
|
||||
restartProgressBar();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Beim Laden der Seite initialisieren
|
||||
initialize();
|
||||
|
||||
// Optional: Auto-Reload alle 5 Minuten, um neue Inhalte zu laden
|
||||
setInterval(async () => {
|
||||
console.log('Prüfe auf neue Konfiguration...');
|
||||
const oldFooterCount = footerContent.length;
|
||||
await loadConfiguration();
|
||||
|
||||
// Wenn Footer-Inhalte hinzugefügt oder entfernt wurden, Seite neu laden
|
||||
if ((oldFooterCount === 0 && footerContent.length > 0) ||
|
||||
(oldFooterCount > 0 && footerContent.length === 0)) {
|
||||
console.log('Footer-Status hat sich geändert - Seite wird neu geladen');
|
||||
location.reload();
|
||||
}
|
||||
}, 5 * 60 * 1000); // 5 Minuten
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
371
public/_cabinet/_old/index-dynamic.html
Normal file
371
public/_cabinet/_old/index-dynamic.html
Normal file
|
|
@ -0,0 +1,371 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Cabinet Digital Signage Bielefeld</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@300;400;600;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
/* --- GRUNDGERÜST --- */
|
||||
body, html {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
|
||||
font-family: 'IBM Plex Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
background-color: #000;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#main-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-width: 100vw;
|
||||
max-height: 100vh;
|
||||
/* Seitenverhältnis 9:16 (1080:1920) beibehalten */
|
||||
aspect-ratio: 9 / 16;
|
||||
}
|
||||
|
||||
/* Wenn Bildschirm breiter als 9:16 ist, nach Höhe skalieren */
|
||||
@media (min-aspect-ratio: 9/16) {
|
||||
#main-container {
|
||||
width: auto;
|
||||
height: 100vh;
|
||||
}
|
||||
}
|
||||
|
||||
/* Wenn Bildschirm höher als 9:16 ist, nach Breite skalieren */
|
||||
@media (max-aspect-ratio: 9/16) {
|
||||
#main-container {
|
||||
width: 100vw;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
/* --- VIDEO BEREICH (Oben) --- */
|
||||
#video-wrapper {
|
||||
flex-grow: 1;
|
||||
position: relative;
|
||||
background: #000;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#video-player {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover; /* Video füllt den Bereich randlos */
|
||||
object-position: center 15%; /* Fallback-Position, wird per JavaScript überschrieben */
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* --- FOOTER BEREICH (Unten - ca. 16.67% der Höhe = 320px bei 1920px) --- */
|
||||
#footer {
|
||||
height: 9.67vh;
|
||||
min-height: 100px;
|
||||
background-color: #1a1a1a; /* Dunkelgrau */
|
||||
color: #ffffff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 20px; /* 60px bei 1080px Breite */
|
||||
box-sizing: border-box;
|
||||
font-size: 10px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Progress Bar am oberen Rand des Footers */
|
||||
#progress-bar {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 3px;
|
||||
background-color: #009FE3; /* Cabinet Blau */
|
||||
width: 0%;
|
||||
transition: none;
|
||||
}
|
||||
|
||||
#progress-bar.animate {
|
||||
animation: progressAnimation 30s linear;
|
||||
}
|
||||
|
||||
@keyframes progressAnimation {
|
||||
from {
|
||||
width: 0%;
|
||||
}
|
||||
to {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
/* --- INHALTE IM FOOTER --- */
|
||||
.cta-text-container {
|
||||
width: 75%;
|
||||
opacity: 1;
|
||||
transition: opacity 1s ease-in-out;
|
||||
}
|
||||
|
||||
.cta-headline {
|
||||
font-size: 2.0em; /* Relativ zur Footer-Schriftgröße */
|
||||
font-weight: 300;
|
||||
margin-bottom: 0.3em;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
color: #bbb;
|
||||
}
|
||||
|
||||
.cta-subline {
|
||||
font-size: 2.4em; /* Relativ zur Footer-Schriftgröße */
|
||||
font-weight: 700;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.qr-container {
|
||||
width: 25%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
opacity: 1;
|
||||
transition: opacity 1s ease-in-out;
|
||||
}
|
||||
|
||||
.qr-code-img {
|
||||
width: 8em; /* Relativ zur Footer-Schriftgröße */
|
||||
height: auto;
|
||||
max-width: 100px;
|
||||
aspect-ratio: 1 / 1; /* Erzwingt quadratische Form */
|
||||
object-fit: contain;
|
||||
background-color: white; /* Weißer Hintergrund für Lesbarkeit */
|
||||
padding: 0.4em;
|
||||
border-radius: 0.6em;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.scan-hint {
|
||||
margin-top: 0.8em;
|
||||
font-size: 1.3em; /* Relativ zur Footer-Schriftgröße */
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
font-weight: 600;
|
||||
color: #009FE3; /* Akzentfarbe */
|
||||
}
|
||||
|
||||
/* Hilfsklasse für den Überblend-Effekt */
|
||||
.fade-out {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
/* Loading Indicator */
|
||||
.loading {
|
||||
text-align: center;
|
||||
padding: 2em;
|
||||
color: #fff;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="main-container">
|
||||
<!-- VIDEO BEREICH -->
|
||||
<div id="video-wrapper">
|
||||
<!-- Videos werden hier geladen. 'muted' ist oft nötig für Autoplay -->
|
||||
<video id="video-player" autoplay muted playsinline></video>
|
||||
</div>
|
||||
|
||||
<!-- FOOTER BEREICH -->
|
||||
<div id="footer">
|
||||
<div id="progress-bar"></div>
|
||||
<div class="cta-text-container" id="text-area">
|
||||
<div class="cta-headline" id="headline">LADEN...</div>
|
||||
<div class="cta-subline" id="subline">Inhalte werden geladen</div>
|
||||
</div>
|
||||
|
||||
<div class="qr-container" id="qr-area">
|
||||
<img src="" id="qr-image" class="qr-code-img" alt="QR Code">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
/* ==============================================
|
||||
KONFIGURATION WIRD DYNAMISCH GELADEN
|
||||
============================================== */
|
||||
|
||||
let videoPlaylist = [];
|
||||
let footerContent = [];
|
||||
|
||||
// API-URL für die Konfiguration
|
||||
const API_URL = '/api/display/config';
|
||||
|
||||
/* ==============================================
|
||||
KONFIGURATION LADEN
|
||||
============================================== */
|
||||
|
||||
async function loadConfiguration() {
|
||||
try {
|
||||
const response = await fetch(API_URL);
|
||||
if (!response.ok) {
|
||||
throw new Error('Fehler beim Laden der Konfiguration');
|
||||
}
|
||||
|
||||
const config = await response.json();
|
||||
videoPlaylist = config.videoPlaylist || [];
|
||||
footerContent = config.footerContent || [];
|
||||
|
||||
console.log('Konfiguration geladen:', config);
|
||||
|
||||
// Überprüfe, ob Videos vorhanden sind
|
||||
if (videoPlaylist.length === 0) {
|
||||
console.warn('Keine Videos in der Playlist vorhanden');
|
||||
document.getElementById('headline').innerText = 'KEINE VIDEOS';
|
||||
document.getElementById('subline').innerText = 'Bitte fügen Sie Videos im CMS hinzu';
|
||||
return false;
|
||||
}
|
||||
|
||||
// Überprüfe, ob Footer-Inhalte vorhanden sind
|
||||
if (footerContent.length === 0) {
|
||||
console.warn('Keine Footer-Inhalte vorhanden');
|
||||
footerContent = [{
|
||||
headline: 'WILLKOMMEN',
|
||||
subline: 'Bitte fügen Sie Footer-Inhalte im CMS hinzu',
|
||||
url: 'https://cabinet.b2in.eu'
|
||||
}];
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Laden der Konfiguration:', error);
|
||||
document.getElementById('headline').innerText = 'FEHLER';
|
||||
document.getElementById('subline').innerText = 'Konfiguration konnte nicht geladen werden';
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* ==============================================
|
||||
PROGRAMM-LOGIK
|
||||
============================================== */
|
||||
|
||||
// --- VIDEO PLAYER LOGIC ---
|
||||
const videoElement = document.getElementById('video-player');
|
||||
let currentVideoIndex = 0;
|
||||
|
||||
function playNextVideo() {
|
||||
if (videoPlaylist.length === 0) return;
|
||||
|
||||
const video = videoPlaylist[currentVideoIndex];
|
||||
videoElement.src = video.src;
|
||||
videoElement.style.objectPosition = `center ${video.position}%`;
|
||||
videoElement.play().catch(e => console.log("Autoplay blocked/failed", e));
|
||||
|
||||
currentVideoIndex++;
|
||||
if (currentVideoIndex >= videoPlaylist.length) {
|
||||
currentVideoIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
videoElement.addEventListener('ended', playNextVideo);
|
||||
|
||||
// --- FOOTER ROTATION LOGIC ---
|
||||
let currentFooterIndex = 0;
|
||||
const textArea = document.getElementById('text-area');
|
||||
const qrArea = document.getElementById('qr-area');
|
||||
const headlineEl = document.getElementById('headline');
|
||||
const sublineEl = document.getElementById('subline');
|
||||
const qrImageEl = document.getElementById('qr-image');
|
||||
const progressBar = document.getElementById('progress-bar');
|
||||
|
||||
function restartProgressBar() {
|
||||
// Animation zurücksetzen und neu starten
|
||||
progressBar.classList.remove('animate');
|
||||
void progressBar.offsetWidth; // Force reflow
|
||||
progressBar.classList.add('animate');
|
||||
}
|
||||
|
||||
function updateFooter() {
|
||||
if (footerContent.length === 0) return;
|
||||
|
||||
// 1. Ausblenden
|
||||
textArea.classList.add('fade-out');
|
||||
qrArea.classList.add('fade-out');
|
||||
|
||||
// Progress Bar neu starten
|
||||
restartProgressBar();
|
||||
|
||||
// 2. Warten, Inhalt tauschen, Einblenden
|
||||
setTimeout(() => {
|
||||
const content = footerContent[currentFooterIndex];
|
||||
|
||||
// Text setzen
|
||||
headlineEl.innerText = content.headline;
|
||||
sublineEl.innerText = content.subline;
|
||||
|
||||
// QR Code generieren (API Aufruf)
|
||||
const qrSize = "300x300";
|
||||
const qrColor = "000000"; // Schwarz
|
||||
const qrBg = "ffffff"; // Weiß
|
||||
const qrUrl = `https://api.qrserver.com/v1/create-qr-code/?size=${qrSize}&color=${qrColor}&bgcolor=${qrBg}&margin=10&data=${encodeURIComponent(content.url)}`;
|
||||
|
||||
qrImageEl.src = qrUrl;
|
||||
|
||||
// Index weiterschalten
|
||||
currentFooterIndex++;
|
||||
if (currentFooterIndex >= footerContent.length) {
|
||||
currentFooterIndex = 0;
|
||||
}
|
||||
|
||||
// 3. Einblenden
|
||||
qrImageEl.onload = () => {
|
||||
textArea.classList.remove('fade-out');
|
||||
qrArea.classList.remove('fade-out');
|
||||
};
|
||||
// Fallback falls Bild sofort da ist (Cache)
|
||||
setTimeout(() => {
|
||||
textArea.classList.remove('fade-out');
|
||||
qrArea.classList.remove('fade-out');
|
||||
}, 100);
|
||||
|
||||
}, 1000); // 1 Sekunde für Fade-Out Animation
|
||||
}
|
||||
|
||||
/* ==============================================
|
||||
INITIALISIERUNG
|
||||
============================================== */
|
||||
|
||||
async function initialize() {
|
||||
const success = await loadConfiguration();
|
||||
|
||||
if (success && videoPlaylist.length > 0) {
|
||||
// Start Video
|
||||
playNextVideo();
|
||||
|
||||
// Start Footer Loop
|
||||
if (footerContent.length > 0) {
|
||||
updateFooter();
|
||||
setInterval(updateFooter, 30000); // Alle 30.000 ms (30 sek) wechseln
|
||||
|
||||
// Progress Bar initial starten
|
||||
restartProgressBar();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Beim Laden der Seite initialisieren
|
||||
initialize();
|
||||
|
||||
// Optional: Auto-Reload alle 5 Minuten, um neue Inhalte zu laden
|
||||
setInterval(async () => {
|
||||
console.log('Prüfe auf neue Konfiguration...');
|
||||
await loadConfiguration();
|
||||
}, 5 * 60 * 1000); // 5 Minuten
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
331
public/_cabinet/_old/index-static-backup.html
Normal file
331
public/_cabinet/_old/index-static-backup.html
Normal file
|
|
@ -0,0 +1,331 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Cabinet Digital Signage Bielefeld</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@300;400;600;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
/* --- GRUNDGERÜST --- */
|
||||
body, html {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
|
||||
font-family: 'IBM Plex Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
background-color: #000;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#main-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-width: 100vw;
|
||||
max-height: 100vh;
|
||||
/* Seitenverhältnis 9:16 (1080:1920) beibehalten */
|
||||
aspect-ratio: 9 / 16;
|
||||
}
|
||||
|
||||
/* Wenn Bildschirm breiter als 9:16 ist, nach Höhe skalieren */
|
||||
@media (min-aspect-ratio: 9/16) {
|
||||
#main-container {
|
||||
width: auto;
|
||||
height: 100vh;
|
||||
}
|
||||
}
|
||||
|
||||
/* Wenn Bildschirm höher als 9:16 ist, nach Breite skalieren */
|
||||
@media (max-aspect-ratio: 9/16) {
|
||||
#main-container {
|
||||
width: 100vw;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
/* --- VIDEO BEREICH (Oben) --- */
|
||||
#video-wrapper {
|
||||
flex-grow: 1;
|
||||
position: relative;
|
||||
background: #000;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#video-player {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover; /* Video füllt den Bereich randlos */
|
||||
object-position: center 15%; /* Fallback-Position, wird per JavaScript überschrieben */
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* --- FOOTER BEREICH (Unten - ca. 16.67% der Höhe = 320px bei 1920px) --- */
|
||||
#footer {
|
||||
height: 9.67vh;
|
||||
min-height: 100px;
|
||||
background-color: #1a1a1a; /* Dunkelgrau */
|
||||
color: #ffffff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 20px; /* 60px bei 1080px Breite */
|
||||
box-sizing: border-box;
|
||||
font-size: 10px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Progress Bar am oberen Rand des Footers */
|
||||
#progress-bar {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 3px;
|
||||
background-color: #009FE3; /* Cabinet Blau */
|
||||
width: 0%;
|
||||
transition: none;
|
||||
}
|
||||
|
||||
#progress-bar.animate {
|
||||
animation: progressAnimation 30s linear;
|
||||
}
|
||||
|
||||
@keyframes progressAnimation {
|
||||
from {
|
||||
width: 0%;
|
||||
}
|
||||
to {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
/* --- INHALTE IM FOOTER --- */
|
||||
.cta-text-container {
|
||||
width: 75%;
|
||||
opacity: 1;
|
||||
transition: opacity 1s ease-in-out;
|
||||
}
|
||||
|
||||
.cta-headline {
|
||||
font-size: 2.0em; /* Relativ zur Footer-Schriftgröße */
|
||||
font-weight: 300;
|
||||
margin-bottom: 0.3em;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
color: #bbb;
|
||||
}
|
||||
|
||||
.cta-subline {
|
||||
font-size: 2.4em; /* Relativ zur Footer-Schriftgröße */
|
||||
font-weight: 700;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.qr-container {
|
||||
width: 25%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
opacity: 1;
|
||||
transition: opacity 1s ease-in-out;
|
||||
}
|
||||
|
||||
.qr-code-img {
|
||||
width: 8em; /* Relativ zur Footer-Schriftgröße */
|
||||
height: auto;
|
||||
max-width: 100px;
|
||||
aspect-ratio: 1 / 1; /* Erzwingt quadratische Form */
|
||||
object-fit: contain;
|
||||
background-color: white; /* Weißer Hintergrund für Lesbarkeit */
|
||||
padding: 0.4em;
|
||||
border-radius: 0.6em;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.scan-hint {
|
||||
margin-top: 0.8em;
|
||||
font-size: 1.3em; /* Relativ zur Footer-Schriftgröße */
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
font-weight: 600;
|
||||
color: #009FE3; /* Akzentfarbe */
|
||||
}
|
||||
|
||||
/* Hilfsklasse für den Überblend-Effekt */
|
||||
.fade-out {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="main-container">
|
||||
<!-- VIDEO BEREICH -->
|
||||
<div id="video-wrapper">
|
||||
<!-- Videos werden hier geladen. 'muted' ist oft nötig für Autoplay -->
|
||||
<video id="video-player" autoplay muted playsinline></video>
|
||||
</div>
|
||||
|
||||
<!-- FOOTER BEREICH -->
|
||||
<div id="footer">
|
||||
<div id="progress-bar"></div>
|
||||
<div class="cta-text-container" id="text-area">
|
||||
<div class="cta-headline" id="headline">LADEN...</div>
|
||||
<div class="cta-subline" id="subline">Inhalte werden geladen</div>
|
||||
</div>
|
||||
|
||||
<div class="qr-container" id="qr-area">
|
||||
<img src="" id="qr-image" class="qr-code-img" alt="QR Code">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
/* ==============================================
|
||||
KONFIGURATION
|
||||
============================================== */
|
||||
|
||||
// 1. VIDEOS (Dateinamen und Position hier anpassen)
|
||||
// position: Prozentwert von 0% (ganz oben) bis 100% (ganz unten)
|
||||
const videoPlaylist = [
|
||||
{ src: "assets/herbst_2025.mp4", position: 25 },
|
||||
{ src: "assets/fruehjahr_2025.mp4", position: 10 },
|
||||
{ src: "assets/fruehjahr_2024.mp4", position: 25 },
|
||||
{ src: "assets/herbst_2024.mp4", position: 25 },
|
||||
];
|
||||
|
||||
// 2. INHALTE & LINKS
|
||||
// Ich habe deine Links hier eingetragen. Die Texte kannst du anpassen.
|
||||
const footerContent = [
|
||||
{
|
||||
headline: "Beratung & Termin",
|
||||
subline: "Jetzt Termin vereinbaren.",
|
||||
url: "https://cabinet.b2in.eu/go.php?z=t"
|
||||
},
|
||||
{
|
||||
headline: "Beratung vor Ort",
|
||||
subline: "Einfach reinkommen.",
|
||||
url: "https://cabinet.b2in.eu/go.php?z=t1"
|
||||
},
|
||||
{
|
||||
headline: "Pinterest",
|
||||
subline: "Inspirationen entdecken.",
|
||||
url: "https://cabinet.b2in.eu/go.php?z=p"
|
||||
},
|
||||
{
|
||||
headline: "Instagram",
|
||||
subline: "Tägliche Einblicke & Design.",
|
||||
url: "https://cabinet.b2in.eu/go.php?z=i"
|
||||
},
|
||||
{
|
||||
headline: "Facebook",
|
||||
subline: "News, Aktionen & Community.",
|
||||
url: "https://cabinet.b2in.eu/go.php?z=f"
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
/* ==============================================
|
||||
PROGRAMM-LOGIK (Ab hier nichts ändern)
|
||||
============================================== */
|
||||
|
||||
// --- VIDEO PLAYER LOGIC ---
|
||||
const videoElement = document.getElementById('video-player');
|
||||
let currentVideoIndex = 0;
|
||||
|
||||
function playNextVideo() {
|
||||
const video = videoPlaylist[currentVideoIndex];
|
||||
videoElement.src = video.src;
|
||||
videoElement.style.objectPosition = `center ${video.position}%`;
|
||||
videoElement.play().catch(e => console.log("Autoplay blocked/failed", e));
|
||||
|
||||
currentVideoIndex++;
|
||||
if (currentVideoIndex >= videoPlaylist.length) {
|
||||
currentVideoIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
videoElement.addEventListener('ended', playNextVideo);
|
||||
|
||||
// Start Video
|
||||
if(videoPlaylist.length > 0) playNextVideo();
|
||||
|
||||
// --- FOOTER ROTATION LOGIC ---
|
||||
let currentFooterIndex = 0;
|
||||
const textArea = document.getElementById('text-area');
|
||||
const qrArea = document.getElementById('qr-area');
|
||||
const headlineEl = document.getElementById('headline');
|
||||
const sublineEl = document.getElementById('subline');
|
||||
const qrImageEl = document.getElementById('qr-image');
|
||||
const progressBar = document.getElementById('progress-bar');
|
||||
|
||||
function restartProgressBar() {
|
||||
// Animation zurücksetzen und neu starten
|
||||
progressBar.classList.remove('animate');
|
||||
void progressBar.offsetWidth; // Force reflow
|
||||
progressBar.classList.add('animate');
|
||||
}
|
||||
|
||||
function updateFooter() {
|
||||
// 1. Ausblenden
|
||||
textArea.classList.add('fade-out');
|
||||
qrArea.classList.add('fade-out');
|
||||
|
||||
// Progress Bar neu starten
|
||||
restartProgressBar();
|
||||
|
||||
// 2. Warten, Inhalt tauschen, Einblenden
|
||||
setTimeout(() => {
|
||||
const content = footerContent[currentFooterIndex];
|
||||
|
||||
// Text setzen
|
||||
headlineEl.innerText = content.headline;
|
||||
sublineEl.innerText = content.subline;
|
||||
|
||||
// QR Code generieren (API Aufruf)
|
||||
// Wir nutzen 'qrserver.com', eine schnelle und kostenlose API
|
||||
const qrSize = "300x300";
|
||||
const qrColor = "000000"; // Schwarz
|
||||
const qrBg = "ffffff"; // Weiß
|
||||
// encodeURIComponent sorgt dafür, dass Sonderzeichen im Link funktionieren
|
||||
const qrUrl = `https://api.qrserver.com/v1/create-qr-code/?size=${qrSize}&color=${qrColor}&bgcolor=${qrBg}&margin=10&data=${encodeURIComponent(content.url)}`;
|
||||
|
||||
qrImageEl.src = qrUrl;
|
||||
|
||||
// Index weiterschalten
|
||||
currentFooterIndex++;
|
||||
if (currentFooterIndex >= footerContent.length) {
|
||||
currentFooterIndex = 0;
|
||||
}
|
||||
|
||||
// 3. Einblenden
|
||||
// Kurze Verzögerung damit das Bild Zeit hat zu laden (optisch schöner)
|
||||
qrImageEl.onload = () => {
|
||||
textArea.classList.remove('fade-out');
|
||||
qrArea.classList.remove('fade-out');
|
||||
};
|
||||
// Fallback falls Bild sofort da ist (Cache)
|
||||
setTimeout(() => {
|
||||
textArea.classList.remove('fade-out');
|
||||
qrArea.classList.remove('fade-out');
|
||||
}, 100);
|
||||
|
||||
}, 1000); // 1 Sekunde für Fade-Out Animation
|
||||
}
|
||||
|
||||
// Start Footer Loop
|
||||
updateFooter();
|
||||
setInterval(updateFooter, 30000); // Alle 30.000 ms (30 sek) wechseln
|
||||
|
||||
// Progress Bar initial starten
|
||||
restartProgressBar();
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
292
public/_cabinet/_old/index_1.html
Normal file
292
public/_cabinet/_old/index_1.html
Normal file
|
|
@ -0,0 +1,292 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Cabinet Digital Signage Bielefeld</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@300;400;600;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
/* --- GRUNDGERÜST --- */
|
||||
body, html {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
|
||||
font-family: 'IBM Plex Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
background-color: #000;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#main-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-width: 100vw;
|
||||
max-height: 100vh;
|
||||
/* Seitenverhältnis 9:16 (1080:1920) beibehalten */
|
||||
aspect-ratio: 9 / 16;
|
||||
}
|
||||
|
||||
/* Wenn Bildschirm breiter als 9:16 ist, nach Höhe skalieren */
|
||||
@media (min-aspect-ratio: 9/16) {
|
||||
#main-container {
|
||||
width: auto;
|
||||
height: 100vh;
|
||||
}
|
||||
}
|
||||
|
||||
/* Wenn Bildschirm höher als 9:16 ist, nach Breite skalieren */
|
||||
@media (max-aspect-ratio: 9/16) {
|
||||
#main-container {
|
||||
width: 100vw;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
/* --- VIDEO BEREICH (Oben) --- */
|
||||
#video-wrapper {
|
||||
flex-grow: 1;
|
||||
position: relative;
|
||||
background: #000;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#video-player {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover; /* Video füllt den Bereich randlos */
|
||||
object-position: center 15%; /* Fallback-Position, wird per JavaScript überschrieben */
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* --- FOOTER BEREICH (Unten - ca. 16.67% der Höhe = 320px bei 1920px) --- */
|
||||
#footer {
|
||||
height: 16.67vh;
|
||||
min-height: 200px;
|
||||
background-color: #1a1a1a; /* Dunkelgrau */
|
||||
color: #ffffff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 20px; /* 60px bei 1080px Breite */
|
||||
box-sizing: border-box;
|
||||
border-top: 0.26vh solid #009FE3; /* Cabinet Blau - 5px bei 1920px */
|
||||
font-size: 10px
|
||||
}
|
||||
|
||||
/* --- INHALTE IM FOOTER --- */
|
||||
.cta-text-container {
|
||||
width: 65%;
|
||||
opacity: 1;
|
||||
transition: opacity 1s ease-in-out;
|
||||
}
|
||||
|
||||
.cta-headline {
|
||||
font-size: 2.2em; /* Relativ zur Footer-Schriftgröße */
|
||||
font-weight: 300;
|
||||
margin-bottom: 0.3em;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
color: #bbb;
|
||||
}
|
||||
|
||||
.cta-subline {
|
||||
font-size: 2.8em; /* Relativ zur Footer-Schriftgröße */
|
||||
font-weight: 700;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.qr-container {
|
||||
width: 30%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
opacity: 1;
|
||||
transition: opacity 1s ease-in-out;
|
||||
}
|
||||
|
||||
.qr-code-img {
|
||||
width: 12em; /* Relativ zur Footer-Schriftgröße */
|
||||
height: auto;
|
||||
max-width: 200px;
|
||||
aspect-ratio: 1 / 1; /* Erzwingt quadratische Form */
|
||||
object-fit: contain;
|
||||
background-color: white; /* Weißer Hintergrund für Lesbarkeit */
|
||||
padding: 0.8em;
|
||||
border-radius: 0.6em;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.scan-hint {
|
||||
margin-top: 0.8em;
|
||||
font-size: 1.3em; /* Relativ zur Footer-Schriftgröße */
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
font-weight: 600;
|
||||
color: #009FE3; /* Akzentfarbe */
|
||||
}
|
||||
|
||||
/* Hilfsklasse für den Überblend-Effekt */
|
||||
.fade-out {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="main-container">
|
||||
<!-- VIDEO BEREICH -->
|
||||
<div id="video-wrapper">
|
||||
<!-- Videos werden hier geladen. 'muted' ist oft nötig für Autoplay -->
|
||||
<video id="video-player" autoplay muted playsinline></video>
|
||||
</div>
|
||||
|
||||
<!-- FOOTER BEREICH -->
|
||||
<div id="footer">
|
||||
<div class="cta-text-container" id="text-area">
|
||||
<div class="cta-headline" id="headline">LADEN...</div>
|
||||
<div class="cta-subline" id="subline">Inhalte werden geladen</div>
|
||||
</div>
|
||||
|
||||
<div class="qr-container" id="qr-area">
|
||||
<img src="" id="qr-image" class="qr-code-img" alt="QR Code">
|
||||
<span class="scan-hint">Scan Me</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
/* ==============================================
|
||||
KONFIGURATION
|
||||
============================================== */
|
||||
|
||||
// 1. VIDEOS (Dateinamen und Position hier anpassen)
|
||||
// position: Prozentwert von 0% (ganz oben) bis 100% (ganz unten)
|
||||
const videoPlaylist = [
|
||||
{ src: "assets/video5.mp4", position: 10 },
|
||||
{ src: "assets/video5.mp4", position: 10 },
|
||||
{ src: "assets/video3.mp4", position: 15 },
|
||||
{ src: "assets/video4.mp4", position: 25 },
|
||||
|
||||
|
||||
|
||||
];
|
||||
|
||||
// 2. INHALTE & LINKS
|
||||
// Ich habe deine Links hier eingetragen. Die Texte kannst du anpassen.
|
||||
const footerContent = [
|
||||
{
|
||||
headline: "Beratung & Termin",
|
||||
subline: "Planen Sie Ihren Traumschrank.\nJetzt Termin vereinbaren.",
|
||||
url: "https://cabinet.b2in.eu/go.php?z=t"
|
||||
},
|
||||
{
|
||||
headline: "Inspiration",
|
||||
subline: "Entdecken Sie unsere Ideen-Pinnwände auf Pinterest.",
|
||||
url: "https://cabinet.b2in.eu/go.php?z=p"
|
||||
},
|
||||
{
|
||||
headline: "Instagram",
|
||||
subline: "Folgen Sie uns für tägliche Einblicke & Design.",
|
||||
url: "https://cabinet.b2in.eu/go.php?z=i"
|
||||
},
|
||||
{
|
||||
headline: "Facebook",
|
||||
subline: "News, Aktionen & Community.\nWerden Sie Fan.",
|
||||
url: "https://cabinet.b2in.eu/go.php?z=f"
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
/* ==============================================
|
||||
PROGRAMM-LOGIK (Ab hier nichts ändern)
|
||||
============================================== */
|
||||
|
||||
// --- VIDEO PLAYER LOGIC ---
|
||||
const videoElement = document.getElementById('video-player');
|
||||
let currentVideoIndex = 0;
|
||||
|
||||
function playNextVideo() {
|
||||
const video = videoPlaylist[currentVideoIndex];
|
||||
videoElement.src = video.src;
|
||||
videoElement.style.objectPosition = `center ${video.position}%`;
|
||||
videoElement.play().catch(e => console.log("Autoplay blocked/failed", e));
|
||||
|
||||
currentVideoIndex++;
|
||||
if (currentVideoIndex >= videoPlaylist.length) {
|
||||
currentVideoIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
videoElement.addEventListener('ended', playNextVideo);
|
||||
|
||||
// Start Video
|
||||
if(videoPlaylist.length > 0) playNextVideo();
|
||||
|
||||
|
||||
// --- FOOTER ROTATION LOGIC ---
|
||||
let currentFooterIndex = 0;
|
||||
const textArea = document.getElementById('text-area');
|
||||
const qrArea = document.getElementById('qr-area');
|
||||
const headlineEl = document.getElementById('headline');
|
||||
const sublineEl = document.getElementById('subline');
|
||||
const qrImageEl = document.getElementById('qr-image');
|
||||
|
||||
function updateFooter() {
|
||||
// 1. Ausblenden
|
||||
textArea.classList.add('fade-out');
|
||||
qrArea.classList.add('fade-out');
|
||||
|
||||
// 2. Warten, Inhalt tauschen, Einblenden
|
||||
setTimeout(() => {
|
||||
const content = footerContent[currentFooterIndex];
|
||||
|
||||
// Text setzen
|
||||
headlineEl.innerText = content.headline;
|
||||
sublineEl.innerText = content.subline;
|
||||
|
||||
// QR Code generieren (API Aufruf)
|
||||
// Wir nutzen 'qrserver.com', eine schnelle und kostenlose API
|
||||
const qrSize = "300x300";
|
||||
const qrColor = "000000"; // Schwarz
|
||||
const qrBg = "ffffff"; // Weiß
|
||||
// encodeURIComponent sorgt dafür, dass Sonderzeichen im Link funktionieren
|
||||
const qrUrl = `https://api.qrserver.com/v1/create-qr-code/?size=${qrSize}&color=${qrColor}&bgcolor=${qrBg}&margin=10&data=${encodeURIComponent(content.url)}`;
|
||||
|
||||
qrImageEl.src = qrUrl;
|
||||
|
||||
// Index weiterschalten
|
||||
currentFooterIndex++;
|
||||
if (currentFooterIndex >= footerContent.length) {
|
||||
currentFooterIndex = 0;
|
||||
}
|
||||
|
||||
// 3. Einblenden
|
||||
// Kurze Verzögerung damit das Bild Zeit hat zu laden (optisch schöner)
|
||||
qrImageEl.onload = () => {
|
||||
textArea.classList.remove('fade-out');
|
||||
qrArea.classList.remove('fade-out');
|
||||
};
|
||||
// Fallback falls Bild sofort da ist (Cache)
|
||||
setTimeout(() => {
|
||||
textArea.classList.remove('fade-out');
|
||||
qrArea.classList.remove('fade-out');
|
||||
}, 100);
|
||||
|
||||
}, 1000); // 1 Sekunde für Fade-Out Animation
|
||||
}
|
||||
|
||||
// Start Footer Loop
|
||||
updateFooter();
|
||||
setInterval(updateFooter, 30000); // Alle 30.000 ms (30 sek) wechseln
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
991
public/_cabinet/_old/index_2.html
Normal file
991
public/_cabinet/_old/index_2.html
Normal file
|
|
@ -0,0 +1,991 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Cabinet Digital Signage Bielefeld</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@300;400;600;700&display=swap" rel="stylesheet">
|
||||
<script>
|
||||
(function() {
|
||||
const LOG_URL = 'https://cabinet.b2in.eu/logger.php';
|
||||
|
||||
// Kontext-Informationen für besseres Debugging
|
||||
let appContext = {
|
||||
currentVideo: null,
|
||||
currentFooter: null,
|
||||
videoPlaylistLength: 0,
|
||||
footerContentLength: 0,
|
||||
lastActivity: Date.now()
|
||||
};
|
||||
|
||||
// Logging-Funktion mit Kontext
|
||||
function sendLog(level, message, additionalData = {}) {
|
||||
try {
|
||||
const logData = {
|
||||
level: level,
|
||||
message: String(message),
|
||||
timestamp: new Date().toISOString(),
|
||||
context: {
|
||||
...appContext,
|
||||
...additionalData
|
||||
},
|
||||
viewport: `${window.innerWidth}x${window.innerHeight}`,
|
||||
connection: navigator.onLine ? 'online' : 'offline'
|
||||
};
|
||||
|
||||
fetch(LOG_URL, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(logData),
|
||||
keepalive: true // Wichtig für Logs beim Verlassen der Seite
|
||||
}).catch(() => {}); // Fehler beim Loggen ignorieren
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
// Globale Fehler abfangen
|
||||
window.onerror = function(msg, url, line, col, error) {
|
||||
sendLog('FATAL', `JavaScript Error: ${msg}`, {
|
||||
file: url,
|
||||
line: line,
|
||||
column: col,
|
||||
stack: error?.stack
|
||||
});
|
||||
return false;
|
||||
};
|
||||
|
||||
// Unhandled Promise Rejections (sehr wichtig für async/await!)
|
||||
window.addEventListener('unhandledrejection', function(event) {
|
||||
sendLog('ERROR', `Unhandled Promise Rejection: ${event.reason}`, {
|
||||
promise: event.promise?.toString()
|
||||
});
|
||||
});
|
||||
|
||||
// Console.error überschreiben
|
||||
const originalError = console.error;
|
||||
console.error = function(...args) {
|
||||
sendLog('ERROR', `Console Error: ${args.join(' ')}`);
|
||||
originalError.apply(console, args);
|
||||
};
|
||||
|
||||
// Console.warn überschreiben (für Warnungen)
|
||||
const originalWarn = console.warn;
|
||||
console.warn = function(...args) {
|
||||
sendLog('WARNING', `Console Warning: ${args.join(' ')}`);
|
||||
originalWarn.apply(console, args);
|
||||
};
|
||||
|
||||
// Resource Loading Errors (z.B. Videos, Bilder)
|
||||
window.addEventListener('error', function(event) {
|
||||
if (event.target !== window) {
|
||||
const element = event.target;
|
||||
const tagName = element.tagName;
|
||||
const src = element.src || element.href;
|
||||
|
||||
sendLog('ERROR', `Resource Failed to Load: ${tagName}`, {
|
||||
src: src,
|
||||
type: tagName
|
||||
});
|
||||
}
|
||||
}, true); // useCapture = true, um alle Events zu fangen
|
||||
|
||||
// Online/Offline Status überwachen
|
||||
window.addEventListener('online', () => {
|
||||
sendLog('INFO', 'Connection restored');
|
||||
});
|
||||
|
||||
window.addEventListener('offline', () => {
|
||||
sendLog('WARNING', 'Connection lost');
|
||||
});
|
||||
|
||||
// Heartbeat: Alle 5 Minuten ein "alive" Signal senden
|
||||
setInterval(() => {
|
||||
sendLog('INFO', 'Heartbeat - Display is running', {
|
||||
uptime: Math.floor((Date.now() - appContext.lastActivity) / 1000) + 's'
|
||||
});
|
||||
}, 5 * 60 * 1000); // Alle 5 Minuten
|
||||
|
||||
// Initial Log beim Start
|
||||
sendLog('INFO', 'Display started', {
|
||||
userAgent: navigator.userAgent,
|
||||
screen: `${screen.width}x${screen.height}`,
|
||||
url: window.location.href
|
||||
});
|
||||
|
||||
// Export für andere Scripts
|
||||
window.displayLogger = {
|
||||
log: (msg, data) => sendLog('INFO', msg, data),
|
||||
warn: (msg, data) => sendLog('WARNING', msg, data),
|
||||
error: (msg, data) => sendLog('ERROR', msg, data),
|
||||
setContext: (key, value) => { appContext[key] = value; }
|
||||
};
|
||||
})();
|
||||
</script>
|
||||
<style>
|
||||
/* --- GRUNDGERÜST --- */
|
||||
body, html {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
|
||||
font-family: 'IBM Plex Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
background-color: #000;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#main-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-width: 100vw;
|
||||
max-height: 100vh;
|
||||
/* Seitenverhältnis 9:16 (1080:1920) beibehalten */
|
||||
aspect-ratio: 9 / 16;
|
||||
}
|
||||
|
||||
/* Wenn Bildschirm breiter als 9:16 ist, nach Höhe skalieren */
|
||||
@media (min-aspect-ratio: 9/16) {
|
||||
#main-container {
|
||||
width: auto;
|
||||
height: 100vh;
|
||||
}
|
||||
}
|
||||
|
||||
/* Wenn Bildschirm höher als 9:16 ist, nach Breite skalieren */
|
||||
@media (max-aspect-ratio: 9/16) {
|
||||
#main-container {
|
||||
width: 100vw;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
/* --- VIDEO BEREICH (Oben) --- */
|
||||
#video-wrapper {
|
||||
flex-grow: 1;
|
||||
position: relative;
|
||||
background: #000;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#video-player {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover; /* Video füllt den Bereich randlos */
|
||||
object-position: center 15%; /* Fallback-Position, wird per JavaScript überschrieben */
|
||||
display: block;
|
||||
|
||||
/* Performance-Optimierungen für Video */
|
||||
will-change: transform; /* Hint für Browser-Optimierung */
|
||||
transform: translateZ(0); /* Hardware-Beschleunigung aktivieren */
|
||||
backface-visibility: hidden; /* Reduziert Rendering-Last */
|
||||
-webkit-backface-visibility: hidden;
|
||||
}
|
||||
|
||||
/* --- FOOTER BEREICH (Unten - ca. 16.67% der Höhe = 320px bei 1920px) --- */
|
||||
#footer {
|
||||
height: 9.67vh;
|
||||
min-height: 100px;
|
||||
background-color: #1a1a1a; /* Dunkelgrau */
|
||||
color: #ffffff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 20px; /* 60px bei 1080px Breite */
|
||||
box-sizing: border-box;
|
||||
font-size: 10px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Progress Bar am oberen Rand des Footers */
|
||||
#progress-bar {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 3px;
|
||||
background-color: #009FE3; /* Cabinet Blau */
|
||||
width: 0%;
|
||||
transition: none;
|
||||
}
|
||||
|
||||
#progress-bar.animate {
|
||||
animation: progressAnimation 30s linear;
|
||||
}
|
||||
|
||||
@keyframes progressAnimation {
|
||||
from {
|
||||
width: 0%;
|
||||
}
|
||||
to {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
/* --- INHALTE IM FOOTER --- */
|
||||
.cta-text-container {
|
||||
width: 75%;
|
||||
opacity: 1;
|
||||
transition: opacity 1s ease-in-out;
|
||||
}
|
||||
|
||||
.cta-headline {
|
||||
font-size: 2.0em; /* Relativ zur Footer-Schriftgröße */
|
||||
font-weight: 300;
|
||||
margin-bottom: 0.3em;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
color: #bbb;
|
||||
}
|
||||
|
||||
.cta-subline {
|
||||
font-size: 2.4em; /* Relativ zur Footer-Schriftgröße */
|
||||
font-weight: 700;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.qr-container {
|
||||
width: 25%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
opacity: 1;
|
||||
transition: opacity 1s ease-in-out;
|
||||
}
|
||||
|
||||
.qr-code-img {
|
||||
width: 8em; /* Relativ zur Footer-Schriftgröße */
|
||||
height: auto;
|
||||
max-width: 100px;
|
||||
aspect-ratio: 1 / 1; /* Erzwingt quadratische Form */
|
||||
object-fit: contain;
|
||||
background-color: white; /* Weißer Hintergrund für Lesbarkeit */
|
||||
padding: 0.4em;
|
||||
border-radius: 0.6em;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.scan-hint {
|
||||
margin-top: 0.8em;
|
||||
font-size: 1.3em; /* Relativ zur Footer-Schriftgröße */
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
font-weight: 600;
|
||||
color: #009FE3; /* Akzentfarbe */
|
||||
}
|
||||
|
||||
/* Hilfsklasse für den Überblend-Effekt */
|
||||
.fade-out {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
/* Loading Indicator */
|
||||
.loading {
|
||||
text-align: center;
|
||||
padding: 2em;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* Fullscreen Button */
|
||||
#fullscreen-btn {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
left: 20px;
|
||||
z-index: 1000;
|
||||
background-color: rgba(0, 159, 227, 0.8);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
padding: 8px 10px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
font-family: 'IBM Plex Sans', sans-serif;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
#fullscreen-btn:hover {
|
||||
background-color: rgba(0, 159, 227, 1);
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
#fullscreen-btn:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
/* Button ausblenden wenn bereits im Fullscreen */
|
||||
#fullscreen-btn.hidden {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Fullscreen Reminder (nach Reload) */
|
||||
#fullscreen-btn.reminder {
|
||||
background-color: rgba(255, 152, 0, 0.95);
|
||||
animation: pulse 2s infinite;
|
||||
padding: 12px 20px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% {
|
||||
transform: scale(1);
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.08);
|
||||
box-shadow: 0 6px 12px rgba(255, 152, 0, 0.6);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- FULLSCREEN BUTTON -->
|
||||
<button id="fullscreen-btn" title="Vollbildmodus aktivieren">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="vertical-align: middle;">
|
||||
<path d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3"/>
|
||||
</svg>
|
||||
<span style="vertical-align: middle;">V 1.3</span>
|
||||
</button>
|
||||
|
||||
<div id="main-container">
|
||||
<!-- VIDEO BEREICH -->
|
||||
<div id="video-wrapper">
|
||||
<!-- Videos werden hier geladen. 'muted' ist oft nötig für Autoplay -->
|
||||
<video id="video-player" autoplay muted playsinline></video>
|
||||
</div>
|
||||
|
||||
<!-- FOOTER BEREICH -->
|
||||
<div id="footer">
|
||||
<div id="progress-bar"></div>
|
||||
<div class="cta-text-container" id="text-area">
|
||||
<div class="cta-headline" id="headline">LADEN...</div>
|
||||
<div class="cta-subline" id="subline">Inhalte werden geladen</div>
|
||||
</div>
|
||||
|
||||
<div class="qr-container" id="qr-area">
|
||||
<img src="" id="qr-image" class="qr-code-img" alt="QR Code">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
/* ==============================================
|
||||
FULLSCREEN BUTTON LOGIC MIT AUTO-REMINDER
|
||||
============================================== */
|
||||
|
||||
const fullscreenBtn = document.getElementById('fullscreen-btn');
|
||||
const FULLSCREEN_STATE_KEY = 'cabinet_fullscreen_was_active';
|
||||
|
||||
// Fullscreen aktivieren
|
||||
function enterFullscreen() {
|
||||
const elem = document.documentElement;
|
||||
|
||||
// Merken dass Fullscreen aktiviert wurde (für nach Reload)
|
||||
localStorage.setItem(FULLSCREEN_STATE_KEY, 'true');
|
||||
|
||||
if (elem.requestFullscreen) {
|
||||
elem.requestFullscreen();
|
||||
} else if (elem.webkitRequestFullscreen) { // Safari/Chrome
|
||||
elem.webkitRequestFullscreen();
|
||||
} else if (elem.mozRequestFullScreen) { // Firefox
|
||||
elem.mozRequestFullScreen();
|
||||
} else if (elem.msRequestFullscreen) { // IE/Edge
|
||||
elem.msRequestFullscreen();
|
||||
}
|
||||
|
||||
window.displayLogger?.log('Fullscreen aktiviert');
|
||||
}
|
||||
|
||||
// Button Event Listener
|
||||
fullscreenBtn.addEventListener('click', () => {
|
||||
enterFullscreen();
|
||||
// Reminder-Klasse entfernen falls vorhanden
|
||||
fullscreenBtn.classList.remove('reminder');
|
||||
});
|
||||
|
||||
// Prüfen ob Fullscreen vorher aktiv war (nach Reload)
|
||||
function checkFullscreenRestore() {
|
||||
const wasFullscreen = localStorage.getItem(FULLSCREEN_STATE_KEY);
|
||||
|
||||
if (wasFullscreen === 'true') {
|
||||
// Fullscreen war vorher aktiv → Auffälliger Reminder
|
||||
fullscreenBtn.classList.add('reminder');
|
||||
fullscreenBtn.innerHTML = `
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="vertical-align: middle;">
|
||||
<path d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3"/>
|
||||
</svg>
|
||||
<span style="vertical-align: middle;">⚠️ Vollbild aktivieren!</span>
|
||||
`;
|
||||
|
||||
window.displayLogger?.warn('Fullscreen-Reminder angezeigt (war vorher aktiv)', {
|
||||
reason: 'Page reload',
|
||||
previousState: 'fullscreen'
|
||||
});
|
||||
|
||||
// Nach 30 Sekunden automatisch versuchen (falls Kiosk-Mode)
|
||||
setTimeout(() => {
|
||||
if (!document.fullscreenElement && !document.webkitFullscreenElement) {
|
||||
window.displayLogger?.log('Versuche Auto-Fullscreen (Kiosk-Mode?)');
|
||||
enterFullscreen();
|
||||
}
|
||||
}, 30000);
|
||||
}
|
||||
}
|
||||
|
||||
// Button ausblenden wenn bereits im Fullscreen
|
||||
document.addEventListener('fullscreenchange', () => {
|
||||
if (document.fullscreenElement) {
|
||||
fullscreenBtn.classList.add('hidden');
|
||||
fullscreenBtn.classList.remove('reminder');
|
||||
localStorage.setItem(FULLSCREEN_STATE_KEY, 'true');
|
||||
} else {
|
||||
fullscreenBtn.classList.remove('hidden');
|
||||
// Fullscreen wurde verlassen → State clearen
|
||||
localStorage.removeItem(FULLSCREEN_STATE_KEY);
|
||||
window.displayLogger?.log('Fullscreen verlassen');
|
||||
}
|
||||
});
|
||||
|
||||
// Webkit Fullscreen Change (Chrome/Safari)
|
||||
document.addEventListener('webkitfullscreenchange', () => {
|
||||
if (document.webkitFullscreenElement) {
|
||||
fullscreenBtn.classList.add('hidden');
|
||||
fullscreenBtn.classList.remove('reminder');
|
||||
localStorage.setItem(FULLSCREEN_STATE_KEY, 'true');
|
||||
} else {
|
||||
fullscreenBtn.classList.remove('hidden');
|
||||
localStorage.removeItem(FULLSCREEN_STATE_KEY);
|
||||
window.displayLogger?.log('Fullscreen verlassen (webkit)');
|
||||
}
|
||||
});
|
||||
|
||||
// Check beim Laden der Seite
|
||||
checkFullscreenRestore();
|
||||
|
||||
/* ==============================================
|
||||
KONFIGURATION WIRD DYNAMISCH GELADEN
|
||||
============================================== */
|
||||
|
||||
let videoPlaylist = [];
|
||||
let footerContent = [];
|
||||
let footerContentLength = 0;
|
||||
|
||||
// Basis-URL für Assets und API (b2in.eu Server)
|
||||
const BASE_URL = 'https://b2in.eu';
|
||||
|
||||
// API-URL für die Konfiguration (CORS ist aktiviert für cabinet.b2in.eu)
|
||||
const API_URL = BASE_URL + '/api/display/config';
|
||||
|
||||
/* ==============================================
|
||||
KONFIGURATION LADEN
|
||||
============================================== */
|
||||
|
||||
async function loadConfiguration() {
|
||||
try {
|
||||
window.displayLogger?.log('Lade Konfiguration...', { url: API_URL });
|
||||
|
||||
const response = await fetch(API_URL);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const config = await response.json();
|
||||
videoPlaylist = config.videoPlaylist || [];
|
||||
footerContent = config.footerContent || [];
|
||||
|
||||
window.displayLogger?.setContext('videoPlaylistLength', videoPlaylist.length);
|
||||
window.displayLogger?.setContext('footerContentLength', footerContent.length);
|
||||
window.displayLogger?.log('Konfiguration erfolgreich geladen', {
|
||||
videos: videoPlaylist.length,
|
||||
footerItems: footerContent.length
|
||||
});
|
||||
|
||||
console.log('Konfiguration geladen:', config);
|
||||
|
||||
// Überprüfe, ob Videos vorhanden sind
|
||||
if (videoPlaylist.length === 0) {
|
||||
console.warn('Keine Videos in der Playlist vorhanden');
|
||||
window.displayLogger?.warn('Keine Videos in Playlist');
|
||||
document.getElementById('headline').innerText = 'KEINE VIDEOS';
|
||||
document.getElementById('subline').innerText = 'Bitte fügen Sie Videos im CMS hinzu';
|
||||
return false;
|
||||
}
|
||||
|
||||
// Überprüfe, ob Footer-Inhalte vorhanden sind
|
||||
if (footerContent.length === 0) {
|
||||
console.warn('Keine Footer-Inhalte vorhanden - Footer wird ausgeblendet');
|
||||
footerContentLength = 0;
|
||||
// Footer ausblenden
|
||||
const footer = document.getElementById('footer');
|
||||
if (footer) {
|
||||
footer.style.display = 'none';
|
||||
}
|
||||
// Video-Wrapper auf 100% Höhe setzen
|
||||
const videoWrapper = document.getElementById('video-wrapper');
|
||||
if (videoWrapper) {
|
||||
videoWrapper.style.flexGrow = '1';
|
||||
videoWrapper.style.height = '100%';
|
||||
}
|
||||
} else {
|
||||
// Footer anzeigen, falls er zuvor ausgeblendet wurde
|
||||
const footer = document.getElementById('footer');
|
||||
if (footer) {
|
||||
footer.style.display = 'flex';
|
||||
}
|
||||
footerContentLength = 1;
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Laden der Konfiguration:', error);
|
||||
window.displayLogger?.error('Konfiguration konnte nicht geladen werden', {
|
||||
error: error.message,
|
||||
stack: error.stack
|
||||
});
|
||||
document.getElementById('headline').innerText = 'FEHLER';
|
||||
document.getElementById('subline').innerText = 'Konfiguration konnte nicht geladen werden';
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* ==============================================
|
||||
PROGRAMM-LOGIK
|
||||
============================================== */
|
||||
|
||||
// --- ROBUSTER VIDEO PLAYER MIT MEMORY-MANAGEMENT ---
|
||||
const videoElement = document.getElementById('video-player');
|
||||
let currentVideoIndex = 0;
|
||||
let videoStartTimeout = null;
|
||||
let videoWatchdogInterval = null;
|
||||
let lastVideoTime = 0;
|
||||
let videoStuckCount = 0;
|
||||
let consecutiveErrors = 0;
|
||||
const MAX_CONSECUTIVE_ERRORS = 3;
|
||||
const VIDEO_START_TIMEOUT = 10000; // 10 Sekunden
|
||||
const VIDEO_WATCHDOG_INTERVAL = 5000; // Alle 5 Sekunden prüfen
|
||||
|
||||
// Video-Element optimieren für Memory-Management
|
||||
videoElement.setAttribute('preload', 'metadata'); // Nur Metadaten vorladen, nicht ganzes Video
|
||||
|
||||
function cleanupVideo() {
|
||||
// Wichtig: Stoppt Video und gibt Speicher frei
|
||||
try {
|
||||
videoElement.pause();
|
||||
videoElement.removeAttribute('src');
|
||||
videoElement.load(); // Triggert Garbage Collection des alten Videos
|
||||
|
||||
// Timeouts clearen
|
||||
if (videoStartTimeout) {
|
||||
clearTimeout(videoStartTimeout);
|
||||
videoStartTimeout = null;
|
||||
}
|
||||
|
||||
window.displayLogger?.log('Video cleanup durchgeführt');
|
||||
} catch (e) {
|
||||
window.displayLogger?.error('Video cleanup error', { error: e.message });
|
||||
}
|
||||
}
|
||||
|
||||
function playNextVideo() {
|
||||
if (videoPlaylist.length === 0) return;
|
||||
|
||||
// Watchdog zurücksetzen
|
||||
lastVideoTime = 0;
|
||||
videoStuckCount = 0;
|
||||
|
||||
const video = videoPlaylist[currentVideoIndex];
|
||||
const videoSrc = BASE_URL + "/_cabinet/" + video.src;
|
||||
|
||||
// Kontext aktualisieren
|
||||
window.displayLogger?.setContext('currentVideo', video.src);
|
||||
window.displayLogger?.setContext('currentVideoIndex', currentVideoIndex);
|
||||
|
||||
// WICHTIG: Altes Video cleanup BEVOR neues geladen wird
|
||||
cleanupVideo();
|
||||
|
||||
// Kleiner Delay um Cleanup abzuschließen
|
||||
setTimeout(() => {
|
||||
try {
|
||||
// Neues Video laden
|
||||
videoElement.src = videoSrc;
|
||||
|
||||
if(footerContentLength !== 0 && video.position !== undefined) {
|
||||
videoElement.style.objectPosition = `center ${video.position}%`;
|
||||
}
|
||||
|
||||
// Timeout für Video-Start
|
||||
videoStartTimeout = setTimeout(() => {
|
||||
window.displayLogger?.error('Video start timeout', {
|
||||
video: video.src,
|
||||
timeout: VIDEO_START_TIMEOUT
|
||||
});
|
||||
// Nächstes Video probieren
|
||||
skipToNextVideo('timeout');
|
||||
}, VIDEO_START_TIMEOUT);
|
||||
|
||||
// Video abspielen
|
||||
videoElement.play()
|
||||
.then(() => {
|
||||
window.displayLogger?.log(`Video started: ${video.src}`);
|
||||
consecutiveErrors = 0; // Erfolg → Error-Counter zurücksetzen
|
||||
|
||||
// Start-Timeout clearen
|
||||
if (videoStartTimeout) {
|
||||
clearTimeout(videoStartTimeout);
|
||||
videoStartTimeout = null;
|
||||
}
|
||||
})
|
||||
.catch(e => {
|
||||
console.log("Autoplay blocked/failed", e);
|
||||
window.displayLogger?.error(`Video play failed: ${video.src}`, {
|
||||
error: e.message
|
||||
});
|
||||
|
||||
consecutiveErrors++;
|
||||
if (consecutiveErrors >= MAX_CONSECUTIVE_ERRORS) {
|
||||
window.displayLogger?.error('Zu viele aufeinanderfolgende Fehler', {
|
||||
count: consecutiveErrors
|
||||
});
|
||||
// Seite nach 30 Sekunden neu laden
|
||||
setTimeout(() => location.reload(), 30000);
|
||||
} else {
|
||||
// Nächstes Video probieren
|
||||
skipToNextVideo('play_failed');
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
window.displayLogger?.error('Exception beim Video-Laden', {
|
||||
error: e.message,
|
||||
stack: e.stack
|
||||
});
|
||||
skipToNextVideo('exception');
|
||||
}
|
||||
}, 100); // 100ms Delay für Cleanup
|
||||
|
||||
// Index weiterschalten
|
||||
currentVideoIndex++;
|
||||
if (currentVideoIndex >= videoPlaylist.length) {
|
||||
currentVideoIndex = 0;
|
||||
// Playlist-Loop abgeschlossen → Log für Monitoring
|
||||
window.displayLogger?.log('Playlist-Loop abgeschlossen, starte von vorne');
|
||||
}
|
||||
}
|
||||
|
||||
function skipToNextVideo(reason) {
|
||||
window.displayLogger?.warn('Überspringe zum nächsten Video', { reason: reason });
|
||||
playNextVideo();
|
||||
}
|
||||
|
||||
// Video Watchdog: Prüft ob Video wirklich läuft
|
||||
function startVideoWatchdog() {
|
||||
if (videoWatchdogInterval) {
|
||||
clearInterval(videoWatchdogInterval);
|
||||
}
|
||||
|
||||
videoWatchdogInterval = setInterval(() => {
|
||||
if (videoPlaylist.length === 0) return;
|
||||
|
||||
const currentTime = videoElement.currentTime;
|
||||
const isPaused = videoElement.paused;
|
||||
const hasEnded = videoElement.ended;
|
||||
const isStuck = (currentTime === lastVideoTime && !isPaused && !hasEnded);
|
||||
|
||||
// Debug-Log
|
||||
if (isStuck) {
|
||||
videoStuckCount++;
|
||||
window.displayLogger?.warn('Video scheint stecken geblieben zu sein', {
|
||||
currentTime: currentTime,
|
||||
isPaused: isPaused,
|
||||
hasEnded: hasEnded,
|
||||
stuckCount: videoStuckCount,
|
||||
src: videoElement.src
|
||||
});
|
||||
|
||||
// Wenn 2x hintereinander stuck → Recovery
|
||||
if (videoStuckCount >= 2) {
|
||||
window.displayLogger?.error('Video definitiv stuck - starte nächstes', {
|
||||
currentTime: currentTime,
|
||||
src: videoElement.src
|
||||
});
|
||||
skipToNextVideo('watchdog_stuck');
|
||||
}
|
||||
} else {
|
||||
// Video läuft normal → Counter zurücksetzen
|
||||
if (videoStuckCount > 0) {
|
||||
window.displayLogger?.log('Video läuft wieder normal');
|
||||
}
|
||||
videoStuckCount = 0;
|
||||
}
|
||||
|
||||
lastVideoTime = currentTime;
|
||||
}, VIDEO_WATCHDOG_INTERVAL);
|
||||
}
|
||||
|
||||
// Video Events
|
||||
videoElement.addEventListener('ended', () => {
|
||||
window.displayLogger?.log('Video ended', {
|
||||
src: videoElement.src
|
||||
});
|
||||
playNextVideo();
|
||||
});
|
||||
|
||||
videoElement.addEventListener('error', (e) => {
|
||||
const error = videoElement.error;
|
||||
const errorCode = error?.code;
|
||||
const errorMessage = {
|
||||
1: 'MEDIA_ERR_ABORTED',
|
||||
2: 'MEDIA_ERR_NETWORK',
|
||||
3: 'MEDIA_ERR_DECODE',
|
||||
4: 'MEDIA_ERR_SRC_NOT_SUPPORTED'
|
||||
}[errorCode] || 'UNKNOWN';
|
||||
|
||||
window.displayLogger?.error('Video Error Event', {
|
||||
code: errorCode,
|
||||
message: error?.message,
|
||||
src: videoElement.src,
|
||||
mediaError: errorMessage
|
||||
});
|
||||
|
||||
// Bei Fehler → Nächstes Video
|
||||
consecutiveErrors++;
|
||||
skipToNextVideo(`error_${errorMessage}`);
|
||||
});
|
||||
|
||||
videoElement.addEventListener('stalled', () => {
|
||||
window.displayLogger?.warn('Video stalled (buffering)', {
|
||||
src: videoElement.src,
|
||||
currentTime: videoElement.currentTime
|
||||
});
|
||||
});
|
||||
|
||||
videoElement.addEventListener('waiting', () => {
|
||||
window.displayLogger?.warn('Video waiting (buffering)', {
|
||||
src: videoElement.src,
|
||||
currentTime: videoElement.currentTime
|
||||
});
|
||||
});
|
||||
|
||||
videoElement.addEventListener('playing', () => {
|
||||
window.displayLogger?.log('Video playing event', {
|
||||
src: videoElement.src,
|
||||
currentTime: videoElement.currentTime
|
||||
});
|
||||
});
|
||||
|
||||
videoElement.addEventListener('canplay', () => {
|
||||
window.displayLogger?.log('Video canplay event', {
|
||||
src: videoElement.src
|
||||
});
|
||||
});
|
||||
|
||||
// --- FOOTER ROTATION LOGIC ---
|
||||
let currentFooterIndex = 0;
|
||||
const textArea = document.getElementById('text-area');
|
||||
const qrArea = document.getElementById('qr-area');
|
||||
const headlineEl = document.getElementById('headline');
|
||||
const sublineEl = document.getElementById('subline');
|
||||
const qrImageEl = document.getElementById('qr-image');
|
||||
const progressBar = document.getElementById('progress-bar');
|
||||
|
||||
function restartProgressBar() {
|
||||
// Animation zurücksetzen und neu starten
|
||||
progressBar.classList.remove('animate');
|
||||
void progressBar.offsetWidth; // Force reflow
|
||||
progressBar.classList.add('animate');
|
||||
}
|
||||
|
||||
function updateFooter() {
|
||||
if (footerContent.length === 0) return;
|
||||
|
||||
// 1. Ausblenden
|
||||
textArea.classList.add('fade-out');
|
||||
qrArea.classList.add('fade-out');
|
||||
|
||||
// Progress Bar neu starten
|
||||
restartProgressBar();
|
||||
|
||||
// 2. Warten, Inhalt tauschen, Einblenden
|
||||
setTimeout(() => {
|
||||
const content = footerContent[currentFooterIndex];
|
||||
|
||||
// Kontext aktualisieren
|
||||
window.displayLogger?.setContext('currentFooter', currentFooterIndex);
|
||||
|
||||
// Text setzen
|
||||
headlineEl.innerText = content.headline;
|
||||
sublineEl.innerText = content.subline;
|
||||
|
||||
// QR Code nur generieren wenn URL vorhanden
|
||||
if (content.url) {
|
||||
// QR Code generieren (API Aufruf)
|
||||
const qrSize = "300x300";
|
||||
const qrColor = "000000"; // Schwarz
|
||||
const qrBg = "ffffff"; // Weiß
|
||||
const qrUrl = `https://api.qrserver.com/v1/create-qr-code/?size=${qrSize}&color=${qrColor}&bgcolor=${qrBg}&margin=10&data=${encodeURIComponent(content.url)}`;
|
||||
|
||||
qrImageEl.src = qrUrl;
|
||||
qrArea.style.display = 'flex'; // QR-Bereich anzeigen
|
||||
} else {
|
||||
// Kein QR-Code - QR-Bereich ausblenden
|
||||
qrArea.style.display = 'none';
|
||||
// Text-Container auf volle Breite
|
||||
textArea.style.width = '100%';
|
||||
}
|
||||
|
||||
// Index weiterschalten
|
||||
currentFooterIndex++;
|
||||
if (currentFooterIndex >= footerContent.length) {
|
||||
currentFooterIndex = 0;
|
||||
}
|
||||
|
||||
// 3. Einblenden
|
||||
if (content.url) {
|
||||
qrImageEl.onload = () => {
|
||||
textArea.classList.remove('fade-out');
|
||||
qrArea.classList.remove('fade-out');
|
||||
};
|
||||
}
|
||||
// Fallback falls Bild sofort da ist (Cache) oder kein QR-Code
|
||||
setTimeout(() => {
|
||||
textArea.classList.remove('fade-out');
|
||||
if (content.url) {
|
||||
qrArea.classList.remove('fade-out');
|
||||
}
|
||||
}, 100);
|
||||
|
||||
}, 1000); // 1 Sekunde für Fade-Out Animation
|
||||
}
|
||||
|
||||
/* ==============================================
|
||||
MEMORY MANAGEMENT & PERFORMANCE
|
||||
============================================== */
|
||||
|
||||
// Memory-Optimierung: Regelmäßig Browser aufräumen
|
||||
function performMemoryOptimization() {
|
||||
try {
|
||||
// Performance-Metriken loggen falls verfügbar
|
||||
if (performance.memory) {
|
||||
const memUsed = Math.round(performance.memory.usedJSHeapSize / 1048576);
|
||||
const memLimit = Math.round(performance.memory.jsHeapSizeLimit / 1048576);
|
||||
const memPercent = Math.round((memUsed / memLimit) * 100);
|
||||
|
||||
window.displayLogger?.log('Memory Status', {
|
||||
usedMB: memUsed,
|
||||
limitMB: memLimit,
|
||||
percentUsed: memPercent
|
||||
});
|
||||
|
||||
// Warnung wenn Speicher über 80%
|
||||
if (memPercent > 80) {
|
||||
window.displayLogger?.warn('Hohe Speicherauslastung', {
|
||||
percentUsed: memPercent,
|
||||
usedMB: memUsed
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Cache-Infos loggen
|
||||
const cacheInfo = {
|
||||
videoBuffered: videoElement.buffered.length,
|
||||
videoDuration: videoElement.duration,
|
||||
videoReadyState: videoElement.readyState
|
||||
};
|
||||
|
||||
window.displayLogger?.log('Video Cache Status', cacheInfo);
|
||||
|
||||
} catch (e) {
|
||||
window.displayLogger?.error('Memory optimization error', {
|
||||
error: e.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Automatischer Page-Reload bei kritischen Problemen (Failsafe)
|
||||
let criticalErrorCount = 0;
|
||||
function checkCriticalErrors() {
|
||||
if (consecutiveErrors >= MAX_CONSECUTIVE_ERRORS) {
|
||||
criticalErrorCount++;
|
||||
window.displayLogger?.error('Kritischer Zustand erkannt', {
|
||||
consecutiveErrors: consecutiveErrors,
|
||||
criticalErrorCount: criticalErrorCount
|
||||
});
|
||||
|
||||
if (criticalErrorCount >= 3) {
|
||||
window.displayLogger?.error('Zu viele kritische Fehler - Seite wird neu geladen');
|
||||
setTimeout(() => location.reload(), 5000);
|
||||
}
|
||||
} else {
|
||||
criticalErrorCount = 0; // Zurücksetzen wenn alles normal läuft
|
||||
}
|
||||
}
|
||||
|
||||
/* ==============================================
|
||||
INITIALISIERUNG
|
||||
============================================== */
|
||||
|
||||
async function initialize() {
|
||||
const success = await loadConfiguration();
|
||||
|
||||
if (success && videoPlaylist.length > 0) {
|
||||
// Start Video
|
||||
playNextVideo();
|
||||
|
||||
// Start Video Watchdog (überwacht ob Videos laufen)
|
||||
startVideoWatchdog();
|
||||
window.displayLogger?.log('Video Watchdog gestartet');
|
||||
|
||||
// Start Footer Loop
|
||||
if (footerContent.length > 0) {
|
||||
updateFooter();
|
||||
setInterval(updateFooter, 30000); // Alle 30.000 ms (30 sek) wechseln
|
||||
|
||||
// Progress Bar initial starten
|
||||
restartProgressBar();
|
||||
}
|
||||
|
||||
// Memory-Optimierung alle 10 Minuten
|
||||
setInterval(performMemoryOptimization, 10 * 60 * 1000);
|
||||
window.displayLogger?.log('Memory Optimizer gestartet (alle 10 Min)');
|
||||
|
||||
// Critical Error Check alle 30 Sekunden
|
||||
setInterval(checkCriticalErrors, 30 * 1000);
|
||||
|
||||
// Initial Memory Check nach 30 Sekunden
|
||||
setTimeout(performMemoryOptimization, 30000);
|
||||
}
|
||||
}
|
||||
|
||||
// Beim Laden der Seite initialisieren
|
||||
initialize();
|
||||
|
||||
// Auto-Reload alle 5 Minuten, um neue Inhalte zu laden
|
||||
setInterval(async () => {
|
||||
console.log('Prüfe auf neue Konfiguration...');
|
||||
const oldFooterCount = footerContent.length;
|
||||
await loadConfiguration();
|
||||
|
||||
// Wenn Footer-Inhalte hinzugefügt oder entfernt wurden, Seite neu laden
|
||||
if ((oldFooterCount === 0 && footerContent.length > 0) ||
|
||||
(oldFooterCount > 0 && footerContent.length === 0)) {
|
||||
console.log('Footer-Status hat sich geändert - Seite wird neu geladen');
|
||||
location.reload();
|
||||
}
|
||||
}, 5 * 60 * 1000); // 5 Minuten
|
||||
|
||||
// Präventiver Page-Reload alle 6 Stunden (verhindert Memory-Leaks über lange Zeit)
|
||||
setTimeout(() => {
|
||||
window.displayLogger?.log('Präventiver Reload nach 6 Stunden');
|
||||
location.reload();
|
||||
}, 6 * 60 * 60 * 1000); // 6 Stunden
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue