Upload files to "/"

This commit is contained in:
ben
2026-07-22 17:39:11 +02:00
parent 4325a35427
commit 33ca64bec5
+552
View File
@@ -0,0 +1,552 @@
<!DOCTYPE html>
<html lang="nl">
<head>
<meta charset="UTF-8">
<title>CAM-01 // Surveillance Feed</title>
<style>
/* ============================================================
CONFIG (makkelijk aanpasbaar)
============================================================ */
:root{
--rec-color:#8a2f28;
--hud-color:#c7cac7;
--hud-dim:#6b6f6b;
--bg-color:#050505;
--frame-color:#83887f;
--panel-bg: rgba(12,12,12,0.72);
}
* { box-sizing: border-box; }
html, body {
margin:0; padding:0; width:100%; height:100%;
background:var(--bg-color);
overflow:hidden;
font-family: "Courier New", ui-monospace, monospace;
}
#stage {
position:relative;
width:100vw; height:100vh;
background:#000;
display:flex;
align-items:center;
justify-content:center;
}
/* ============================================================
LETTERBOX (bioscoop-achtige balken boven/onder)
============================================================ */
.letterbox {
position:absolute;
left:0; width:100%;
background:#000;
z-index:15;
pointer-events:none;
}
#letterbox-top { top:0; height:3.6vh; }
#letterbox-bottom { bottom:0; height:3.6vh; }
/* ============================================================
VIDEO / MJPEG STREAM
============================================================ */
#video-wrapper {
position:absolute;
top:0; left:0; width:100%; height:100%;
overflow:hidden;
display:flex;
align-items:center;
justify-content:center;
background:#000;
}
#camera-stream {
/* ROTATIE: --rotate wordt via JS gezet (0/90/180/270) */
width:100%;
height:100%;
object-fit:cover;
transform: rotate(var(--rotate, 0deg));
filter: grayscale(0.55) contrast(1.12) brightness(0.82) saturate(0.75);
transition: transform 0.3s ease;
background:#000;
}
#camera-stream.no-signal { display:none; }
#no-signal-msg {
display:none;
color:var(--hud-dim);
font-size:1.1em;
letter-spacing:3px;
text-align:center;
}
#no-signal-msg.active { display:block; }
/* ============================================================
OVERLAY EFFECTEN
============================================================ */
#effects-layer {
position:absolute;
top:0; left:0; width:100%; height:100%;
pointer-events:none;
}
#scanlines {
position:absolute;
top:0; left:0; width:100%; height:100%;
background: repeating-linear-gradient(
to bottom,
rgba(255,255,255,0.03) 0px,
rgba(255,255,255,0.03) 1px,
rgba(0,0,0,0.045) 2px,
rgba(0,0,0,0.045) 3px
);
mix-blend-mode:overlay;
pointer-events:none;
z-index:5;
}
/* Zwaardere, filmischere vignette */
#vignette {
position:absolute;
top:0; left:0; width:100%; height:100%;
background: radial-gradient(ellipse at center, rgba(0,0,0,0) 42%, rgba(0,0,0,0.75) 100%);
pointer-events:none;
z-index:6;
}
/* Grijze filmkorrel-tint over het geheel */
#grade-overlay {
position:absolute;
top:0; left:0; width:100%; height:100%;
background: linear-gradient(180deg, rgba(120,125,120,0.05), rgba(0,0,0,0.12));
mix-blend-mode:multiply;
pointer-events:none;
z-index:3;
}
#noise-canvas {
position:absolute;
top:0; left:0; width:100%; height:100%;
opacity:0.06;
pointer-events:none;
z-index:4;
}
#glitch-flash {
position:absolute;
top:0; left:0; width:100%; height:100%;
background:rgba(255,255,255,0.035);
opacity:0;
pointer-events:none;
z-index:7;
}
#glitch-flash.flash { opacity:1; transition:none; }
#glitch-flash.fade { opacity:0; transition:opacity 0.15s linear; }
/* ============================================================
HUD
============================================================ */
#hud {
position:absolute;
top:0; left:0; width:100%; height:100%;
z-index:10;
pointer-events:none;
color:var(--hud-color);
text-shadow: 0 0 3px rgba(0,0,0,0.95);
}
/* Hoekmarkeringen — puur decoratief, ruim buiten de tekstzones gehouden */
.corner {
position:absolute;
width:26px; height:26px;
opacity:0.55;
}
.corner span { position:absolute; background:var(--frame-color); }
.corner .h { width:100%; height:1px; }
.corner .v { width:1px; height:100%; }
.corner.tl { top:14px; left:14px; }
.corner.tl .h { top:0; left:0; }
.corner.tl .v { top:0; left:0; }
.corner.tr { top:14px; right:14px; }
.corner.tr .h { top:0; right:0; }
.corner.tr .v { top:0; right:0; }
.corner.bl { bottom:14px; left:14px; }
.corner.bl .h { bottom:0; left:0; }
.corner.bl .v { bottom:0; left:0; }
.corner.br { bottom:14px; right:14px; }
.corner.br .h { bottom:0; right:0; }
.corner.br .v { bottom:0; right:0; }
/* Center crosshair */
#crosshair {
position:absolute;
top:50%; left:50%;
width:20px; height:20px;
transform:translate(-50%,-50%);
opacity:0.45;
}
#crosshair::before, #crosshair::after {
content:"";
position:absolute;
background:var(--frame-color);
}
#crosshair::before { top:50%; left:0; width:100%; height:1px; transform:translateY(-50%); }
#crosshair::after { left:50%; top:0; height:100%; width:1px; transform:translateX(-50%); }
#crosshair .dot {
position:absolute; top:50%; left:50%;
width:2px; height:2px;
background:var(--frame-color);
transform:translate(-50%,-50%);
border-radius:50%;
}
/* ============================================================
HEADER — alle topinfo in één rustige rij, ruim uit de hoeken
============================================================ */
#top-bar {
position:absolute;
top:0; left:0; width:100%;
padding:22px 56px;
display:flex;
align-items:flex-start;
justify-content:space-between;
font-size:12px;
letter-spacing:1px;
background:linear-gradient(to bottom, rgba(0,0,0,0.6), rgba(0,0,0,0));
}
#rec-block {
display:flex;
flex-direction:column;
gap:4px;
}
#rec-indicator { display:flex; align-items:center; gap:7px; }
#rec-dot {
width:8px; height:8px;
border-radius:50%;
background:var(--rec-color);
box-shadow:0 0 5px var(--rec-color);
animation: blink 1s steps(1) infinite;
}
@keyframes blink {
0%, 49% { opacity:1; }
50%, 100% { opacity:0.12; }
}
#rec-label { color:var(--rec-color); font-weight:bold; letter-spacing:2px; }
#rec-timer { color:var(--hud-dim); font-size:11px; letter-spacing:2px; }
#cam-name {
font-size:13px;
letter-spacing:3px;
text-align:center;
align-self:center;
color:var(--hud-color);
opacity:0.9;
}
#top-status {
font-size:11px;
color:var(--hud-dim);
text-align:right;
line-height:1.6;
}
/* ============================================================
BOTTOM BAR
============================================================ */
#bottom-bar {
position:absolute;
bottom:0; left:0; width:100%;
padding:18px 56px;
display:flex;
align-items:flex-end;
justify-content:space-between;
font-size:11px;
letter-spacing:1px;
background:linear-gradient(to top, rgba(0,0,0,0.65), rgba(0,0,0,0));
}
.status-block { display:flex; flex-direction:column; gap:3px; line-height:1.4; }
.status-block .label { color:var(--hud-dim); font-size:9px; letter-spacing:2px; }
.status-block .value { color:var(--hud-color); }
.status-ok { color:#7c8f7c; }
.status-warn { color:#a8875a; }
#datetime-block { text-align:right; }
/* ============================================================
CONTROLE PANEEL — eigen rij, los van hoeken en van bottom-bar
============================================================ */
#controls {
position:absolute;
bottom:78px;
right:56px;
z-index:20;
display:flex;
gap:1px;
pointer-events:auto;
background:var(--panel-bg);
border:1px solid rgba(131,136,127,0.4);
}
#controls .ctrl-label {
display:flex;
align-items:center;
padding:0 8px;
font-size:9px;
letter-spacing:2px;
color:var(--hud-dim);
border-right:1px solid rgba(131,136,127,0.3);
}
#controls button {
background:transparent;
border:none;
border-right:1px solid rgba(131,136,127,0.25);
color:var(--hud-color);
font-family:inherit;
font-size:11px;
letter-spacing:1px;
padding:7px 11px;
cursor:pointer;
}
#controls button:last-child { border-right:none; }
#controls button:hover { background:rgba(255,255,255,0.06); }
#controls button.active { background:rgba(255,255,255,0.1); color:#fff; }
</style>
</head>
<body>
<div id="stage">
<div class="letterbox" id="letterbox-top"></div>
<div class="letterbox" id="letterbox-bottom"></div>
<!-- VIDEO -->
<div id="video-wrapper">
<img id="camera-stream"
src="http://192.168.1.50:8080/video"
alt="Camera stream"
onerror="handleStreamError()"
onload="handleStreamOk()">
<div id="no-signal-msg">// GEEN SIGNAAL //<br>STREAM ONBEREIKBAAR</div>
</div>
<!-- EFFECTEN -->
<div id="effects-layer">
<canvas id="noise-canvas"></canvas>
<div id="grade-overlay"></div>
<div id="scanlines"></div>
<div id="vignette"></div>
<div id="glitch-flash"></div>
</div>
<!-- HUD -->
<div id="hud">
<div class="corner tl"><span class="h"></span><span class="v"></span></div>
<div class="corner tr"><span class="h"></span><span class="v"></span></div>
<div class="corner bl"><span class="h"></span><span class="v"></span></div>
<div class="corner br"><span class="h"></span><span class="v"></span></div>
<div id="crosshair"><div class="dot"></div></div>
<div id="top-bar">
<div id="rec-block">
<div id="rec-indicator">
<div id="rec-dot"></div>
<span id="rec-label">REC</span>
</div>
<span id="rec-timer">00:00:00</span>
</div>
<div id="cam-name">CAM-01 // VOORINGANG</div>
<div id="top-status">
RES 1920×1080<br>
FPS <span id="fps-value">--</span>
</div>
</div>
<div id="bottom-bar">
<div class="status-block">
<span class="label">CAMERA STATUS</span>
<span class="value status-ok" id="camera-status">ONLINE</span>
</div>
<div class="status-block">
<span class="label">NETWERK</span>
<span class="value status-ok" id="network-status">VERBONDEN</span>
</div>
<div class="status-block">
<span class="label">MODUS</span>
<span class="value" id="camera-mode">AUTO / KLEUR</span>
</div>
<div class="status-block" id="datetime-block">
<span class="label">DATUM / TIJD</span>
<span class="value" id="datetime-value">--:--:--</span>
</div>
</div>
</div>
<!-- ROTATIE CONTROLS — eigen paneel, ruim boven de bottom-bar -->
<div id="controls">
<span class="ctrl-label">ROTATIE</span>
<button data-deg="0" onclick="setRotation(0, this)"></button>
<button data-deg="90" onclick="setRotation(90, this)">90°</button>
<button data-deg="180" onclick="setRotation(180, this)">180°</button>
<button data-deg="270" onclick="setRotation(270, this)">270°</button>
</div>
</div>
<script>
/* ============================================================
CONFIGURATIE
============================================================ */
const STREAM_URL = "http://192.168.1.50:8080/video"; // <-- pas hier de MJPEG-stream URL aan
const CAMERA_NAME = "CAM-01 // VOORINGANG"; // <-- pas hier de cameranaam aan
const DEFAULT_ROTATION = 0; // 0, 90, 180 of 270
document.getElementById("camera-stream").src = STREAM_URL;
document.getElementById("cam-name").textContent = CAMERA_NAME;
/* ============================================================
ROTATIE (puur CSS transform, stream blijft ongewijzigd)
============================================================ */
function setRotation(deg, btn) {
document.documentElement.style.setProperty("--rotate", deg + "deg");
document.querySelectorAll("#controls button").forEach(b => b.classList.remove("active"));
if (btn) btn.classList.add("active");
}
(function initRotation(){
const btn = document.querySelector(`#controls button[data-deg="${DEFAULT_ROTATION}"]`);
setRotation(DEFAULT_ROTATION, btn);
})();
/* ============================================================
STREAM STATUS HANDLING
============================================================ */
let streamOk = true;
function handleStreamError() {
streamOk = false;
document.getElementById("camera-stream").classList.add("no-signal");
document.getElementById("no-signal-msg").classList.add("active");
setStatus("camera-status", "OFFLINE", "status-warn");
setStatus("network-status", "GEEN VERBINDING", "status-warn");
}
function handleStreamOk() {
streamOk = true;
document.getElementById("camera-stream").classList.remove("no-signal");
document.getElementById("no-signal-msg").classList.remove("active");
setStatus("camera-status", "ONLINE", "status-ok");
setStatus("network-status", "VERBONDEN", "status-ok");
}
function setStatus(id, text, cls) {
const el = document.getElementById(id);
el.textContent = text;
el.classList.remove("status-ok", "status-warn");
el.classList.add(cls);
}
/* ============================================================
KLOK
============================================================ */
function updateClock() {
const now = new Date();
const pad = n => String(n).padStart(2, "0");
const dateStr = `${pad(now.getDate())}-${pad(now.getMonth()+1)}-${now.getFullYear()}`;
const timeStr = `${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`;
document.getElementById("datetime-value").textContent = `${dateStr} ${timeStr}`;
}
updateClock();
setInterval(updateClock, 1000);
/* ============================================================
REC-TIMER (loopt vanaf het laden van de pagina)
============================================================ */
const recStart = performance.now();
function updateRecTimer() {
const elapsed = Math.floor((performance.now() - recStart) / 1000);
const h = String(Math.floor(elapsed / 3600)).padStart(2, "0");
const m = String(Math.floor((elapsed % 3600) / 60)).padStart(2, "0");
const s = String(elapsed % 60).padStart(2, "0");
document.getElementById("rec-timer").textContent = `${h}:${m}:${s}`;
}
setInterval(updateRecTimer, 1000);
/* ============================================================
FPS INDICATOR (indicatief, gebaseerd op MJPEG frame-updates)
============================================================ */
let frameCount = 0;
let lastFpsCheck = performance.now();
document.getElementById("camera-stream").addEventListener("load", () => {
frameCount++;
});
setInterval(() => {
const now = performance.now();
const elapsed = (now - lastFpsCheck) / 1000;
const fps = streamOk ? Math.round(frameCount / elapsed) : 0;
document.getElementById("fps-value").textContent = streamOk ? fps : "--";
frameCount = 0;
lastFpsCheck = now;
}, 2000);
/* ============================================================
NOISE EFFECT (lichtgewicht, lage update-frequentie i.v.m. Raspberry Pi)
============================================================ */
const noiseCanvas = document.getElementById("noise-canvas");
const noiseCtx = noiseCanvas.getContext("2d");
function resizeNoiseCanvas() {
noiseCanvas.width = 160;
noiseCanvas.height = 90;
}
resizeNoiseCanvas();
window.addEventListener("resize", resizeNoiseCanvas);
function renderNoise() {
const w = noiseCanvas.width, h = noiseCanvas.height;
const imgData = noiseCtx.createImageData(w, h);
const buffer = imgData.data;
for (let i = 0; i < buffer.length; i += 4) {
const shade = Math.random() * 255;
buffer[i] = shade;
buffer[i+1] = shade;
buffer[i+2] = shade;
buffer[i+3] = 40;
}
noiseCtx.putImageData(imgData, 0, 0);
}
setInterval(renderNoise, 150);
/* ============================================================
OCCASIONELE SUBTIELE GLITCH FLASH
============================================================ */
function triggerGlitch() {
const flash = document.getElementById("glitch-flash");
flash.classList.remove("fade");
flash.classList.add("flash");
setTimeout(() => {
flash.classList.remove("flash");
flash.classList.add("fade");
}, 60);
}
function scheduleGlitch() {
const delay = 5000 + Math.random() * 9000;
setTimeout(() => {
triggerGlitch();
scheduleGlitch();
}, delay);
}
scheduleGlitch();
</script>
</body>
</html>