Spaces:
Build error
Build error
Upload 5 files
Browse files- app.py +164 -0
- index.html +530 -0
- main.js +792 -0
- requirements.txt +9 -0
- styles.css +1159 -0
app.py
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from flask import Flask, request, jsonify
|
| 2 |
+
from flask_cors import CORS
|
| 3 |
+
import cv2
|
| 4 |
+
import numpy as np
|
| 5 |
+
import base64
|
| 6 |
+
import io
|
| 7 |
+
from collections import Counter
|
| 8 |
+
from PIL import Image
|
| 9 |
+
from ultralytics import YOLO
|
| 10 |
+
import os
|
| 11 |
+
|
| 12 |
+
# App Configuration
|
| 13 |
+
app = Flask(__name__)
|
| 14 |
+
CORS(app)
|
| 15 |
+
|
| 16 |
+
# Model paths
|
| 17 |
+
MODELS = {
|
| 18 |
+
'yolov8s.pt': './model/yolov8s.pt',
|
| 19 |
+
'yolov9m.pt': './model/yolov9m.pt'
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
# Load models on demand
|
| 23 |
+
loaded_models = {}
|
| 24 |
+
|
| 25 |
+
def get_model(model_name):
|
| 26 |
+
"""Load model if not already loaded"""
|
| 27 |
+
if model_name not in loaded_models:
|
| 28 |
+
if model_name in MODELS and os.path.exists(MODELS[model_name]):
|
| 29 |
+
loaded_models[model_name] = YOLO(MODELS[model_name])
|
| 30 |
+
else:
|
| 31 |
+
raise ValueError(f"Model {model_name} not found")
|
| 32 |
+
return loaded_models[model_name]
|
| 33 |
+
|
| 34 |
+
def decode_base64_image(base64_string):
|
| 35 |
+
"""Base64 image string ko decode karna"""
|
| 36 |
+
# Remove data URL prefix if present
|
| 37 |
+
if ',' in base64_string:
|
| 38 |
+
base64_string = base64_string.split(',')[1]
|
| 39 |
+
|
| 40 |
+
image_data = base64.b64decode(base64_string)
|
| 41 |
+
image = Image.open(io.BytesIO(image_data))
|
| 42 |
+
return np.array(image)
|
| 43 |
+
|
| 44 |
+
@app.route('/detect', methods=['POST'])
|
| 45 |
+
def detect_objects():
|
| 46 |
+
try:
|
| 47 |
+
# Image receive karna
|
| 48 |
+
image_base64 = request.json.get('image', '')
|
| 49 |
+
|
| 50 |
+
# Get selected model
|
| 51 |
+
model_name = request.json.get('model', 'yolov9m.pt')
|
| 52 |
+
model = get_model(model_name)
|
| 53 |
+
|
| 54 |
+
# Get confidence threshold
|
| 55 |
+
confidence = request.json.get('confidence', 0.25)
|
| 56 |
+
|
| 57 |
+
# Image decode karna
|
| 58 |
+
image = decode_base64_image(image_base64)
|
| 59 |
+
|
| 60 |
+
# Object detection
|
| 61 |
+
results = model(image, conf=confidence)
|
| 62 |
+
|
| 63 |
+
# Detected objects ko process karna
|
| 64 |
+
detections = []
|
| 65 |
+
for result in results:
|
| 66 |
+
boxes = result.boxes
|
| 67 |
+
for box in boxes:
|
| 68 |
+
# Bounding box coordinates
|
| 69 |
+
x1, y1, x2, y2 = box.xyxy[0]
|
| 70 |
+
|
| 71 |
+
# Confidence aur class
|
| 72 |
+
conf = box.conf[0]
|
| 73 |
+
cls = int(box.cls[0])
|
| 74 |
+
class_name = model.names[cls]
|
| 75 |
+
|
| 76 |
+
# Detection object banana
|
| 77 |
+
detection = {
|
| 78 |
+
'bbox': [float(x1), float(y1), float(x2-x1), float(y2-y1)],
|
| 79 |
+
'class': class_name,
|
| 80 |
+
'confidence': float(conf)
|
| 81 |
+
}
|
| 82 |
+
detections.append(detection)
|
| 83 |
+
|
| 84 |
+
# Object grouping
|
| 85 |
+
object_counts = Counter(det['class'] for det in detections)
|
| 86 |
+
grouped_objects = [
|
| 87 |
+
{'class': obj, 'count': count}
|
| 88 |
+
for obj, count in object_counts.items()
|
| 89 |
+
]
|
| 90 |
+
|
| 91 |
+
return jsonify({
|
| 92 |
+
'detections': detections,
|
| 93 |
+
'grouped_objects': grouped_objects,
|
| 94 |
+
'model_used': model_name
|
| 95 |
+
})
|
| 96 |
+
|
| 97 |
+
except Exception as e:
|
| 98 |
+
return jsonify({'error': str(e)}), 500
|
| 99 |
+
|
| 100 |
+
@app.route('/available-models', methods=['GET'])
|
| 101 |
+
def get_available_models():
|
| 102 |
+
"""Available object detection models"""
|
| 103 |
+
return jsonify({
|
| 104 |
+
'models': [
|
| 105 |
+
{'name': 'yolov8s.pt', 'type': 'Object Detection', 'description': 'YOLOv8s (Fastest)'},
|
| 106 |
+
{'name': 'yolov9m.pt', 'type': 'Object Detection', 'description': 'YOLOv9m (Highest Accuracy)'},
|
| 107 |
+
]
|
| 108 |
+
})
|
| 109 |
+
|
| 110 |
+
@app.route('/detect-multiple', methods=['POST'])
|
| 111 |
+
def detect_multiple_objects():
|
| 112 |
+
"""Multiple images ke liye detection support"""
|
| 113 |
+
try:
|
| 114 |
+
images_base64 = request.json.get('images', [])
|
| 115 |
+
|
| 116 |
+
# Get selected model
|
| 117 |
+
model_name = request.json.get('model', 'yolov9m.pt')
|
| 118 |
+
model = get_model(model_name)
|
| 119 |
+
|
| 120 |
+
# Get confidence threshold
|
| 121 |
+
confidence = request.json.get('confidence', 0.25)
|
| 122 |
+
|
| 123 |
+
all_detections = []
|
| 124 |
+
|
| 125 |
+
for image_base64 in images_base64:
|
| 126 |
+
# Image decode karna
|
| 127 |
+
image = decode_base64_image(image_base64)
|
| 128 |
+
|
| 129 |
+
# Object detection
|
| 130 |
+
results = model(image, conf=confidence)
|
| 131 |
+
|
| 132 |
+
# Detected objects ko process karna
|
| 133 |
+
image_detections = []
|
| 134 |
+
for result in results:
|
| 135 |
+
boxes = result.boxes
|
| 136 |
+
for box in boxes:
|
| 137 |
+
# Bounding box coordinates
|
| 138 |
+
x1, y1, x2, y2 = box.xyxy[0]
|
| 139 |
+
|
| 140 |
+
# Confidence aur class
|
| 141 |
+
conf = box.conf[0]
|
| 142 |
+
cls = int(box.cls[0])
|
| 143 |
+
class_name = model.names[cls]
|
| 144 |
+
|
| 145 |
+
# Detection object banana
|
| 146 |
+
detection = {
|
| 147 |
+
'bbox': [float(x1), float(y1), float(x2-x1), float(y2-y1)],
|
| 148 |
+
'class': class_name,
|
| 149 |
+
'confidence': float(conf)
|
| 150 |
+
}
|
| 151 |
+
image_detections.append(detection)
|
| 152 |
+
|
| 153 |
+
all_detections.append(image_detections)
|
| 154 |
+
|
| 155 |
+
return jsonify({
|
| 156 |
+
'detections': all_detections,
|
| 157 |
+
'model_used': model_name
|
| 158 |
+
})
|
| 159 |
+
|
| 160 |
+
except Exception as e:
|
| 161 |
+
return jsonify({'error': str(e)}), 500
|
| 162 |
+
|
| 163 |
+
if __name__ == '__main__':
|
| 164 |
+
app.run(debug=True, port=5000)
|
index.html
ADDED
|
@@ -0,0 +1,530 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>VisionAI - Advanced Object Detection</title>
|
| 7 |
+
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
| 8 |
+
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css" rel="stylesheet">
|
| 9 |
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css">
|
| 10 |
+
<link rel="stylesheet" href="styles.css">
|
| 11 |
+
</head>
|
| 12 |
+
<body>
|
| 13 |
+
<!-- Particle Background Canvas -->
|
| 14 |
+
<canvas id="particleCanvas"></canvas>
|
| 15 |
+
|
| 16 |
+
<!-- Navbar -->
|
| 17 |
+
<nav class="navbar navbar-expand-lg navbar-dark fixed-top">
|
| 18 |
+
<div class="container">
|
| 19 |
+
<a class="navbar-brand" href="#">
|
| 20 |
+
<span class="logo-text">Vision<span class="logo-accent">AI</span></span>
|
| 21 |
+
</a>
|
| 22 |
+
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
|
| 23 |
+
<span class="navbar-toggler-icon"></span>
|
| 24 |
+
</button>
|
| 25 |
+
<div class="collapse navbar-collapse" id="navbarNav">
|
| 26 |
+
<ul class="navbar-nav ms-auto">
|
| 27 |
+
<li class="nav-item">
|
| 28 |
+
<a class="nav-link active" href="#home">
|
| 29 |
+
<i class="bi bi-house-door me-1"></i>Home
|
| 30 |
+
</a>
|
| 31 |
+
</li>
|
| 32 |
+
<li class="nav-item">
|
| 33 |
+
<a class="nav-link" href="#features">
|
| 34 |
+
<i class="bi bi-gem me-1"></i>Features
|
| 35 |
+
</a>
|
| 36 |
+
</li>
|
| 37 |
+
<li class="nav-item">
|
| 38 |
+
<a class="nav-link" href="#team">
|
| 39 |
+
<i class="bi bi-people me-1"></i>Team
|
| 40 |
+
</a>
|
| 41 |
+
</li>
|
| 42 |
+
<li class="nav-item">
|
| 43 |
+
<a class="nav-link" href="#" data-bs-toggle="modal" data-bs-target="#contactModal">
|
| 44 |
+
<i class="bi bi-envelope me-1"></i>Contact
|
| 45 |
+
</a>
|
| 46 |
+
</li>
|
| 47 |
+
<li class="nav-item ms-2">
|
| 48 |
+
<a class="btn btn-demo rounded-pill px-3 py-2" href="#detection">
|
| 49 |
+
<i class="bi bi-eye me-1"></i>Try Demo
|
| 50 |
+
</a>
|
| 51 |
+
</li>
|
| 52 |
+
</ul>
|
| 53 |
+
</div>
|
| 54 |
+
</div>
|
| 55 |
+
</nav>
|
| 56 |
+
|
| 57 |
+
<!-- Hero Section -->
|
| 58 |
+
<section class="hero-section" id="home">
|
| 59 |
+
<div class="container">
|
| 60 |
+
<div class="row align-items-center">
|
| 61 |
+
<div class="col-lg-6 hero-content">
|
| 62 |
+
<h1 class="hero-title animate__animated animate__fadeInUp">
|
| 63 |
+
Next-Gen <span class="gradient-text">Object Detection</span>
|
| 64 |
+
</h1>
|
| 65 |
+
<p class="hero-subtitle animate__animated animate__fadeInUp animate__delay-1s">
|
| 66 |
+
Powerful computer vision that detects objects in real-time with state-of-the-art accuracy
|
| 67 |
+
</p>
|
| 68 |
+
<div class="hero-btns animate__animated animate__fadeInUp animate__delay-2s">
|
| 69 |
+
<a href="#detection" class="btn btn-primary btn-lg rounded-pill">
|
| 70 |
+
<i class="bi bi-camera me-2"></i>Start Detection
|
| 71 |
+
</a>
|
| 72 |
+
<a href="#features" class="btn btn-outline btn-lg rounded-pill ms-3">
|
| 73 |
+
<i class="bi bi-info-circle me-2"></i>Learn More
|
| 74 |
+
</a>
|
| 75 |
+
</div>
|
| 76 |
+
<div class="tech-badges animate__animated animate__fadeInUp animate__delay-3s">
|
| 77 |
+
<span class="badge">YOLOv9</span>
|
| 78 |
+
<span class="badge">TensorFlow</span>
|
| 79 |
+
<span class="badge">Computer Vision</span>
|
| 80 |
+
<span class="badge">Real-time</span>
|
| 81 |
+
</div>
|
| 82 |
+
</div>
|
| 83 |
+
<!-- <div class="col-lg-6 d-flex justify-content-center">
|
| 84 |
+
<div class="hero-image animate__animated animate__zoomIn animate__delay-1s">
|
| 85 |
+
<img src="../assets/hero.jpeg" alt="AI Vision Illustration">
|
| 86 |
+
<div class="floating-tag tag-1">Person</div>
|
| 87 |
+
<div class="floating-tag tag-2">Dog</div>
|
| 88 |
+
<div class="floating-tag tag-3">Car</div>
|
| 89 |
+
<div class="pulse-circle"></div>
|
| 90 |
+
</div>
|
| 91 |
+
</div> -->
|
| 92 |
+
</div>
|
| 93 |
+
</div>
|
| 94 |
+
<div class="hero-shape">
|
| 95 |
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1440 320">
|
| 96 |
+
<path fill="#ffffff" fill-opacity="1" d="M0,160L48,176C96,192,192,224,288,213.3C384,203,480,149,576,149.3C672,149,768,203,864,208C960,213,1056,171,1152,149.3C1248,128,1344,128,1392,128L1440,128L1440,320L1392,320C1344,320,1248,320,1152,320C1056,320,960,320,864,320C768,320,672,320,576,320C480,320,384,320,288,320C192,320,96,320,48,320L0,320Z"></path>
|
| 97 |
+
</svg>
|
| 98 |
+
</div>
|
| 99 |
+
</section>
|
| 100 |
+
|
| 101 |
+
<!-- Features Section -->
|
| 102 |
+
<section class="features-section" id="features">
|
| 103 |
+
<div class="container">
|
| 104 |
+
<div class="section-header text-center">
|
| 105 |
+
<h6 class="section-pre-title">POWERFUL CAPABILITIES</h6>
|
| 106 |
+
<h2 class="section-title">Advanced <span class="gradient-text">Features</span></h2>
|
| 107 |
+
<p class="section-subtitle">Our AI detection system offers cutting-edge capabilities</p>
|
| 108 |
+
</div>
|
| 109 |
+
|
| 110 |
+
<div class="row features-container">
|
| 111 |
+
<div class="col-md-4 feature-card-wrapper">
|
| 112 |
+
<div class="feature-card">
|
| 113 |
+
<div class="feature-icon">
|
| 114 |
+
<i class="bi bi-camera"></i>
|
| 115 |
+
</div>
|
| 116 |
+
<h4>Multi-Model Detection</h4>
|
| 117 |
+
<p>Choose between YOLOv8 and YOLOv9 models for optimal performance on different devices</p>
|
| 118 |
+
</div>
|
| 119 |
+
</div>
|
| 120 |
+
<div class="col-md-4 feature-card-wrapper">
|
| 121 |
+
<div class="feature-card">
|
| 122 |
+
<div class="feature-icon">
|
| 123 |
+
<i class="bi bi-camera-video"></i>
|
| 124 |
+
</div>
|
| 125 |
+
<h4>Real-time Processing</h4>
|
| 126 |
+
<p>Detect objects in real-time through your camera with high-speed inference</p>
|
| 127 |
+
</div>
|
| 128 |
+
</div>
|
| 129 |
+
<div class="col-md-4 feature-card-wrapper">
|
| 130 |
+
<div class="feature-card">
|
| 131 |
+
<div class="feature-icon">
|
| 132 |
+
<i class="bi bi-cloud-upload"></i>
|
| 133 |
+
</div>
|
| 134 |
+
<h4>Image Analysis</h4>
|
| 135 |
+
<p>Upload and analyze images with precise object detection and classification</p>
|
| 136 |
+
</div>
|
| 137 |
+
</div>
|
| 138 |
+
<div class="col-md-4 feature-card-wrapper">
|
| 139 |
+
<div class="feature-card">
|
| 140 |
+
<div class="feature-icon">
|
| 141 |
+
<i class="bi bi-soundwave"></i>
|
| 142 |
+
</div>
|
| 143 |
+
<h4>Audio Descriptions</h4>
|
| 144 |
+
<p>Generate voice descriptions of detected objects with customizable settings</p>
|
| 145 |
+
</div>
|
| 146 |
+
</div>
|
| 147 |
+
<div class="col-md-4 feature-card-wrapper">
|
| 148 |
+
<div class="feature-card">
|
| 149 |
+
<div class="feature-icon">
|
| 150 |
+
<i class="bi bi-pie-chart"></i>
|
| 151 |
+
</div>
|
| 152 |
+
<h4>Statistical Analysis</h4>
|
| 153 |
+
<p>Get detailed statistics about detected objects with confidence scores</p>
|
| 154 |
+
</div>
|
| 155 |
+
</div>
|
| 156 |
+
<div class="col-md-4 feature-card-wrapper">
|
| 157 |
+
<div class="feature-card">
|
| 158 |
+
<div class="feature-icon">
|
| 159 |
+
<i class="bi bi-phone"></i>
|
| 160 |
+
</div>
|
| 161 |
+
<h4>Mobile Compatibility</h4>
|
| 162 |
+
<p>Works seamlessly on mobile devices with responsive design</p>
|
| 163 |
+
</div>
|
| 164 |
+
</div>
|
| 165 |
+
</div>
|
| 166 |
+
</div>
|
| 167 |
+
</section>
|
| 168 |
+
|
| 169 |
+
<!-- Detection Section -->
|
| 170 |
+
<section class="detection-section" id="detection">
|
| 171 |
+
<div class="container">
|
| 172 |
+
<div class="section-header text-center">
|
| 173 |
+
<h6 class="section-pre-title">INTERACTIVE DEMO</h6>
|
| 174 |
+
<h2 class="section-title">Try <span class="gradient-text">VisionAI</span> Now</h2>
|
| 175 |
+
<p class="section-subtitle">Experience our AI object detection in real-time</p>
|
| 176 |
+
</div>
|
| 177 |
+
|
| 178 |
+
<div class="detection-container">
|
| 179 |
+
<!-- Model Selection Header -->
|
| 180 |
+
<div class="model-header">
|
| 181 |
+
<h5><i class="bi bi-sliders me-2"></i>Detection Settings</h5>
|
| 182 |
+
<div class="model-controls">
|
| 183 |
+
<div class="model-select-group">
|
| 184 |
+
<label for="modelSelect">AI Model:</label>
|
| 185 |
+
<select id="modelSelect" class="form-select">
|
| 186 |
+
<option value="yolov8s.pt">YOLOv8s (Accuracy)</option>
|
| 187 |
+
<option value="yolov9m.pt">YOLOv9m (Highest Accuracy & Fastest)</option>
|
| 188 |
+
</select>
|
| 189 |
+
</div>
|
| 190 |
+
<div class="threshold-slider">
|
| 191 |
+
<label for="thresholdRange">Confidence: <span id="thresholdValue">75%</span></label>
|
| 192 |
+
<input type="range" class="form-range" id="thresholdRange" min="10" max="95" value="75">
|
| 193 |
+
</div>
|
| 194 |
+
</div>
|
| 195 |
+
</div>
|
| 196 |
+
|
| 197 |
+
<div class="detection-content">
|
| 198 |
+
<div class="row">
|
| 199 |
+
<div class="col-lg-8">
|
| 200 |
+
<!-- Camera Section -->
|
| 201 |
+
<div class="camera-section">
|
| 202 |
+
<div class="camera-actions">
|
| 203 |
+
<button id="uploadBtn" class="action-btn upload-btn">
|
| 204 |
+
<i class="bi bi-cloud-upload"></i>
|
| 205 |
+
<span>Upload Image</span>
|
| 206 |
+
</button>
|
| 207 |
+
<input type="file" id="imageUpload" accept="image/*" style="display:none;">
|
| 208 |
+
|
| 209 |
+
<button id="liveCaptureBtn" class="action-btn capture-btn">
|
| 210 |
+
<i class="bi bi-camera-video"></i>
|
| 211 |
+
<span>Live Camera</span>
|
| 212 |
+
</button>
|
| 213 |
+
|
| 214 |
+
<button id="screenshotBtn" class="action-btn screenshot-btn" disabled>
|
| 215 |
+
<i class="bi bi-camera"></i>
|
| 216 |
+
<span>Take Snapshot</span>
|
| 217 |
+
</button>
|
| 218 |
+
</div>
|
| 219 |
+
|
| 220 |
+
<div id="captureContainer" class="capture-container">
|
| 221 |
+
<div class="video-wrapper">
|
| 222 |
+
<video id="liveVideo" playsinline style="display:none;"></video>
|
| 223 |
+
<canvas id="detectedCanvas"></canvas>
|
| 224 |
+
<div id="loadingOverlay" style="display:none;">
|
| 225 |
+
<div class="spinner"></div>
|
| 226 |
+
<p>Processing...</p>
|
| 227 |
+
</div>
|
| 228 |
+
<div class="corner-label top-left">VisionAI</div>
|
| 229 |
+
<div class="corner-label bottom-right" id="modelLabel"></div>
|
| 230 |
+
</div>
|
| 231 |
+
</div>
|
| 232 |
+
</div>
|
| 233 |
+
</div>
|
| 234 |
+
|
| 235 |
+
<div class="col-lg-4">
|
| 236 |
+
<!-- Results Panel -->
|
| 237 |
+
<div class="results-panel p-3">
|
| 238 |
+
<div class="panel-tabs">
|
| 239 |
+
<button class="panel-tab active" data-tab="objects">
|
| 240 |
+
<i class="bi bi-eye"></i> Objects
|
| 241 |
+
</button>
|
| 242 |
+
<button class="panel-tab" data-tab="stats">
|
| 243 |
+
<i class="bi bi-graph-up"></i> Stats
|
| 244 |
+
</button>
|
| 245 |
+
<button class="panel-tab" data-tab="audio">
|
| 246 |
+
<i class="bi bi-volume-up"></i> Audio
|
| 247 |
+
</button>
|
| 248 |
+
</div>
|
| 249 |
+
|
| 250 |
+
<div class="panel-content mt-2 p-1">
|
| 251 |
+
<!-- Objects Tab -->
|
| 252 |
+
<div class="tab-pane active" id="objectsTab">
|
| 253 |
+
<h6 class="panel-title">
|
| 254 |
+
<i class="bi bi-list-check me-2"></i>
|
| 255 |
+
Detected Objects
|
| 256 |
+
<span class="object-counter">0</span>
|
| 257 |
+
</h6>
|
| 258 |
+
<ul id="objectList" class="object-list">
|
| 259 |
+
<li class="no-objects">No objects detected yet</li>
|
| 260 |
+
</ul>
|
| 261 |
+
</div>
|
| 262 |
+
|
| 263 |
+
<!-- Stats Tab -->
|
| 264 |
+
<div class="tab-pane" id="statsTab">
|
| 265 |
+
<h6 class="panel-title">
|
| 266 |
+
<i class="bi bi-bar-chart me-2"></i>
|
| 267 |
+
Detection Statistics
|
| 268 |
+
</h6>
|
| 269 |
+
<div class="stats-content">
|
| 270 |
+
<div class="stats-item">
|
| 271 |
+
<label>Total Objects:</label>
|
| 272 |
+
<span id="totalObjects">0</span>
|
| 273 |
+
</div>
|
| 274 |
+
<div class="stats-item">
|
| 275 |
+
<label>Categories:</label>
|
| 276 |
+
<span id="totalCategories">0</span>
|
| 277 |
+
</div>
|
| 278 |
+
<div class="stats-item">
|
| 279 |
+
<label>Avg. Confidence:</label>
|
| 280 |
+
<span id="avgConfidence">0%</span>
|
| 281 |
+
</div>
|
| 282 |
+
<div class="stats-chart">
|
| 283 |
+
<canvas id="objectTypeChart"></canvas>
|
| 284 |
+
</div>
|
| 285 |
+
</div>
|
| 286 |
+
</div>
|
| 287 |
+
|
| 288 |
+
<!-- Audio Tab -->
|
| 289 |
+
<div class="tab-pane" id="audioTab">
|
| 290 |
+
<h6 class="panel-title">
|
| 291 |
+
<i class="bi bi-soundwave me-2"></i>
|
| 292 |
+
Audio Description
|
| 293 |
+
</h6>
|
| 294 |
+
<div class="audio-controls">
|
| 295 |
+
<div class="audio-option">
|
| 296 |
+
<label for="voiceTypeSelect">Voice Type:</label>
|
| 297 |
+
<select id="voiceTypeSelect" class="form-select form-select-sm">
|
| 298 |
+
<option value="male">Male Voice</option>
|
| 299 |
+
<option value="female">Female Voice</option>
|
| 300 |
+
</select>
|
| 301 |
+
</div>
|
| 302 |
+
<div class="audio-option">
|
| 303 |
+
<label for="speechRateSelect">Speech Rate:</label>
|
| 304 |
+
<select id="speechRateSelect" class="form-select form-select-sm">
|
| 305 |
+
<option value="0.5">Slow</option>
|
| 306 |
+
<option value="1" selected>Normal</option>
|
| 307 |
+
<option value="1.5">Fast</option>
|
| 308 |
+
</select>
|
| 309 |
+
</div>
|
| 310 |
+
<button id="generateAudioBtn" class="btn btn-generate-audio" disabled>
|
| 311 |
+
<i class="bi bi-play-circle me-2"></i>
|
| 312 |
+
Generate Audio
|
| 313 |
+
</button>
|
| 314 |
+
</div>
|
| 315 |
+
</div>
|
| 316 |
+
</div>
|
| 317 |
+
</div>
|
| 318 |
+
</div>
|
| 319 |
+
</div>
|
| 320 |
+
</div>
|
| 321 |
+
</div>
|
| 322 |
+
</div>
|
| 323 |
+
</section>
|
| 324 |
+
|
| 325 |
+
<!-- Team Section -->
|
| 326 |
+
<!-- Team Section -->
|
| 327 |
+
<section id="team" class="team-section">
|
| 328 |
+
<div class="container">
|
| 329 |
+
<div class="section-header">
|
| 330 |
+
<div class="section-pre-title">OUR AMAZING TEAM</div>
|
| 331 |
+
<h2 class="section-title">Meet The <span class="gradient-text">Experts</span></h2>
|
| 332 |
+
<p class="section-subtitle">Our dedicated team of professionals working together to build innovative computer vision solutions.</p>
|
| 333 |
+
</div>
|
| 334 |
+
|
| 335 |
+
<div class="team-container">
|
| 336 |
+
<div class="row">
|
| 337 |
+
<!-- Team Member 1 -->
|
| 338 |
+
<div class="col-lg-4 col-md-6">
|
| 339 |
+
<div class="team-card">
|
| 340 |
+
<span class="experience-badge">5+ Years Experience</span>
|
| 341 |
+
<div class="team-profile">
|
| 342 |
+
<img src="../assets/Ali hassan.jpg" alt="Team Member 1">
|
| 343 |
+
</div>
|
| 344 |
+
<div class="team-info">
|
| 345 |
+
<h4>Ali Hassan</h4>
|
| 346 |
+
<div class="team-role">Lead Developer</div>
|
| 347 |
+
<div class="team-skills">
|
| 348 |
+
<span class="skill">Full Stack</span>
|
| 349 |
+
<span class="skill">Python</span>
|
| 350 |
+
<span class="skill">Computer Vision</span>
|
| 351 |
+
<span class="skill">ML Algorithms</span>
|
| 352 |
+
</div>
|
| 353 |
+
<p class="team-contributions">Lead development of core detection models & implemented real-time object detection system.</p>
|
| 354 |
+
<div class="team-social">
|
| 355 |
+
<a href="#" class="social-icon"><i class="bi bi-github"></i></a>
|
| 356 |
+
<a href="#" class="social-icon"><i class="bi bi-linkedin"></i></a>
|
| 357 |
+
<a href="#" class="social-icon"><i class="bi bi-instagram"></i></a>
|
| 358 |
+
<a href="#" class="social-icon"><i class="bi bi-facebook"></i></a>
|
| 359 |
+
</div>
|
| 360 |
+
</div>
|
| 361 |
+
</div>
|
| 362 |
+
</div>
|
| 363 |
+
|
| 364 |
+
<!-- Team Member 2 -->
|
| 365 |
+
<div class="col-lg-4 col-md-6">
|
| 366 |
+
<div class="team-card">
|
| 367 |
+
<span class="experience-badge">4+ Years Experience</span>
|
| 368 |
+
<div class="team-profile">
|
| 369 |
+
<img src="../assets/Ali hassan.jpg" alt="Team Member 2">
|
| 370 |
+
</div>
|
| 371 |
+
<div class="team-info">
|
| 372 |
+
<h4>M Zuhaib</h4>
|
| 373 |
+
<div class="team-role">UI/UX Designer</div>
|
| 374 |
+
<div class="team-skills">
|
| 375 |
+
<span class="skill">UI Design</span>
|
| 376 |
+
<span class="skill">Figma</span>
|
| 377 |
+
<span class="skill">Front-end</span>
|
| 378 |
+
<span class="skill">CSS/SCSS</span>
|
| 379 |
+
</div>
|
| 380 |
+
<p class="team-contributions">Designed the intuitive user interface and created the responsive layout for all device types.</p>
|
| 381 |
+
<div class="team-social">
|
| 382 |
+
<a href="#" class="social-icon"><i class="bi bi-github"></i></a>
|
| 383 |
+
<a href="#" class="social-icon"><i class="bi bi-linkedin"></i></a>
|
| 384 |
+
<a href="#" class="social-icon"><i class="bi bi-instagram"></i></a>
|
| 385 |
+
<a href="#" class="social-icon"><i class="bi bi-facebook"></i></a>
|
| 386 |
+
</div>
|
| 387 |
+
</div>
|
| 388 |
+
</div>
|
| 389 |
+
</div>
|
| 390 |
+
|
| 391 |
+
<!-- Team Member 3 -->
|
| 392 |
+
<div class="col-lg-4 col-md-6">
|
| 393 |
+
<div class="team-card">
|
| 394 |
+
<span class="experience-badge">3+ Years Experience</span>
|
| 395 |
+
<div class="team-profile">
|
| 396 |
+
<img src="../assets/M Irfan.jpg" alt="Team Member 3">
|
| 397 |
+
</div>
|
| 398 |
+
<div class="team-info">
|
| 399 |
+
<h4>M Irfan</h4>
|
| 400 |
+
<div class="team-role">Backend Engineer</div>
|
| 401 |
+
<div class="team-skills">
|
| 402 |
+
<span class="skill">Node.js</span>
|
| 403 |
+
<span class="skill">MongoDB</span>
|
| 404 |
+
<span class="skill">API Design</span>
|
| 405 |
+
<span class="skill">AWS</span>
|
| 406 |
+
</div>
|
| 407 |
+
<p class="team-contributions">Developed the API infrastructure and engineered the scalable cloud deployment solution.</p>
|
| 408 |
+
<div class="team-social">
|
| 409 |
+
<a href="#" class="social-icon"><i class="bi bi-github"></i></a>
|
| 410 |
+
<a href="#" class="social-icon"><i class="bi bi-linkedin"></i></a>
|
| 411 |
+
<a href="#" class="social-icon"><i class="bi bi-instagram"></i></a>
|
| 412 |
+
<a href="#" class="social-icon"><i class="bi bi-facebook"></i></a>
|
| 413 |
+
</div>
|
| 414 |
+
</div>
|
| 415 |
+
</div>
|
| 416 |
+
</div>
|
| 417 |
+
</div>
|
| 418 |
+
</div>
|
| 419 |
+
</div>
|
| 420 |
+
</section>
|
| 421 |
+
|
| 422 |
+
<!-- Contact Modal -->
|
| 423 |
+
<div class="modal fade" id="contactModal" tabindex="-1">
|
| 424 |
+
<div class="modal-dialog modal-dialog-centered">
|
| 425 |
+
<div class="modal-content">
|
| 426 |
+
<div class="modal-header">
|
| 427 |
+
<h5 class="modal-title">Get In Touch</h5>
|
| 428 |
+
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
| 429 |
+
</div>
|
| 430 |
+
<div class="modal-body">
|
| 431 |
+
<div class="contact-form">
|
| 432 |
+
<div class="mb-3">
|
| 433 |
+
<label for="nameInput" class="form-label">Your Name</label>
|
| 434 |
+
<div class="input-with-icon">
|
| 435 |
+
<i class="bi bi-person"></i>
|
| 436 |
+
<input type="text" class="form-control" id="nameInput" placeholder="Enter your name">
|
| 437 |
+
</div>
|
| 438 |
+
</div>
|
| 439 |
+
<div class="mb-3">
|
| 440 |
+
<label for="emailInput" class="form-label">Email Address</label>
|
| 441 |
+
<div class="input-with-icon">
|
| 442 |
+
<i class="bi bi-envelope"></i>
|
| 443 |
+
<input type="email" class="form-control" id="emailInput" placeholder="Enter your email">
|
| 444 |
+
</div>
|
| 445 |
+
</div>
|
| 446 |
+
<div class="mb-3">
|
| 447 |
+
<label for="subjectInput" class="form-label">Subject</label>
|
| 448 |
+
<div class="input-with-icon">
|
| 449 |
+
<i class="bi bi-chat"></i>
|
| 450 |
+
<input type="text" class="form-control" id="subjectInput" placeholder="Message subject">
|
| 451 |
+
</div>
|
| 452 |
+
</div>
|
| 453 |
+
<div class="mb-3">
|
| 454 |
+
<label for="messageInput" class="form-label">Message</label>
|
| 455 |
+
<div class="input-with-icon textarea">
|
| 456 |
+
<i class="bi bi-pencil"></i>
|
| 457 |
+
<textarea class="form-control" id="messageInput" rows="4" placeholder="Your message"></textarea>
|
| 458 |
+
</div>
|
| 459 |
+
</div>
|
| 460 |
+
<button type="submit" class="btn btn-primary w-100">
|
| 461 |
+
<i class="bi bi-send me-2"></i>Send Message
|
| 462 |
+
</button>
|
| 463 |
+
</div>
|
| 464 |
+
</div>
|
| 465 |
+
</div>
|
| 466 |
+
</div>
|
| 467 |
+
</div>
|
| 468 |
+
|
| 469 |
+
<!-- Footer -->
|
| 470 |
+
<footer class="footer">
|
| 471 |
+
<div class="container">
|
| 472 |
+
<div class="footer-content">
|
| 473 |
+
<div class="row">
|
| 474 |
+
<div class="col-md-4">
|
| 475 |
+
<div class="footer-brand">
|
| 476 |
+
<h3>Vision<span>AI</span></h3>
|
| 477 |
+
<p>Advanced object detection with cutting-edge computer vision technology.</p>
|
| 478 |
+
<div class="footer-social">
|
| 479 |
+
<a href="#"><i class="bi bi-facebook"></i></a>
|
| 480 |
+
<a href="#"><i class="bi bi-twitter"></i></a>
|
| 481 |
+
<a href="#"><i class="bi bi-github"></i></a>
|
| 482 |
+
<a href="#"><i class="bi bi-linkedin"></i></a>
|
| 483 |
+
</div>
|
| 484 |
+
</div>
|
| 485 |
+
</div>
|
| 486 |
+
<div class="col-md-2">
|
| 487 |
+
<h5>Quick Links</h5>
|
| 488 |
+
<ul class="footer-links">
|
| 489 |
+
<li><a href="#home">Home</a></li>
|
| 490 |
+
<li><a href="#features">Features</a></li>
|
| 491 |
+
<li><a href="#detection">Demo</a></li>
|
| 492 |
+
<li><a href="#team">Team</a></li>
|
| 493 |
+
</ul>
|
| 494 |
+
</div>
|
| 495 |
+
<div class="col-md-3">
|
| 496 |
+
<h5>Resources</h5>
|
| 497 |
+
<ul class="footer-links">
|
| 498 |
+
<li><a href="#">Documentation</a></li>
|
| 499 |
+
<li><a href="#">API Reference</a></li>
|
| 500 |
+
<li><a href="#">GitHub Repository</a></li>
|
| 501 |
+
<li><a href="#">Privacy Policy</a></li>
|
| 502 |
+
</ul>
|
| 503 |
+
</div>
|
| 504 |
+
<div class="col-md-3">
|
| 505 |
+
<h5>Contact</h5>
|
| 506 |
+
<ul class="footer-contact">
|
| 507 |
+
<li><i class="bi bi-envelope"></i> ali0454hassan@gmail.com</li>
|
| 508 |
+
<li><i class="bi bi-telephone"></i> +92 302 5329536</li>
|
| 509 |
+
<li><i class="bi bi-geo-alt"></i> Lahore, Pakistan</li>
|
| 510 |
+
</ul>
|
| 511 |
+
</div>
|
| 512 |
+
</div>
|
| 513 |
+
</div>
|
| 514 |
+
<div class="footer-bottom">
|
| 515 |
+
<p>© 2025 VisionAI. All Rights Reserved.</p>
|
| 516 |
+
<p class="made-with">
|
| 517 |
+
Made with <i class="bi bi-heart-fill"></i> by Team VisionAI
|
| 518 |
+
</p>
|
| 519 |
+
</div>
|
| 520 |
+
</div>
|
| 521 |
+
</footer>
|
| 522 |
+
|
| 523 |
+
<!-- Scripts -->
|
| 524 |
+
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
| 525 |
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.9.1/chart.min.js"></script>
|
| 526 |
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.11.4/gsap.min.js"></script>
|
| 527 |
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.11.4/ScrollTrigger.min.js"></script>
|
| 528 |
+
<script src="main.js"></script>
|
| 529 |
+
</body>
|
| 530 |
+
</html>
|
main.js
ADDED
|
@@ -0,0 +1,792 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// Particle Animation Background
|
| 2 |
+
class ParticleBackground {
|
| 3 |
+
constructor(canvasId) {
|
| 4 |
+
this.canvas = document.getElementById(canvasId);
|
| 5 |
+
this.ctx = this.canvas.getContext('2d');
|
| 6 |
+
this.particles = [];
|
| 7 |
+
this.particleCount = 50;
|
| 8 |
+
this.init();
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
init() {
|
| 12 |
+
// Set canvas to full window size
|
| 13 |
+
this.resizeCanvas();
|
| 14 |
+
window.addEventListener('resize', () => this.resizeCanvas());
|
| 15 |
+
|
| 16 |
+
// Create particles
|
| 17 |
+
this.createParticles();
|
| 18 |
+
|
| 19 |
+
// Start animation loop
|
| 20 |
+
this.animate();
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
resizeCanvas() {
|
| 24 |
+
this.canvas.width = window.innerWidth;
|
| 25 |
+
this.canvas.height = window.innerHeight;
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
createParticles() {
|
| 29 |
+
this.particles = [];
|
| 30 |
+
for (let i = 0; i < this.particleCount; i++) {
|
| 31 |
+
this.particles.push({
|
| 32 |
+
x: Math.random() * this.canvas.width,
|
| 33 |
+
y: Math.random() * this.canvas.height,
|
| 34 |
+
radius: Math.random() * 3 + 1,
|
| 35 |
+
speed: Math.random() * 1 + 0.2,
|
| 36 |
+
directionX: Math.random() * 2 - 1,
|
| 37 |
+
directionY: Math.random() * 2 - 1,
|
| 38 |
+
opacity: Math.random() * 0.5 + 0.1
|
| 39 |
+
});
|
| 40 |
+
}
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
drawParticles() {
|
| 44 |
+
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
| 45 |
+
|
| 46 |
+
for (let i = 0; i < this.particles.length; i++) {
|
| 47 |
+
const p = this.particles[i];
|
| 48 |
+
|
| 49 |
+
// Draw particle
|
| 50 |
+
this.ctx.beginPath();
|
| 51 |
+
this.ctx.arc(p.x, p.y, p.radius, 0, Math.PI * 2);
|
| 52 |
+
this.ctx.fillStyle = `rgba(255, 255, 255, ${p.opacity})`;
|
| 53 |
+
this.ctx.fill();
|
| 54 |
+
|
| 55 |
+
// Update position
|
| 56 |
+
p.x += p.directionX * p.speed;
|
| 57 |
+
p.y += p.directionY * p.speed;
|
| 58 |
+
|
| 59 |
+
// Bounce off edges
|
| 60 |
+
if (p.x < 0 || p.x > this.canvas.width) p.directionX *= -1;
|
| 61 |
+
if (p.y < 0 || p.y > this.canvas.height) p.directionY *= -1;
|
| 62 |
+
|
| 63 |
+
// Draw connections
|
| 64 |
+
for (let j = i + 1; j < this.particles.length; j++) {
|
| 65 |
+
const p2 = this.particles[j];
|
| 66 |
+
const distance = Math.sqrt(
|
| 67 |
+
Math.pow(p.x - p2.x, 2) +
|
| 68 |
+
Math.pow(p.y - p2.y, 2)
|
| 69 |
+
);
|
| 70 |
+
|
| 71 |
+
if (distance < 150) {
|
| 72 |
+
this.ctx.beginPath();
|
| 73 |
+
this.ctx.strokeStyle = `rgba(255, 255, 255, ${0.2 * (1 - distance/150)})`;
|
| 74 |
+
this.ctx.lineWidth = 0.5;
|
| 75 |
+
this.ctx.moveTo(p.x, p.y);
|
| 76 |
+
this.ctx.lineTo(p2.x, p2.y);
|
| 77 |
+
this.ctx.stroke();
|
| 78 |
+
}
|
| 79 |
+
}
|
| 80 |
+
}
|
| 81 |
+
}
|
| 82 |
+
|
| 83 |
+
animate() {
|
| 84 |
+
this.drawParticles();
|
| 85 |
+
requestAnimationFrame(() => this.animate());
|
| 86 |
+
}
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
+
/* ==============================================
|
| 90 |
+
Object Detection Handler
|
| 91 |
+
================================================== */
|
| 92 |
+
class VisionAIDetector {
|
| 93 |
+
constructor() {
|
| 94 |
+
// DOM Elements
|
| 95 |
+
this.modelSelect = document.getElementById('modelSelect');
|
| 96 |
+
this.thresholdRange = document.getElementById('thresholdRange');
|
| 97 |
+
this.thresholdValue = document.getElementById('thresholdValue');
|
| 98 |
+
this.uploadBtn = document.getElementById('uploadBtn');
|
| 99 |
+
this.imageUpload = document.getElementById('imageUpload');
|
| 100 |
+
this.liveCaptureBtn = document.getElementById('liveCaptureBtn');
|
| 101 |
+
this.screenshotBtn = document.getElementById('screenshotBtn');
|
| 102 |
+
this.liveVideo = document.getElementById('liveVideo');
|
| 103 |
+
this.detectedCanvas = document.getElementById('detectedCanvas');
|
| 104 |
+
this.loadingOverlay = document.getElementById('loadingOverlay');
|
| 105 |
+
this.modelLabel = document.getElementById('modelLabel');
|
| 106 |
+
this.objectList = document.getElementById('objectList');
|
| 107 |
+
this.objectCounter = document.querySelector('.object-counter');
|
| 108 |
+
this.totalObjects = document.getElementById('totalObjects');
|
| 109 |
+
this.totalCategories = document.getElementById('totalCategories');
|
| 110 |
+
this.avgConfidence = document.getElementById('avgConfidence');
|
| 111 |
+
this.objectTypeChart = document.getElementById('objectTypeChart');
|
| 112 |
+
this.generateAudioBtn = document.getElementById('generateAudioBtn');
|
| 113 |
+
this.voiceTypeSelect = document.getElementById('voiceTypeSelect');
|
| 114 |
+
this.speechRateSelect = document.getElementById('speechRateSelect');
|
| 115 |
+
|
| 116 |
+
// Tab panel elements
|
| 117 |
+
this.objectsTab = document.querySelector('[data-tab="objects"]');
|
| 118 |
+
this.statsTab = document.querySelector('[data-tab="stats"]');
|
| 119 |
+
this.audioTab = document.querySelector('[data-tab="audio"]');
|
| 120 |
+
this.objectsTabPane = document.getElementById('objectsTab');
|
| 121 |
+
this.statsTabPane = document.getElementById('statsTab');
|
| 122 |
+
this.audioTabPane = document.getElementById('audioTab');
|
| 123 |
+
|
| 124 |
+
// Canvas context
|
| 125 |
+
this.ctx = this.detectedCanvas.getContext('2d');
|
| 126 |
+
|
| 127 |
+
// State variables
|
| 128 |
+
this.stream = null;
|
| 129 |
+
this.chart = null;
|
| 130 |
+
this.detectionResults = null;
|
| 131 |
+
this.currentImageDataUrl = null; // Store the current image for reprocessing
|
| 132 |
+
this.processingLock = false; // Lock to prevent multiple simultaneous processings
|
| 133 |
+
|
| 134 |
+
// Backend URL - change this to match your production setup
|
| 135 |
+
this.apiUrl = 'http://localhost:5000';
|
| 136 |
+
|
| 137 |
+
// Initialize
|
| 138 |
+
this.init();
|
| 139 |
+
}
|
| 140 |
+
|
| 141 |
+
init() {
|
| 142 |
+
// Set initial values
|
| 143 |
+
this.modelLabel.textContent = this.modelSelect.value.split('.')[0];
|
| 144 |
+
|
| 145 |
+
// Event listeners for image input
|
| 146 |
+
this.uploadBtn.addEventListener('click', () => this.imageUpload.click());
|
| 147 |
+
this.imageUpload.addEventListener('change', (e) => this.handleImageUpload(e));
|
| 148 |
+
this.liveCaptureBtn.addEventListener('click', () => this.toggleLiveCapture());
|
| 149 |
+
this.screenshotBtn.addEventListener('click', () => this.captureScreenshot());
|
| 150 |
+
|
| 151 |
+
// Event listeners for settings changes with real-time processing
|
| 152 |
+
this.modelSelect.addEventListener('change', () => {
|
| 153 |
+
this.modelLabel.textContent = this.modelSelect.value.split('.')[0];
|
| 154 |
+
this.reprocessCurrentImage();
|
| 155 |
+
});
|
| 156 |
+
|
| 157 |
+
this.thresholdRange.addEventListener('input', () => {
|
| 158 |
+
this.thresholdValue.textContent = `${this.thresholdRange.value}%`;
|
| 159 |
+
// Debounce threshold changes to prevent too many API calls
|
| 160 |
+
clearTimeout(this.thresholdTimeout);
|
| 161 |
+
this.thresholdTimeout = setTimeout(() => {
|
| 162 |
+
this.reprocessCurrentImage();
|
| 163 |
+
}, 300);
|
| 164 |
+
});
|
| 165 |
+
|
| 166 |
+
// Tab panel handlers - Enhanced for direct tab navigation
|
| 167 |
+
this.objectsTab.addEventListener('click', () => this.switchTab('objects'));
|
| 168 |
+
this.statsTab.addEventListener('click', () => this.switchTab('stats'));
|
| 169 |
+
this.audioTab.addEventListener('click', () => this.switchTab('audio'));
|
| 170 |
+
|
| 171 |
+
// Initialize charts
|
| 172 |
+
this.initChart();
|
| 173 |
+
}
|
| 174 |
+
|
| 175 |
+
/* ==============================================
|
| 176 |
+
Tab Switching Logic under Detection Section
|
| 177 |
+
================================================== */
|
| 178 |
+
|
| 179 |
+
switchTab(tabId) {
|
| 180 |
+
// Remove active class from all tabs
|
| 181 |
+
[this.objectsTab, this.statsTab, this.audioTab].forEach(tab =>
|
| 182 |
+
tab.classList.remove('active'));
|
| 183 |
+
|
| 184 |
+
// Hide all panes first
|
| 185 |
+
this.objectsTabPane.style.display = 'none';
|
| 186 |
+
this.statsTabPane.style.display = 'none';
|
| 187 |
+
this.audioTabPane.style.display = 'none';
|
| 188 |
+
|
| 189 |
+
// Add active class to selected tab and show only its pane
|
| 190 |
+
if (tabId === 'objects') {
|
| 191 |
+
this.objectsTab.classList.add('active');
|
| 192 |
+
this.objectsTabPane.style.display = 'block';
|
| 193 |
+
} else if (tabId === 'stats') {
|
| 194 |
+
this.statsTab.classList.add('active');
|
| 195 |
+
this.statsTabPane.style.display = 'block';
|
| 196 |
+
// Refresh stats content if we have results
|
| 197 |
+
if (this.detectionResults) {
|
| 198 |
+
this.updateStats(this.detectionResults);
|
| 199 |
+
}
|
| 200 |
+
} else if (tabId === 'audio') {
|
| 201 |
+
this.audioTab.classList.add('active');
|
| 202 |
+
this.objectsTabPane.style.display = 'block';
|
| 203 |
+
this.audioTabPane.style.display = 'block';
|
| 204 |
+
}
|
| 205 |
+
}
|
| 206 |
+
|
| 207 |
+
|
| 208 |
+
async handleImageUpload(e) {
|
| 209 |
+
const file = e.target.files[0];
|
| 210 |
+
if (!file) return;
|
| 211 |
+
|
| 212 |
+
try {
|
| 213 |
+
// Show loading overlay
|
| 214 |
+
this.loadingOverlay.style.display = 'flex';
|
| 215 |
+
|
| 216 |
+
// Read the image file
|
| 217 |
+
const imageDataUrl = await this.readFileAsDataURL(file);
|
| 218 |
+
this.currentImageDataUrl = imageDataUrl; // Store for later reprocessing
|
| 219 |
+
|
| 220 |
+
// Load image to get dimensions
|
| 221 |
+
const img = await this.loadImage(imageDataUrl);
|
| 222 |
+
|
| 223 |
+
// Set canvas dimensions
|
| 224 |
+
this.detectedCanvas.width = img.width;
|
| 225 |
+
this.detectedCanvas.height = img.height;
|
| 226 |
+
|
| 227 |
+
// Draw original image on canvas
|
| 228 |
+
this.ctx.drawImage(img, 0, 0);
|
| 229 |
+
|
| 230 |
+
// Get selected model and confidence threshold
|
| 231 |
+
const model = this.modelSelect.value;
|
| 232 |
+
const confidenceThreshold = parseInt(this.thresholdRange.value) / 100;
|
| 233 |
+
|
| 234 |
+
// Process the image
|
| 235 |
+
await this.processImage(imageDataUrl, model, confidenceThreshold);
|
| 236 |
+
|
| 237 |
+
// Enable screenshot button
|
| 238 |
+
this.screenshotBtn.disabled = false;
|
| 239 |
+
|
| 240 |
+
// Hide loading overlay
|
| 241 |
+
this.loadingOverlay.style.display = 'none';
|
| 242 |
+
} catch (error) {
|
| 243 |
+
console.error('Error processing image:', error);
|
| 244 |
+
this.showError('Failed to process image. Please try again.');
|
| 245 |
+
this.loadingOverlay.style.display = 'none';
|
| 246 |
+
}
|
| 247 |
+
}
|
| 248 |
+
|
| 249 |
+
async reprocessCurrentImage() {
|
| 250 |
+
// If no image is loaded or processing is already happening, do nothing
|
| 251 |
+
if (!this.currentImageDataUrl || this.processingLock) return;
|
| 252 |
+
|
| 253 |
+
this.processingLock = true;
|
| 254 |
+
|
| 255 |
+
try {
|
| 256 |
+
// Show loading overlay
|
| 257 |
+
this.loadingOverlay.style.display = 'flex';
|
| 258 |
+
|
| 259 |
+
// Get current settings
|
| 260 |
+
const model = this.modelSelect.value;
|
| 261 |
+
const confidenceThreshold = parseInt(this.thresholdRange.value) / 100;
|
| 262 |
+
|
| 263 |
+
// Reprocess with new settings
|
| 264 |
+
await this.processImage(this.currentImageDataUrl, model, confidenceThreshold);
|
| 265 |
+
|
| 266 |
+
// Hide loading overlay
|
| 267 |
+
this.loadingOverlay.style.display = 'none';
|
| 268 |
+
} catch (error) {
|
| 269 |
+
console.error('Error reprocessing image:', error);
|
| 270 |
+
this.showError('Failed to reprocess image. Please try again.');
|
| 271 |
+
this.loadingOverlay.style.display = 'none';
|
| 272 |
+
} finally {
|
| 273 |
+
this.processingLock = false;
|
| 274 |
+
}
|
| 275 |
+
}
|
| 276 |
+
|
| 277 |
+
async toggleLiveCapture() {
|
| 278 |
+
if (!this.stream) {
|
| 279 |
+
// Start camera
|
| 280 |
+
try {
|
| 281 |
+
this.stream = await navigator.mediaDevices.getUserMedia({
|
| 282 |
+
video: {
|
| 283 |
+
facingMode: 'environment',
|
| 284 |
+
width: { ideal: 1280 },
|
| 285 |
+
height: { ideal: 720 }
|
| 286 |
+
}
|
| 287 |
+
});
|
| 288 |
+
|
| 289 |
+
// Display video
|
| 290 |
+
this.liveVideo.srcObject = this.stream;
|
| 291 |
+
this.liveVideo.style.display = 'block';
|
| 292 |
+
this.detectedCanvas.style.display = 'none';
|
| 293 |
+
this.liveVideo.play();
|
| 294 |
+
|
| 295 |
+
// Change button text
|
| 296 |
+
this.liveCaptureBtn.innerHTML = '<i class="bi bi-camera"></i><span>Capture</span>';
|
| 297 |
+
|
| 298 |
+
// Enable screenshot button
|
| 299 |
+
this.screenshotBtn.disabled = false;
|
| 300 |
+
} catch (error) {
|
| 301 |
+
console.error('Error accessing camera:', error);
|
| 302 |
+
this.showError('Could not access camera. Please check permissions.');
|
| 303 |
+
}
|
| 304 |
+
} else {
|
| 305 |
+
// Take a snapshot and process
|
| 306 |
+
this.captureScreenshot();
|
| 307 |
+
}
|
| 308 |
+
}
|
| 309 |
+
|
| 310 |
+
captureScreenshot() {
|
| 311 |
+
if (!this.stream && this.liveVideo.style.display !== 'block') return;
|
| 312 |
+
|
| 313 |
+
try {
|
| 314 |
+
// Show loading overlay
|
| 315 |
+
this.loadingOverlay.style.display = 'flex';
|
| 316 |
+
|
| 317 |
+
// Create temporary canvas to capture frame
|
| 318 |
+
const tempCanvas = document.createElement('canvas');
|
| 319 |
+
tempCanvas.width = this.liveVideo.videoWidth;
|
| 320 |
+
tempCanvas.height = this.liveVideo.videoHeight;
|
| 321 |
+
const tempCtx = tempCanvas.getContext('2d');
|
| 322 |
+
tempCtx.drawImage(this.liveVideo, 0, 0);
|
| 323 |
+
|
| 324 |
+
// Convert to data URL
|
| 325 |
+
const imageDataUrl = tempCanvas.toDataURL('image/jpeg');
|
| 326 |
+
this.currentImageDataUrl = imageDataUrl; // Store for later reprocessing
|
| 327 |
+
|
| 328 |
+
// Set canvas dimensions
|
| 329 |
+
this.detectedCanvas.width = tempCanvas.width;
|
| 330 |
+
this.detectedCanvas.height = tempCanvas.height;
|
| 331 |
+
|
| 332 |
+
// Draw captured frame on main canvas
|
| 333 |
+
this.ctx.drawImage(tempCanvas, 0, 0);
|
| 334 |
+
|
| 335 |
+
// Stop video stream
|
| 336 |
+
this.stopVideoStream();
|
| 337 |
+
|
| 338 |
+
// Show canvas
|
| 339 |
+
this.detectedCanvas.style.display = 'block';
|
| 340 |
+
|
| 341 |
+
// Get selected model and confidence threshold
|
| 342 |
+
const model = this.modelSelect.value;
|
| 343 |
+
const confidenceThreshold = parseInt(this.thresholdRange.value) / 100;
|
| 344 |
+
|
| 345 |
+
// Process the image
|
| 346 |
+
this.processImage(imageDataUrl, model, confidenceThreshold);
|
| 347 |
+
|
| 348 |
+
} catch (error) {
|
| 349 |
+
console.error('Error capturing screenshot:', error);
|
| 350 |
+
this.showError('Failed to capture image. Please try again.');
|
| 351 |
+
this.loadingOverlay.style.display = 'none';
|
| 352 |
+
}
|
| 353 |
+
}
|
| 354 |
+
|
| 355 |
+
stopVideoStream() {
|
| 356 |
+
if (this.stream) {
|
| 357 |
+
this.stream.getTracks().forEach(track => track.stop());
|
| 358 |
+
this.stream = null;
|
| 359 |
+
this.liveVideo.style.display = 'none';
|
| 360 |
+
this.liveCaptureBtn.innerHTML = '<i class="bi bi-camera-video"></i><span>Live Camera</span>';
|
| 361 |
+
}
|
| 362 |
+
}
|
| 363 |
+
|
| 364 |
+
async processImage(imageDataUrl, selectedModel, confidenceThreshold) {
|
| 365 |
+
try {
|
| 366 |
+
// Make API request to backend
|
| 367 |
+
const response = await fetch(`${this.apiUrl}/detect`, {
|
| 368 |
+
method: 'POST',
|
| 369 |
+
headers: { 'Content-Type': 'application/json' },
|
| 370 |
+
body: JSON.stringify({
|
| 371 |
+
image: imageDataUrl,
|
| 372 |
+
model: selectedModel,
|
| 373 |
+
confidence: confidenceThreshold
|
| 374 |
+
})
|
| 375 |
+
});
|
| 376 |
+
|
| 377 |
+
if (!response.ok) {
|
| 378 |
+
throw new Error(`Server returned ${response.status}`);
|
| 379 |
+
}
|
| 380 |
+
|
| 381 |
+
const data = await response.json();
|
| 382 |
+
|
| 383 |
+
// Store results for use in other tabs
|
| 384 |
+
this.detectionResults = data;
|
| 385 |
+
|
| 386 |
+
// Get original image from canvas (important to preserve it when reprocessing)
|
| 387 |
+
const originalImage = new Image();
|
| 388 |
+
originalImage.src = imageDataUrl;
|
| 389 |
+
|
| 390 |
+
// Wait for image to load
|
| 391 |
+
await new Promise(resolve => {
|
| 392 |
+
originalImage.onload = resolve;
|
| 393 |
+
});
|
| 394 |
+
|
| 395 |
+
// Clear canvas and redraw original image
|
| 396 |
+
this.ctx.clearRect(0, 0, this.detectedCanvas.width, this.detectedCanvas.height);
|
| 397 |
+
this.ctx.drawImage(originalImage, 0, 0, this.detectedCanvas.width, this.detectedCanvas.height);
|
| 398 |
+
|
| 399 |
+
// Draw detection results
|
| 400 |
+
this.drawDetections(data.detections);
|
| 401 |
+
|
| 402 |
+
// Update object list
|
| 403 |
+
this.updateObjectList(data.grouped_objects);
|
| 404 |
+
|
| 405 |
+
// Update stats
|
| 406 |
+
this.updateStats(data);
|
| 407 |
+
|
| 408 |
+
// Enable audio generation
|
| 409 |
+
this.generateAudioBtn.disabled = false;
|
| 410 |
+
this.generateAudioBtn.onclick = () => this.generateAudioDescription(data.grouped_objects);
|
| 411 |
+
|
| 412 |
+
// Hide loading overlay
|
| 413 |
+
this.loadingOverlay.style.display = 'none';
|
| 414 |
+
|
| 415 |
+
} catch (error) {
|
| 416 |
+
console.error('Detection Error:', error);
|
| 417 |
+
this.showError('Detection failed. Please try again.');
|
| 418 |
+
this.loadingOverlay.style.display = 'none';
|
| 419 |
+
}
|
| 420 |
+
}
|
| 421 |
+
|
| 422 |
+
drawDetections(detections) {
|
| 423 |
+
// Draw each detection
|
| 424 |
+
detections.forEach(detection => {
|
| 425 |
+
const [x, y, width, height] = detection.bbox;
|
| 426 |
+
|
| 427 |
+
// Draw bounding box
|
| 428 |
+
this.ctx.beginPath();
|
| 429 |
+
this.ctx.rect(x, y, width, height);
|
| 430 |
+
this.ctx.lineWidth = 3;
|
| 431 |
+
this.ctx.strokeStyle = 'rgba(255, 0, 0, 0.8)';
|
| 432 |
+
this.ctx.stroke();
|
| 433 |
+
|
| 434 |
+
// Create label background
|
| 435 |
+
const label = `${detection.class} (${(detection.confidence * 100).toFixed(0)}%)`;
|
| 436 |
+
this.ctx.font = '16px Arial';
|
| 437 |
+
const textWidth = this.ctx.measureText(label).width + 10;
|
| 438 |
+
this.ctx.fillStyle = 'rgba(255, 0, 0, 0.7)';
|
| 439 |
+
this.ctx.fillRect(
|
| 440 |
+
x,
|
| 441 |
+
y > 25 ? y - 25 : y,
|
| 442 |
+
textWidth,
|
| 443 |
+
25
|
| 444 |
+
);
|
| 445 |
+
|
| 446 |
+
// Draw label text
|
| 447 |
+
this.ctx.fillStyle = 'white';
|
| 448 |
+
this.ctx.fillText(
|
| 449 |
+
label,
|
| 450 |
+
x + 5,
|
| 451 |
+
y > 25 ? y - 7 : y + 18
|
| 452 |
+
);
|
| 453 |
+
});
|
| 454 |
+
}
|
| 455 |
+
|
| 456 |
+
updateObjectList(groupedObjects) {
|
| 457 |
+
// Clear previous list
|
| 458 |
+
this.objectList.innerHTML = '';
|
| 459 |
+
|
| 460 |
+
if (groupedObjects.length === 0) {
|
| 461 |
+
const li = document.createElement('li');
|
| 462 |
+
li.className = 'no-objects';
|
| 463 |
+
li.textContent = 'No objects detected';
|
| 464 |
+
this.objectList.appendChild(li);
|
| 465 |
+
this.objectCounter.textContent = '0';
|
| 466 |
+
return;
|
| 467 |
+
}
|
| 468 |
+
|
| 469 |
+
// Update counter
|
| 470 |
+
const totalCount = groupedObjects.reduce((sum, obj) => sum + obj.count, 0);
|
| 471 |
+
this.objectCounter.textContent = totalCount;
|
| 472 |
+
|
| 473 |
+
// Add each object group to the list
|
| 474 |
+
groupedObjects.forEach(group => {
|
| 475 |
+
const li = document.createElement('li');
|
| 476 |
+
|
| 477 |
+
const confidence = this.detectionResults.detections
|
| 478 |
+
.filter(d => d.class === group.class)
|
| 479 |
+
.reduce((sum, d) => sum + d.confidence, 0) / group.count;
|
| 480 |
+
|
| 481 |
+
li.innerHTML = `
|
| 482 |
+
<div class="object-info">
|
| 483 |
+
<div class="object-name">${group.class}</div>
|
| 484 |
+
<span class="object-confidence">${(confidence * 100).toFixed(0)}% confidence</span>
|
| 485 |
+
</div>
|
| 486 |
+
<div class="object-count">
|
| 487 |
+
<span>${group.count}</span>
|
| 488 |
+
</div>
|
| 489 |
+
`;
|
| 490 |
+
this.objectList.appendChild(li);
|
| 491 |
+
});
|
| 492 |
+
}
|
| 493 |
+
|
| 494 |
+
initChart() {
|
| 495 |
+
if (this.chart) {
|
| 496 |
+
this.chart.destroy();
|
| 497 |
+
}
|
| 498 |
+
|
| 499 |
+
const ctx = this.objectTypeChart.getContext('2d');
|
| 500 |
+
this.chart = new Chart(ctx, {
|
| 501 |
+
type: 'doughnut',
|
| 502 |
+
data: {
|
| 503 |
+
labels: [],
|
| 504 |
+
datasets: [{
|
| 505 |
+
data: [],
|
| 506 |
+
backgroundColor: [
|
| 507 |
+
'rgba(255, 99, 132, 0.8)',
|
| 508 |
+
'rgba(54, 162, 235, 0.8)',
|
| 509 |
+
'rgba(255, 206, 86, 0.8)',
|
| 510 |
+
'rgba(75, 192, 192, 0.8)',
|
| 511 |
+
'rgba(153, 102, 255, 0.8)',
|
| 512 |
+
'rgba(255, 159, 64, 0.8)',
|
| 513 |
+
'rgba(199, 199, 199, 0.8)',
|
| 514 |
+
'rgba(83, 102, 255, 0.8)',
|
| 515 |
+
'rgba(40, 159, 64, 0.8)',
|
| 516 |
+
'rgba(210, 199, 199, 0.8)'
|
| 517 |
+
],
|
| 518 |
+
borderWidth: 1
|
| 519 |
+
}]
|
| 520 |
+
},
|
| 521 |
+
options: {
|
| 522 |
+
responsive: true,
|
| 523 |
+
maintainAspectRatio: false,
|
| 524 |
+
plugins: {
|
| 525 |
+
legend: {
|
| 526 |
+
position: 'right',
|
| 527 |
+
labels: {
|
| 528 |
+
color: '#fff',
|
| 529 |
+
font: {
|
| 530 |
+
size: 12
|
| 531 |
+
}
|
| 532 |
+
}
|
| 533 |
+
}
|
| 534 |
+
}
|
| 535 |
+
}
|
| 536 |
+
});
|
| 537 |
+
}
|
| 538 |
+
|
| 539 |
+
updateStats(data) {
|
| 540 |
+
if (!data || !data.grouped_objects) return;
|
| 541 |
+
|
| 542 |
+
const { detections, grouped_objects } = data;
|
| 543 |
+
|
| 544 |
+
// Basic stats
|
| 545 |
+
const totalCount = grouped_objects.reduce((sum, obj) => sum + obj.count, 0);
|
| 546 |
+
const categoryCount = grouped_objects.length;
|
| 547 |
+
const avgConfidence = detections.length > 0
|
| 548 |
+
? detections.reduce((sum, d) => sum + d.confidence, 0) / detections.length * 100
|
| 549 |
+
: 0;
|
| 550 |
+
|
| 551 |
+
// Update DOM
|
| 552 |
+
this.totalObjects.textContent = totalCount;
|
| 553 |
+
this.totalCategories.textContent = categoryCount;
|
| 554 |
+
this.avgConfidence.textContent = `${avgConfidence.toFixed(1)}%`;
|
| 555 |
+
|
| 556 |
+
// Update chart
|
| 557 |
+
this.updateChart(grouped_objects);
|
| 558 |
+
}
|
| 559 |
+
|
| 560 |
+
updateChart(groupedObjects) {
|
| 561 |
+
// Only take top 5 categories if more than 5
|
| 562 |
+
let chartData = [...groupedObjects];
|
| 563 |
+
if (chartData.length > 5) {
|
| 564 |
+
chartData.sort((a, b) => b.count - a.count);
|
| 565 |
+
const others = chartData.slice(5).reduce(
|
| 566 |
+
(sum, obj) => sum + obj.count, 0
|
| 567 |
+
);
|
| 568 |
+
chartData = chartData.slice(0, 5);
|
| 569 |
+
if (others > 0) {
|
| 570 |
+
chartData.push({ class: 'Others', count: others });
|
| 571 |
+
}
|
| 572 |
+
}
|
| 573 |
+
|
| 574 |
+
// Update chart data
|
| 575 |
+
this.chart.data.labels = chartData.map(obj => obj.class);
|
| 576 |
+
this.chart.data.datasets[0].data = chartData.map(obj => obj.count);
|
| 577 |
+
this.chart.update();
|
| 578 |
+
}
|
| 579 |
+
|
| 580 |
+
generateAudioDescription(groupedObjects) {
|
| 581 |
+
// Cancel any ongoing speech
|
| 582 |
+
window.speechSynthesis.cancel();
|
| 583 |
+
|
| 584 |
+
if (groupedObjects.length === 0) return;
|
| 585 |
+
|
| 586 |
+
// Get settings
|
| 587 |
+
const voiceType = this.voiceTypeSelect.value;
|
| 588 |
+
const speechRate = parseFloat(this.speechRateSelect.value);
|
| 589 |
+
|
| 590 |
+
// Build description
|
| 591 |
+
let description;
|
| 592 |
+
if (groupedObjects.length === 1) {
|
| 593 |
+
const obj = groupedObjects[0];
|
| 594 |
+
description = `I detected ${obj.count} ${obj.class}${obj.count > 1 ? 's' : ''}.`;
|
| 595 |
+
} else {
|
| 596 |
+
const lastItem = groupedObjects[groupedObjects.length - 1];
|
| 597 |
+
const itemsExceptLast = groupedObjects.slice(0, -1).map(
|
| 598 |
+
obj => `${obj.count} ${obj.class}${obj.count > 1 ? 's' : ''}`
|
| 599 |
+
).join(', ');
|
| 600 |
+
|
| 601 |
+
description = `I detected ${itemsExceptLast} and ${lastItem.count} ${lastItem.class}${lastItem.count > 1 ? 's' : ''}.`;
|
| 602 |
+
}
|
| 603 |
+
|
| 604 |
+
// Create utterance
|
| 605 |
+
const utterance = new SpeechSynthesisUtterance(description);
|
| 606 |
+
|
| 607 |
+
// Get available voices
|
| 608 |
+
const voices = window.speechSynthesis.getVoices();
|
| 609 |
+
if (voices.length === 0) {
|
| 610 |
+
// If voices aren't loaded yet, wait and try again
|
| 611 |
+
window.speechSynthesis.onvoiceschanged = () => {
|
| 612 |
+
this.generateAudioDescription(groupedObjects);
|
| 613 |
+
};
|
| 614 |
+
return;
|
| 615 |
+
}
|
| 616 |
+
|
| 617 |
+
// Select voice based on gender preference
|
| 618 |
+
let selectedVoice;
|
| 619 |
+
if (voiceType === 'male') {
|
| 620 |
+
selectedVoice = voices.find(v =>
|
| 621 |
+
v.name.toLowerCase().includes('male') ||
|
| 622 |
+
(!v.name.toLowerCase().includes('female') && v.lang.startsWith('en'))
|
| 623 |
+
);
|
| 624 |
+
} else {
|
| 625 |
+
selectedVoice = voices.find(v =>
|
| 626 |
+
v.name.toLowerCase().includes('female') ||
|
| 627 |
+
v.lang.startsWith('en')
|
| 628 |
+
);
|
| 629 |
+
}
|
| 630 |
+
|
| 631 |
+
// Set voice and rate
|
| 632 |
+
if (selectedVoice) utterance.voice = selectedVoice;
|
| 633 |
+
utterance.rate = speechRate;
|
| 634 |
+
|
| 635 |
+
// Speak
|
| 636 |
+
window.speechSynthesis.speak(utterance);
|
| 637 |
+
}
|
| 638 |
+
|
| 639 |
+
showError(message) {
|
| 640 |
+
this.objectList.innerHTML = `
|
| 641 |
+
<li class="no-objects error">
|
| 642 |
+
<i class="bi bi-exclamation-triangle"></i>
|
| 643 |
+
${message}
|
| 644 |
+
</li>
|
| 645 |
+
`;
|
| 646 |
+
}
|
| 647 |
+
|
| 648 |
+
// Utility methods
|
| 649 |
+
readFileAsDataURL(file) {
|
| 650 |
+
return new Promise((resolve, reject) => {
|
| 651 |
+
const reader = new FileReader();
|
| 652 |
+
reader.onload = e => resolve(e.target.result);
|
| 653 |
+
reader.onerror = e => reject(e);
|
| 654 |
+
reader.readAsDataURL(file);
|
| 655 |
+
});
|
| 656 |
+
}
|
| 657 |
+
|
| 658 |
+
loadImage(src) {
|
| 659 |
+
return new Promise((resolve, reject) => {
|
| 660 |
+
const img = new Image();
|
| 661 |
+
img.onload = () => resolve(img);
|
| 662 |
+
img.onerror = reject;
|
| 663 |
+
img.src = src;
|
| 664 |
+
});
|
| 665 |
+
}
|
| 666 |
+
}
|
| 667 |
+
|
| 668 |
+
// Smooth Scrolling for Navigation
|
| 669 |
+
function initSmoothScrolling() {
|
| 670 |
+
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
|
| 671 |
+
anchor.addEventListener('click', function(e) {
|
| 672 |
+
e.preventDefault();
|
| 673 |
+
|
| 674 |
+
// Don't scroll for modal triggers
|
| 675 |
+
if (this.getAttribute('data-bs-toggle') === 'modal') return;
|
| 676 |
+
|
| 677 |
+
const targetId = this.getAttribute('href');
|
| 678 |
+
const targetElement = document.querySelector(targetId);
|
| 679 |
+
|
| 680 |
+
if (targetElement) {
|
| 681 |
+
const navbarHeight = document.querySelector('.navbar').offsetHeight;
|
| 682 |
+
const targetPosition = targetElement.getBoundingClientRect().top + window.pageYOffset - navbarHeight;
|
| 683 |
+
|
| 684 |
+
window.scrollTo({
|
| 685 |
+
top: targetPosition,
|
| 686 |
+
behavior: 'smooth'
|
| 687 |
+
});
|
| 688 |
+
}
|
| 689 |
+
});
|
| 690 |
+
});
|
| 691 |
+
}
|
| 692 |
+
|
| 693 |
+
// Animation on scroll using GSAP
|
| 694 |
+
function initScrollAnimations() {
|
| 695 |
+
// Register ScrollTrigger plugin
|
| 696 |
+
gsap.registerPlugin(ScrollTrigger);
|
| 697 |
+
|
| 698 |
+
// Animate feature cards
|
| 699 |
+
const featureCards = document.querySelectorAll('.feature-card');
|
| 700 |
+
featureCards.forEach((card, index) => {
|
| 701 |
+
gsap.fromTo(
|
| 702 |
+
card,
|
| 703 |
+
{ y: 50, opacity: 0 },
|
| 704 |
+
{
|
| 705 |
+
y: 0,
|
| 706 |
+
opacity: 1,
|
| 707 |
+
duration: 0.6,
|
| 708 |
+
delay: index * 0.1,
|
| 709 |
+
scrollTrigger: {
|
| 710 |
+
trigger: card,
|
| 711 |
+
start: "top 85%",
|
| 712 |
+
toggleActions: "play none none none"
|
| 713 |
+
}
|
| 714 |
+
}
|
| 715 |
+
);
|
| 716 |
+
});
|
| 717 |
+
|
| 718 |
+
// Animate team cards
|
| 719 |
+
const teamCards = document.querySelectorAll('.team-card');
|
| 720 |
+
teamCards.forEach((card, index) => {
|
| 721 |
+
gsap.fromTo(
|
| 722 |
+
card,
|
| 723 |
+
{ y: 50, opacity: 0 },
|
| 724 |
+
{
|
| 725 |
+
y: 0,
|
| 726 |
+
opacity: 1,
|
| 727 |
+
duration: 0.6,
|
| 728 |
+
delay: index * 0.1,
|
| 729 |
+
scrollTrigger: {
|
| 730 |
+
trigger: card,
|
| 731 |
+
start: "top 85%",
|
| 732 |
+
toggleActions: "play none none none"
|
| 733 |
+
}
|
| 734 |
+
}
|
| 735 |
+
);
|
| 736 |
+
});
|
| 737 |
+
|
| 738 |
+
// Animate section headers
|
| 739 |
+
const sectionHeaders = document.querySelectorAll('.section-header');
|
| 740 |
+
sectionHeaders.forEach((header) => {
|
| 741 |
+
gsap.fromTo(
|
| 742 |
+
header,
|
| 743 |
+
{ y: 30, opacity: 0 },
|
| 744 |
+
{
|
| 745 |
+
y: 0,
|
| 746 |
+
opacity: 1,
|
| 747 |
+
duration: 0.8,
|
| 748 |
+
scrollTrigger: {
|
| 749 |
+
trigger: header,
|
| 750 |
+
start: "top 85%",
|
| 751 |
+
toggleActions: "play none none none"
|
| 752 |
+
}
|
| 753 |
+
}
|
| 754 |
+
);
|
| 755 |
+
});
|
| 756 |
+
}
|
| 757 |
+
|
| 758 |
+
// Navbar background on scroll
|
| 759 |
+
function initNavbarScroll() {
|
| 760 |
+
const navbar = document.querySelector('.navbar');
|
| 761 |
+
|
| 762 |
+
window.addEventListener('scroll', () => {
|
| 763 |
+
if (window.scrollY > 50) {
|
| 764 |
+
navbar.classList.add('scrolled');
|
| 765 |
+
} else {
|
| 766 |
+
navbar.classList.remove('scrolled');
|
| 767 |
+
}
|
| 768 |
+
});
|
| 769 |
+
}
|
| 770 |
+
|
| 771 |
+
// Document Ready
|
| 772 |
+
document.addEventListener('DOMContentLoaded', () => {
|
| 773 |
+
// Initialize particle background
|
| 774 |
+
new ParticleBackground('particleCanvas');
|
| 775 |
+
|
| 776 |
+
// Initialize vision AI detector
|
| 777 |
+
const detector = new VisionAIDetector();
|
| 778 |
+
|
| 779 |
+
// Initialize smooth scrolling
|
| 780 |
+
initSmoothScrolling();
|
| 781 |
+
|
| 782 |
+
// Initialize animations
|
| 783 |
+
initScrollAnimations();
|
| 784 |
+
|
| 785 |
+
// Initialize navbar scroll effect
|
| 786 |
+
initNavbarScroll();
|
| 787 |
+
|
| 788 |
+
// Handle voice API loading
|
| 789 |
+
window.speechSynthesis.onvoiceschanged = () => {
|
| 790 |
+
window.speechSynthesis.getVoices();
|
| 791 |
+
};
|
| 792 |
+
});
|
requirements.txt
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# File: backend/requirements.txt
|
| 2 |
+
flask
|
| 3 |
+
flask-cors
|
| 4 |
+
ultralytics
|
| 5 |
+
opencv-python-headless
|
| 6 |
+
numpy
|
| 7 |
+
pillow
|
| 8 |
+
torch
|
| 9 |
+
torchvision
|
styles.css
ADDED
|
@@ -0,0 +1,1159 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* ============================
|
| 2 |
+
GLOBAL STYLES
|
| 3 |
+
============================= */
|
| 4 |
+
:root {
|
| 5 |
+
/* Main color scheme */
|
| 6 |
+
--primary: #6366f1;
|
| 7 |
+
--primary-dark: #4f46e5;
|
| 8 |
+
--primary-light: #818cf8;
|
| 9 |
+
--secondary: #10b981;
|
| 10 |
+
--secondary-dark: #059669;
|
| 11 |
+
--accent: #f43f5e;
|
| 12 |
+
--accent-dark: #e11d48;
|
| 13 |
+
--light: #f8fafc;
|
| 14 |
+
--dark: #0f172a;
|
| 15 |
+
--gray: #64748b;
|
| 16 |
+
--gray-light: #cbd5e1;
|
| 17 |
+
--gray-dark: #334155;
|
| 18 |
+
|
| 19 |
+
/* Gradients */
|
| 20 |
+
--gradient-primary: linear-gradient(135deg, #6366f1, #8b5cf6);
|
| 21 |
+
--gradient-secondary: linear-gradient(135deg, #10b981, #06b6d4);
|
| 22 |
+
--gradient-accent: linear-gradient(135deg, #f43f5e, #fb7185);
|
| 23 |
+
--gradient-dark: linear-gradient(135deg, #1e293b, #0f172a);
|
| 24 |
+
|
| 25 |
+
/* Shadows */
|
| 26 |
+
--shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.05);
|
| 27 |
+
--shadow-md: 0 4px 8px rgba(0, 0, 0, 0.1);
|
| 28 |
+
--shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.1);
|
| 29 |
+
--shadow-xl: 0 15px 25px rgba(0, 0, 0, 0.1);
|
| 30 |
+
|
| 31 |
+
/* Animations */
|
| 32 |
+
--transition-fast: 0.2s ease;
|
| 33 |
+
--transition-normal: 0.3s ease;
|
| 34 |
+
--transition-slow: 0.5s ease;
|
| 35 |
+
|
| 36 |
+
/* Border radius */
|
| 37 |
+
--radius-sm: 4px;
|
| 38 |
+
--radius-md: 8px;
|
| 39 |
+
--radius-lg: 16px;
|
| 40 |
+
--radius-xl: 24px;
|
| 41 |
+
--radius-full: 9999px;
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
@font-face {
|
| 45 |
+
font-family: 'Cabinet Grotesk';
|
| 46 |
+
src: url('https://fonts.cdnfonts.com/css/cabinet-grotesk');
|
| 47 |
+
font-weight: normal;
|
| 48 |
+
font-style: normal;
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
* {
|
| 52 |
+
margin: 0;
|
| 53 |
+
padding: 0;
|
| 54 |
+
box-sizing: border-box;
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
+
html {
|
| 58 |
+
scroll-behavior: smooth;
|
| 59 |
+
scroll-padding-top: 80px;
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
body {
|
| 63 |
+
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
| 64 |
+
background-color: var(--light);
|
| 65 |
+
color: var(--dark);
|
| 66 |
+
overflow-x: hidden;
|
| 67 |
+
line-height: 1.6;
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
.container {
|
| 71 |
+
max-width: 1280px;
|
| 72 |
+
padding: 0 1.5rem;
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
h1, h2, h3, h4, h5, h6 {
|
| 76 |
+
font-family: 'Cabinet Grotesk', 'Segoe UI', sans-serif;
|
| 77 |
+
font-weight: 700;
|
| 78 |
+
line-height: 1.2;
|
| 79 |
+
}
|
| 80 |
+
|
| 81 |
+
a {
|
| 82 |
+
text-decoration: none;
|
| 83 |
+
color: var(--primary);
|
| 84 |
+
transition: var(--transition-normal);
|
| 85 |
+
}
|
| 86 |
+
|
| 87 |
+
.btn {
|
| 88 |
+
display: inline-flex;
|
| 89 |
+
align-items: center;
|
| 90 |
+
justify-content: center;
|
| 91 |
+
padding: 0.75rem 1.5rem;
|
| 92 |
+
border-radius: var(--radius-md);
|
| 93 |
+
font-weight: 600;
|
| 94 |
+
transition: var(--transition-normal);
|
| 95 |
+
cursor: pointer;
|
| 96 |
+
border: none;
|
| 97 |
+
outline: none;
|
| 98 |
+
text-align: center;
|
| 99 |
+
}
|
| 100 |
+
|
| 101 |
+
.btn-primary {
|
| 102 |
+
background: var(--gradient-primary);
|
| 103 |
+
color: white;
|
| 104 |
+
box-shadow: 0 4px 12px rgba(99, 102, 241, 0.25);
|
| 105 |
+
}
|
| 106 |
+
|
| 107 |
+
.btn-primary:hover {
|
| 108 |
+
transform: translateY(-2px);
|
| 109 |
+
box-shadow: 0 6px 16px rgba(99, 102, 241, 0.35);
|
| 110 |
+
background: linear-gradient(135deg, #4f46e5, #7c3aed);
|
| 111 |
+
color: white;
|
| 112 |
+
}
|
| 113 |
+
|
| 114 |
+
.btn-primary:active {
|
| 115 |
+
transform: translateY(0);
|
| 116 |
+
}
|
| 117 |
+
|
| 118 |
+
.btn-outline {
|
| 119 |
+
background: transparent;
|
| 120 |
+
color: var(--primary);
|
| 121 |
+
border: 2px solid var(--primary-light);
|
| 122 |
+
}
|
| 123 |
+
|
| 124 |
+
.btn-outline:hover {
|
| 125 |
+
background: rgba(99, 102, 241, 0.1);
|
| 126 |
+
transform: translateY(-2px);
|
| 127 |
+
color: var(--primary-dark);
|
| 128 |
+
border-color: var(--primary);
|
| 129 |
+
}
|
| 130 |
+
|
| 131 |
+
.btn-demo {
|
| 132 |
+
background: var(--gradient-secondary);
|
| 133 |
+
color: white !important;
|
| 134 |
+
border-radius: var(--radius-full);
|
| 135 |
+
padding: 0.5rem 1.25rem;
|
| 136 |
+
font-size: 0.9rem;
|
| 137 |
+
font-weight: 600;
|
| 138 |
+
box-shadow: 0 4px 12px rgba(16, 185, 129, 0.25);
|
| 139 |
+
transition: all 0.3s cubic-bezier(0.17, 0.67, 0.83, 0.67);
|
| 140 |
+
}
|
| 141 |
+
|
| 142 |
+
.btn-demo:hover {
|
| 143 |
+
transform: translateY(-2px) scale(1.05);
|
| 144 |
+
box-shadow: 0 6px 16px rgba(16, 185, 129, 0.35);
|
| 145 |
+
background: linear-gradient(135deg, #059669, #0891b2);
|
| 146 |
+
}
|
| 147 |
+
|
| 148 |
+
.gradient-text {
|
| 149 |
+
background: var(--gradient-primary);
|
| 150 |
+
-webkit-background-clip: text;
|
| 151 |
+
background-clip: text;
|
| 152 |
+
color: transparent;
|
| 153 |
+
position: relative;
|
| 154 |
+
display: inline-block;
|
| 155 |
+
}
|
| 156 |
+
|
| 157 |
+
/* ============================
|
| 158 |
+
PARTICLE BACKGROUND
|
| 159 |
+
============================= */
|
| 160 |
+
#particleCanvas {
|
| 161 |
+
position: fixed;
|
| 162 |
+
top: 0;
|
| 163 |
+
left: 0;
|
| 164 |
+
width: 100%;
|
| 165 |
+
height: 100%;
|
| 166 |
+
z-index: -1;
|
| 167 |
+
opacity: 0.6;
|
| 168 |
+
}
|
| 169 |
+
|
| 170 |
+
/* ============================
|
| 171 |
+
NAVBAR
|
| 172 |
+
============================= */
|
| 173 |
+
.navbar {
|
| 174 |
+
background: rgba(15, 23, 42, 0.95);
|
| 175 |
+
backdrop-filter: blur(10px);
|
| 176 |
+
-webkit-backdrop-filter: blur(10px);
|
| 177 |
+
transition: all 0.4s ease;
|
| 178 |
+
padding: 1rem 0;
|
| 179 |
+
box-shadow: var(--shadow-md);
|
| 180 |
+
z-index: 1000;
|
| 181 |
+
}
|
| 182 |
+
|
| 183 |
+
.navbar-shrink {
|
| 184 |
+
padding: 0.5rem 0;
|
| 185 |
+
box-shadow: var(--shadow-lg);
|
| 186 |
+
}
|
| 187 |
+
|
| 188 |
+
.logo-text {
|
| 189 |
+
font-family: 'Cabinet Grotesk', sans-serif;
|
| 190 |
+
font-weight: 700;
|
| 191 |
+
font-size: 1.5rem;
|
| 192 |
+
color: white;
|
| 193 |
+
position: relative;
|
| 194 |
+
}
|
| 195 |
+
|
| 196 |
+
.logo-accent {
|
| 197 |
+
background: var(--gradient-secondary);
|
| 198 |
+
-webkit-background-clip: text;
|
| 199 |
+
background-clip: text;
|
| 200 |
+
color: transparent;
|
| 201 |
+
}
|
| 202 |
+
|
| 203 |
+
.nav-link {
|
| 204 |
+
color: var(--gray-light) !important;
|
| 205 |
+
font-weight: 500;
|
| 206 |
+
margin: 0 0.75rem;
|
| 207 |
+
padding: 0.5rem 0;
|
| 208 |
+
position: relative;
|
| 209 |
+
transition: var(--transition-normal);
|
| 210 |
+
}
|
| 211 |
+
|
| 212 |
+
.nav-link:hover, .nav-link.active {
|
| 213 |
+
color: white !important;
|
| 214 |
+
}
|
| 215 |
+
|
| 216 |
+
.nav-link::after {
|
| 217 |
+
content: '';
|
| 218 |
+
position: absolute;
|
| 219 |
+
width: 0;
|
| 220 |
+
height: 2px;
|
| 221 |
+
bottom: 0;
|
| 222 |
+
left: 0;
|
| 223 |
+
background: var(--gradient-secondary);
|
| 224 |
+
transition: width 0.3s ease;
|
| 225 |
+
}
|
| 226 |
+
|
| 227 |
+
.nav-link:hover::after, .nav-link.active::after {
|
| 228 |
+
width: 100%;
|
| 229 |
+
}
|
| 230 |
+
|
| 231 |
+
/* ============================
|
| 232 |
+
HERO SECTION
|
| 233 |
+
============================= */
|
| 234 |
+
.hero-section {
|
| 235 |
+
position: relative;
|
| 236 |
+
min-height: 100vh;
|
| 237 |
+
display: flex;
|
| 238 |
+
align-items: center;
|
| 239 |
+
padding: 100px 0;
|
| 240 |
+
color: white;
|
| 241 |
+
overflow: hidden;
|
| 242 |
+
}
|
| 243 |
+
.hero-section::before {
|
| 244 |
+
content: "";
|
| 245 |
+
position: absolute;
|
| 246 |
+
top: 0;
|
| 247 |
+
left: 0;
|
| 248 |
+
height: 100%;
|
| 249 |
+
width: 100%;
|
| 250 |
+
background: url(../assets/hero-bg.jpg) no-repeat center center/cover;
|
| 251 |
+
filter: brightness(0.2); /* 👈 Adjust brightness here */
|
| 252 |
+
z-index: -1;
|
| 253 |
+
}
|
| 254 |
+
|
| 255 |
+
.hero-title {
|
| 256 |
+
font-size: 3.5rem;
|
| 257 |
+
margin-bottom: 1.5rem;
|
| 258 |
+
letter-spacing: -0.025em;
|
| 259 |
+
}
|
| 260 |
+
|
| 261 |
+
.hero-subtitle {
|
| 262 |
+
font-size: 1.25rem;
|
| 263 |
+
opacity: 0.9;
|
| 264 |
+
margin-bottom: 2rem;
|
| 265 |
+
max-width: 500px;
|
| 266 |
+
}
|
| 267 |
+
|
| 268 |
+
.hero-btns {
|
| 269 |
+
margin-bottom: 2rem;
|
| 270 |
+
}
|
| 271 |
+
|
| 272 |
+
.tech-badges {
|
| 273 |
+
display: flex;
|
| 274 |
+
flex-wrap: wrap;
|
| 275 |
+
gap: 0.75rem;
|
| 276 |
+
margin-top: 2rem;
|
| 277 |
+
}
|
| 278 |
+
|
| 279 |
+
.tech-badges .badge {
|
| 280 |
+
background: rgba(255, 255, 255, 0.1);
|
| 281 |
+
color: white;
|
| 282 |
+
border-radius: var(--radius-full);
|
| 283 |
+
padding: 0.5rem 1rem;
|
| 284 |
+
font-size: 0.85rem;
|
| 285 |
+
backdrop-filter: blur(5px);
|
| 286 |
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
| 287 |
+
transition: var(--transition-normal);
|
| 288 |
+
}
|
| 289 |
+
|
| 290 |
+
.tech-badges .badge:hover {
|
| 291 |
+
transform: translateY(-3px);
|
| 292 |
+
background: rgba(255, 255, 255, 0.15);
|
| 293 |
+
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
|
| 294 |
+
}
|
| 295 |
+
|
| 296 |
+
.hero-image {
|
| 297 |
+
position: relative;
|
| 298 |
+
box-shadow: var(--shadow-xl);
|
| 299 |
+
border-radius: var(--radius-lg);
|
| 300 |
+
animation: float 6s ease-in-out infinite;
|
| 301 |
+
z-index: 2;
|
| 302 |
+
clip-path: polygon(25% 0%, 100% 0%, 75% 100%, 0% 100%);
|
| 303 |
+
}
|
| 304 |
+
|
| 305 |
+
.hero-image img {
|
| 306 |
+
border-radius: var(--radius-lg);
|
| 307 |
+
max-width: 100%;
|
| 308 |
+
height: auto;
|
| 309 |
+
border: 4px solid rgba(255, 255, 255, 0.1);
|
| 310 |
+
}
|
| 311 |
+
|
| 312 |
+
.floating-tag {
|
| 313 |
+
position: absolute;
|
| 314 |
+
background: var(--gradient-primary);
|
| 315 |
+
color: white;
|
| 316 |
+
padding: 0.5rem 1rem;
|
| 317 |
+
border-radius: var(--radius-full);
|
| 318 |
+
font-size: 0.85rem;
|
| 319 |
+
font-weight: 600;
|
| 320 |
+
box-shadow: var(--shadow-md);
|
| 321 |
+
z-index: 3;
|
| 322 |
+
animation: pulse 3s ease-in-out infinite;
|
| 323 |
+
}
|
| 324 |
+
|
| 325 |
+
.tag-1 {
|
| 326 |
+
top: -15px;
|
| 327 |
+
left: 5rem;
|
| 328 |
+
animation-delay: 0s;
|
| 329 |
+
}
|
| 330 |
+
|
| 331 |
+
.tag-2 {
|
| 332 |
+
bottom: -1rem;
|
| 333 |
+
right: 3rem;
|
| 334 |
+
animation-delay: -1s;
|
| 335 |
+
background: var(--gradient-secondary);
|
| 336 |
+
}
|
| 337 |
+
|
| 338 |
+
.tag-3 {
|
| 339 |
+
top: 4.5rem;
|
| 340 |
+
left: -2rem;
|
| 341 |
+
animation-delay: -2s;
|
| 342 |
+
background: var(--gradient-accent);
|
| 343 |
+
}
|
| 344 |
+
|
| 345 |
+
.pulse-circle {
|
| 346 |
+
position: absolute;
|
| 347 |
+
width: 200px;
|
| 348 |
+
height: 200px;
|
| 349 |
+
border-radius: 50%;
|
| 350 |
+
background: rgba(99, 102, 241, 0.15);
|
| 351 |
+
z-index: 1;
|
| 352 |
+
top: 50%;
|
| 353 |
+
left: 50%;
|
| 354 |
+
transform: translate(-50%, -50%);
|
| 355 |
+
animation: pulseCircle 3s infinite;
|
| 356 |
+
}
|
| 357 |
+
|
| 358 |
+
@keyframes float {
|
| 359 |
+
0%, 100% {
|
| 360 |
+
transform: translateY(0);
|
| 361 |
+
}
|
| 362 |
+
50% {
|
| 363 |
+
transform: translateY(-20px);
|
| 364 |
+
}
|
| 365 |
+
}
|
| 366 |
+
|
| 367 |
+
@keyframes pulse {
|
| 368 |
+
0%, 100% {
|
| 369 |
+
transform: scale(1);
|
| 370 |
+
}
|
| 371 |
+
50% {
|
| 372 |
+
transform: scale(1.1);
|
| 373 |
+
}
|
| 374 |
+
}
|
| 375 |
+
|
| 376 |
+
@keyframes pulseCircle {
|
| 377 |
+
0% {
|
| 378 |
+
transform: translate(-50%, -50%) scale(0.8);
|
| 379 |
+
opacity: 0.6;
|
| 380 |
+
}
|
| 381 |
+
50% {
|
| 382 |
+
transform: translate(-50%, -50%) scale(1);
|
| 383 |
+
opacity: 0.3;
|
| 384 |
+
}
|
| 385 |
+
100% {
|
| 386 |
+
transform: translate(-50%, -50%) scale(0.8);
|
| 387 |
+
opacity: 0.6;
|
| 388 |
+
}
|
| 389 |
+
}
|
| 390 |
+
|
| 391 |
+
.hero-shape {
|
| 392 |
+
position: absolute;
|
| 393 |
+
bottom: -1px;
|
| 394 |
+
left: 0;
|
| 395 |
+
width: 100%;
|
| 396 |
+
line-height: 0;
|
| 397 |
+
z-index: 3;
|
| 398 |
+
}
|
| 399 |
+
|
| 400 |
+
/* ============================
|
| 401 |
+
SECTION HEADERS
|
| 402 |
+
============================= */
|
| 403 |
+
.section-header {
|
| 404 |
+
text-align: center;
|
| 405 |
+
margin-bottom: 3rem;
|
| 406 |
+
}
|
| 407 |
+
|
| 408 |
+
.section-pre-title {
|
| 409 |
+
font-size: 0.95rem;
|
| 410 |
+
color: var(--primary);
|
| 411 |
+
letter-spacing: 2px;
|
| 412 |
+
font-weight: 600;
|
| 413 |
+
margin-bottom: 1rem;
|
| 414 |
+
}
|
| 415 |
+
|
| 416 |
+
.section-title {
|
| 417 |
+
font-size: 2.5rem;
|
| 418 |
+
margin-bottom: 1rem;
|
| 419 |
+
position: relative;
|
| 420 |
+
display: inline-block;
|
| 421 |
+
}
|
| 422 |
+
|
| 423 |
+
.section-subtitle {
|
| 424 |
+
font-size: 1.1rem;
|
| 425 |
+
color: var(--gray);
|
| 426 |
+
max-width: 600px;
|
| 427 |
+
margin: 0 auto;
|
| 428 |
+
}
|
| 429 |
+
|
| 430 |
+
/* ============================
|
| 431 |
+
FEATURES SECTION
|
| 432 |
+
============================= */
|
| 433 |
+
.features-section {
|
| 434 |
+
padding: 6rem 0;
|
| 435 |
+
position: relative;
|
| 436 |
+
overflow: hidden;
|
| 437 |
+
}
|
| 438 |
+
|
| 439 |
+
.features-container {
|
| 440 |
+
margin-top: 3rem;
|
| 441 |
+
}
|
| 442 |
+
|
| 443 |
+
.feature-card-wrapper {
|
| 444 |
+
padding: 15px;
|
| 445 |
+
transition: var(--transition-normal);
|
| 446 |
+
}
|
| 447 |
+
|
| 448 |
+
.feature-card {
|
| 449 |
+
background: white;
|
| 450 |
+
border-radius: var(--radius-lg);
|
| 451 |
+
padding: 2rem;
|
| 452 |
+
height: 100%;
|
| 453 |
+
border: 1px solid var(--gray-light);
|
| 454 |
+
transition: var(--transition-normal);
|
| 455 |
+
position: relative;
|
| 456 |
+
overflow: hidden;
|
| 457 |
+
box-shadow: var(--shadow-sm);
|
| 458 |
+
display: flex;
|
| 459 |
+
flex-direction: column;
|
| 460 |
+
align-items: flex-start;
|
| 461 |
+
}
|
| 462 |
+
|
| 463 |
+
.feature-card::before {
|
| 464 |
+
content: '';
|
| 465 |
+
position: absolute;
|
| 466 |
+
top: 0;
|
| 467 |
+
left: 0;
|
| 468 |
+
width: 100%;
|
| 469 |
+
height: 4px;
|
| 470 |
+
background: var(--gradient-primary);
|
| 471 |
+
transform: scaleX(0);
|
| 472 |
+
transform-origin: left;
|
| 473 |
+
transition: transform 0.5s ease;
|
| 474 |
+
}
|
| 475 |
+
|
| 476 |
+
.feature-card:hover {
|
| 477 |
+
transform: translateY(-10px);
|
| 478 |
+
box-shadow: var(--shadow-lg);
|
| 479 |
+
border-color: transparent;
|
| 480 |
+
}
|
| 481 |
+
|
| 482 |
+
.feature-card:hover::before {
|
| 483 |
+
transform: scaleX(1);
|
| 484 |
+
}
|
| 485 |
+
|
| 486 |
+
.feature-icon {
|
| 487 |
+
width: 60px;
|
| 488 |
+
height: 60px;
|
| 489 |
+
display: flex;
|
| 490 |
+
align-items: center;
|
| 491 |
+
justify-content: center;
|
| 492 |
+
background: var(--gradient-primary);
|
| 493 |
+
color: white;
|
| 494 |
+
border-radius: var(--radius-md);
|
| 495 |
+
margin-bottom: 1.5rem;
|
| 496 |
+
font-size: 1.5rem;
|
| 497 |
+
box-shadow: 0 5px 15px rgba(99, 102, 241, 0.25);
|
| 498 |
+
position: relative;
|
| 499 |
+
z-index: 1;
|
| 500 |
+
overflow: hidden;
|
| 501 |
+
}
|
| 502 |
+
|
| 503 |
+
.feature-card:nth-child(2n) .feature-icon,
|
| 504 |
+
.feature-card:nth-child(2n)::before {
|
| 505 |
+
background: var(--gradient-secondary);
|
| 506 |
+
}
|
| 507 |
+
|
| 508 |
+
.feature-card:nth-child(3n) .feature-icon,
|
| 509 |
+
.feature-card:nth-child(3n)::before {
|
| 510 |
+
background: var(--gradient-accent);
|
| 511 |
+
}
|
| 512 |
+
|
| 513 |
+
.feature-icon::after {
|
| 514 |
+
content: '';
|
| 515 |
+
position: absolute;
|
| 516 |
+
top: 0;
|
| 517 |
+
left: 0;
|
| 518 |
+
width: 100%;
|
| 519 |
+
height: 100%;
|
| 520 |
+
background: rgba(255, 255, 255, 0.2);
|
| 521 |
+
z-index: -1;
|
| 522 |
+
transform: translateX(-100%);
|
| 523 |
+
transition: transform 0.5s ease;
|
| 524 |
+
}
|
| 525 |
+
|
| 526 |
+
.feature-card:hover .feature-icon::after {
|
| 527 |
+
transform: translateX(0);
|
| 528 |
+
}
|
| 529 |
+
|
| 530 |
+
.feature-card h4 {
|
| 531 |
+
font-size: 1.25rem;
|
| 532 |
+
margin-bottom: 1rem;
|
| 533 |
+
transition: var(--transition-normal);
|
| 534 |
+
}
|
| 535 |
+
|
| 536 |
+
.feature-card p {
|
| 537 |
+
color: var(--gray);
|
| 538 |
+
margin-bottom: 0;
|
| 539 |
+
}
|
| 540 |
+
|
| 541 |
+
/* ============================
|
| 542 |
+
DETECTION SECTION
|
| 543 |
+
============================= */
|
| 544 |
+
.detection-section {
|
| 545 |
+
padding: 6rem 0;
|
| 546 |
+
background-color: #f1f5f9;
|
| 547 |
+
position: relative;
|
| 548 |
+
}
|
| 549 |
+
|
| 550 |
+
.detection-container {
|
| 551 |
+
background: white;
|
| 552 |
+
border-radius: var(--radius-lg);
|
| 553 |
+
overflow: hidden;
|
| 554 |
+
box-shadow: var(--shadow-lg);
|
| 555 |
+
border: 1px solid var(--gray-light);
|
| 556 |
+
}
|
| 557 |
+
|
| 558 |
+
.model-header {
|
| 559 |
+
background: var(--gradient-dark);
|
| 560 |
+
color: white;
|
| 561 |
+
padding: 1.5rem;
|
| 562 |
+
display: flex;
|
| 563 |
+
align-items: center;
|
| 564 |
+
justify-content: space-between;
|
| 565 |
+
flex-wrap: wrap;
|
| 566 |
+
gap: 1rem;
|
| 567 |
+
}
|
| 568 |
+
|
| 569 |
+
.model-header h5 {
|
| 570 |
+
margin: 0;
|
| 571 |
+
display: flex;
|
| 572 |
+
align-items: center;
|
| 573 |
+
gap: 0.5rem;
|
| 574 |
+
font-size: 1.25rem;
|
| 575 |
+
}
|
| 576 |
+
|
| 577 |
+
.model-controls {
|
| 578 |
+
display: flex;
|
| 579 |
+
align-items: center;
|
| 580 |
+
flex-wrap: wrap;
|
| 581 |
+
gap: 1.5rem;
|
| 582 |
+
}
|
| 583 |
+
|
| 584 |
+
.model-select-group {
|
| 585 |
+
display: flex;
|
| 586 |
+
align-items: center;
|
| 587 |
+
gap: 0.75rem;
|
| 588 |
+
}
|
| 589 |
+
|
| 590 |
+
.model-select-group label {
|
| 591 |
+
color: var(--gray-light);
|
| 592 |
+
margin: 0;
|
| 593 |
+
white-space: nowrap;
|
| 594 |
+
}
|
| 595 |
+
|
| 596 |
+
.model-select-group select {
|
| 597 |
+
background: rgba(255, 255, 255, 0.1);
|
| 598 |
+
border: 1px solid rgba(255, 255, 255, 0.2);
|
| 599 |
+
color: white;
|
| 600 |
+
padding: 0.5rem 1rem;
|
| 601 |
+
border-radius: var(--radius-md);
|
| 602 |
+
min-width: 150px;
|
| 603 |
+
}
|
| 604 |
+
|
| 605 |
+
/* Style the options (may not work in all browsers like Firefox) */
|
| 606 |
+
.model-select-group select option {
|
| 607 |
+
background: rgba(255, 255, 255, 0.1) !important;
|
| 608 |
+
color: rgb(0, 0, 0) !important; ;
|
| 609 |
+
}
|
| 610 |
+
|
| 611 |
+
.threshold-slider {
|
| 612 |
+
display: flex;
|
| 613 |
+
flex-direction: column;
|
| 614 |
+
gap: 0.5rem;
|
| 615 |
+
min-width: 200px;
|
| 616 |
+
}
|
| 617 |
+
|
| 618 |
+
.threshold-slider label {
|
| 619 |
+
color: var(--gray-light);
|
| 620 |
+
display: flex;
|
| 621 |
+
justify-content: space-between;
|
| 622 |
+
width: 100%;
|
| 623 |
+
margin: 0;
|
| 624 |
+
}
|
| 625 |
+
|
| 626 |
+
.threshold-slider input {
|
| 627 |
+
width: 100%;
|
| 628 |
+
}
|
| 629 |
+
|
| 630 |
+
.detection-content {
|
| 631 |
+
padding: 1.5rem;
|
| 632 |
+
}
|
| 633 |
+
|
| 634 |
+
.camera-section {
|
| 635 |
+
background: #f8fafc;
|
| 636 |
+
border-radius: var(--radius-lg);
|
| 637 |
+
overflow: hidden;
|
| 638 |
+
box-shadow: var(--shadow-sm);
|
| 639 |
+
border: 1px solid var(--gray-light);
|
| 640 |
+
}
|
| 641 |
+
|
| 642 |
+
.camera-actions {
|
| 643 |
+
display: flex;
|
| 644 |
+
gap: 0.75rem;
|
| 645 |
+
padding: 1rem;
|
| 646 |
+
background: white;
|
| 647 |
+
border-bottom: 1px solid var(--gray-light);
|
| 648 |
+
}
|
| 649 |
+
|
| 650 |
+
.action-btn {
|
| 651 |
+
display: flex;
|
| 652 |
+
align-items: center;
|
| 653 |
+
gap: 0.5rem;
|
| 654 |
+
padding: 0.75rem 1.25rem;
|
| 655 |
+
border-radius: var(--radius-md);
|
| 656 |
+
font-size: 0.95rem;
|
| 657 |
+
font-weight: 600;
|
| 658 |
+
transition: var(--transition-fast);
|
| 659 |
+
cursor: pointer;
|
| 660 |
+
border: none;
|
| 661 |
+
}
|
| 662 |
+
|
| 663 |
+
.upload-btn {
|
| 664 |
+
background: var(--gradient-primary);
|
| 665 |
+
color: white;
|
| 666 |
+
}
|
| 667 |
+
|
| 668 |
+
.capture-btn {
|
| 669 |
+
background: var(--gradient-secondary);
|
| 670 |
+
color: white;
|
| 671 |
+
}
|
| 672 |
+
|
| 673 |
+
.screenshot-btn {
|
| 674 |
+
background: var(--gradient-accent);
|
| 675 |
+
color: white;
|
| 676 |
+
}
|
| 677 |
+
|
| 678 |
+
/* .screenshot-btn:disabled {
|
| 679 |
+
background: var(--gray-light);
|
| 680 |
+
cursor: not-allowed;
|
| 681 |
+
} */
|
| 682 |
+
|
| 683 |
+
.action-btn:hover:not(:disabled) {
|
| 684 |
+
transform: translateY(-2px);
|
| 685 |
+
box-shadow: var(--shadow-md);
|
| 686 |
+
}
|
| 687 |
+
|
| 688 |
+
.capture-container {
|
| 689 |
+
padding: 1rem;
|
| 690 |
+
}
|
| 691 |
+
|
| 692 |
+
.video-wrapper {
|
| 693 |
+
position: relative;
|
| 694 |
+
width: 100%;
|
| 695 |
+
height: 500px;
|
| 696 |
+
background-color: #000;
|
| 697 |
+
border-radius: var(--radius-md);
|
| 698 |
+
overflow: hidden;
|
| 699 |
+
border: 2px solid var(--gray-light);
|
| 700 |
+
}
|
| 701 |
+
|
| 702 |
+
#detectedCanvas {
|
| 703 |
+
width: 100%;
|
| 704 |
+
height: 100%;
|
| 705 |
+
display: block;
|
| 706 |
+
object-fit: contain;
|
| 707 |
+
}
|
| 708 |
+
|
| 709 |
+
#liveVideo {
|
| 710 |
+
position: absolute;
|
| 711 |
+
top: 0;
|
| 712 |
+
left: 0;
|
| 713 |
+
width: 100%;
|
| 714 |
+
height: 100%;
|
| 715 |
+
object-fit: cover;
|
| 716 |
+
}
|
| 717 |
+
|
| 718 |
+
#loadingOverlay {
|
| 719 |
+
position: absolute;
|
| 720 |
+
top: 0;
|
| 721 |
+
left: 0;
|
| 722 |
+
width: 100%;
|
| 723 |
+
height: 100%;
|
| 724 |
+
background: rgba(0, 0, 0, 0.7);
|
| 725 |
+
display: flex;
|
| 726 |
+
flex-direction: column;
|
| 727 |
+
align-items: center;
|
| 728 |
+
justify-content: center;
|
| 729 |
+
color: white;
|
| 730 |
+
z-index: 10;
|
| 731 |
+
}
|
| 732 |
+
|
| 733 |
+
.spinner {
|
| 734 |
+
width: 50px;
|
| 735 |
+
height: 50px;
|
| 736 |
+
border: 4px solid rgba(255, 255, 255, 0.3);
|
| 737 |
+
border-radius: 50%;
|
| 738 |
+
border-top-color: white;
|
| 739 |
+
animation: spin 1s linear infinite;
|
| 740 |
+
margin-bottom: 1rem;
|
| 741 |
+
}
|
| 742 |
+
|
| 743 |
+
@keyframes spin {
|
| 744 |
+
to {
|
| 745 |
+
transform: rotate(360deg);
|
| 746 |
+
}
|
| 747 |
+
}
|
| 748 |
+
|
| 749 |
+
.corner-label {
|
| 750 |
+
position: absolute;
|
| 751 |
+
background: rgba(0, 0, 0, 0.7);
|
| 752 |
+
color: white;
|
| 753 |
+
padding: 0.5rem 1rem;
|
| 754 |
+
font-size: 0.85rem;
|
| 755 |
+
font-weight: 600;
|
| 756 |
+
z-index: 5;
|
| 757 |
+
}
|
| 758 |
+
|
| 759 |
+
.top-left {
|
| 760 |
+
top: 1rem;
|
| 761 |
+
left: 1rem;
|
| 762 |
+
border-radius: var(--radius-md);
|
| 763 |
+
background: var(--gradient-primary);
|
| 764 |
+
}
|
| 765 |
+
|
| 766 |
+
.bottom-right {
|
| 767 |
+
bottom: 1rem;
|
| 768 |
+
right: 1rem;
|
| 769 |
+
border-radius: var(--radius-md);
|
| 770 |
+
background: var(--gradient-secondary);
|
| 771 |
+
}
|
| 772 |
+
|
| 773 |
+
/* Results Panel */
|
| 774 |
+
.results-panel {
|
| 775 |
+
background: white;
|
| 776 |
+
border-radius: var(--radius-lg);
|
| 777 |
+
overflow: hidden;
|
| 778 |
+
height: 100%;
|
| 779 |
+
border: 1px solid var(--gray-light);
|
| 780 |
+
box-shadow: var(--shadow-sm);
|
| 781 |
+
display: flex;
|
| 782 |
+
flex-direction: column;
|
| 783 |
+
}
|
| 784 |
+
|
| 785 |
+
.panel-tabs {
|
| 786 |
+
display: flex;
|
| 787 |
+
border-bottom: 1px solid var(--gray-light);
|
| 788 |
+
}
|
| 789 |
+
|
| 790 |
+
.panel-tab {
|
| 791 |
+
flex: 1;
|
| 792 |
+
text-align: center;
|
| 793 |
+
padding: 1rem;
|
| 794 |
+
background: none;
|
| 795 |
+
border: none;
|
| 796 |
+
font-weight: 600;
|
| 797 |
+
color: var(--gray);
|
| 798 |
+
cursor: pointer;
|
| 799 |
+
transition: var(--transition-fast);
|
| 800 |
+
position: relative;
|
| 801 |
+
}
|
| 802 |
+
|
| 803 |
+
.panel-tab.active {
|
| 804 |
+
color: var(--primary);
|
| 805 |
+
}
|
| 806 |
+
|
| 807 |
+
.panel-tab::after {
|
| 808 |
+
content: '';
|
| 809 |
+
position: absolute;
|
| 810 |
+
bottom: 0;
|
| 811 |
+
left: 0;
|
| 812 |
+
width: 100%;
|
| 813 |
+
height: 3px;
|
| 814 |
+
background: var(--primary);
|
| 815 |
+
transform: scaleX(0);
|
| 816 |
+
transition: transform 0.3s ease;
|
| 817 |
+
}
|
| 818 |
+
|
| 819 |
+
.panel-tab.active::after {
|
| 820 |
+
transform: scaleX(1);
|
| 821 |
+
}
|
| 822 |
+
|
| 823 |
+
/* ============================
|
| 824 |
+
TEAM MEMBER CARDS
|
| 825 |
+
============================= */
|
| 826 |
+
.team-section {
|
| 827 |
+
padding: 6rem 0;
|
| 828 |
+
position: relative;
|
| 829 |
+
}
|
| 830 |
+
|
| 831 |
+
.team-container {
|
| 832 |
+
margin-top: 3rem;
|
| 833 |
+
}
|
| 834 |
+
|
| 835 |
+
.team-card {
|
| 836 |
+
background: white;
|
| 837 |
+
border-radius: var(--radius-lg);
|
| 838 |
+
padding: 2rem;
|
| 839 |
+
border: 1px solid var(--gray-light);
|
| 840 |
+
box-shadow: var(--shadow-sm);
|
| 841 |
+
margin-bottom: 2rem;
|
| 842 |
+
transition: var(--transition-normal);
|
| 843 |
+
position: relative;
|
| 844 |
+
overflow: hidden;
|
| 845 |
+
display: flex;
|
| 846 |
+
flex-direction: column;
|
| 847 |
+
align-items: center;
|
| 848 |
+
text-align: center;
|
| 849 |
+
}
|
| 850 |
+
|
| 851 |
+
.team-card::before {
|
| 852 |
+
content: '';
|
| 853 |
+
position: absolute;
|
| 854 |
+
top: 0;
|
| 855 |
+
left: 0;
|
| 856 |
+
width: 100%;
|
| 857 |
+
height: 4px;
|
| 858 |
+
background: var(--gradient-primary);
|
| 859 |
+
transform: scaleX(0);
|
| 860 |
+
transform-origin: left;
|
| 861 |
+
transition: transform 0.5s ease;
|
| 862 |
+
}
|
| 863 |
+
|
| 864 |
+
.team-card:hover {
|
| 865 |
+
transform: translateY(-10px);
|
| 866 |
+
box-shadow: var(--shadow-lg);
|
| 867 |
+
border-color: transparent;
|
| 868 |
+
}
|
| 869 |
+
|
| 870 |
+
.team-card:hover::before {
|
| 871 |
+
transform: scaleX(1);
|
| 872 |
+
}
|
| 873 |
+
|
| 874 |
+
.team-card:nth-child(2) .team-card::before {
|
| 875 |
+
background: var(--gradient-secondary);
|
| 876 |
+
}
|
| 877 |
+
|
| 878 |
+
.team-card:nth-child(3) .team-card::before {
|
| 879 |
+
background: var(--gradient-accent);
|
| 880 |
+
}
|
| 881 |
+
|
| 882 |
+
.team-profile {
|
| 883 |
+
width: 150px;
|
| 884 |
+
height: 150px;
|
| 885 |
+
border-radius: 50%;
|
| 886 |
+
overflow: hidden;
|
| 887 |
+
margin-bottom: 1.5rem;
|
| 888 |
+
border: 5px solid white;
|
| 889 |
+
box-shadow: var(--shadow-md);
|
| 890 |
+
position: relative;
|
| 891 |
+
transition: transform 0.5s cubic-bezier(0.17, 0.67, 0.83, 0.67);
|
| 892 |
+
}
|
| 893 |
+
|
| 894 |
+
.team-card:hover .team-profile {
|
| 895 |
+
transform: scale(1.1);
|
| 896 |
+
}
|
| 897 |
+
|
| 898 |
+
.team-profile img {
|
| 899 |
+
width: 100%;
|
| 900 |
+
height: 100%;
|
| 901 |
+
object-fit: cover;
|
| 902 |
+
transition: var(--transition-normal);
|
| 903 |
+
}
|
| 904 |
+
|
| 905 |
+
.team-card:hover .team-profile img {
|
| 906 |
+
transform: scale(1.1);
|
| 907 |
+
}
|
| 908 |
+
|
| 909 |
+
.team-info h4 {
|
| 910 |
+
font-size: 1.5rem;
|
| 911 |
+
margin-bottom: 0.5rem;
|
| 912 |
+
color: var(--dark);
|
| 913 |
+
}
|
| 914 |
+
|
| 915 |
+
.team-role {
|
| 916 |
+
color: var(--primary);
|
| 917 |
+
font-weight: 600;
|
| 918 |
+
margin-bottom: 1rem;
|
| 919 |
+
font-size: 1.1rem;
|
| 920 |
+
}
|
| 921 |
+
|
| 922 |
+
.team-skills {
|
| 923 |
+
display: flex;
|
| 924 |
+
flex-wrap: wrap;
|
| 925 |
+
gap: 0.5rem;
|
| 926 |
+
justify-content: center;
|
| 927 |
+
margin-bottom: 1.5rem;
|
| 928 |
+
}
|
| 929 |
+
|
| 930 |
+
.team-skills .skill {
|
| 931 |
+
background: var(--light);
|
| 932 |
+
color: var(--primary);
|
| 933 |
+
border-radius: var(--radius-full);
|
| 934 |
+
padding: 0.35rem 0.75rem;
|
| 935 |
+
font-size: 0.85rem;
|
| 936 |
+
font-weight: 500;
|
| 937 |
+
transition: var(--transition-normal);
|
| 938 |
+
border: 1px solid var(--primary-light);
|
| 939 |
+
}
|
| 940 |
+
|
| 941 |
+
.team-card:hover .skill {
|
| 942 |
+
background: rgba(99, 102, 241, 0.1);
|
| 943 |
+
transform: translateY(-3px);
|
| 944 |
+
}
|
| 945 |
+
|
| 946 |
+
.team-contributions {
|
| 947 |
+
color: var(--gray);
|
| 948 |
+
margin-bottom: 1.5rem;
|
| 949 |
+
font-size: 0.95rem;
|
| 950 |
+
line-height: 1.6;
|
| 951 |
+
}
|
| 952 |
+
|
| 953 |
+
.team-social {
|
| 954 |
+
display: flex;
|
| 955 |
+
gap: 1rem;
|
| 956 |
+
margin-top: 1rem;
|
| 957 |
+
}
|
| 958 |
+
|
| 959 |
+
.social-icon {
|
| 960 |
+
width: 40px;
|
| 961 |
+
height: 40px;
|
| 962 |
+
border-radius: 50%;
|
| 963 |
+
background: var(--light);
|
| 964 |
+
display: flex;
|
| 965 |
+
align-items: center;
|
| 966 |
+
justify-content: center;
|
| 967 |
+
color: var(--primary);
|
| 968 |
+
font-size: 1.2rem;
|
| 969 |
+
transition: var(--transition-normal);
|
| 970 |
+
border: 1px solid var(--gray-light);
|
| 971 |
+
}
|
| 972 |
+
|
| 973 |
+
.social-icon:hover {
|
| 974 |
+
background: var(--primary);
|
| 975 |
+
color: white;
|
| 976 |
+
transform: translateY(-5px) rotate(10deg);
|
| 977 |
+
box-shadow: 0 5px 15px rgba(99, 102, 241, 0.3);
|
| 978 |
+
}
|
| 979 |
+
|
| 980 |
+
.experience-badge {
|
| 981 |
+
position: absolute;
|
| 982 |
+
top: 20px;
|
| 983 |
+
right: 20px;
|
| 984 |
+
background: var(--gradient-secondary);
|
| 985 |
+
color: white;
|
| 986 |
+
padding: 0.5rem 1rem;
|
| 987 |
+
border-radius: var(--radius-full);
|
| 988 |
+
font-size: 0.85rem;
|
| 989 |
+
font-weight: 600;
|
| 990 |
+
box-shadow: var(--shadow-md);
|
| 991 |
+
z-index: 1;
|
| 992 |
+
animation: pulse 3s ease-in-out infinite;
|
| 993 |
+
}
|
| 994 |
+
|
| 995 |
+
/* ============================
|
| 996 |
+
FOOTER STYLES
|
| 997 |
+
============================= */
|
| 998 |
+
.footer {
|
| 999 |
+
background: rgba(15, 23, 42, 0.95);
|
| 1000 |
+
backdrop-filter: blur(10px);
|
| 1001 |
+
-webkit-backdrop-filter: blur(10px);
|
| 1002 |
+
color: white;
|
| 1003 |
+
padding: 4rem 0 1rem;
|
| 1004 |
+
position: relative;
|
| 1005 |
+
overflow: hidden;
|
| 1006 |
+
}
|
| 1007 |
+
|
| 1008 |
+
.footer-content {
|
| 1009 |
+
padding-bottom: 2rem;
|
| 1010 |
+
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
| 1011 |
+
}
|
| 1012 |
+
|
| 1013 |
+
.footer-brand h3 {
|
| 1014 |
+
font-family: 'Cabinet Grotesk', sans-serif;
|
| 1015 |
+
font-weight: 700;
|
| 1016 |
+
font-size: 1.8rem;
|
| 1017 |
+
margin-bottom: 1rem;
|
| 1018 |
+
color: white;
|
| 1019 |
+
}
|
| 1020 |
+
|
| 1021 |
+
.footer-brand span {
|
| 1022 |
+
background: var(--gradient-secondary);
|
| 1023 |
+
-webkit-background-clip: text;
|
| 1024 |
+
background-clip: text;
|
| 1025 |
+
color: transparent;
|
| 1026 |
+
}
|
| 1027 |
+
|
| 1028 |
+
.footer-brand p {
|
| 1029 |
+
color: var(--gray-light);
|
| 1030 |
+
margin-bottom: 1.5rem;
|
| 1031 |
+
max-width: 300px;
|
| 1032 |
+
}
|
| 1033 |
+
|
| 1034 |
+
.footer-social {
|
| 1035 |
+
display: flex;
|
| 1036 |
+
gap: 1rem;
|
| 1037 |
+
margin-bottom: 2rem;
|
| 1038 |
+
}
|
| 1039 |
+
|
| 1040 |
+
.footer-social a {
|
| 1041 |
+
width: 40px;
|
| 1042 |
+
height: 40px;
|
| 1043 |
+
border-radius: 50%;
|
| 1044 |
+
background: rgba(255, 255, 255, 0.1);
|
| 1045 |
+
display: flex;
|
| 1046 |
+
align-items: center;
|
| 1047 |
+
justify-content: center;
|
| 1048 |
+
color: white;
|
| 1049 |
+
font-size: 1.2rem;
|
| 1050 |
+
transition: var(--transition-normal);
|
| 1051 |
+
}
|
| 1052 |
+
|
| 1053 |
+
.footer-social a:hover {
|
| 1054 |
+
background: var(--primary);
|
| 1055 |
+
transform: translateY(-5px);
|
| 1056 |
+
box-shadow: 0 5px 15px rgba(99, 102, 241, 0.3);
|
| 1057 |
+
}
|
| 1058 |
+
|
| 1059 |
+
.footer h5 {
|
| 1060 |
+
color: white;
|
| 1061 |
+
font-size: 1.2rem;
|
| 1062 |
+
margin-bottom: 1.5rem;
|
| 1063 |
+
position: relative;
|
| 1064 |
+
padding-bottom: 0.75rem;
|
| 1065 |
+
}
|
| 1066 |
+
|
| 1067 |
+
.footer h5::after {
|
| 1068 |
+
content: '';
|
| 1069 |
+
position: absolute;
|
| 1070 |
+
bottom: 0;
|
| 1071 |
+
left: 0;
|
| 1072 |
+
width: 40px;
|
| 1073 |
+
height: 2px;
|
| 1074 |
+
background: var(--gradient-primary);
|
| 1075 |
+
}
|
| 1076 |
+
|
| 1077 |
+
.footer-links, .footer-contact {
|
| 1078 |
+
list-style: none;
|
| 1079 |
+
padding: 0;
|
| 1080 |
+
margin: 0;
|
| 1081 |
+
}
|
| 1082 |
+
|
| 1083 |
+
.footer-links li, .footer-contact li {
|
| 1084 |
+
margin-bottom: 0.75rem;
|
| 1085 |
+
}
|
| 1086 |
+
|
| 1087 |
+
.footer-links a {
|
| 1088 |
+
color: var(--gray-light);
|
| 1089 |
+
transition: var(--transition-normal);
|
| 1090 |
+
display: inline-block;
|
| 1091 |
+
position: relative;
|
| 1092 |
+
padding-left: 0;
|
| 1093 |
+
}
|
| 1094 |
+
|
| 1095 |
+
.footer-links a::before {
|
| 1096 |
+
content: '→';
|
| 1097 |
+
position: absolute;
|
| 1098 |
+
left: 0;
|
| 1099 |
+
opacity: 0;
|
| 1100 |
+
transform: translateX(-10px);
|
| 1101 |
+
transition: var(--transition-normal);
|
| 1102 |
+
}
|
| 1103 |
+
|
| 1104 |
+
.footer-links a:hover {
|
| 1105 |
+
color: white;
|
| 1106 |
+
padding-left: 20px;
|
| 1107 |
+
}
|
| 1108 |
+
|
| 1109 |
+
.footer-links a:hover::before {
|
| 1110 |
+
opacity: 1;
|
| 1111 |
+
transform: translateX(0);
|
| 1112 |
+
}
|
| 1113 |
+
|
| 1114 |
+
.footer-contact li {
|
| 1115 |
+
display: flex;
|
| 1116 |
+
align-items: center;
|
| 1117 |
+
gap: 0.75rem;
|
| 1118 |
+
color: var(--gray-light);
|
| 1119 |
+
}
|
| 1120 |
+
|
| 1121 |
+
.footer-contact li i {
|
| 1122 |
+
color: var(--primary-light);
|
| 1123 |
+
font-size: 1.1rem;
|
| 1124 |
+
}
|
| 1125 |
+
|
| 1126 |
+
.footer-bottom {
|
| 1127 |
+
display: flex;
|
| 1128 |
+
justify-content: space-between;
|
| 1129 |
+
align-items: center;
|
| 1130 |
+
padding-top: 1.5rem;
|
| 1131 |
+
flex-wrap: wrap;
|
| 1132 |
+
gap: 1rem;
|
| 1133 |
+
}
|
| 1134 |
+
|
| 1135 |
+
.footer-bottom p {
|
| 1136 |
+
color: var(--gray-light);
|
| 1137 |
+
margin: 0;
|
| 1138 |
+
font-size: 0.9rem;
|
| 1139 |
+
}
|
| 1140 |
+
|
| 1141 |
+
.made-with {
|
| 1142 |
+
display: flex;
|
| 1143 |
+
align-items: center;
|
| 1144 |
+
gap: 0.5rem;
|
| 1145 |
+
}
|
| 1146 |
+
|
| 1147 |
+
.made-with i {
|
| 1148 |
+
color: var(--accent);
|
| 1149 |
+
animation: heartBeat 1.5s ease infinite;
|
| 1150 |
+
}
|
| 1151 |
+
|
| 1152 |
+
@keyframes heartBeat {
|
| 1153 |
+
0%, 100% {
|
| 1154 |
+
transform: scale(1);
|
| 1155 |
+
}
|
| 1156 |
+
50% {
|
| 1157 |
+
transform: scale(1.2);
|
| 1158 |
+
}
|
| 1159 |
+
}
|