Files
WKVS/html/externe-geraete/display.php
T

814 lines
32 KiB
PHP

<?php
if (!isset($segments) || !defined('APP_ROUTER')) {
http_response_code(403);
include $baseDir . "/error-pages/403.php";
exit;
}
$type = $segments[1] ?? null;
$requested_geraet = htmlspecialchars($segments[2] ?? '');
$allowed_types = ["display"];
if (!in_array($type, $allowed_types)) {
http_response_code(400);
include $baseDir . "/error-pages/400.html";
exit;
}
$baseDir = $_SERVER['DOCUMENT_ROOT'];
require_once $baseDir . '/../scripts/session_functions.php';
ini_wkvs_session(true);
$csrf_token = $_SESSION['csrf_token'] ?? '';
require $baseDir . '/../scripts/websocket/ws-create-token.php';
$WSaccesstype = "display";
$WSaccessPermission = "R";
require_once $baseDir . '/../scripts/db/db-verbindung-script-guest.php';
require_once $baseDir . '/../scripts/db/db-functions.php';
require_once $baseDir . '/../scripts/db/db-tables.php';
$stmt = $guest->prepare("SELECT `name`, `id` FROM $db_tabelle_disziplinen ORDER BY start_index ASC");
if (!$stmt->execute()) {
http_response_code(500);
exit;
}
$result = $stmt->get_result();
$disciplines = $result->fetch_all(MYSQLI_ASSOC);
$lowerDisciplines = array_change_key_case(
array_column($disciplines, 'id', 'name'),
CASE_LOWER
);
if (!array_key_exists($requested_geraet, $lowerDisciplines)) {
http_response_code(400);
$message = 'Das gesuchte Gerät kann nicht gefunden werden';
include $baseDir . "/error-pages/400.php";
exit;
}
if (!isset($lowerDisciplines[$requested_geraet])) {
http_response_code(500);
include $baseDir . "/error-pages/500.html";
exit;
}
$geraetId = $lowerDisciplines[$requested_geraet];
$WSaccess = (string) $geraetId;
$stmt->close();
// Define a small helper function to keep the code DRY (Don't Repeat Yourself)
function sanitize_hex($color) {
return preg_replace('/[^0-9a-fA-F#]/', '', $color);
}
$keysNeeded = [
'wkName', 'displayColourLogo', 'displayTextColourLogo', 'displayColorScoringBg',
'displayColorScoringBgSoft', 'displayColorScoringPanel', 'displayColorScoringPanelSoft', 'displayColorScoringBorderColor',
'displayColorScoringShadowColor', 'displayColorScoringPanelText', 'displayColorScoringPanelTextSoft',
'displayColorScoringPanelTextNoteL', 'displayColorScoringPanelTextNoteR', 'displayIdNoteL',
'displayIdNoteR', 'displayCTextLogo', 'displayCTextWKName'
];
$placeholders = implode(',', array_fill(0, count($keysNeeded), '?'));
$query = "SELECT `name`, `value` FROM $db_tabelle_var WHERE `name` IN ($placeholders)";
$stmt = $guest->prepare($query);
$params = str_repeat("s", count($keysNeeded));
$stmt->bind_param($params, ...$keysNeeded);
$stmt->execute();
$result = $stmt->get_result();
$config = [];
while ($row = $result->fetch_assoc()) {
$config[$row['name']] = $row['value'];
}
$wkName = $config['wkName'] ?? null;
$displayCTextDisplayLogo = $config['displayCTextLogo'] ?? null;
$displayCTextDisplayWKName = $config['displayCTextWKName'] ?? null;
$cleanColor = $config['displayColourLogo'] ?? null;
$cleanColorText = $config['displayTextColourLogo'] ?? null;
$displayColorScoringBg = $config['displayColorScoringBg'] ?? null;
$displayColorScoringBgSoft = $config['displayColorScoringBgSoft'] ?? null;
$displayColorScoringPanel = $config['displayColorScoringPanel'] ?? null;
$displayColorScoringPanelSoft = $config['displayColorScoringPanelSoft'] ?? null;
$displayColorScoringShadowColor = $config['displayColorScoringShadowColor'] ?? null;
$displayColorScoringBorderColor = $config['displayColorScoringBorderColor'] ?? null;
$displayColorScoringPanelText = $config['displayColorScoringPanelText'] ?? null;
$displayColorScoringPanelTextSoft = $config['displayColorScoringPanelTextSoft'] ?? null;
$displayColorScoringPanelTextNoteL = $config['displayColorScoringPanelTextNoteL'] ?? null;
$displayColorScoringPanelTextNoteR = $config['displayColorScoringPanelTextNoteR'] ?? null;
$stmt->close();
$guest->close();
$jsonUrlconfig = '/externe-geraete/json/config.json';
$jsonUrl = '/externe-geraete/json/display-id-' . $geraetId . '.json';
?>
<!DOCTYPE html>
<html>
<head>
<title><?= $wkName ?> Anzeigen</title>
<meta name="robots" content="noindex">
<link rel="stylesheet" href="/externe-geraete/css/display.css">
<script src="/intern/js/gsap/gsap.min.js"></script>
<script src="/intern/js/gsap/MorphSVGPlugin.min.js"></script>
<style>
:root {
/* Brand */
--display-panelBgLogo: <?= $cleanColor ?>;
--display-text-color: <?= $cleanColorText ?>;
/* Backgrounds */
--display-bg: <?= $displayColorScoringBg ?>;
--display-bg-soft: <?= $displayColorScoringBgSoft ?>;
--display-panel: <?= $displayColorScoringPanel ?>;
--display-panel-soft: <?= $displayColorScoringPanelSoft ?>;
/* Shadow */
--display-shadow: <?= $displayColorScoringShadowColor ?>;
/* Border */
--display-border: <?= $displayColorScoringBorderColor ?>;
/* Typography */
--display-text-main: <?= $displayColorScoringPanelText ?>;
--display-text-muted: <?= $displayColorScoringPanelTextSoft ?>;
/* Noten */
--display-color-note-l: <?= $displayColorScoringPanelTextNoteL ?>;
--display-color-note-r: <?= $displayColorScoringPanelTextNoteR ?>;
}
</style>
</head>
<body>
<div class="displayDiv">
<div class="errorws">
<svg xmlns="http://www.w3.org/2000/svg" id="errorSvg" viewBox="0 0 800 200">
<defs>
<filter id="a" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="8" result="blur"/>
<feMerge>
<feMergeNode in="blur"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
<linearGradient id="myLinearGradient" x1="0%" y1="5%" x2="100%" y2="0%">
<stop offset="0%" stop-color="#22c55e00" />
<stop offset="100%" stop-color="#22c55e90" />
</linearGradient>
</defs>
<path d="M150 100h200m100 0h200" id="errorLine" opacity=".3" stroke="#3b82f6" stroke-width="4" stroke-dasharray="10,15" stroke-linecap="round"/>
<g transform="translate(150 100)">
<circle r="45" fill="#1e293b" stroke="#3b82f6" stroke-width="3"/>
<circle r="45" fill="#1e293b" stroke="#3b82f6" stroke-width="3"/>
<rect x="-30" y="-19" width="60" height="38" rx="2" fill="#334155"/>
<rect x="-28" y="-17" width="56" height="10" rx="2" fill="#555"/>
<rect x="-28" y="-5" width="56" height="10" rx="2" fill="#555"/>
<rect x="-28" y="7" width="56" height="10" rx="2" fill="#555"/>
<text x="-26" y="-11" fill="#94a3b8" font-size="4" font-family="monospace" letter-spacing="0.5">Test WKVS</text>
<text x="-26" y="-1" fill="#94a3b8" font-size="2.5" font-family="monospace" letter-spacing="0">WKVS,</text>
<text x="-26" y="2.5" fill="#94a3b8" font-size="2" font-family="monospace" letter-spacing="0">EP</text>
<rect x="0" y="-5" width="28" rx="2" height="10" fill="url(#myLinearGradient)" />
<text x="10" y="1" fill="#22c55e" font-size="4" font-family="monospace" letter-spacing="0.5">START</text>
<text x="-26" y="13" fill="#94a3b8" font-size="4" font-family="monospace" letter-spacing="0.5">D 1.40</text>
<text x="15" y="13" fill="#94a3b8" font-size="4" font-family="monospace" letter-spacing="0.5">9.40</text>
<text y="70" text-anchor="middle" fill="#94a3b8" font-size="12" font-family="monospace" letter-spacing="1.5">Anzeige</text>
</g>
<g transform="translate(650 100)">
<circle r="45" fill="#1e293b" stroke="#3b82f6" stroke-width="3"/>
<rect x="-18" y="-15" width="36" height="10" rx="2" fill="#334155"/>
<rect x="-18" y="2" width="36" height="10" rx="2" fill="#334155"/>
<circle cx="10" cy="-10" r="2" fill="#10b981"/>
<circle cx="10" cy="7" r="2" fill="#10b981"/>
<text y="70" text-anchor="middle" fill="#94a3b8" font-size="12" font-family="monospace" letter-spacing="1.5">WKVS Server</text>
</g>
<path fill="#f43f5e" class="opacity0gsap" opacity=".4" d="m-14.142-70.71 5.657 5.656-5.657 5.657-5.657-5.657z" transform="translate(400 100)"/>
<path fill="#f43f5e" class="opacity0gsap" opacity=".4" d="m42.67 39.74 5.869 1.247-1.248 5.87-5.868-1.248z" transform="translate(400 100)"/>
<path fill="#3b82f6" class="opacity0gsap" opacity=".4" d="m-50.98 28.301 8.66 5-5 8.66-8.66-5z" transform="translate(400 100)"/>
<g filter="url(#a)" transform="translate(400 100)">
<path id="cross"
d="M-30,-30 L30,30 M30,-30 L-30,30"
stroke="#f43f5e"
stroke-width="8"
stroke-linecap="round"
fill="none"/>
</g>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" id="okSvg" viewBox="0 0 800 200" style="display: none">
<defs>
<filter id="a" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="8" result="blur"/>
<feMerge>
<feMergeNode in="blur"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
<linearGradient id="myLinearGradient" x1="0%" y1="5%" x2="100%" y2="0%">
<stop offset="0%" stop-color="#22c55e00" />
<stop offset="100%" stop-color="#22c55e90" />
</linearGradient>
</defs>
<path d="M150 100 h500" opacity=".3" stroke="#3b82f6" stroke-width="4" stroke-dasharray="10, 15" stroke-linecap="round"/>
<g transform="translate(150 100)">
<circle r="45" fill="#1e293b" stroke="#3b82f6" stroke-width="3"/>
<circle r="45" fill="#1e293b" stroke="#3b82f6" stroke-width="3"/>
<rect x="-30" y="-19" width="60" height="38" rx="2" fill="#334155"/>
<rect x="-28" y="-17" width="56" height="10" rx="2" fill="#555"/>
<rect x="-28" y="-5" width="56" height="10" rx="2" fill="#555"/>
<rect x="-28" y="7" width="56" height="10" rx="2" fill="#555"/>
<text x="-26" y="-11" fill="#94a3b8" font-size="4" font-family="monospace" letter-spacing="0.5">Test WKVS</text>
<text x="-26" y="-1" fill="#94a3b8" font-size="2.5" font-family="monospace" letter-spacing="0">WKVS,</text>
<text x="-26" y="2.5" fill="#94a3b8" font-size="2" font-family="monospace" letter-spacing="0">EP</text>
<rect x="0" y="-5" width="28" rx="2" height="10" fill="url(#myLinearGradient)" />
<text x="10" y="1" fill="#22c55e" font-size="4" font-family="monospace" letter-spacing="0.5">START</text>
<text x="-26" y="13" fill="#94a3b8" font-size="4" font-family="monospace" letter-spacing="0.5">D 1.40</text>
<text x="15" y="13" fill="#94a3b8" font-size="4" font-family="monospace" letter-spacing="0.5">9.40</text>
<text y="70" text-anchor="middle" fill="#94a3b8" font-size="12" font-family="monospace" letter-spacing="1.5">Anzeige</text>
</g>
<g transform="translate(650 100)">
<circle r="45" fill="#1e293b" stroke="#3b82f6" stroke-width="3"/>
<rect x="-18" y="-15" width="36" height="10" rx="2" fill="#334155"/>
<rect x="-18" y="2" width="36" height="10" rx="2" fill="#334155"/>
<circle cx="10" cy="-10" r="2" fill="#10b981"/>
<circle cx="10" cy="7" r="2" fill="#10b981"/>
<text y="70" text-anchor="middle" fill="#94a3b8" font-size="12" font-family="monospace" letter-spacing="1.5">WKVS Server</text>
</g>
<g filter="url(#a)" transform="translate(400 100)">
<path d="m-15-60 15 30 m30-60 -30 60" stroke="#0e0" stroke-width="8" stroke-linecap="round"/>
</g>
</svg>
<div class="divErrorTextWs">
<p class="errortext">Keine WebSocket Verbindung</p>
<p class="errortextSmall">Versuche Verbindung... (Versuch <span id="counterTries"></span> / <span id="counterMaxTries"></span>)</p>
</div>
</div>
<div class="noWsConnection">
<div class="rotator">
<p>Keine WebSocket Verbindung, Syncronisation via FETCH</p>
</div>
</div>
<div class="logobg">
<img id="jsImgLogo" class="logoimg <?= (!$displayCTextDisplayLogo) ? 'cTextHidden' : '' ?>" src="/intern/img/logo-normal.png">
<p class="logotext <?= (!$displayCTextDisplayWKName) ? 'cTextHidden' : '' ?>"><?= $wkName ?></p>
<p class="logoctext"></p>
</div>
<div class="pagediv">
<div class="display-row row1">
<p class="row1text"></p>
</div>
<div class="display-row row2">
<div class="row2text">
<p class="row2_1text sds"></p>
<p class="row2_2text"></p>
</div>
<div class="start_div">
<p class="start_text"></p>
</div>
</div>
<div class="display-row row3">
<p class="row3_1text"></p>
<p class="row3_2text"></p>
</div>
</div>
</div>
<script>
const userAccess = "<?php echo $lastSegment; ?>_display";
const jsonUrl = "<?php echo $jsonUrl; ?>";
const jsonUrlconfig = "<?php echo $jsonUrlconfig; ?>";
// --- State Management ---
let ws;
let filedConectCount = 1;
const fallbackConectCount = 5;
const RETRY_DELAY = 3000;
const FALLBACK_POLL_INTERVAL = 3000; // Fetch every 3 seconds if WS is dead
const LONG_TERM_RETRY_DELAY_MULTIPLIER = 5;
let morphOnConnect = false;
let morphOk = true;
const WSaccesstype = "<?= $WSaccesstype ?>";
const WSaccess = "<?= $WSaccess ?>";
const csrf_token = "<?= $csrf_token ?>";
const WSaccessPermission = "<?= $WSaccessPermission ?>";
let firstConnect = true;
let longTermConnection = false;
const urlAjaxNewWSToken = '/intern/scripts/ajax-create-ws-token.php';
let fallbackTimer = null;
// Hold our current data in memory
let displayConfig = { type: 'logo', ctext: '' };
let currentScore = {};
let lastUniqueId = null;
const logobg = document.querySelector('.logobg');
const ctext = document.querySelector('.logoctext');
// UI Elements
let counterTriesEl = document.getElementById('counterTries');
let counterMaxTriesEl = document.getElementById('counterMaxTries');
if(counterTriesEl) counterTriesEl.innerHTML = filedConectCount;
if(counterMaxTriesEl) counterMaxTriesEl.innerHTML = fallbackConectCount;
// --- Fullscreen Listener ---
const fs = document.documentElement;
document.body.addEventListener('click', () => {
if (fs.requestFullscreen) fs.requestFullscreen();
else if (fs.webkitRequestFullscreen) fs.webkitRequestFullscreen();
else if (fs.mozRequestFullScreen) fs.mozRequestFullScreen();
else if (fs.msRequestFullscreen) fs.msRequestFullscreen();
});
async function fetchNewWSToken(accesstype, access, accessPermission) {
try {
const response = await fetch(urlAjaxNewWSToken, {
method: "POST",
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
csrf_token,
accesstype,
access,
accessPermission
})
});
if (!response.ok) return null;
const data = await response.json();
return data.success ? data.token : null;
} catch (error) {
console.error("Token fetch failed:", error);
return null;
}
}
// --- WebSocket Logic ---
async function startWebSocket() {
console.log("Attempting WebSocket connection...");
let token;
if (firstConnect) {
token = '<?= generateWSReadToken($WSaccesstype, $WSaccess) ?>';
} else {
token = await fetchNewWSToken(WSaccesstype, WSaccess, WSaccessPermission);
}
if (!token) {
console.error("No valid token available. Retrying...");
scheduleRetry();
return;
}
try {
ws = new WebSocket(`wss://${window.location.hostname}/ws/?access=token&token=${token}`);
} catch (err) {
console.error("Malformed WebSocket URL", err);
scheduleRetry();
return;
}
ws.onopen = () => {
console.log("WebSocket connected!");
if (morphOnConnect) {
morph();
} else {
document.querySelector('.errorws').style.display = 'none';
document.querySelector('.noWsConnection').style.display = 'none';
document.querySelector('.pagediv').classList.remove("manuel");
}
filedConectCount = 1;
if(counterTriesEl) counterTriesEl.innerHTML = filedConectCount;
firstConnect = false;
longTermConnection = false;
morphOnConnect = false;
morphOk = true;
// Stop fallback polling since WS is alive
stopFallbackPolling();
// Do ONE initial fetch to sync current state
fetchFullState();
};
ws.onerror = () => {
if (!longTermConnection) {
document.querySelector('.errorws').style.display = 'flex';
document.querySelector('.errorws').style.opacity = '1';
}
morphOk = false;
restore();
};
ws.onclose = () => {
if (!longTermConnection) {
document.querySelector('.errorws').style.display = 'flex';
document.querySelector('.errorws').style.opacity = '1';
}
morphOnConnect = true;
morphOk = false;
restore();
scheduleRetry();
};
ws.addEventListener("message", msg => {
let msgJSON;
try {
msgJSON = JSON.parse(msg.data);
} catch (error) {
return; // Ignore malformed messages
}
// Route the incoming push data
switch (msgJSON.type) {
case "EINSTELLUNGEN_DISPLAY_UPDATE":
updateSettings(msgJSON.payload.key, msgJSON.payload.value);
break;
case "UPDATE_DISPLAYCONTROL":
// Expecting payload: { type: 'logo'|'ctext'|'scoring', ctext: '...' }
displayConfig = msgJSON.payload;
renderDOM();
break;
case "UPDATE_SCORE":
// Expecting payload: the exact same structure as your jsonUrl outputs
currentScore = msgJSON.payload;
renderDOM();
break;
}
});
}
function scheduleRetry() {
if (longTermConnection) {
setTimeout(startWebSocket, RETRY_DELAY * LONG_TERM_RETRY_DELAY_MULTIPLIER);
} else {
console.log(`Retrying in ${RETRY_DELAY}ms...`);
if (filedConectCount >= fallbackConectCount) {
// MAX RETRIES REACHED -> Enter Fallback Mode
longTermConnection = true;
document.querySelector('.errorws').style.display = 'none';
document.querySelector('.noWsConnection').style.display = 'flex';
document.querySelector('.pagediv').classList.add("manuel");
startFallbackPolling();
} else {
if(counterTriesEl) counterTriesEl.innerHTML = filedConectCount;
setTimeout(startWebSocket, RETRY_DELAY);
filedConectCount++;
}
}
}
// --- Fallback Polling Logic ---
function startFallbackPolling() {
if (fallbackTimer === null) {
console.warn("Starting JSON fallback polling...");
fetchFullState(); // Fetch immediately once
fallbackTimer = setInterval(fetchFullState, FALLBACK_POLL_INTERVAL);
}
}
function stopFallbackPolling() {
if (fallbackTimer !== null) {
clearInterval(fallbackTimer);
fallbackTimer = null;
console.log("Stopped JSON fallback polling.");
}
}
// --- Data Fetching (Initial Sync & Fallback) ---
async function fetchFullState() {
try {
// Fetch both config and score simultaneously
const [resConfig, resScore] = await Promise.all([
fetch(jsonUrlconfig + '?t=' + Date.now(), { cache: "no-store" }),
fetch(jsonUrl + '?t=' + Date.now(), { cache: "no-store" })
]);
if (resConfig.ok) displayConfig = await resConfig.json();
if (resScore.ok) currentScore = await resScore.json();
renderDOM();
} catch (err) {
console.error("Error fetching JSON:", err);
const container = document.getElementById('score');
if(container) {
container.innerHTML = "";
container.style.backgroundColor = "black";
}
}
}
// --- The Master Renderer ---
function renderDOM() {
if (displayConfig.type === 'logo' || displayConfig.type === 'ctext') {
// LOGO OR CUSTOM TEXT MODE
if (logobg) {
logobg.style.opacity = "1";
if (displayConfig.type === 'logo') logobg.classList.remove('ctext');
if (displayConfig.type === 'ctext') logobg.classList.add('ctext');
}
if (ctext) {
ctext.innerText = (displayConfig.type === 'ctext') ? (displayConfig.ctext || '') : '';
resizeCText();
}
} else if (displayConfig.type === 'scoring') {
// SCORING MODE
if (logobg) logobg.style.opacity = "0";
if (currentScore.uniqueid !== lastUniqueId) {
lastUniqueId = currentScore.uniqueid;
// Reset any animation/repeat logic here if needed
}
const safeText = (selector, text) => {
const el = document.querySelector(selector);
if (el) el.innerText = text !== undefined ? text : '';
};
safeText('.row1text', `${currentScore.vorname || ''} ${currentScore.name || ''}`);
safeText('.row2_1text', currentScore.verein ? `${currentScore.verein}, ` : '');
safeText('.row2_2text', currentScore.programm || '');
const starttext = document.querySelector('.start_text');
const row2El = document.querySelector('.row2');
if (starttext && row2El) {
const rootStyles = getComputedStyle(document.documentElement);
const dangerColor = rootStyles.getPropertyValue('--display-danger').trim();
const successColor = rootStyles.getPropertyValue('--display-success').trim();
if (currentScore.start === true) {
row2El.style.setProperty('--display-colorStartDiv', successColor);
starttext.innerHTML = 'Start';
} else {
row2El.style.setProperty('--display-colorStartDiv', dangerColor);
starttext.innerHTML = 'Stop';
}
}
safeText('.row3_1text', currentScore.noteLinks);
safeText('.row3_2text', currentScore.noteRechts);
}
fitTextAll();
}
// --- Settings & UI Handlers ---
function updateSettings(type, value) {
const sanitizeHex = (val) => val.replace(/[^0-9a-fA-F#]/g, '');
switch (type) {
case 'wkName':
const logotext = document.querySelector('.logotext');
if (logotext) logotext.innerHTML = value;
break;
case 'displayColourLogo':
document.documentElement.style.setProperty('--display-panelBgLogo', sanitizeHex(value));
break;
case 'displayTextColourLogo':
document.documentElement.style.setProperty('--display-text-color', sanitizeHex(value));
break;
case 'displayCTextLogo':
document.querySelector('.logoimg').classList.toggle('cTextHidden');
break;
case 'displayCTextWKName':
document.querySelector('.logotext').classList.toggle('cTextHidden');
break;
case 'displayColorScoringBg':
document.documentElement.style.setProperty('--display-bg', sanitizeHex(value));
break;
case 'displayColorScoringBgSoft':
document.documentElement.style.setProperty('--display-bg-soft', sanitizeHex(value));
break;
case 'displayColorScoringShadowColor':
document.documentElement.style.setProperty('--display-shadow', sanitizeHex(value));
break;
case 'displayColorScoringBorderColor':
document.documentElement.style.setProperty('--display-border', sanitizeHex(value));
break;
case 'displayColorScoringPanel':
document.documentElement.style.setProperty('--display-panel', sanitizeHex(value));
break;
case 'displayColorScoringPanelSoft':
document.documentElement.style.setProperty('--display-panel-soft', sanitizeHex(value));
break;
case 'displayColorScoringPanelText':
document.documentElement.style.setProperty('--display-text-main', sanitizeHex(value));
break;
case 'displayColorScoringPanelTextSoft':
document.documentElement.style.setProperty('--display-text-muted', sanitizeHex(value));
break;
case 'displayColorScoringPanelTextNoteL': // Matching your PHP typo
document.documentElement.style.setProperty('--display-color-note-l', sanitizeHex(value));
break;
case 'displayColorScoringPanelTextNoteR': // Matching your PHP typo
document.documentElement.style.setProperty('--display-color-note-r', sanitizeHex(value));
break;
case 'logo-normal':
const jsImgLogo = document.getElementById('jsImgLogo');
if(jsImgLogo) jsImgLogo.src = '/intern/img/logo-normal.png?' + Date.now();
break;
}
}
// --- Text Resizing Engine ---
function isOverflown(parent, elem, heightscale, widthscale, paddingtext) {
return (
(elem.scrollWidth + paddingtext) > (parent.clientWidth / widthscale) ||
(elem.scrollHeight + paddingtext) > (parent.clientHeight / heightscale)
);
}
function fitTextElement(elem, { minSize = 10, maxSize = 1000, step = 1, unit = 'px' } = {}) {
if (!elem) return;
const parent = elem.parentElement;
if (!parent) return;
// FIXED: Declare variables properly so they don't leak into the global scope
let heightscale = 1;
let widthscale = 1;
let paddingtext = 60;
if (parent.classList.contains('row2text')) {
heightscale = 2;
paddingtext = 0;
}
if (parent.classList.contains('row3')) {
widthscale = 2;
}
let size = minSize;
elem.style.whiteSpace = 'nowrap';
elem.style.fontSize = size + unit;
while (size < maxSize && !isOverflown(parent, elem, heightscale, widthscale, paddingtext)) {
size += step;
elem.style.fontSize = size + unit;
}
elem.style.fontSize = (size - step) + unit;
}
function resizeCText() {
ctext.style.fontSize = '';
if (isOverflown(logobg, ctext, 1, 1, 0)) fitTextElement(ctext);
}
function fitTextAll() {
const paragraphs = document.querySelectorAll('.pagediv p');
paragraphs.forEach(p => fitTextElement(p));
resizeCText();
}
window.addEventListener('resize', fitTextAll);
// --- Initialize ---
startWebSocket();
async function morph() {
gsap.to("#cross", {
duration: 1,
attr: {
d: "M-15,-60 L0,-30 M30,-90 L0,-30"
},
stroke: "#0e0",
ease: "power2.inOut"
});
gsap.to(".opacity0gsap", {
duration: 1,
opacity: "0",
ease: "back.out(1.7)"
});
const pathStart = "M150 100 h200 m100 0 h200";
const pathEnd = "M150 100 h250 m0 0 h250";
gsap.to("#errorLine", {
duration: 1,
attr: { d: pathEnd },
ease: "power2.inOut"
});
gsap.to(".errortext", {
duration: 1,
textContent: "Verbindung Wiederhergestellt",
ease: "back.out(1.7)"
});
gsap.to(".errortextSmall", {
duration: 1,
textContent: "Stelle auf Wettkampfbetrieb um...",
ease: "back.out(1.7)"
});
setTimeout(() => {
if (morphOk) {
gsap.to('.errorws', {
opacity: 0,
duration: 0.5,
overwrite: "auto"
});
}
}, 3000);
setTimeout(() => {
if (morphOk) {
document.querySelector('.errorws').style.display = 'none';
}
}, 3800);
}
function restore() {
// 1. Reset the Cross/Check path and color
gsap.set("#cross", {
attr: { d: "M-30,-30 L30,30 M30,-30 L-30,30" },
stroke: "#f43f5e"
});
// 2. Reset opacity for elements with that class
gsap.set(".opacity0gsap", {
opacity: ".4"
});
// 3. Reset the Error Line path
gsap.set("#errorLine", {
attr: { d: "M150 100 h200 m100 0 h200" }
});
gsap.to(".errortext", {
duration: 1,
textContent: "Keine WebSocket Verbindung",
ease: "back.out(1.7)"
});
document.querySelector(".errortextSmall").innerHTML = 'Versuche Verbindung... (Versuch <span id="counterTries"></span> / <span id="counterMaxTries"></span>)';
counterTriesEl = document.getElementById('counterTries');
counterMaxTriesEl = document.getElementById('counterMaxTries');
if(counterTriesEl) counterTriesEl.innerHTML = filedConectCount;
if(counterMaxTriesEl) counterMaxTriesEl.innerHTML = fallbackConectCount;
}
</script>
</body>
</html>