Evocam Webcam Html Page
LAN/enterprise low-latency
Public-facing streaming / many viewers
Choose MJPEG for simplicity, HLS for wide support and streaming to many clients, and WebRTC for real-time interaction. Match the method to your Evocam model’s capabilities and your security requirements.
Would you like a ready-to-paste example with a specific Evocam stream URL or a WebRTC sample setup?
Searching for the string "evocam webcam html" is a common technique used by security researchers and privacy enthusiasts to identify potentially exposed webcams.
This specific URL pattern is often associated with older or misconfigured webcam software (like EvoCam) that serves a web interface via a file named webcam.html evocam webcam html
. If these devices are connected to the internet without proper security, they can be indexed by search engines. 🛡️ How to Secure Your Webcam
If you use webcam software to stream or monitor a space, follow these steps to ensure your feed isn't publicly accessible: Set a Strong Password
: Never leave your camera or software on "admin/admin" or no password at all. Check the Chrome Site Settings
to manage which sites have permission to access your hardware. Disable UPnP
: Many cameras use Universal Plug and Play to automatically open ports on your router. Disabling this prevents the camera from "poking a hole" through your firewall. Use Modern Streaming Methods LAN/enterprise low-latency
: Instead of legacy HTML pages, use modern protocols like WebRTC or secure third-party services that require authentication. You can access a webcam in HTML5 safely using the getUserMedia
API, which requires explicit user permission and a secure (HTTPS) connection. Keep Software Updated
: Ensure your camera's firmware and any hosting software are up to date to patch known vulnerabilities. 🔍 Protecting Your Privacy in Public
If you are concerned about privacy in places like hotels or rentals: Scan the Network
: Use apps to see what devices are connected to the local Wi-Fi. Visual Inspection : Look for unusual gadgets or small lenses. Search Queries Public-facing streaming / many viewers
: Privacy advocates sometimes use "dorks" (specific search strings like the one you mentioned) to find and report exposed cameras to the owners. Are you looking to secure your own camera or are you trying to embed a live stream into a website?
Use your camera and microphone in Chrome - Computer - Google Help
Building an EvoCam-style webcam interface — that clean, surveillance aesthetic with a modern twist. Here's something that feels like a premium security camera dashboard:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>EvoCam | Live Monitor</title>
<script src="https://cdn.tailwindcss.com"></script>
<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=JetBrains+Mono:wght@300;400;500;700&family=Space+Grotesk:wght@400;500;700&display=swap" rel="stylesheet">
<style>
:root
--bg: #0a0d10;
--bg-elevated: #12171d;
--card: #161c24;
--border: #2a3544;
--fg: #e8edf4;
--muted: #6b7a8f;
--accent: #00e5a0;
--accent-dim: rgba(0, 229, 160, 0.15);
--danger: #ff4757;
--warning: #ffa502;
*
box-sizing: border-box;
body
font-family: 'Space Grotesk', sans-serif;
background: var(--bg);
color: var(--fg);
margin: 0;
min-height: 100vh;
overflow-x: hidden;
.mono
font-family: 'JetBrains Mono', monospace;
/* Background pattern */
.bg-grid
background-image:
linear-gradient(rgba(42, 53, 68, 0.3) 1px, transparent 1px),
linear-gradient(90deg, rgba(42, 53, 68, 0.3) 1px, transparent 1px);
background-size: 40px 40px;
/* Scanline effect */
.scanlines::after
content: '';
position: absolute;
inset: 0;
background: repeating-linear-gradient(
0deg,
transparent,
transparent 2px,
rgba(0, 0, 0, 0.15) 2px,
rgba(0, 0, 0, 0.15) 4px
);
pointer-events: none;
opacity: 0.4;
/* Noise overlay */
.noise::before
content: '';
position: absolute;
inset: 0;
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)'/%3E%3C/svg%3E");
opacity: 0.03;
pointer-events: none;
/* Pulse animation for REC */
@keyframes pulse-rec
0%, 100% opacity: 1;
50% opacity: 0.4;
.rec-pulse
animation: pulse-rec 1.5s ease-in-out infinite;
/* Status indicator pulse */
@keyframes status-pulse
0%, 100% transform: scale(1); opacity: 1;
50% transform: scale(1.8); opacity: 0;
.status-ring::after
content: '';
position: absolute;
inset: -4px;
border-radius: 50%;
border: 2px solid var(--accent);
animation: status-pulse 2s ease-out infinite;
/* Entrance animations */
@keyframes slide-up
from opacity: 0; transform: translateY(30px);
to opacity: 1; transform: translateY(0);
@keyframes fade-in
from opacity: 0;
to opacity: 1;
@keyframes scale-in
from opacity: 0; transform: scale(0.95);
to opacity: 1; transform: scale(1);
.animate-slide-up
animation: slide-up 0.6s cubic-bezier(0.16, 1, 0.3, 1) forwards;
opacity: 0;
.animate-fade-in
animation: fade-in 0.8s ease forwards;
opacity: 0;
.animate-scale-in
animation: scale-in 0.5s cubic-bezier(0.16, 1, 0.3, 1) forwards;
opacity: 0;
.delay-1 animation-delay: 0.1s;
.delay-2 animation-delay: 0.2s;
.delay-3 animation-delay: 0.3s;
.delay-4 animation-delay: 0.4s;
.delay-5 animation-delay: 0.5s;
/* Video frame */
.video-frame
position: relative;
background: #000;
border-radius: 4px;
overflow: hidden;
.video-frame::before
content: '';
position: absolute;
inset: 0;
border: 1px solid var(--border);
border-radius: 4px;
pointer-events: none;
z-index: 10;
/* Corner brackets */
.corner-bracket
position: absolute;
width: 20px;
height: 20px;
border-color: var(--accent);
border-style: solid;
border-width: 0;
z-index: 20;
.corner-bracket.tl top: 8px; left: 8px; border-top-width: 2px; border-left-width: 2px;
.corner-bracket.tr top: 8px; right: 8px; border-top-width: 2px; border-right-width: 2px;
.corner-bracket.bl bottom: 8px; left: 8px; border-bottom-width: 2px; border-left-width: 2px;
.corner-bracket.br bottom: 8px; right: 8px; border-bottom-width: 2px; border-right-width: 2px;
/* Motion detection zone */
.motion-zone
position: absolute;
border: 2px dashed var(--warning);
background: rgba(255, 165, 2, 0.1);
opacity: 0;
transition: opacity 0.3s;
.motion-zone.active
opacity: 1;
/* Recording indicator */
@keyframes rec-blink
0%, 100% opacity: 1;
50% opacity: 0.3;
.rec-indicator
animation: rec-blink 1s ease-in-out infinite;
/* Timestamp scroll */
@keyframes timestamp-scroll
from transform: translateX(0);
to transform: translateX(-50%);
/* Timeline */
.timeline-track
height: 4px;
background: var(--border);
border-radius: 2px;
position: relative;
cursor: pointer;
.timeline-progress
position: absolute;
left: 0;
top: 0;
height: 100%;
background: var(--accent);
border-radius: 2px;
transition: width 0.1s linear;
.timeline-marker
position: absolute;
top: -4px;
width: 12px;
height: 12px;
background: var(--accent);
border-radius: 50%;
transform: translateX(-50%);
cursor: grab;
transition: transform 0.15s;
.timeline-marker:hover
transform: translateX(-50%) scale(1.3);
/* Buttons */
.btn
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
padding: 10px 16px;
border-radius: 6px;
font-weight: 500;
font-size: 14px;
border: none;
cursor: pointer;
transition: all 0.2s cubic-bezier(0.16, 1, 0.3, 1);
position: relative;
overflow: hidden;
.btn:focus-visible
outline: 2px solid var(--accent);
outline-offset: 2px;
.btn-primary
background: var(--accent);
color: var(--bg);
.btn-primary:hover
background: #00ffb3;
transform: translateY(-1px);
.btn-primary:active
transform: translateY(0);
.btn-secondary
background: var(--card);
color: var(--fg);
border: 1px solid var(--border);
.btn-secondary:hover
background: var(--bg-elevated);
border-color: var(--muted);
.btn-icon
width: 40px;
height: 40px;
padding: 0;
border-radius: 8px;
/* Cards */
.card
background: var(--card);
border: 1px solid var(--border);
border-radius: 8px;
padding: 16px;
/* Glow effect */
.glow
box-shadow: 0 0 40px -10px var(--accent);
/* Custom scrollbar */
::-webkit-scrollbar
width: 6px;
height: 6px;
::-webkit-scrollbar-track
background: var(--bg);
::-webkit-scrollbar-thumb
background: var(--border);
border-radius: 3px;
::-webkit-scrollbar-thumb:hover
background: var(--muted);
/* Reduced motion */
@media (prefers-reduced-motion: reduce)
*, *::before, *::after
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
</style>
</head>
<body class="bg-grid noise">
<!-- Header -->
<header class="fixed top-0 left-0 right-0 z-50 border-b border-[var(--border)] bg-[var(--bg)]/90 backdrop-blur-md animate-fade-in">
<div class="max-w-7xl mx-auto px-4 h-16 flex items-center justify-between">
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-lg bg-[var(--accent-dim)] flex items-center justify-center">
<svg class="w-5 h-5 text-[var(--accent)]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z"/>
</svg>
</div>
<div>
<h1 class="text-lg font-bold tracking-tight">EvoCam</h1>
<p class="text-xs text-[var(--muted)] mono">v2.4.1</p>
</div>
</div>
<div class="flex items-center gap-2">
<div class="hidden sm:flex items-center gap-2 px-3 py-1.5 rounded-full bg-[var(--accent-dim)] border border-[var(--accent)]/30">
<span class="relative w-2 h-2 rounded-full bg-[var(--accent)] status-ring"></span>
<span class="text-xs font-medium text-[var(--accent)] mono">LIVE</span>
</div>
<button id="settingsBtn" class="btn btn-secondary btn-icon" aria-label="Settings">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"/>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
</svg>
</button>
</div>
</div>
</header>
<!-- Main Content -->
<main class="pt-24 pb-8 px-4 max-w-7xl mx-auto">
<div class="grid lg:grid-cols-3 gap-6">
<!-- Video Panel -->
<div class="lg:col-span-2 space-y-4">
<!-- Main Video -->
<div class="animate-scale-in delay-1">
<div class="video-frame scanlines aspect-video relative">
<video id="webcam" autoplay playsinline muted class="w-full h-full object-cover"></video>
<!-- Corner brackets -->
<div class="corner-bracket tl"></div>
<div class="corner-bracket tr"></div>
<div class="corner-bracket bl"></div>
<div class="corner-bracket br"></div>
<!-- Motion zones -->
<div id="motionZone1" class="motion-zone" style="top: 20%; left: 10%; width: 30%; height: 40%;"></div>
<div id="motionZone2" class="motion-zone" style="top: 30%; right: 15%; width: 25%; height: 35%;"></div>
<!-- Overlay HUD -->
<div class="absolute top-4 left-4 right-4 flex justify-between items-start pointer-events-none z-30">
<div class="flex flex-col gap-2">
<div class="flex items-center gap-2 px-2 py-1 bg-black/60 backdrop-blur-sm rounded mono text-xs">
<span class="rec-indicator w-2 h-2 rounded-full bg-[var(--danger)]"></span>
<span class="text-[var(--danger)]">REC</span>
</div>
<div class="px-2 py-1 bg-black/60 backdrop-blur-sm rounded mono text-xs text-[var(--muted)]" id="timestamp">
2024-01-15 14:32:47
</div>
</div>
<div class="px-2 py-1 bg-black/60 backdrop-blur-sm rounded mono text-xs text-[var(--accent)]">
CAM-01
</div>
</div>
<!-- Camera info bar -->
<div class="absolute bottom-0 left-0 right-0 p-3 bg-gradient-to-t from-black/80 to-transparent z-30">
<div class="flex items-center justify-between">
<div class="flex items-center gap-4 mono text-xs text-[var(--muted)]">
<span>1080p</span>
<span>30fps</span>
<span id="bitrate">4.2 Mbps</span>
</div>
<div class="flex items-center gap-2 mono text-xs">
<span class="text-[var(--warning)]" id="motionStatus">MOTION: 0</span>
</div>
</div>
</div>
</div>
</div>
<!-- Timeline -->
<div class="card animate-slide-up delay-2">
<div class="flex items-center justify-between mb-3">
<span class="text-sm font-medium">Timeline</span>
<span class="mono text-xs text-[var(--muted)]" id="currentTime">00:00:00</span>
</div>
<div class="timeline-track" id="timeline">
<div class="timeline-progress" id="timelineProgress" style="width: 0%"></div>
<div class="timeline-marker" id="timelineMarker" style="left: 0%"></div>
</div>
<div class="flex justify-between mt-2 mono text-xs text-[var(--muted)]">
<span>00:00</span>
<span>06:00</span>
<span>12:00</span>
<span>18:00</span>
<span>24:00</span>
</div>
</div>
<!-- Controls -->
<div class="flex flex-wrap items-center justify-center gap-3 animate-slide-up delay-3">
<button id="snapshotBtn" class="btn btn-primary">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 9a2 2 0 012-2h.93a2 2 0 001.664-.89l.812-1.22A2 2 0 0110.07 4h3.86a2 2 0 011.664.89l.812 1.22A2 2 0 0018.07 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V9z"/>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 13a3 3 0 11-6 0 3 3 0 016 0z"/>
</svg>
Snapshot
</button>
<button id="recordBtn" class="btn btn-secondary">
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24">
<circle cx="12" cy="12" r="6"/>
</svg>
Record
</button>
<button id="motionBtn" class="btn btn-secondary">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"/>
</svg>
Motion Detect
</button>
<button id="fullscreenBtn" class="btn btn-secondary btn-icon" aria-label="Fullscreen">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"/>
</svg>
</button>
</div>
</div>
<!-- Side Panel -->
<div class="space-y-4">
<!-- Stats Card -->
<div class="card animate-slide-up delay-2">
<h3 class="text-sm font-medium mb-4 flex items-center gap-2">
<svg class="w-4 h-4 text-[var(--accent)]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"/>
</svg>
Statistics
</h3>
<div class="space-y-3">
<div class="flex justify-between items-center">
<span class="text-sm text-[var(--muted)]">Resolution</span>
<span class="mono text-sm" id="resolution">1920x1080</span>
</div>
<div class="flex justify-between items-center">
<span class="text-sm text-[var(--muted)]">Frame Rate</span>
<span class="mono text-sm" id="framerate">30 fps</span>
</div>
<div class="flex justify-between items-center">
<span class="text-sm text-[var(--muted)]">Uptime</span>
<span class="mono text-sm" id="uptime">02:34:17</span>
</div>
<div class="flex justify-between items-center">
<span class="text-sm text-[var(--muted)]">Events Today</span>
<span class="mono text-sm text-[var(--warning)]" id="eventCount">14</span>
</div>
<div class="flex justify-between items-center">
<span class="text-sm text-[var(--muted)]">Storage Used</span>
<span class="mono text-sm">47.3 GB</span>
</div>
</div>
</div>
<!-- Activity Feed -->
<div class="card animate-slide-up delay-3">
<h3 class="text-sm font-medium mb-4 flex items-center gap-2">
<svg class="w-4 h-4 text-[var(--accent)]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
Activity Log
</h3>
<div class="space-y-2 max-h-64 overflow-y-auto" id="activityLog">
<!-- Populated by JS -->
</div>
</div>
<!-- Quick Actions -->
<div class="card animate-slide-up delay-4">
<h3 class="text-sm font-medium mb-4">Quick Settings</h3>
<div class="space-y-3">
<div class="flex items-center justify-between">
<span class="text-sm text-[var(--muted)]">Night Vision</span>
<button id="nightVisionToggle" class="w-12 h-6 rounded-full bg-[var(--border)] relative transition-colors duration-200" role="switch" aria-checked="false">
<span class="absolute left-1 top-1 w-4 h-4 rounded-full bg-[var(--muted)] transition-all duration-200"></span>
</button>
</div>
<div class="flex items-center justify-between">
<span class="text-sm text-[var(--muted)]">Audio Recording</span>
<button id="audioToggle" class="w-12 h-6 rounded-full bg-[var(--border)] relative transition-colors duration-200" role="switch" aria-checked="false">
<span class="absolute left-1 top-1 w-4 h-4 rounded-full bg-[var(--muted)] transition-all duration-200"></span>
</button>
</div>
<div class="flex items-center justify-between">
<span class="text-sm text-[var(--muted)]">Motion Zones</span>
<button id="zonesToggle" class="w-12 h-6 rounded-full bg-[var(--border)] relative transition-colors duration-200" role="switch" aria-checked="false">
<span class="absolute left-1 top-1 w-4 h-4 rounded-full bg-[var(--muted)] transition-all duration-200"></span>
</button>
</div>
</div>
</div>
<!-- Camera Select -->
<div class="card animate-slide-up delay-5">
<h3 class="text-sm font-medium mb-3">Camera Source</h3>
<select id="cameraSelect" class="w-full bg-[var(--bg)] border border-[var(--border)] rounded-lg px-3 py-2 text-sm focus:outline-none focus:border-[var(--accent)]">
<option value="">Select camera...</option>
</select>
</div>
</div>
</div>
</main>
<!-- Snapshot Modal -->
<div id="snapshotModal" class="fixed inset-0 z-50 hidden items-center justify-center p-4 bg-black/80 backdrop-blur-sm">
<div class="bg-[var(--card)] border border-[var(--border)] rounded-lg max-w-2xl w-full p-4">
<div class="flex justify-between items-center mb-4">
<h3 class="font-medium">Snapshot Preview</h3>
<button id="closeModal" class="btn btn-secondary btn-icon" aria-label="Close">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
</svg>
</button>
</div>
<img id="snapshotImage" src="" alt="Snapshot" class="w-full rounded">
<div class="flex justify-end gap-2 mt-4">
<button id="downloadSnapshot" class="btn btn-primary">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"/>
</svg>
Download
</button>
</div>
</div>
</div>
<script>
// State
const state =
isRecording: false,
motionDetection: false,
motionLevel: 0,
startTime: Date.now(),
activityLog: [
time: '14:32:15', type: 'motion', message: 'Motion detected - Zone A' ,
time: '14:28:03', type: 'info', message: 'Camera connected' ,
time: '14:27:45', type: 'warning', message: 'Connection restored' ,
time: '14:25:12', type: 'motion', message: 'Motion detected - Zone B' ,
time: '14:15:00', type: 'info', message: 'Recording started' ,
]
;
// DOM Elements
const video = document.getElementById('webcam');
const cameraSelect = document.getElementById('cameraSelect');
const timestampEl = document.getElementById('timestamp');
const currentTimeEl = document.getElementById('currentTime');
const timelineProgress = document.getElementById('timelineProgress');
const timelineMarker = document.getElementById('timelineMarker');
const uptimeEl = document.getElementById('uptime');
const motionStatusEl = document.getElementById('motionStatus');
const activityLogEl = document.getElementById('activityLog');
const snapshotModal = document.getElementById('snapshotModal');
const snapshotImage = document.getElementById('snapshotImage');
// Initialize camera
async function initCamera()
try
const devices = await navigator.mediaDevices.enumerateDevices();
const cameras = devices.filter(d => d.kind === 'videoinput');
cameraSelect.innerHTML = '<option value="">Select camera...</option>';
cameras.forEach((camera, i) => );
if (cameras.length > 0)
await startCamera(cameras[0].deviceId);
catch (err)
console.error('Camera init error:', err);
addLogEntry('error', 'Camera access denied');
async function startCamera(deviceId)
try
const stream = await navigator.mediaDevices.getUserMedia(
video: deviceId: deviceId ? exact: deviceId : undefined, width: 1920, height: 1080 ,
audio: false
);
video.srcObject = stream;
addLogEntry('info', 'Camera connected');
catch (err)
console.error('Start camera error:', err);
cameraSelect.addEventListener('change', (e) =>
if (e.target.value)
startCamera(e.target.value);
);
// Update timestamp
function updateTimestamp()
const now = new Date();
const timestamp = now.toISOString().replace('T', ' ').substr(0, 19);
timestampEl.textContent = timestamp;
const hours = now.getHours();
const minutes = now.getMinutes();
const seconds = now.getSeconds();
currentTimeEl.textContent = `$String(hours).padStart(2, '0'):$String(minutes).padStart(2, '0'):$String(seconds).padStart(2, '0')`;
// Timeline progress (percentage of day)
const dayProgress = ((hours * 3600 + minutes * 60 + seconds) / 86400) * 100;
timelineProgress.style.width = `$dayProgress%`;
timelineMarker.style.left = `$dayProgress%`;
// Uptime
const uptime = Math.floor((Date.now() - state.startTime) / 1000);
const upH = Math.floor(uptime / 3600);
const upM = Math.floor((uptime % 3600) / 60);
const upS = uptime % 60;
uptimeEl.textContent = `$String(upH).padStart(2, '0'):$String(upM).padStart(2, '0'):$String(upS).padStart(2, '0')`;
// Activity Log
function renderActivityLog() {
activityLogEl.innerHTML = state.activityLog.map(entry => `
<div class="flex items-start gap-2 p-2 rounded bg-[var(--bg)] text-sm">
<span class="mono text-xs text-[var(--muted)] shrink-0">$entry.time</span>
<span class="inline-flex items-center gap-1 shrink-0">
$entry.type === 'motion' ? '<span class="w-2 h-2 rounded-full bg-[var(--warning)]"></span>' : ''
$entry.type === 'warning' ? '<span class="w-2 h-2 rounded-full bg-[var(--warning)]"></span>' : ''
$entry.type === 'error' ? '<span class="w-2 h-2 rounded-full bg-[var(--danger)]"></span>' : ''
${entry.type === 'info' ? '<span class="w-2 h-2
If your camera exposes HLS (.m3u8) or MP4 segments, use a tag:
<video controls autoplay muted playsinline width="640" height="360">
<source src="https://example.com/stream.m3u8" type="application/vnd.apple.mpegurl" />
Your browser does not support the video tag.
</video>
HLS playback in desktop browsers often needs JavaScript players (hls.js) for non-Safari browsers.