| | <!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> |
| |
|