aleehassan commited on
Commit
e2b8364
·
verified ·
1 Parent(s): dc3f604

Upload 5 files

Browse files
Files changed (5) hide show
  1. app.py +164 -0
  2. index.html +530 -0
  3. main.js +792 -0
  4. requirements.txt +9 -0
  5. 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>&copy; 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
+ }