Add tournament archives and results for multiple events
- Created JSON files for tournament archives on 2025-07-28 and 2025-07-29, including detailed player scores and shot results. - Added a new tournament results file summarizing the outcomes of the tournament held on 2025-07-29, including participant scores and completion status.
This commit is contained in:
@@ -0,0 +1,766 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>{{ title }} - Fullscreen</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<style>
|
||||
/* Reset and base styles */
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html, body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: black;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
|
||||
/* Navigation bar */
|
||||
.fullscreen-navbar {
|
||||
background: white;
|
||||
color: black;
|
||||
padding: 15px 25px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border-bottom: 2px solid #ccc;
|
||||
height: 70px;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.navbar-title {
|
||||
font-size: 1.8rem;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
text-align: left;
|
||||
flex: 1;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.navbar-controls {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
/* Control buttons */
|
||||
.control-btn {
|
||||
background: #f8f9fa !important;
|
||||
border: 2px solid #e9ecef !important;
|
||||
cursor: pointer;
|
||||
padding: 12px;
|
||||
border-radius: 8px;
|
||||
transition: all 0.2s ease;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
color: #333 !important;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
font-size: 1.2rem;
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.control-btn:hover {
|
||||
background: #e9ecef !important;
|
||||
border-color: #007bff !important;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15) !important;
|
||||
transform: translateY(-1px) !important;
|
||||
color: #007bff !important;
|
||||
}
|
||||
|
||||
.control-btn:active {
|
||||
transform: translateY(0) !important;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1) !important;
|
||||
}
|
||||
|
||||
.control-btn:disabled {
|
||||
opacity: 0.5 !important;
|
||||
cursor: not-allowed !important;
|
||||
transform: none !important;
|
||||
background: #f8f9fa !important;
|
||||
border-color: #e9ecef !important;
|
||||
color: #333 !important;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1) !important;
|
||||
}
|
||||
|
||||
.control-btn.close-btn:hover {
|
||||
border-color: #dc3545 !important;
|
||||
color: #dc3545 !important;
|
||||
}
|
||||
|
||||
.control-btn svg {
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
/* Stream container */
|
||||
.stream-container {
|
||||
height: calc(100vh - 70px);
|
||||
width: 100%;
|
||||
background: #111;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.stream-viewport {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
cursor: grab;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.stream-viewport.dragging {
|
||||
cursor: grabbing !important;
|
||||
}
|
||||
|
||||
.stream-viewport.zoomed {
|
||||
cursor: grab !important;
|
||||
}
|
||||
|
||||
.fullscreen-stream {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
width: auto;
|
||||
height: auto;
|
||||
object-fit: contain;
|
||||
display: block;
|
||||
transition: transform 0.2s ease;
|
||||
transform-origin: center center;
|
||||
}
|
||||
|
||||
.fullscreen-stream.instant-transition {
|
||||
transition: none;
|
||||
}
|
||||
|
||||
/* Zoom controls */
|
||||
.zoom-controls {
|
||||
position: absolute;
|
||||
bottom: 20px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
background: rgba(248, 249, 250, 0.95);
|
||||
backdrop-filter: blur(10px);
|
||||
border-radius: 12px;
|
||||
padding: 10px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.zoom-btn {
|
||||
background: #f8f9fa !important;
|
||||
border: 2px solid #e9ecef !important;
|
||||
cursor: pointer;
|
||||
padding: 10px;
|
||||
border-radius: 8px;
|
||||
transition: all 0.2s ease;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
color: #333 !important;
|
||||
font-weight: bold;
|
||||
font-size: 1rem;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.zoom-btn:hover {
|
||||
background: #e9ecef !important;
|
||||
border-color: #007bff !important;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15) !important;
|
||||
transform: translateY(-1px) !important;
|
||||
color: #007bff !important;
|
||||
}
|
||||
|
||||
.zoom-btn:active {
|
||||
transform: translateY(0) !important;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1) !important;
|
||||
}
|
||||
|
||||
.zoom-btn:disabled {
|
||||
opacity: 0.5 !important;
|
||||
cursor: not-allowed !important;
|
||||
transform: none !important;
|
||||
background: #f8f9fa !important;
|
||||
border-color: #e9ecef !important;
|
||||
color: #333 !important;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1) !important;
|
||||
}
|
||||
|
||||
.zoom-level {
|
||||
padding: 10px 12px;
|
||||
background: #f8f9fa;
|
||||
border: 2px solid #e9ecef;
|
||||
border-radius: 8px;
|
||||
font-size: 0.9rem;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
min-width: 50px;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* Loading indicator */
|
||||
.loading {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
color: white;
|
||||
font-size: 1.1rem;
|
||||
opacity: 0.7;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.stream-loaded .loading {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Mobile optimizations */
|
||||
@media (max-width: 768px) {
|
||||
.fullscreen-navbar {
|
||||
padding: 10px 15px;
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
.navbar-title {
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
|
||||
.navbar-controls {
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.control-btn {
|
||||
padding: 10px;
|
||||
width: 45px;
|
||||
height: 45px;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.stream-container {
|
||||
height: calc(100vh - 60px);
|
||||
}
|
||||
|
||||
.zoom-controls {
|
||||
bottom: 15px;
|
||||
padding: 8px;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.zoom-btn {
|
||||
padding: 8px;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.zoom-level {
|
||||
padding: 8px 10px;
|
||||
min-width: 45px;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* Very small mobile devices */
|
||||
@media (max-width: 480px) {
|
||||
.fullscreen-navbar {
|
||||
padding: 8px 12px;
|
||||
height: 55px;
|
||||
}
|
||||
|
||||
.navbar-title {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.navbar-controls {
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.control-btn {
|
||||
padding: 8px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.stream-container {
|
||||
height: calc(100vh - 55px);
|
||||
}
|
||||
|
||||
.zoom-controls {
|
||||
bottom: 12px;
|
||||
gap: 6px;
|
||||
padding: 6px;
|
||||
}
|
||||
|
||||
.zoom-btn {
|
||||
padding: 6px;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.zoom-level {
|
||||
padding: 6px 8px;
|
||||
min-width: 40px;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* Landscape mobile optimization */
|
||||
@media (max-height: 500px) and (orientation: landscape) {
|
||||
.fullscreen-navbar {
|
||||
padding: 8px 15px;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.navbar-title {
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
|
||||
.navbar-controls {
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.control-btn {
|
||||
padding: 6px;
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.stream-container {
|
||||
height: calc(100vh - 50px);
|
||||
}
|
||||
|
||||
.zoom-controls {
|
||||
bottom: 10px;
|
||||
gap: 4px;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.zoom-btn {
|
||||
padding: 4px;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.zoom-level {
|
||||
padding: 4px 6px;
|
||||
min-width: 35px;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="fullscreen-navbar">
|
||||
<div class="navbar-title">{{ title }}</div>
|
||||
<div class="navbar-controls">
|
||||
<button class="control-btn" id="fullscreenToggleBtn" title="Toggle Browser Fullscreen (F)">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<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>
|
||||
</button>
|
||||
<a href="/" class="control-btn close-btn" title="Close Fullscreen">✕</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stream-container" id="streamContainer">
|
||||
<div class="loading" id="loadingIndicator">Loading stream...</div>
|
||||
<div class="stream-viewport" id="streamViewport">
|
||||
<img
|
||||
src="{{ stream.url }}"
|
||||
alt="{{ title }}"
|
||||
class="fullscreen-stream"
|
||||
id="fullscreenStream"
|
||||
onerror="handleStreamError()"
|
||||
onload="handleStreamLoad()"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="zoom-controls" id="zoomControls">
|
||||
<button class="zoom-btn" id="zoomOutBtnBottom" title="Zoom Out (-)">−</button>
|
||||
<div class="zoom-level" id="zoomLevel">1.0x</div>
|
||||
<button class="zoom-btn" id="zoomInBtnBottom" title="Zoom In (+)">+</button>
|
||||
<button class="zoom-btn" id="resetZoomBtn" title="Reset Zoom (R)">⌂</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let retryCount = 0;
|
||||
const maxRetries = 3;
|
||||
|
||||
// Zoom functionality
|
||||
let currentZoom = 1;
|
||||
const minZoom = 1;
|
||||
const maxZoom = 5;
|
||||
const zoomStep = 0.1;
|
||||
|
||||
// Pan functionality
|
||||
let isPanning = false;
|
||||
let panStartX = 0;
|
||||
let panStartY = 0;
|
||||
let panOffsetX = 0;
|
||||
let panOffsetY = 0;
|
||||
let lastTouchDistance = 0;
|
||||
|
||||
function handleStreamLoad() {
|
||||
document.getElementById('streamContainer').classList.add('stream-loaded');
|
||||
retryCount = 0;
|
||||
resetZoom();
|
||||
}
|
||||
|
||||
function handleStreamError() {
|
||||
const streamImg = document.getElementById('fullscreenStream');
|
||||
const loadingIndicator = document.getElementById('loadingIndicator');
|
||||
|
||||
if (retryCount < maxRetries) {
|
||||
retryCount++;
|
||||
loadingIndicator.textContent = `Retrying... (${retryCount}/${maxRetries})`;
|
||||
|
||||
setTimeout(() => {
|
||||
streamImg.src = streamImg.src + '?retry=' + Date.now();
|
||||
}, 2000);
|
||||
} else {
|
||||
loadingIndicator.textContent = 'Stream unavailable';
|
||||
streamImg.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// Zoom functions
|
||||
function updateZoom(smoothTransition = true) {
|
||||
const streamImg = document.getElementById('fullscreenStream');
|
||||
const zoomLevel = document.getElementById('zoomLevel');
|
||||
const zoomInBtnBottom = document.getElementById('zoomInBtnBottom');
|
||||
const zoomOutBtnBottom = document.getElementById('zoomOutBtnBottom');
|
||||
const viewport = document.getElementById('streamViewport');
|
||||
|
||||
if (smoothTransition) {
|
||||
streamImg.classList.remove('instant-transition');
|
||||
} else {
|
||||
streamImg.classList.add('instant-transition');
|
||||
}
|
||||
|
||||
if (currentZoom <= 1) {
|
||||
panOffsetX = 0;
|
||||
panOffsetY = 0;
|
||||
currentZoom = 1;
|
||||
streamImg.style.transform = '';
|
||||
} else {
|
||||
streamImg.style.transform = `scale(${currentZoom}) translate(${panOffsetX}px, ${panOffsetY}px)`;
|
||||
}
|
||||
|
||||
zoomLevel.textContent = `${currentZoom.toFixed(1)}x`;
|
||||
|
||||
const atMinZoom = currentZoom <= minZoom;
|
||||
const atMaxZoom = currentZoom >= maxZoom;
|
||||
|
||||
if (zoomOutBtnBottom) zoomOutBtnBottom.disabled = atMinZoom;
|
||||
if (zoomInBtnBottom) zoomInBtnBottom.disabled = atMaxZoom;
|
||||
|
||||
if (currentZoom > 1) {
|
||||
viewport.classList.add('zoomed');
|
||||
} else {
|
||||
viewport.classList.remove('zoomed');
|
||||
panOffsetX = 0;
|
||||
panOffsetY = 0;
|
||||
}
|
||||
}
|
||||
|
||||
function zoomIn() {
|
||||
if (currentZoom < maxZoom) {
|
||||
currentZoom = Math.min(maxZoom, currentZoom + zoomStep);
|
||||
updateZoom(false);
|
||||
}
|
||||
}
|
||||
|
||||
function zoomOut() {
|
||||
if (currentZoom > minZoom) {
|
||||
const newZoom = Math.max(minZoom, currentZoom - zoomStep);
|
||||
|
||||
if (newZoom <= minZoom) {
|
||||
currentZoom = minZoom;
|
||||
panOffsetX = 0;
|
||||
panOffsetY = 0;
|
||||
const streamImg = document.getElementById('fullscreenStream');
|
||||
streamImg.style.transform = '';
|
||||
updateZoom(true);
|
||||
} else {
|
||||
currentZoom = newZoom;
|
||||
const zoomRatio = currentZoom / (currentZoom + zoomStep);
|
||||
panOffsetX *= zoomRatio;
|
||||
panOffsetY *= zoomRatio;
|
||||
updateZoom(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function resetZoom() {
|
||||
currentZoom = 1;
|
||||
panOffsetX = 0;
|
||||
panOffsetY = 0;
|
||||
const streamImg = document.getElementById('fullscreenStream');
|
||||
streamImg.style.transform = '';
|
||||
updateZoom(true);
|
||||
}
|
||||
|
||||
// Pan functions
|
||||
function startPan(clientX, clientY) {
|
||||
if (currentZoom > 1) {
|
||||
isPanning = true;
|
||||
panStartX = clientX - panOffsetX;
|
||||
panStartY = clientY - panOffsetY;
|
||||
document.getElementById('streamViewport').classList.add('dragging');
|
||||
}
|
||||
}
|
||||
|
||||
function updatePan(clientX, clientY) {
|
||||
if (isPanning && currentZoom > 1) {
|
||||
const viewport = document.getElementById('streamViewport');
|
||||
const streamImg = document.getElementById('fullscreenStream');
|
||||
|
||||
const newOffsetX = clientX - panStartX;
|
||||
const newOffsetY = clientY - panStartY;
|
||||
|
||||
const imgRect = streamImg.getBoundingClientRect();
|
||||
const viewportRect = viewport.getBoundingClientRect();
|
||||
|
||||
const scaledWidth = imgRect.width / currentZoom;
|
||||
const scaledHeight = imgRect.height / currentZoom;
|
||||
const maxOffsetX = Math.max(0, (scaledWidth * currentZoom - viewportRect.width) / (2 * currentZoom));
|
||||
const maxOffsetY = Math.max(0, (scaledHeight * currentZoom - viewportRect.height) / (2 * currentZoom));
|
||||
|
||||
panOffsetX = Math.max(-maxOffsetX, Math.min(maxOffsetX, newOffsetX));
|
||||
panOffsetY = Math.max(-maxOffsetY, Math.min(maxOffsetY, newOffsetY));
|
||||
|
||||
updateZoom(false);
|
||||
}
|
||||
}
|
||||
|
||||
function endPan() {
|
||||
isPanning = false;
|
||||
document.getElementById('streamViewport').classList.remove('dragging');
|
||||
|
||||
if (currentZoom <= 1) {
|
||||
panOffsetX = 0;
|
||||
panOffsetY = 0;
|
||||
updateZoom();
|
||||
}
|
||||
}
|
||||
|
||||
// Event listeners
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const zoomInBtnBottom = document.getElementById('zoomInBtnBottom');
|
||||
const zoomOutBtnBottom = document.getElementById('zoomOutBtnBottom');
|
||||
const resetZoomBtn = document.getElementById('resetZoomBtn');
|
||||
const fullscreenToggleBtn = document.getElementById('fullscreenToggleBtn');
|
||||
const viewport = document.getElementById('streamViewport');
|
||||
|
||||
if (zoomInBtnBottom) zoomInBtnBottom.addEventListener('click', zoomIn);
|
||||
if (zoomOutBtnBottom) zoomOutBtnBottom.addEventListener('click', zoomOut);
|
||||
if (resetZoomBtn) resetZoomBtn.addEventListener('click', resetZoom);
|
||||
|
||||
if (fullscreenToggleBtn) {
|
||||
fullscreenToggleBtn.addEventListener('click', toggleBrowserFullscreen);
|
||||
}
|
||||
|
||||
// Mouse events for panning
|
||||
viewport.addEventListener('mousedown', function(e) {
|
||||
e.preventDefault();
|
||||
startPan(e.clientX, e.clientY);
|
||||
});
|
||||
|
||||
document.addEventListener('mousemove', function(e) {
|
||||
if (isPanning) {
|
||||
e.preventDefault();
|
||||
updatePan(e.clientX, e.clientY);
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('mouseup', endPan);
|
||||
|
||||
// Mouse wheel zoom
|
||||
viewport.addEventListener('wheel', function(e) {
|
||||
e.preventDefault();
|
||||
if (e.deltaY < 0) {
|
||||
zoomIn();
|
||||
} else {
|
||||
zoomOut();
|
||||
}
|
||||
});
|
||||
|
||||
// Touch events for mobile
|
||||
viewport.addEventListener('touchstart', function(e) {
|
||||
e.preventDefault();
|
||||
if (e.touches.length === 1) {
|
||||
startPan(e.touches[0].clientX, e.touches[0].clientY);
|
||||
} else if (e.touches.length === 2) {
|
||||
const touch1 = e.touches[0];
|
||||
const touch2 = e.touches[1];
|
||||
lastTouchDistance = Math.sqrt(
|
||||
Math.pow(touch2.clientX - touch1.clientX, 2) +
|
||||
Math.pow(touch2.clientY - touch1.clientY, 2)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
viewport.addEventListener('touchmove', function(e) {
|
||||
e.preventDefault();
|
||||
if (e.touches.length === 1 && isPanning) {
|
||||
updatePan(e.touches[0].clientX, e.touches[0].clientY);
|
||||
} else if (e.touches.length === 2) {
|
||||
const touch1 = e.touches[0];
|
||||
const touch2 = e.touches[1];
|
||||
const currentDistance = Math.sqrt(
|
||||
Math.pow(touch2.clientX - touch1.clientX, 2) +
|
||||
Math.pow(touch2.clientY - touch1.clientY, 2)
|
||||
);
|
||||
|
||||
if (lastTouchDistance > 0) {
|
||||
const distanceRatio = currentDistance / lastTouchDistance;
|
||||
if (distanceRatio > 1.02) {
|
||||
zoomIn();
|
||||
} else if (distanceRatio < 0.98) {
|
||||
zoomOut();
|
||||
}
|
||||
}
|
||||
lastTouchDistance = currentDistance;
|
||||
}
|
||||
});
|
||||
|
||||
viewport.addEventListener('touchend', function(e) {
|
||||
endPan();
|
||||
if (e.touches.length < 2) {
|
||||
lastTouchDistance = 0;
|
||||
}
|
||||
});
|
||||
|
||||
updateZoom(false);
|
||||
});
|
||||
|
||||
// Handle keyboard shortcuts
|
||||
document.addEventListener('keydown', function(event) {
|
||||
switch(event.key) {
|
||||
case 'Escape':
|
||||
window.location.href = '/';
|
||||
break;
|
||||
case 'f':
|
||||
case 'F':
|
||||
toggleBrowserFullscreen();
|
||||
break;
|
||||
case 'r':
|
||||
case 'R':
|
||||
event.preventDefault();
|
||||
resetZoom();
|
||||
break;
|
||||
case '=':
|
||||
case '+':
|
||||
event.preventDefault();
|
||||
zoomIn();
|
||||
break;
|
||||
case '-':
|
||||
event.preventDefault();
|
||||
zoomOut();
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
function toggleBrowserFullscreen() {
|
||||
if (!document.fullscreenElement && !document.webkitFullscreenElement &&
|
||||
!document.mozFullScreenElement && !document.msFullscreenElement) {
|
||||
const docEl = document.documentElement;
|
||||
if (docEl.requestFullscreen) {
|
||||
docEl.requestFullscreen().catch(err => console.log('Fullscreen error:', err));
|
||||
} else if (docEl.webkitRequestFullscreen) {
|
||||
docEl.webkitRequestFullscreen();
|
||||
} else if (docEl.mozRequestFullScreen) {
|
||||
docEl.mozRequestFullScreen();
|
||||
} else if (docEl.msRequestFullscreen) {
|
||||
docEl.msRequestFullscreen();
|
||||
}
|
||||
} else {
|
||||
if (document.exitFullscreen) {
|
||||
document.exitFullscreen().catch(err => console.log('Exit fullscreen error:', err));
|
||||
} else if (document.webkitExitFullscreen) {
|
||||
document.webkitExitFullscreen();
|
||||
} else if (document.mozCancelFullScreen) {
|
||||
document.mozCancelFullScreen();
|
||||
} else if (document.msExitFullscreen) {
|
||||
document.msExitFullscreen();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handleFullscreenChange() {
|
||||
const isFullscreen = document.fullscreenElement || document.webkitFullscreenElement ||
|
||||
document.mozFullScreenElement || document.msFullscreenElement;
|
||||
const fullscreenBtn = document.getElementById('fullscreenToggleBtn');
|
||||
|
||||
if (fullscreenBtn) {
|
||||
if (isFullscreen) {
|
||||
fullscreenBtn.innerHTML = `
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M8 3v3a2 2 0 0 1-2 2H3m18 0h-3a2 2 0 0 1-2-2V3m0 18v-3a2 2 0 0 1 2-2h3M3 16h3a2 2 0 0 1 2 2v3"/>
|
||||
</svg>
|
||||
`;
|
||||
fullscreenBtn.title = "Exit Browser Fullscreen (F)";
|
||||
} else {
|
||||
fullscreenBtn.innerHTML = `
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<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>
|
||||
`;
|
||||
fullscreenBtn.title = "Toggle Browser Fullscreen (F)";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Listen for fullscreen changes
|
||||
document.addEventListener('fullscreenchange', handleFullscreenChange);
|
||||
document.addEventListener('webkitfullscreenchange', handleFullscreenChange);
|
||||
document.addEventListener('mozfullscreenchange', handleFullscreenChange);
|
||||
document.addEventListener('MSFullscreenChange', handleFullscreenChange);
|
||||
|
||||
// Auto-refresh stream every 30 seconds
|
||||
setInterval(() => {
|
||||
const streamImg = document.getElementById('fullscreenStream');
|
||||
if (streamImg && !streamImg.complete) {
|
||||
streamImg.src = streamImg.src.split('?')[0] + '?refresh=' + Date.now();
|
||||
}
|
||||
}, 30000);
|
||||
|
||||
// Prevent context menu and dragging on stream
|
||||
document.getElementById('fullscreenStream').addEventListener('contextmenu', function(e) {
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
document.getElementById('fullscreenStream').addEventListener('dragstart', function(e) {
|
||||
e.preventDefault();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user