|
|
<!DOCTYPE html> |
|
|
<html lang="en" class="dark"> |
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
|
<title>CamPulse - Camera Control Hub</title> |
|
|
<link rel="icon" type="image/x-icon" href="/static/favicon.ico"> |
|
|
<script src="https://cdn.tailwindcss.com"></script> |
|
|
<script src="https://unpkg.com/feather-icons"></script> |
|
|
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script> |
|
|
<script src="https://cdn.jsdelivr.net/npm/vanta@latest/dist/vanta.globe.min.js"></script> |
|
|
<script src="https://cdn.jsdelivr.net/npm/animejs/lib/anime.iife.min.js"></script> |
|
|
<style> |
|
|
.vanta-bg { |
|
|
position: fixed; |
|
|
top: 0; |
|
|
left: 0; |
|
|
width: 100%; |
|
|
height: 100%; |
|
|
z-index: -1; |
|
|
opacity: 0.15; |
|
|
} |
|
|
.glass-card { |
|
|
background: rgba(15, 23, 42, 0.7); |
|
|
backdrop-filter: blur(10px); |
|
|
border: 1px solid rgba(255, 255, 255, 0.1); |
|
|
} |
|
|
.camera-feed { |
|
|
transition: all 0.3s ease; |
|
|
} |
|
|
.camera-feed:hover { |
|
|
transform: scale(1.02); |
|
|
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); |
|
|
} |
|
|
.ptz-button { |
|
|
transition: all 0.2s ease; |
|
|
} |
|
|
.ptz-button:hover { |
|
|
transform: translateY(-2px); |
|
|
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); |
|
|
} |
|
|
.status-indicator { |
|
|
width: 10px; |
|
|
height: 10px; |
|
|
border-radius: 50%; |
|
|
display: inline-block; |
|
|
margin-right: 8px; |
|
|
} |
|
|
.connected { |
|
|
background-color: #10B981; |
|
|
} |
|
|
.disconnected { |
|
|
background-color: #EF4444; |
|
|
} |
|
|
</style> |
|
|
</head> |
|
|
<body class="bg-slate-900 text-slate-100 min-h-screen"> |
|
|
<div id="vanta-bg" class="vanta-bg"></div> |
|
|
|
|
|
<div class="container mx-auto px-4 py-8"> |
|
|
|
|
|
<header class="flex justify-between items-center mb-8"> |
|
|
<div> |
|
|
<h1 class="text-3xl font-bold text-sky-400 flex items-center"> |
|
|
<i data-feather="video" class="mr-3"></i> CamPulse |
|
|
</h1> |
|
|
<p class="text-slate-400">Multi-camera PTZ control hub</p> |
|
|
</div> |
|
|
<div class="flex items-center space-x-4"> |
|
|
<button id="theme-toggle" class="p-2 rounded-full bg-slate-800 hover:bg-slate-700 transition"> |
|
|
<i data-feather="moon"></i> |
|
|
</button> |
|
|
<div class="relative"> |
|
|
<button id="settings-btn" class="p-2 rounded-full bg-sky-600 hover:bg-sky-500 transition"> |
|
|
<i data-feather="settings"></i> |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
</header> |
|
|
|
|
|
|
|
|
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6"> |
|
|
|
|
|
<div class="lg:col-span-2"> |
|
|
<div class="glass-card rounded-xl p-4 mb-6"> |
|
|
<div class="flex justify-between items-center mb-4"> |
|
|
<h2 class="text-xl font-semibold flex items-center"> |
|
|
<i data-feather="camera" class="mr-2"></i> Live Feed |
|
|
</h2> |
|
|
<div class="flex space-x-2"> |
|
|
<button class="bg-emerald-600 hover:bg-emerald-500 px-3 py-1 rounded-lg text-sm flex items-center"> |
|
|
<i data-feather="maximize-2" class="mr-1" style="width: 14px; height: 14px;"></i> Fullscreen |
|
|
</button> |
|
|
<button class="bg-red-600 hover:bg-red-500 px-3 py-1 rounded-lg text-sm flex items-center"> |
|
|
<i data-feather="circle" class="mr-1" style="width: 14px; height: 14px;"></i> Record |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="relative bg-black rounded-lg overflow-hidden aspect-video flex items-center justify-center"> |
|
|
<div class="absolute inset-0 flex items-center justify-center"> |
|
|
<i data-feather="camera-off" class="text-slate-500" style="width: 48px; height: 48px;"></i> |
|
|
</div> |
|
|
<div class="absolute bottom-4 left-4 bg-black bg-opacity-50 px-2 py-1 rounded text-sm"> |
|
|
<span class="status-indicator connected"></span> |
|
|
Camera 1 - 192.168.1.178 |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="mt-4 grid grid-cols-3 gap-4"> |
|
|
<div class="camera-feed bg-slate-800 rounded-lg aspect-video flex items-center justify-center cursor-pointer border-2 border-sky-500"> |
|
|
<i data-feather="camera" class="text-slate-500" style="width: 24px; height: 24px;"></i> |
|
|
</div> |
|
|
<div class="camera-feed bg-slate-800 rounded-lg aspect-video flex items-center justify-center cursor-pointer"> |
|
|
<i data-feather="camera" class="text-slate-500" style="width: 24px; height: 24px;"></i> |
|
|
</div> |
|
|
<div class="camera-feed bg-slate-800 rounded-lg aspect-video flex items-center justify-center cursor-pointer"> |
|
|
<i data-feather="camera" class="text-slate-500" style="width: 24px; height: 24px;"></i> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="glass-card rounded-xl p-4"> |
|
|
<h2 class="text-xl font-semibold mb-4 flex items-center"> |
|
|
<i data-feather="navigation" class="mr-2"></i> PTZ Controls |
|
|
</h2> |
|
|
|
|
|
<div class="flex justify-center"> |
|
|
<div class="grid grid-cols-3 gap-4"> |
|
|
|
|
|
<div></div> |
|
|
|
|
|
<button class="ptz-button bg-sky-600 hover:bg-sky-500 w-16 h-16 rounded-full flex items-center justify-center"> |
|
|
<i data-feather="arrow-up" style="width: 24px; height: 24px;"></i> |
|
|
</button> |
|
|
|
|
|
<div></div> |
|
|
|
|
|
|
|
|
<button class="ptz-button bg-sky-600 hover:bg-sky-500 w-16 h-16 rounded-full flex items-center justify-center"> |
|
|
<i data-feather="arrow-left" style="width: 24px; height: 24px;"></i> |
|
|
</button> |
|
|
|
|
|
<button class="ptz-button bg-sky-700 hover:bg-sky-600 w-16 h-16 rounded-full flex items-center justify-center"> |
|
|
<i data-feather="target" style="width: 24px; height: 24px;"></i> |
|
|
</button> |
|
|
|
|
|
<button class="ptz-button bg-sky-600 hover:bg-sky-500 w-16 h-16 rounded-full flex items-center justify-center"> |
|
|
<i data-feather="arrow-right" style="width: 24px; height: 24px;"></i> |
|
|
</button> |
|
|
|
|
|
|
|
|
<div></div> |
|
|
|
|
|
<button class="ptz-button bg-sky-600 hover:bg-sky-500 w-16 h-16 rounded-full flex items-center justify-center"> |
|
|
<i data-feather="arrow-down" style="width: 24px; height: 24px;"></i> |
|
|
</button> |
|
|
|
|
|
<div></div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="mt-6"> |
|
|
<div class="flex justify-between items-center mb-2"> |
|
|
<span class="text-sm">Zoom Control</span> |
|
|
<span class="text-sm text-slate-400">1.0x</span> |
|
|
</div> |
|
|
<input type="range" min="1" max="8" step="0.1" value="1" class="w-full h-2 bg-slate-700 rounded-lg appearance-none cursor-pointer"> |
|
|
|
|
|
<div class="flex justify-between mt-4"> |
|
|
<button class="bg-slate-700 hover:bg-slate-600 px-4 py-2 rounded-lg text-sm flex items-center"> |
|
|
<i data-feather="zoom-in" class="mr-1" style="width: 16px; height: 16px;"></i> Zoom In |
|
|
</button> |
|
|
<button class="bg-slate-700 hover:bg-slate-600 px-4 py-2 rounded-lg text-sm flex items-center"> |
|
|
<i data-feather="zoom-out" class="mr-1" style="width: 16px; height: 16px;"></i> Zoom Out |
|
|
</button> |
|
|
<button class="bg-slate-700 hover:bg-slate-600 px-4 py-2 rounded-lg text-sm flex items-center"> |
|
|
<i data-feather="refresh-ccw" class="mr-1" style="width: 16px; height: 16px;"></i> Reset |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="space-y-6"> |
|
|
|
|
|
<div class="glass-card rounded-xl p-4"> |
|
|
<h2 class="text-xl font-semibold mb-4 flex items-center"> |
|
|
<i data-feather="sliders" class="mr-2"></i> Camera Settings |
|
|
</h2> |
|
|
|
|
|
<div class="space-y-4"> |
|
|
<div> |
|
|
<label class="block text-sm mb-1">Resolution</label> |
|
|
<select class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-2 text-sm"> |
|
|
<option>640x360</option> |
|
|
<option selected>1280x720</option> |
|
|
<option>1920x1080</option> |
|
|
<option>2560x1440</option> |
|
|
</select> |
|
|
</div> |
|
|
|
|
|
<div> |
|
|
<label class="block text-sm mb-1">Frame Rate</label> |
|
|
<select class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-2 text-sm"> |
|
|
<option>5 FPS</option> |
|
|
<option>10 FPS</option> |
|
|
<option selected>12 FPS</option> |
|
|
<option>15 FPS</option> |
|
|
<option>20 FPS</option> |
|
|
<option>25 FPS</option> |
|
|
<option>30 FPS</option> |
|
|
</select> |
|
|
</div> |
|
|
|
|
|
<div> |
|
|
<label class="block text-sm mb-1">Bitrate</label> |
|
|
<select class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-2 text-sm"> |
|
|
<option>512 Kbps</option> |
|
|
<option>1024 Kbps</option> |
|
|
<option selected>2048 Kbps</option> |
|
|
<option>4096 Kbps</option> |
|
|
<option>8192 Kbps</option> |
|
|
</select> |
|
|
</div> |
|
|
|
|
|
<div class="pt-2"> |
|
|
<div class="flex items-center justify-between mb-2"> |
|
|
<span class="text-sm">White Light</span> |
|
|
<label class="relative inline-flex items-center cursor-pointer"> |
|
|
<input type="checkbox" class="sr-only peer"> |
|
|
<div class="w-11 h-6 bg-slate-700 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-emerald-500"></div> |
|
|
</label> |
|
|
</div> |
|
|
|
|
|
<div class="flex items-center justify-between mb-2"> |
|
|
<span class="text-sm">IR Light</span> |
|
|
<label class="relative inline-flex items-center cursor-pointer"> |
|
|
<input type="checkbox" class="sr-only peer"> |
|
|
<div class="w-11 h-6 bg-slate-700 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-emerald-500"></div> |
|
|
</label> |
|
|
</div> |
|
|
|
|
|
<div class="flex items-center justify-between"> |
|
|
<span class="text-sm">Day/Night Mode</span> |
|
|
<label class="relative inline-flex items-center cursor-pointer"> |
|
|
<input type="checkbox" checked class="sr-only peer"> |
|
|
<div class="w-11 h-6 bg-slate-700 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-emerald-500"></div> |
|
|
</label> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="glass-card rounded-xl p-4"> |
|
|
<h2 class="text-xl font-semibold mb-4 flex items-center"> |
|
|
<i data-feather="list" class="mr-2"></i> Camera Management |
|
|
</h2> |
|
|
|
|
|
<div class="space-y-4"> |
|
|
<div> |
|
|
<label class="block text-sm mb-1">Add New Camera</label> |
|
|
<input type="text" placeholder="Camera IP" class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-2 text-sm mb-2"> |
|
|
<input type="text" placeholder="Camera Name" class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-2 text-sm mb-3"> |
|
|
<button class="w-full bg-sky-600 hover:bg-sky-500 py-2 rounded-lg text-sm flex items-center justify-center"> |
|
|
<i data-feather="plus" class="mr-1" style="width: 16px; height: 16px;"></i> Add Camera |
|
|
</button> |
|
|
</div> |
|
|
|
|
|
<div class="border-t border-slate-700 pt-4"> |
|
|
<h3 class="text-sm font-semibold mb-2">Connected Cameras</h3> |
|
|
<div class="space-y-2"> |
|
|
<div class="flex items-center justify-between bg-slate-800 p-3 rounded-lg border border-sky-500"> |
|
|
<div class="flex items-center"> |
|
|
<span class="status-indicator connected"></span> |
|
|
<span>Camera 1</span> |
|
|
</div> |
|
|
<button class="text-red-500 hover:text-red-400"> |
|
|
<i data-feather="trash-2" style="width: 16px; height: 16px;"></i> |
|
|
</button> |
|
|
</div> |
|
|
<div class="flex items-center justify-between bg-slate-800 p-3 rounded-lg"> |
|
|
<div class="flex items-center"> |
|
|
<span class="status-indicator connected"></span> |
|
|
<span>Camera 2</span> |
|
|
</div> |
|
|
<button class="text-red-500 hover:text-red-400"> |
|
|
<i data-feather="trash-2" style="width: 16px; height: 16px;"></i> |
|
|
</button> |
|
|
</div> |
|
|
<div class="flex items-center justify-between bg-slate-800 p-3 rounded-lg"> |
|
|
<div class="flex items-center"> |
|
|
<span class="status-indicator disconnected"></span> |
|
|
<span>Camera 3</span> |
|
|
</div> |
|
|
<button class="text-red-500 hover:text-red-400"> |
|
|
<i data-feather="trash-2" style="width: 16px; height: 16px;"></i> |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="glass-card rounded-lg p-3 mt-6 text-sm flex items-center"> |
|
|
<i data-feather="info" class="mr-2" style="width: 16px; height: 16px;"></i> |
|
|
<span>System ready. Connected to 2 of 3 cameras.</span> |
|
|
</div> |
|
|
</div> |
|
|
<script> |
|
|
|
|
|
VANTA.GLOBE({ |
|
|
el: "#vanta-bg", |
|
|
mouseControls: true, |
|
|
touchControls: true, |
|
|
gyroControls: false, |
|
|
minHeight: 200.00, |
|
|
minWidth: 200.00, |
|
|
scale: 1.00, |
|
|
scaleMobile: 1.00, |
|
|
color: 0x3b82f6, |
|
|
backgroundColor: 0x0f172a, |
|
|
size: 0.8 |
|
|
}); |
|
|
|
|
|
|
|
|
const themeToggle = document.getElementById('theme-toggle'); |
|
|
const htmlElement = document.documentElement; |
|
|
|
|
|
themeToggle.addEventListener('click', () => { |
|
|
if (htmlElement.classList.contains('dark')) { |
|
|
htmlElement.classList.remove('dark'); |
|
|
htmlElement.classList.add('light'); |
|
|
feather.replace(); |
|
|
themeToggle.innerHTML = feather.icons['sun'].toSvg(); |
|
|
} else { |
|
|
htmlElement.classList.remove('light'); |
|
|
htmlElement.classList.add('dark'); |
|
|
feather.replace(); |
|
|
themeToggle.innerHTML = feather.icons['moon'].toSvg(); |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
const cameraFeeds = document.querySelectorAll('.camera-feed'); |
|
|
const mainFeed = document.querySelector('.relative.bg-black.rounded-lg'); |
|
|
|
|
|
cameraFeeds.forEach((feed, index) => { |
|
|
feed.addEventListener('click', () => { |
|
|
|
|
|
cameraFeeds.forEach(f => f.classList.remove('border-2', 'border-sky-500')); |
|
|
|
|
|
|
|
|
feed.classList.add('border-2', 'border-sky-500'); |
|
|
|
|
|
|
|
|
const statusDiv = mainFeed.querySelector('.absolute.bottom-4.left-4'); |
|
|
statusDiv.innerHTML = ` |
|
|
<span class="status-indicator connected"></span> |
|
|
Camera ${index + 1} - 192.168.1.${178 + index} |
|
|
`; |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
const ptzButtons = document.querySelectorAll('.ptz-button'); |
|
|
ptzButtons.forEach(button => { |
|
|
button.addEventListener('click', () => { |
|
|
anime({ |
|
|
targets: button, |
|
|
scale: [1, 0.9, 1], |
|
|
duration: 200, |
|
|
easing: 'easeInOutQuad' |
|
|
}); |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
const zoomSlider = document.querySelector('input[type="range"]'); |
|
|
const zoomValue = document.querySelector('.text-sm.text-slate-400'); |
|
|
|
|
|
zoomSlider.addEventListener('input', () => { |
|
|
const zoomLevel = zoomSlider.value; |
|
|
zoomValue.textContent = `${zoomLevel}x`; |
|
|
|
|
|
anime({ |
|
|
targets: zoomValue, |
|
|
scale: [1, 1.2, 1], |
|
|
duration: 300, |
|
|
easing: 'easeInOutQuad' |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
feather.replace(); |
|
|
</script> |
|
|
</body> |
|
|
</html> |
|
|
|