Upload files to "/"
This commit is contained in:
+552
@@ -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)">0°</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>
|
||||
Reference in New Issue
Block a user