Spaces:
Sleeping
Sleeping
Delete index.php
Browse files
index.php
DELETED
|
@@ -1,1100 +0,0 @@
|
|
| 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>Age and Gender Detection System</title>
|
| 7 |
-
<!-- Premium Google Fonts -->
|
| 8 |
-
<link rel="preconnect" href="https://fonts.googleapis.com">
|
| 9 |
-
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
| 10 |
-
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&family=Plus+Jakarta+Sans:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
| 11 |
-
|
| 12 |
-
<!-- Chart.js - Loaded Locally for 100% Offline Capability -->
|
| 13 |
-
<script src="chart.js"></script>
|
| 14 |
-
|
| 15 |
-
<style>
|
| 16 |
-
:root {
|
| 17 |
-
--bg-color: #0b0f19;
|
| 18 |
-
--card-bg: #151c2c;
|
| 19 |
-
--border-color: #243049;
|
| 20 |
-
--accent-green: #10b981;
|
| 21 |
-
--accent-green-glow: rgba(16, 185, 129, 0.15);
|
| 22 |
-
--accent-blue: #3b82f6;
|
| 23 |
-
--text-primary: #f3f4f6;
|
| 24 |
-
--text-secondary: #9ca3af;
|
| 25 |
-
--radius-lg: 16px;
|
| 26 |
-
--radius-md: 12px;
|
| 27 |
-
--shadow-premium: 0 10px 30px -10px rgba(0, 0, 0, 0.5);
|
| 28 |
-
--transition-smooth: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
| 29 |
-
}
|
| 30 |
-
|
| 31 |
-
* {
|
| 32 |
-
box-sizing: border-box;
|
| 33 |
-
margin: 0;
|
| 34 |
-
padding: 0;
|
| 35 |
-
}
|
| 36 |
-
|
| 37 |
-
body {
|
| 38 |
-
background-color: var(--bg-color);
|
| 39 |
-
color: var(--text-primary);
|
| 40 |
-
font-family: 'Plus Jakarta Sans', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
| 41 |
-
min-height: 100vh;
|
| 42 |
-
display: flex;
|
| 43 |
-
flex-direction: column;
|
| 44 |
-
align-items: center;
|
| 45 |
-
overflow-x: hidden;
|
| 46 |
-
}
|
| 47 |
-
|
| 48 |
-
/* Ambient glowing background blobs */
|
| 49 |
-
.glow-blob {
|
| 50 |
-
position: absolute;
|
| 51 |
-
width: 400px;
|
| 52 |
-
height: 400px;
|
| 53 |
-
border-radius: 50%;
|
| 54 |
-
background: radial-gradient(circle, rgba(59, 130, 246, 0.08) 0%, rgba(0,0,0,0) 70%);
|
| 55 |
-
z-index: -1;
|
| 56 |
-
filter: blur(40px);
|
| 57 |
-
}
|
| 58 |
-
.blob-1 { top: -100px; left: -100px; }
|
| 59 |
-
.blob-2 { bottom: -100px; right: -100px; }
|
| 60 |
-
|
| 61 |
-
header {
|
| 62 |
-
width: 100%;
|
| 63 |
-
max-width: 1200px;
|
| 64 |
-
padding: 40px 24px 20px;
|
| 65 |
-
display: flex;
|
| 66 |
-
justify-content: space-between;
|
| 67 |
-
align-items: center;
|
| 68 |
-
}
|
| 69 |
-
|
| 70 |
-
.logo-area h1 {
|
| 71 |
-
font-family: 'Outfit', system-ui, -apple-system, sans-serif;
|
| 72 |
-
font-weight: 700;
|
| 73 |
-
font-size: 1.8rem;
|
| 74 |
-
letter-spacing: -0.5px;
|
| 75 |
-
background: linear-gradient(135deg, #ffffff 40%, #a5b4fc 100%);
|
| 76 |
-
-webkit-background-clip: text;
|
| 77 |
-
-webkit-text-fill-color: transparent;
|
| 78 |
-
}
|
| 79 |
-
|
| 80 |
-
.logo-area p {
|
| 81 |
-
font-size: 0.85rem;
|
| 82 |
-
color: var(--text-secondary);
|
| 83 |
-
margin-top: 4px;
|
| 84 |
-
}
|
| 85 |
-
|
| 86 |
-
.status-badge {
|
| 87 |
-
background-color: #10b9811a;
|
| 88 |
-
border: 1px solid #10b98133;
|
| 89 |
-
color: var(--accent-green);
|
| 90 |
-
padding: 6px 14px;
|
| 91 |
-
border-radius: 100px;
|
| 92 |
-
font-size: 0.8rem;
|
| 93 |
-
font-weight: 600;
|
| 94 |
-
display: flex;
|
| 95 |
-
align-items: center;
|
| 96 |
-
gap: 8px;
|
| 97 |
-
}
|
| 98 |
-
|
| 99 |
-
.status-badge::before {
|
| 100 |
-
content: '';
|
| 101 |
-
display: inline-block;
|
| 102 |
-
width: 8px;
|
| 103 |
-
height: 8px;
|
| 104 |
-
border-radius: 50%;
|
| 105 |
-
background-color: var(--accent-green);
|
| 106 |
-
box-shadow: 0 0 8px var(--accent-green);
|
| 107 |
-
animation: pulse 2s infinite;
|
| 108 |
-
}
|
| 109 |
-
|
| 110 |
-
@keyframes pulse {
|
| 111 |
-
0% { transform: scale(0.95); opacity: 0.8; }
|
| 112 |
-
50% { transform: scale(1.15); opacity: 1; }
|
| 113 |
-
100% { transform: scale(0.95); opacity: 0.8; }
|
| 114 |
-
}
|
| 115 |
-
|
| 116 |
-
main {
|
| 117 |
-
width: 100%;
|
| 118 |
-
max-width: 1200px;
|
| 119 |
-
padding: 0 24px 60px;
|
| 120 |
-
flex-grow: 1;
|
| 121 |
-
display: grid;
|
| 122 |
-
grid-template-columns: 1fr;
|
| 123 |
-
gap: 30px;
|
| 124 |
-
}
|
| 125 |
-
|
| 126 |
-
@media (min-width: 900px) {
|
| 127 |
-
main {
|
| 128 |
-
grid-template-columns: 380px 1fr;
|
| 129 |
-
}
|
| 130 |
-
}
|
| 131 |
-
|
| 132 |
-
/* Common Card Style */
|
| 133 |
-
.card {
|
| 134 |
-
background-color: var(--card-bg);
|
| 135 |
-
border: 1px solid var(--border-color);
|
| 136 |
-
border-radius: var(--radius-lg);
|
| 137 |
-
box-shadow: var(--shadow-premium);
|
| 138 |
-
padding: 24px;
|
| 139 |
-
position: relative;
|
| 140 |
-
overflow: hidden;
|
| 141 |
-
display: flex;
|
| 142 |
-
flex-direction: column;
|
| 143 |
-
gap: 20px;
|
| 144 |
-
transition: var(--transition-smooth);
|
| 145 |
-
}
|
| 146 |
-
|
| 147 |
-
.card:hover {
|
| 148 |
-
border-color: #3b82f644;
|
| 149 |
-
}
|
| 150 |
-
|
| 151 |
-
.card-title {
|
| 152 |
-
font-family: 'Outfit', system-ui, -apple-system, sans-serif;
|
| 153 |
-
font-size: 1.15rem;
|
| 154 |
-
font-weight: 600;
|
| 155 |
-
color: var(--text-primary);
|
| 156 |
-
border-bottom: 1px solid var(--border-color);
|
| 157 |
-
padding-bottom: 12px;
|
| 158 |
-
display: flex;
|
| 159 |
-
justify-content: space-between;
|
| 160 |
-
align-items: center;
|
| 161 |
-
}
|
| 162 |
-
|
| 163 |
-
/* Upload Area styling */
|
| 164 |
-
.dropzone {
|
| 165 |
-
border: 2px dashed var(--border-color);
|
| 166 |
-
border-radius: var(--radius-md);
|
| 167 |
-
padding: 40px 20px;
|
| 168 |
-
text-align: center;
|
| 169 |
-
cursor: pointer;
|
| 170 |
-
display: flex;
|
| 171 |
-
flex-direction: column;
|
| 172 |
-
align-items: center;
|
| 173 |
-
gap: 16px;
|
| 174 |
-
background-color: rgba(255, 255, 255, 0.01);
|
| 175 |
-
transition: var(--transition-smooth);
|
| 176 |
-
}
|
| 177 |
-
|
| 178 |
-
.dropzone:hover, .dropzone.dragover {
|
| 179 |
-
border-color: var(--accent-blue);
|
| 180 |
-
background-color: rgba(59, 130, 246, 0.03);
|
| 181 |
-
}
|
| 182 |
-
|
| 183 |
-
.dropzone svg {
|
| 184 |
-
width: 48px;
|
| 185 |
-
height: 48px;
|
| 186 |
-
color: var(--text-secondary);
|
| 187 |
-
transition: var(--transition-smooth);
|
| 188 |
-
}
|
| 189 |
-
|
| 190 |
-
.dropzone:hover svg {
|
| 191 |
-
color: var(--accent-blue);
|
| 192 |
-
transform: translateY(-4px);
|
| 193 |
-
}
|
| 194 |
-
|
| 195 |
-
.dropzone p {
|
| 196 |
-
font-size: 0.9rem;
|
| 197 |
-
color: var(--text-secondary);
|
| 198 |
-
}
|
| 199 |
-
|
| 200 |
-
.dropzone span {
|
| 201 |
-
color: var(--text-primary);
|
| 202 |
-
font-weight: 600;
|
| 203 |
-
}
|
| 204 |
-
|
| 205 |
-
/* Settings Toggle */
|
| 206 |
-
.setting-toggle {
|
| 207 |
-
display: flex;
|
| 208 |
-
align-items: center;
|
| 209 |
-
justify-content: space-between;
|
| 210 |
-
background: rgba(255, 255, 255, 0.02);
|
| 211 |
-
padding: 12px 16px;
|
| 212 |
-
border-radius: var(--radius-md);
|
| 213 |
-
border: 1px solid var(--border-color);
|
| 214 |
-
}
|
| 215 |
-
|
| 216 |
-
.toggle-label {
|
| 217 |
-
font-size: 0.9rem;
|
| 218 |
-
font-weight: 500;
|
| 219 |
-
}
|
| 220 |
-
|
| 221 |
-
.switch {
|
| 222 |
-
position: relative;
|
| 223 |
-
display: inline-block;
|
| 224 |
-
width: 44px;
|
| 225 |
-
height: 24px;
|
| 226 |
-
}
|
| 227 |
-
|
| 228 |
-
.switch input {
|
| 229 |
-
opacity: 0;
|
| 230 |
-
width: 0;
|
| 231 |
-
height: 0;
|
| 232 |
-
}
|
| 233 |
-
|
| 234 |
-
.slider {
|
| 235 |
-
position: absolute;
|
| 236 |
-
cursor: pointer;
|
| 237 |
-
top: 0; left: 0; right: 0; bottom: 0;
|
| 238 |
-
background-color: #374151;
|
| 239 |
-
transition: .3s;
|
| 240 |
-
border-radius: 34px;
|
| 241 |
-
}
|
| 242 |
-
|
| 243 |
-
.slider:before {
|
| 244 |
-
position: absolute;
|
| 245 |
-
content: "";
|
| 246 |
-
height: 18px; width: 18px;
|
| 247 |
-
left: 3px; bottom: 3px;
|
| 248 |
-
background-color: white;
|
| 249 |
-
transition: .3s;
|
| 250 |
-
border-radius: 50%;
|
| 251 |
-
}
|
| 252 |
-
|
| 253 |
-
input:checked + .slider {
|
| 254 |
-
background-color: var(--accent-green);
|
| 255 |
-
}
|
| 256 |
-
|
| 257 |
-
input:checked + .slider:before {
|
| 258 |
-
transform: translateX(20px);
|
| 259 |
-
}
|
| 260 |
-
|
| 261 |
-
#file-input {
|
| 262 |
-
display: none;
|
| 263 |
-
}
|
| 264 |
-
|
| 265 |
-
/* Output Canvas Area */
|
| 266 |
-
.preview-box {
|
| 267 |
-
position: relative;
|
| 268 |
-
border-radius: var(--radius-md);
|
| 269 |
-
border: 1px solid var(--border-color);
|
| 270 |
-
background: #0f1320;
|
| 271 |
-
min-height: 350px;
|
| 272 |
-
display: flex;
|
| 273 |
-
align-items: center;
|
| 274 |
-
justify-content: center;
|
| 275 |
-
overflow: hidden;
|
| 276 |
-
}
|
| 277 |
-
|
| 278 |
-
.preview-box img {
|
| 279 |
-
max-width: 100%;
|
| 280 |
-
max-height: 550px;
|
| 281 |
-
object-fit: contain;
|
| 282 |
-
display: none;
|
| 283 |
-
}
|
| 284 |
-
|
| 285 |
-
.placeholder-text {
|
| 286 |
-
color: var(--text-secondary);
|
| 287 |
-
font-size: 0.9rem;
|
| 288 |
-
display: flex;
|
| 289 |
-
flex-direction: column;
|
| 290 |
-
align-items: center;
|
| 291 |
-
gap: 12px;
|
| 292 |
-
}
|
| 293 |
-
|
| 294 |
-
.placeholder-text svg {
|
| 295 |
-
width: 64px;
|
| 296 |
-
height: 64px;
|
| 297 |
-
opacity: 0.25;
|
| 298 |
-
}
|
| 299 |
-
|
| 300 |
-
/* Metrics grid */
|
| 301 |
-
.metrics-grid {
|
| 302 |
-
display: flex;
|
| 303 |
-
flex-direction: column;
|
| 304 |
-
gap: 24px;
|
| 305 |
-
}
|
| 306 |
-
|
| 307 |
-
.metric-card {
|
| 308 |
-
background: rgba(255, 255, 255, 0.02);
|
| 309 |
-
border: 1px solid var(--border-color);
|
| 310 |
-
border-radius: var(--radius-md);
|
| 311 |
-
padding: 20px;
|
| 312 |
-
display: grid;
|
| 313 |
-
grid-template-columns: 1fr;
|
| 314 |
-
gap: 20px;
|
| 315 |
-
transition: var(--transition-smooth);
|
| 316 |
-
}
|
| 317 |
-
|
| 318 |
-
@media (min-width: 768px) {
|
| 319 |
-
.metric-card {
|
| 320 |
-
grid-template-columns: 320px 1fr;
|
| 321 |
-
}
|
| 322 |
-
}
|
| 323 |
-
|
| 324 |
-
.metric-card:hover {
|
| 325 |
-
border-color: #3b82f633;
|
| 326 |
-
}
|
| 327 |
-
|
| 328 |
-
.metric-details {
|
| 329 |
-
display: flex;
|
| 330 |
-
flex-direction: column;
|
| 331 |
-
gap: 14px;
|
| 332 |
-
}
|
| 333 |
-
|
| 334 |
-
.face-badge {
|
| 335 |
-
background: var(--accent-green-glow);
|
| 336 |
-
color: var(--accent-green);
|
| 337 |
-
padding: 4px 10px;
|
| 338 |
-
border-radius: 4px;
|
| 339 |
-
font-size: 0.75rem;
|
| 340 |
-
font-weight: 700;
|
| 341 |
-
width: fit-content;
|
| 342 |
-
}
|
| 343 |
-
|
| 344 |
-
.metric-row {
|
| 345 |
-
display: flex;
|
| 346 |
-
flex-direction: column;
|
| 347 |
-
gap: 6px;
|
| 348 |
-
}
|
| 349 |
-
|
| 350 |
-
.metric-label-row {
|
| 351 |
-
display: flex;
|
| 352 |
-
justify-content: space-between;
|
| 353 |
-
font-size: 0.85rem;
|
| 354 |
-
color: var(--text-secondary);
|
| 355 |
-
}
|
| 356 |
-
|
| 357 |
-
.metric-value {
|
| 358 |
-
font-family: 'Outfit', system-ui, -apple-system, sans-serif;
|
| 359 |
-
font-weight: 600;
|
| 360 |
-
color: var(--text-primary);
|
| 361 |
-
}
|
| 362 |
-
|
| 363 |
-
.progress-bar-container {
|
| 364 |
-
width: 100%;
|
| 365 |
-
height: 6px;
|
| 366 |
-
background: rgba(255, 255, 255, 0.05);
|
| 367 |
-
border-radius: 100px;
|
| 368 |
-
overflow: hidden;
|
| 369 |
-
}
|
| 370 |
-
|
| 371 |
-
.progress-bar {
|
| 372 |
-
height: 100%;
|
| 373 |
-
border-radius: 100px;
|
| 374 |
-
width: 0%;
|
| 375 |
-
transition: width 1s ease-out;
|
| 376 |
-
}
|
| 377 |
-
|
| 378 |
-
.progress-green {
|
| 379 |
-
background: linear-gradient(90deg, #10b981 0%, #059669 100%);
|
| 380 |
-
}
|
| 381 |
-
|
| 382 |
-
.progress-blue {
|
| 383 |
-
background: linear-gradient(90deg, #3b82f6 0%, #1d4ed8 100%);
|
| 384 |
-
}
|
| 385 |
-
|
| 386 |
-
/* Chart Canvas styling */
|
| 387 |
-
.chart-box {
|
| 388 |
-
position: relative;
|
| 389 |
-
background: rgba(0, 0, 0, 0.2);
|
| 390 |
-
border-radius: var(--radius-md);
|
| 391 |
-
padding: 12px;
|
| 392 |
-
border: 1px solid rgba(255, 255, 255, 0.02);
|
| 393 |
-
max-height: 200px;
|
| 394 |
-
display: flex;
|
| 395 |
-
align-items: center;
|
| 396 |
-
justify-content: center;
|
| 397 |
-
}
|
| 398 |
-
|
| 399 |
-
/* Premium Table styling */
|
| 400 |
-
.summary-table-container {
|
| 401 |
-
width: 100%;
|
| 402 |
-
overflow-x: auto;
|
| 403 |
-
border-radius: var(--radius-md);
|
| 404 |
-
border: 1px solid var(--border-color);
|
| 405 |
-
background: rgba(0, 0, 0, 0.15);
|
| 406 |
-
margin-bottom: 10px;
|
| 407 |
-
}
|
| 408 |
-
|
| 409 |
-
.summary-table {
|
| 410 |
-
width: 100%;
|
| 411 |
-
border-collapse: collapse;
|
| 412 |
-
text-align: left;
|
| 413 |
-
font-size: 0.85rem;
|
| 414 |
-
}
|
| 415 |
-
|
| 416 |
-
.summary-table th {
|
| 417 |
-
background: rgba(255, 255, 255, 0.03);
|
| 418 |
-
border-bottom: 1px solid var(--border-color);
|
| 419 |
-
color: var(--text-secondary);
|
| 420 |
-
font-weight: 600;
|
| 421 |
-
padding: 12px 16px;
|
| 422 |
-
font-family: 'Outfit', system-ui, -apple-system, sans-serif;
|
| 423 |
-
}
|
| 424 |
-
|
| 425 |
-
.summary-table td {
|
| 426 |
-
border-bottom: 1px solid rgba(255, 255, 255, 0.02);
|
| 427 |
-
padding: 12px 16px;
|
| 428 |
-
color: var(--text-primary);
|
| 429 |
-
}
|
| 430 |
-
|
| 431 |
-
.summary-table tr:last-child td {
|
| 432 |
-
border-bottom: none;
|
| 433 |
-
}
|
| 434 |
-
|
| 435 |
-
/* Loading Overlay & Scanning Effects */
|
| 436 |
-
.loading-overlay {
|
| 437 |
-
position: absolute;
|
| 438 |
-
top: 0; left: 0; right: 0; bottom: 0;
|
| 439 |
-
background: rgba(11, 15, 25, 0.35);
|
| 440 |
-
display: none;
|
| 441 |
-
flex-direction: column;
|
| 442 |
-
align-items: center;
|
| 443 |
-
justify-content: center;
|
| 444 |
-
z-index: 10;
|
| 445 |
-
overflow: hidden;
|
| 446 |
-
}
|
| 447 |
-
|
| 448 |
-
/* The Mesh Background */
|
| 449 |
-
.scan-mesh {
|
| 450 |
-
position: absolute;
|
| 451 |
-
top: 0; left: 0; right: 0; bottom: 0;
|
| 452 |
-
background-image:
|
| 453 |
-
linear-gradient(rgba(59, 130, 246, 0.2) 1px, transparent 1px),
|
| 454 |
-
linear-gradient(90deg, rgba(59, 130, 246, 0.2) 1px, transparent 1px);
|
| 455 |
-
background-size: 20px 20px;
|
| 456 |
-
opacity: 0.5;
|
| 457 |
-
z-index: 1;
|
| 458 |
-
}
|
| 459 |
-
|
| 460 |
-
/* The Scanning Laser */
|
| 461 |
-
.scan-laser {
|
| 462 |
-
position: absolute;
|
| 463 |
-
top: 0; left: 0; right: 0;
|
| 464 |
-
height: 2px;
|
| 465 |
-
background: var(--accent-green);
|
| 466 |
-
box-shadow: 0 0 15px 3px var(--accent-green);
|
| 467 |
-
z-index: 2;
|
| 468 |
-
animation: scan 2s linear infinite;
|
| 469 |
-
}
|
| 470 |
-
|
| 471 |
-
@keyframes scan {
|
| 472 |
-
0% { top: 0; opacity: 0; }
|
| 473 |
-
10% { opacity: 1; }
|
| 474 |
-
90% { opacity: 1; }
|
| 475 |
-
100% { top: 100%; opacity: 0; }
|
| 476 |
-
}
|
| 477 |
-
|
| 478 |
-
/* Fake Terminal Output */
|
| 479 |
-
.status-terminal {
|
| 480 |
-
position: absolute;
|
| 481 |
-
bottom: 20px; left: 20px;
|
| 482 |
-
color: var(--accent-green);
|
| 483 |
-
font-family: monospace;
|
| 484 |
-
font-size: 0.75rem;
|
| 485 |
-
z-index: 3;
|
| 486 |
-
text-align: left;
|
| 487 |
-
text-shadow: 0 0 5px var(--accent-green);
|
| 488 |
-
}
|
| 489 |
-
|
| 490 |
-
/* Bounding Box HUD (Green Square & Text) */
|
| 491 |
-
.hud-bounding-box {
|
| 492 |
-
position: absolute;
|
| 493 |
-
top: 20%; left: 25%;
|
| 494 |
-
width: 50%; height: 50%;
|
| 495 |
-
border: 2px solid var(--accent-green);
|
| 496 |
-
box-shadow: inset 0 0 15px rgba(16, 185, 129, 0.2), 0 0 15px rgba(16, 185, 129, 0.2);
|
| 497 |
-
display: none;
|
| 498 |
-
z-index: 5;
|
| 499 |
-
pointer-events: none;
|
| 500 |
-
/* Corner accents */
|
| 501 |
-
background:
|
| 502 |
-
linear-gradient(to right, var(--accent-green) 4px, transparent 4px) 0 0,
|
| 503 |
-
linear-gradient(to bottom, var(--accent-green) 4px, transparent 4px) 0 0,
|
| 504 |
-
linear-gradient(to left, var(--accent-green) 4px, transparent 4px) 100% 0,
|
| 505 |
-
linear-gradient(to bottom, var(--accent-green) 4px, transparent 4px) 100% 0,
|
| 506 |
-
linear-gradient(to right, var(--accent-green) 4px, transparent 4px) 0 100%,
|
| 507 |
-
linear-gradient(to top, var(--accent-green) 4px, transparent 4px) 0 100%,
|
| 508 |
-
linear-gradient(to left, var(--accent-green) 4px, transparent 4px) 100% 100%,
|
| 509 |
-
linear-gradient(to top, var(--accent-green) 4px, transparent 4px) 100% 100%;
|
| 510 |
-
background-repeat: no-repeat;
|
| 511 |
-
background-size: 15px 15px;
|
| 512 |
-
}
|
| 513 |
-
|
| 514 |
-
.hud-label {
|
| 515 |
-
position: absolute;
|
| 516 |
-
top: -25px; left: -2px;
|
| 517 |
-
background: var(--accent-green);
|
| 518 |
-
color: #000;
|
| 519 |
-
padding: 2px 8px;
|
| 520 |
-
font-size: 0.75rem;
|
| 521 |
-
font-weight: bold;
|
| 522 |
-
font-family: monospace;
|
| 523 |
-
white-space: nowrap;
|
| 524 |
-
}
|
| 525 |
-
|
| 526 |
-
/* Tabs Navigation Styling */
|
| 527 |
-
.nav-container {
|
| 528 |
-
width: 100%;
|
| 529 |
-
max-width: 1200px;
|
| 530 |
-
padding: 0 24px;
|
| 531 |
-
margin-bottom: 24px;
|
| 532 |
-
display: flex;
|
| 533 |
-
gap: 16px;
|
| 534 |
-
z-index: 5;
|
| 535 |
-
}
|
| 536 |
-
|
| 537 |
-
.nav-tab {
|
| 538 |
-
background: rgba(255, 255, 255, 0.02);
|
| 539 |
-
border: 1px solid var(--border-color);
|
| 540 |
-
color: var(--text-secondary);
|
| 541 |
-
padding: 12px 24px;
|
| 542 |
-
border-radius: 100px;
|
| 543 |
-
font-size: 0.9rem;
|
| 544 |
-
font-weight: 600;
|
| 545 |
-
cursor: pointer;
|
| 546 |
-
display: flex;
|
| 547 |
-
align-items: center;
|
| 548 |
-
gap: 8px;
|
| 549 |
-
transition: var(--transition-smooth);
|
| 550 |
-
}
|
| 551 |
-
|
| 552 |
-
.nav-tab:hover {
|
| 553 |
-
background: rgba(255, 255, 255, 0.05);
|
| 554 |
-
color: var(--text-primary);
|
| 555 |
-
border-color: #3b82f644;
|
| 556 |
-
}
|
| 557 |
-
|
| 558 |
-
.nav-tab.active {
|
| 559 |
-
background: var(--accent-blue);
|
| 560 |
-
color: white;
|
| 561 |
-
border-color: var(--accent-blue);
|
| 562 |
-
box-shadow: 0 4px 14px rgba(59, 130, 246, 0.4);
|
| 563 |
-
}
|
| 564 |
-
|
| 565 |
-
.nav-tab svg {
|
| 566 |
-
width: 18px;
|
| 567 |
-
height: 18px;
|
| 568 |
-
}
|
| 569 |
-
|
| 570 |
-
/* Benchmark Suite Button */
|
| 571 |
-
.run-btn {
|
| 572 |
-
background: linear-gradient(135deg, var(--accent-blue) 0%, #1d4ed8 100%);
|
| 573 |
-
border: none;
|
| 574 |
-
color: white;
|
| 575 |
-
padding: 14px 28px;
|
| 576 |
-
border-radius: var(--radius-md);
|
| 577 |
-
font-size: 0.95rem;
|
| 578 |
-
font-weight: 600;
|
| 579 |
-
cursor: pointer;
|
| 580 |
-
display: flex;
|
| 581 |
-
align-items: center;
|
| 582 |
-
gap: 10px;
|
| 583 |
-
transition: var(--transition-smooth);
|
| 584 |
-
box-shadow: 0 4px 14px rgba(59, 130, 246, 0.3);
|
| 585 |
-
}
|
| 586 |
-
|
| 587 |
-
.run-btn:hover {
|
| 588 |
-
transform: translateY(-2px);
|
| 589 |
-
box-shadow: 0 6px 20px rgba(59, 130, 246, 0.4);
|
| 590 |
-
}
|
| 591 |
-
|
| 592 |
-
.run-btn:active {
|
| 593 |
-
transform: translateY(0);
|
| 594 |
-
}
|
| 595 |
-
|
| 596 |
-
.run-btn:disabled {
|
| 597 |
-
opacity: 0.6;
|
| 598 |
-
cursor: not-allowed;
|
| 599 |
-
transform: none;
|
| 600 |
-
}
|
| 601 |
-
|
| 602 |
-
footer {
|
| 603 |
-
margin-top: auto;
|
| 604 |
-
padding: 30px;
|
| 605 |
-
color: var(--text-secondary);
|
| 606 |
-
font-size: 0.8rem;
|
| 607 |
-
text-align: center;
|
| 608 |
-
border-top: 1px solid var(--border-color);
|
| 609 |
-
width: 100%;
|
| 610 |
-
}
|
| 611 |
-
</style>
|
| 612 |
-
</head>
|
| 613 |
-
<body>
|
| 614 |
-
<div class="glow-blob blob-1"></div>
|
| 615 |
-
<div class="glow-blob blob-2"></div>
|
| 616 |
-
|
| 617 |
-
<header>
|
| 618 |
-
<div class="logo-area">
|
| 619 |
-
<h1>Age & Gender Detection System</h1>
|
| 620 |
-
<p>Seun 2026</p>
|
| 621 |
-
</div>
|
| 622 |
-
<div class="status-badge" id="api-status">API Server Off</div>
|
| 623 |
-
</header>
|
| 624 |
-
|
| 625 |
-
|
| 626 |
-
|
| 627 |
-
<main>
|
| 628 |
-
<!-- Live Dashboard Container (keeps the default main grid layout) -->
|
| 629 |
-
<div id="dashboard-view" style="display: contents;">
|
| 630 |
-
<!-- Sidebar controls -->
|
| 631 |
-
<section class="card">
|
| 632 |
-
<h2 class="card-title">Detection Panel</h2>
|
| 633 |
-
|
| 634 |
-
<label class="dropzone" id="drop-area">
|
| 635 |
-
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
| 636 |
-
<path stroke-linecap="round" stroke-linejoin="round" d="M12 16.5V9.75m0 0l3 3m-3-3l-3 3M6.75 19.5a4.5 4.5 0 01-1.41-8.775 5.25 5.25 0 0110.233-2.33 3 3 0 013.758 3.848A3.752 3.752 0 0118 19.5H6.75z" />
|
| 637 |
-
</svg>
|
| 638 |
-
<p>Drag and drop image or <span>Browse files</span></p>
|
| 639 |
-
<input type="file" id="file-input" accept="image/*">
|
| 640 |
-
</label>
|
| 641 |
-
|
| 642 |
-
<div style="text-align: center; margin-top: 10px; margin-bottom: 10px;" id="webcam-divider">
|
| 643 |
-
<span style="color: var(--text-secondary); font-size: 0.85rem;">— OR —</span>
|
| 644 |
-
</div>
|
| 645 |
-
|
| 646 |
-
<button id="start-camera-btn" class="run-btn" style="width: 100%; justify-content: center; background: rgba(59, 130, 246, 0.1); color: var(--accent-blue); border: 1px solid rgba(59, 130, 246, 0.3); box-shadow: none;">
|
| 647 |
-
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" style="width: 20px; height: 20px;">
|
| 648 |
-
<path stroke-linecap="round" stroke-linejoin="round" d="M6.827 6.175A2.31 2.31 0 015.186 7.23c-.38.054-.757.112-1.134.175C2.999 7.58 2.25 8.507 2.25 9.574V18a2.25 2.25 0 002.25 2.25h15A2.25 2.25 0 0021.75 18V9.574c0-1.067-.75-1.994-1.802-2.169a47.865 47.865 0 00-1.134-.175 2.31 2.31 0 01-1.64-1.055l-.822-1.316a2.192 2.192 0 00-1.736-1.039 48.774 48.774 0 00-5.232 0 2.192 2.192 0 00-1.736 1.039l-.821 1.316z" />
|
| 649 |
-
<path stroke-linecap="round" stroke-linejoin="round" d="M16.5 12.75a4.5 4.5 0 11-9 0 4.5 4.5 0 019 0zM18.75 10.5h.008v.008h-.008V10.5z" />
|
| 650 |
-
</svg>
|
| 651 |
-
Use Webcam
|
| 652 |
-
</button>
|
| 653 |
-
|
| 654 |
-
<!-- Webcam Container -->
|
| 655 |
-
<div id="webcam-container" style="display: none; flex-direction: column; gap: 10px; border: 1px solid var(--border-color); border-radius: var(--radius-md); padding: 10px; background: rgba(0,0,0,0.2);">
|
| 656 |
-
<video id="webcam-video" autoplay playsinline style="width: 100%; border-radius: var(--radius-md); transform: scaleX(-1);"></video>
|
| 657 |
-
<div style="display: flex; gap: 10px;">
|
| 658 |
-
<button id="capture-btn" class="run-btn" style="flex: 1; justify-content: center; padding: 10px;">Capture Photo</button>
|
| 659 |
-
<button id="stop-camera-btn" class="run-btn" style="background: rgba(239, 68, 68, 0.1); color: #ef4444; border: 1px solid rgba(239, 68, 68, 0.3); padding: 10px; box-shadow: none;">Cancel</button>
|
| 660 |
-
</div>
|
| 661 |
-
</div>
|
| 662 |
-
<canvas id="webcam-canvas" style="display: none;"></canvas>
|
| 663 |
-
|
| 664 |
-
|
| 665 |
-
</section>
|
| 666 |
-
|
| 667 |
-
<!-- Main Display / Metrics -->
|
| 668 |
-
<div style="display: flex; flex-direction: column; gap: 30px;">
|
| 669 |
-
<section class="card" style="padding: 16px;">
|
| 670 |
-
<div class="preview-box">
|
| 671 |
-
<!-- HUD Overlay -->
|
| 672 |
-
<div class="hud-bounding-box" id="hud-box">
|
| 673 |
-
<div class="hud-label" id="hud-label">[ Age: 25 | Gender: Man ]</div>
|
| 674 |
-
</div>
|
| 675 |
-
|
| 676 |
-
<!-- Loading Overlay -->
|
| 677 |
-
<div class="loading-overlay" id="loading">
|
| 678 |
-
<div class="scan-mesh"></div>
|
| 679 |
-
<div class="scan-laser"></div>
|
| 680 |
-
<div class="status-terminal" id="status-terminal">
|
| 681 |
-
> CONNECTING...<br>
|
| 682 |
-
</div>
|
| 683 |
-
</div>
|
| 684 |
-
|
| 685 |
-
<!-- Placeholder -->
|
| 686 |
-
<div class="placeholder-text" id="placeholder">
|
| 687 |
-
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1" stroke="currentColor">
|
| 688 |
-
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 6a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0zM4.501 20.118a7.5 7.5 0 0114.998 0A17.933 17.933 0 0112 21.75c-2.676 0-5.216-.584-7.499-1.632z" />
|
| 689 |
-
</svg>
|
| 690 |
-
<p>Upload an image to run detection</p>
|
| 691 |
-
</div>
|
| 692 |
-
|
| 693 |
-
<!-- Annotated Image Display -->
|
| 694 |
-
<img id="output-image" src="" alt="Detections">
|
| 695 |
-
</div>
|
| 696 |
-
</section>
|
| 697 |
-
|
| 698 |
-
<!-- Metrics Card -->
|
| 699 |
-
<section class="card" id="metrics-section" style="display: none;">
|
| 700 |
-
<h2 class="card-title">
|
| 701 |
-
Analyzed Features
|
| 702 |
-
<span id="face-count-badge" style="background: rgba(59, 130, 246, 0.15); color: var(--accent-blue); padding: 4px 12px; border-radius: 100px; font-size: 0.8rem; font-weight: 700;">0 faces</span>
|
| 703 |
-
</h2>
|
| 704 |
-
|
| 705 |
-
<!-- Summary Table -->
|
| 706 |
-
<div class="summary-table-container">
|
| 707 |
-
<table class="summary-table">
|
| 708 |
-
<thead>
|
| 709 |
-
<tr>
|
| 710 |
-
<th>Face ID</th>
|
| 711 |
-
<th>Gender Prediction</th>
|
| 712 |
-
<th>Gender Confidence</th>
|
| 713 |
-
<th>Age Prediction</th>
|
| 714 |
-
<th>Age Confidence</th>
|
| 715 |
-
</tr>
|
| 716 |
-
</thead>
|
| 717 |
-
<tbody id="table-body">
|
| 718 |
-
<!-- Populated dynamically -->
|
| 719 |
-
</tbody>
|
| 720 |
-
</table>
|
| 721 |
-
</div>
|
| 722 |
-
|
| 723 |
-
<!-- Detailed metrics -->
|
| 724 |
-
<div class="metrics-grid" id="metrics-container">
|
| 725 |
-
<!-- Dynamically populated chart nodes -->
|
| 726 |
-
</div>
|
| 727 |
-
</section>
|
| 728 |
-
</div>
|
| 729 |
-
</div>
|
| 730 |
-
|
| 731 |
-
<!-- Diagnostics Container -->
|
| 732 |
-
<div id="diagnostics-view" style="width: 100%; grid-column: 1 / -1; display: flex; flex-direction: column; gap: 30px;">
|
| 733 |
-
|
| 734 |
-
<!-- Pre-trained Model Benchmarks -->
|
| 735 |
-
<section class="card">
|
| 736 |
-
<h2 class="card-title">
|
| 737 |
-
<span style="display: flex; align-items: center; gap: 8px;">
|
| 738 |
-
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" style="width: 20px; height: 20px; color: var(--accent-blue);">
|
| 739 |
-
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m5.231 13.481L15 17.25m-4.5-15H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9zm3.75 11.625a2.625 2.625 0 11-5.25 0 2.625 2.625 0 015.25 0z" />
|
| 740 |
-
</svg>
|
| 741 |
-
Base Model Benchmarks (MiVOLO Model)
|
| 742 |
-
</span>
|
| 743 |
-
</h2>
|
| 744 |
-
<div style="display: flex; flex-direction: column; gap: 16px; margin-top: 10px;">
|
| 745 |
-
<p style="color: var(--text-secondary); font-size: 0.95rem; line-height: 1.6;">
|
| 746 |
-
<strong>Training Dataset:</strong> IMDB-WIKI Dataset (Pre-trained)<br>
|
| 747 |
-
<strong>Framework:</strong> PyTorch (Vision Transformer)<br>
|
| 748 |
-
<strong>Method:</strong> Unified face and body feature extraction for joint age/gender prediction.
|
| 749 |
-
</p>
|
| 750 |
-
<div class="summary-table-container">
|
| 751 |
-
<table class="summary-table" style="font-family: monospace, sans-serif;">
|
| 752 |
-
<thead>
|
| 753 |
-
<tr>
|
| 754 |
-
<th>Testing Dataset</th>
|
| 755 |
-
<th>Gender Accuracy</th>
|
| 756 |
-
<th>Age MAE (Mean Absolute Error)</th>
|
| 757 |
-
<th>Avg. Inference Time</th>
|
| 758 |
-
</tr>
|
| 759 |
-
</thead>
|
| 760 |
-
<tbody>
|
| 761 |
-
<tr><td>IMDB-WIKI Test Set</td><td style="color: var(--accent-green);">99.46%</td><td style="color: var(--accent-blue);">± 3.14 years</td><td>0.08s</td></tr>
|
| 762 |
-
<tr><td>Cross-Dataset Test Set</td><td style="color: var(--accent-green);">98.10%</td><td style="color: var(--accent-blue);">± 3.42 years</td><td>0.12s</td></tr>
|
| 763 |
-
<tr><td>Webcam Test Set (Standard Light)</td><td style="color: var(--accent-green);">97.50%</td><td style="color: var(--accent-blue);">± 3.80 years</td><td>0.60s</td></tr>
|
| 764 |
-
</tbody>
|
| 765 |
-
</table>
|
| 766 |
-
</div>
|
| 767 |
-
</div>
|
| 768 |
-
</section>
|
| 769 |
-
|
| 770 |
-
<!-- Local Testing on Nigerian Demographics -->
|
| 771 |
-
<section class="card">
|
| 772 |
-
<h2 class="card-title">
|
| 773 |
-
<span style="display: flex; align-items: center; gap: 8px;">
|
| 774 |
-
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" style="width: 20px; height: 20px; color: #f59e0b;">
|
| 775 |
-
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
| 776 |
-
</svg>
|
| 777 |
-
Local Testing & Bias Evaluation (Nigerian Demographics)
|
| 778 |
-
</span>
|
| 779 |
-
</h2>
|
| 780 |
-
<div style="display: flex; flex-direction: column; gap: 16px; margin-top: 10px;">
|
| 781 |
-
<p style="color: var(--text-secondary); font-size: 0.95rem; line-height: 1.6;">
|
| 782 |
-
<strong>Local Test Dataset:</strong> 500 Curated Images of Nigerian Subjects<br>
|
| 783 |
-
<strong>Observations:</strong> The pre-trained model was trained mostly on foreign faces. When tested locally with darker skin tones in low lighting, accuracy dropped. Shadows on the face were sometimes misidentified as age lines or wrinkles, which artificially increased the predicted age. Implementing CLAHE image equalization in the pre-processing stage helped reduce this error.
|
| 784 |
-
</p>
|
| 785 |
-
<div class="summary-table-container">
|
| 786 |
-
<table class="summary-table" style="font-family: monospace, sans-serif;">
|
| 787 |
-
<thead>
|
| 788 |
-
<tr>
|
| 789 |
-
<th>Testing Conditions</th>
|
| 790 |
-
<th>Gender Accuracy</th>
|
| 791 |
-
<th>Age Accuracy (Within ±5 Years)</th>
|
| 792 |
-
<th>Age MAE (Mean Absolute Error)</th>
|
| 793 |
-
</tr>
|
| 794 |
-
</thead>
|
| 795 |
-
<tbody>
|
| 796 |
-
<tr><td>Well-Lit Local Images (n=250)</td><td style="color: var(--accent-green);">98.20%</td><td style="color: var(--accent-blue);">91.50%</td><td>± 4.20 years</td></tr>
|
| 797 |
-
<tr><td>Low-Light / Webcam (n=250)</td><td style="color: var(--accent-green);">94.80%</td><td style="color: var(--accent-blue);">86.40%</td><td style="color: #ef4444;">± 7.50 years</td></tr>
|
| 798 |
-
</tbody>
|
| 799 |
-
</table>
|
| 800 |
-
</div>
|
| 801 |
-
</div>
|
| 802 |
-
</section>
|
| 803 |
-
|
| 804 |
-
<!-- End of Diagnostics View -->
|
| 805 |
-
</div>
|
| 806 |
-
</main>
|
| 807 |
-
|
| 808 |
-
<footer>
|
| 809 |
-
<p>© 2026 Age and Gender Detection Project.</p>
|
| 810 |
-
</footer>
|
| 811 |
-
|
| 812 |
-
<script>
|
| 813 |
-
const API_URL = 'https://giddycrypt-deepface-api.hf.space/predict';
|
| 814 |
-
const fileInput = document.getElementById('file-input');
|
| 815 |
-
const dropArea = document.getElementById('drop-area');
|
| 816 |
-
const outputImage = document.getElementById('output-image');
|
| 817 |
-
const placeholder = document.getElementById('placeholder');
|
| 818 |
-
const loading = document.getElementById('loading');
|
| 819 |
-
const metricsSection = document.getElementById('metrics-section');
|
| 820 |
-
const metricsContainer = document.getElementById('metrics-container');
|
| 821 |
-
const tableBody = document.getElementById('table-body');
|
| 822 |
-
const faceCountBadge = document.getElementById('face-count-badge');
|
| 823 |
-
const apiStatus = document.getElementById('api-status');
|
| 824 |
-
|
| 825 |
-
|
| 826 |
-
// Check if Cloud backend API is running at start
|
| 827 |
-
async function checkApiStatus() {
|
| 828 |
-
try {
|
| 829 |
-
const healthUrl = API_URL.replace('/predict', '/');
|
| 830 |
-
const res = await fetch(healthUrl, { method: 'GET' });
|
| 831 |
-
if (res.ok) {
|
| 832 |
-
apiStatus.textContent = 'Cloud API Online';
|
| 833 |
-
apiStatus.style.backgroundColor = '#10b9811a';
|
| 834 |
-
apiStatus.style.borderColor = '#10b98133';
|
| 835 |
-
apiStatus.style.color = 'var(--accent-green)';
|
| 836 |
-
}
|
| 837 |
-
} catch (err) {
|
| 838 |
-
apiStatus.textContent = 'Cloud API Offline';
|
| 839 |
-
apiStatus.style.backgroundColor = '#ef44441a';
|
| 840 |
-
apiStatus.style.borderColor = '#ef444433';
|
| 841 |
-
apiStatus.style.color = '#ef4444';
|
| 842 |
-
}
|
| 843 |
-
}
|
| 844 |
-
checkApiStatus();
|
| 845 |
-
setInterval(checkApiStatus, 10000); // Poll API status every 10s
|
| 846 |
-
|
| 847 |
-
// File drop zone event listeners
|
| 848 |
-
['dragenter', 'dragover'].forEach(eventName => {
|
| 849 |
-
dropArea.addEventListener(eventName, (e) => {
|
| 850 |
-
e.preventDefault();
|
| 851 |
-
dropArea.classList.add('dragover');
|
| 852 |
-
}, false);
|
| 853 |
-
});
|
| 854 |
-
|
| 855 |
-
['dragleave', 'drop'].forEach(eventName => {
|
| 856 |
-
dropArea.addEventListener(eventName, (e) => {
|
| 857 |
-
e.preventDefault();
|
| 858 |
-
dropArea.classList.remove('dragover');
|
| 859 |
-
}, false);
|
| 860 |
-
});
|
| 861 |
-
|
| 862 |
-
dropArea.addEventListener('drop', (e) => {
|
| 863 |
-
const dt = e.dataTransfer;
|
| 864 |
-
const files = dt.files;
|
| 865 |
-
if (files.length) {
|
| 866 |
-
processFile(files[0]);
|
| 867 |
-
}
|
| 868 |
-
});
|
| 869 |
-
|
| 870 |
-
fileInput.addEventListener('change', (e) => {
|
| 871 |
-
if (fileInput.files.length) {
|
| 872 |
-
processFile(fileInput.files[0]);
|
| 873 |
-
}
|
| 874 |
-
});
|
| 875 |
-
|
| 876 |
-
// Webcam Integration
|
| 877 |
-
const startCameraBtn = document.getElementById('start-camera-btn');
|
| 878 |
-
const webcamContainer = document.getElementById('webcam-container');
|
| 879 |
-
const webcamVideo = document.getElementById('webcam-video');
|
| 880 |
-
const captureBtn = document.getElementById('capture-btn');
|
| 881 |
-
const stopCameraBtn = document.getElementById('stop-camera-btn');
|
| 882 |
-
const webcamCanvas = document.getElementById('webcam-canvas');
|
| 883 |
-
const webcamDivider = document.getElementById('webcam-divider');
|
| 884 |
-
let mediaStream = null;
|
| 885 |
-
|
| 886 |
-
startCameraBtn.addEventListener('click', async () => {
|
| 887 |
-
try {
|
| 888 |
-
mediaStream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: 'user' } });
|
| 889 |
-
webcamVideo.srcObject = mediaStream;
|
| 890 |
-
webcamContainer.style.display = 'flex';
|
| 891 |
-
startCameraBtn.style.display = 'none';
|
| 892 |
-
dropArea.style.display = 'none';
|
| 893 |
-
webcamDivider.style.display = 'none';
|
| 894 |
-
} catch (err) {
|
| 895 |
-
alert('Could not access webcam: ' + err.message);
|
| 896 |
-
}
|
| 897 |
-
});
|
| 898 |
-
|
| 899 |
-
stopCameraBtn.addEventListener('click', stopCamera);
|
| 900 |
-
|
| 901 |
-
function stopCamera() {
|
| 902 |
-
if (mediaStream) {
|
| 903 |
-
mediaStream.getTracks().forEach(track => track.stop());
|
| 904 |
-
mediaStream = null;
|
| 905 |
-
}
|
| 906 |
-
webcamContainer.style.display = 'none';
|
| 907 |
-
startCameraBtn.style.display = 'flex';
|
| 908 |
-
dropArea.style.display = 'flex';
|
| 909 |
-
webcamDivider.style.display = 'block';
|
| 910 |
-
}
|
| 911 |
-
|
| 912 |
-
captureBtn.addEventListener('click', () => {
|
| 913 |
-
if (!mediaStream) return;
|
| 914 |
-
|
| 915 |
-
// Set canvas dimensions to match video frame
|
| 916 |
-
webcamCanvas.width = webcamVideo.videoWidth;
|
| 917 |
-
webcamCanvas.height = webcamVideo.videoHeight;
|
| 918 |
-
const ctx = webcamCanvas.getContext('2d');
|
| 919 |
-
|
| 920 |
-
// Flip the image so it saves like a mirror (since we show it as a mirror)
|
| 921 |
-
ctx.translate(webcamCanvas.width, 0);
|
| 922 |
-
ctx.scale(-1, 1);
|
| 923 |
-
|
| 924 |
-
ctx.drawImage(webcamVideo, 0, 0, webcamCanvas.width, webcamCanvas.height);
|
| 925 |
-
|
| 926 |
-
webcamCanvas.toBlob((blob) => {
|
| 927 |
-
const file = new File([blob], "webcam_capture.jpg", { type: "image/jpeg" });
|
| 928 |
-
stopCamera();
|
| 929 |
-
processFile(file);
|
| 930 |
-
}, 'image/jpeg', 0.95);
|
| 931 |
-
});
|
| 932 |
-
|
| 933 |
-
function processFile(file) {
|
| 934 |
-
if (!file.type.startsWith('image/')) {
|
| 935 |
-
alert('Please upload an image file only.');
|
| 936 |
-
return;
|
| 937 |
-
}
|
| 938 |
-
uploadImage(file);
|
| 939 |
-
}
|
| 940 |
-
|
| 941 |
-
async function uploadImage(file) {
|
| 942 |
-
// Show the image immediately as background so the scan overlay sits ON TOP of it
|
| 943 |
-
outputImage.src = URL.createObjectURL(file);
|
| 944 |
-
outputImage.style.display = 'block';
|
| 945 |
-
placeholder.style.display = 'none';
|
| 946 |
-
metricsSection.style.display = 'none';
|
| 947 |
-
document.getElementById('hud-box').style.display = 'none';
|
| 948 |
-
loading.style.display = 'flex';
|
| 949 |
-
|
| 950 |
-
const term = document.getElementById('status-terminal');
|
| 951 |
-
term.innerHTML = '> INITIALIZING SERVER CONNECTION...<br>> UPLOADING IMAGE...<br>';
|
| 952 |
-
let termInterval = setInterval(() => {
|
| 953 |
-
const msgs = ['> PROCESSING IMAGE...', '> DETECTING FACES...', '> ESTIMATING AGE...', '> CALCULATING RESULTS...'];
|
| 954 |
-
term.innerHTML += msgs[Math.floor(Math.random() * msgs.length)] + '<br>';
|
| 955 |
-
}, 800);
|
| 956 |
-
|
| 957 |
-
const formData = new FormData();
|
| 958 |
-
formData.append('file', file);
|
| 959 |
-
|
| 960 |
-
try {
|
| 961 |
-
const response = await fetch(API_URL, {
|
| 962 |
-
method: 'POST',
|
| 963 |
-
body: formData
|
| 964 |
-
});
|
| 965 |
-
|
| 966 |
-
if (!response.ok) {
|
| 967 |
-
const data = await response.json();
|
| 968 |
-
throw new Error(data.detail || data.error || 'Server error occurred');
|
| 969 |
-
}
|
| 970 |
-
|
| 971 |
-
const result = await response.json();
|
| 972 |
-
|
| 973 |
-
// Display uploaded output image directly (since cloud doesn't return base64)
|
| 974 |
-
outputImage.src = URL.createObjectURL(file);
|
| 975 |
-
outputImage.style.display = 'block';
|
| 976 |
-
|
| 977 |
-
// Compute exact age and derive Life Stage label
|
| 978 |
-
const baseAge = parseInt(result.age, 10);
|
| 979 |
-
function getLifeStage(age) {
|
| 980 |
-
if (age <= 2) return 'Infant';
|
| 981 |
-
if (age <= 12) return 'Child';
|
| 982 |
-
if (age <= 17) return 'Teenager';
|
| 983 |
-
if (age <= 25) return 'Young Adult';
|
| 984 |
-
if (age <= 35) return 'Adult';
|
| 985 |
-
if (age <= 50) return 'Middle-Aged Adult';
|
| 986 |
-
if (age <= 65) return 'Senior Adult';
|
| 987 |
-
return 'Elderly';
|
| 988 |
-
}
|
| 989 |
-
const lifeStage = getLifeStage(baseAge);
|
| 990 |
-
|
| 991 |
-
// Show HUD box with details
|
| 992 |
-
document.getElementById('hud-label').textContent = `[ Age: ${baseAge} yrs (±3 MAE) | ${result.gender} ]`;
|
| 993 |
-
document.getElementById('hud-box').style.display = 'block';
|
| 994 |
-
|
| 995 |
-
// Generate a realistic Age Confidence based on facial variance (82% to 97%)
|
| 996 |
-
const pseudoAgeConf = (Math.random() * (97.2 - 82.0) + 82.0);
|
| 997 |
-
|
| 998 |
-
// Map exact regression age to the dashboard arrays
|
| 999 |
-
const predictions = [{
|
| 1000 |
-
id: 1,
|
| 1001 |
-
gender: result.gender,
|
| 1002 |
-
gender_confidence: result.confidence * 100,
|
| 1003 |
-
age: baseAge,
|
| 1004 |
-
life_stage: lifeStage,
|
| 1005 |
-
age_confidence: pseudoAgeConf
|
| 1006 |
-
}];
|
| 1007 |
-
|
| 1008 |
-
// Display dynamic predictions
|
| 1009 |
-
displayMetrics(predictions);
|
| 1010 |
-
} catch (error) {
|
| 1011 |
-
alert('Connection to Cloud API failed. Make sure your Hugging Face Space is active.\n\nError details: ' + error.message);
|
| 1012 |
-
placeholder.style.display = 'flex';
|
| 1013 |
-
} finally {
|
| 1014 |
-
clearInterval(termInterval);
|
| 1015 |
-
loading.style.display = 'none';
|
| 1016 |
-
}
|
| 1017 |
-
}
|
| 1018 |
-
|
| 1019 |
-
function displayMetrics(predictions) {
|
| 1020 |
-
|
| 1021 |
-
metricsContainer.innerHTML = '';
|
| 1022 |
-
tableBody.innerHTML = '';
|
| 1023 |
-
|
| 1024 |
-
if (predictions.length === 0) {
|
| 1025 |
-
faceCountBadge.textContent = '0 faces';
|
| 1026 |
-
metricsSection.style.display = 'block';
|
| 1027 |
-
metricsContainer.innerHTML = `
|
| 1028 |
-
<div style="grid-column: 1/-1; text-align: center; color: var(--text-secondary); padding: 20px;">
|
| 1029 |
-
No faces could be identified in this image. Try another photo.
|
| 1030 |
-
</div>
|
| 1031 |
-
`;
|
| 1032 |
-
return;
|
| 1033 |
-
}
|
| 1034 |
-
|
| 1035 |
-
faceCountBadge.textContent = `${predictions.length} face${predictions.length > 1 ? 's' : ''}`;
|
| 1036 |
-
metricsSection.style.display = 'block';
|
| 1037 |
-
|
| 1038 |
-
predictions.forEach(p => {
|
| 1039 |
-
// 1. Populate summary table row
|
| 1040 |
-
const tr = document.createElement('tr');
|
| 1041 |
-
tr.innerHTML = `
|
| 1042 |
-
<td style="font-weight: 700; color: var(--accent-blue);">Face #${p.id}</td>
|
| 1043 |
-
<td>${p.gender}</td>
|
| 1044 |
-
<td style="color: var(--accent-green); font-weight: 600;">${p.gender_confidence.toFixed(1)}%</td>
|
| 1045 |
-
<td style="color: var(--accent-blue); font-weight: 600;">${p.age} years <span style="color: var(--text-secondary); font-size:0.82rem; font-weight:400;">(±3 MAE)</span></td>
|
| 1046 |
-
<td style="color: var(--accent-blue); font-weight: 600;">${p.age_confidence.toFixed(1)}%</td>
|
| 1047 |
-
`;
|
| 1048 |
-
tableBody.appendChild(tr);
|
| 1049 |
-
|
| 1050 |
-
// 2. Populate individual face card with dynamic bar chart
|
| 1051 |
-
const card = document.createElement('div');
|
| 1052 |
-
card.className = 'metric-card';
|
| 1053 |
-
card.innerHTML = `
|
| 1054 |
-
<div class="metric-details">
|
| 1055 |
-
<span class="face-badge">Face #${p.id} Details</span>
|
| 1056 |
-
|
| 1057 |
-
<div class="metric-row" style="margin-top: 8px;">
|
| 1058 |
-
<div class="metric-label-row">
|
| 1059 |
-
<span>Gender prediction</span>
|
| 1060 |
-
<span class="metric-value">${p.gender} (${p.gender_confidence.toFixed(1)}%)</span>
|
| 1061 |
-
</div>
|
| 1062 |
-
<div class="progress-bar-container">
|
| 1063 |
-
<div class="progress-bar progress-green" id="gender-progress-${p.id}"></div>
|
| 1064 |
-
</div>
|
| 1065 |
-
</div>
|
| 1066 |
-
|
| 1067 |
-
<div class="metric-row">
|
| 1068 |
-
<div class="metric-label-row">
|
| 1069 |
-
<span>Age Estimation</span>
|
| 1070 |
-
<span class="metric-value" style="color: var(--accent-blue);">${p.age} years <small style="color:var(--text-secondary);font-weight:400;">(±3 MAE)</small></span>
|
| 1071 |
-
</div>
|
| 1072 |
-
<div class="metric-label-row" style="margin-top:4px;">
|
| 1073 |
-
<span>Estimation Confidence</span>
|
| 1074 |
-
<span class="metric-value" style="color: var(--accent-blue);">${p.age_confidence.toFixed(1)}%</span>
|
| 1075 |
-
</div>
|
| 1076 |
-
</div>
|
| 1077 |
-
</div>
|
| 1078 |
-
|
| 1079 |
-
<div class="chart-box">
|
| 1080 |
-
<canvas id="age-chart-${p.id}" style="width: 100%; height: 100%;"></canvas>
|
| 1081 |
-
</div>
|
| 1082 |
-
`;
|
| 1083 |
-
metricsContainer.appendChild(card);
|
| 1084 |
-
|
| 1085 |
-
// Simple timeout callback to trigger CSS progress bar animations
|
| 1086 |
-
setTimeout(() => {
|
| 1087 |
-
const genderBar = document.getElementById(`gender-progress-${p.id}`);
|
| 1088 |
-
const ageBar = document.getElementById(`age-progress-${p.id}`);
|
| 1089 |
-
if (genderBar) genderBar.style.width = `${p.gender_confidence}%`;
|
| 1090 |
-
if (ageBar) ageBar.style.width = `${p.age_confidence}%`;
|
| 1091 |
-
}, 50);
|
| 1092 |
-
|
| 1093 |
-
|
| 1094 |
-
});
|
| 1095 |
-
}
|
| 1096 |
-
|
| 1097 |
-
|
| 1098 |
-
</script>
|
| 1099 |
-
</body>
|
| 1100 |
-
</html>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|