devarajns commited on
Commit
281469b
·
verified ·
1 Parent(s): 404e31c

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +633 -19
index.html CHANGED
@@ -1,19 +1,633 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>Webcam Eye Tracker</title>
7
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
8
+ <style>
9
+ :root {
10
+ --primary-color: #5d69b2;
11
+ --secondary-color: #3a416f;
12
+ --accent-color: #ff7e5f;
13
+ --bg-color: #f5f7fa;
14
+ --text-color: #333;
15
+ }
16
+
17
+ * {
18
+ margin: 0;
19
+ padding: 0;
20
+ box-sizing: border-box;
21
+ }
22
+
23
+ body {
24
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
25
+ background-color: var(--bg-color);
26
+ color: var(--text-color);
27
+ display: flex;
28
+ flex-direction: column;
29
+ align-items: center;
30
+ min-height: 100vh;
31
+ padding: 2rem;
32
+ position: relative;
33
+ overflow-x: hidden;
34
+ }
35
+
36
+ header {
37
+ text-align: center;
38
+ margin-bottom: 2rem;
39
+ width: 100%;
40
+ }
41
+
42
+ h1 {
43
+ color: var(--primary-color);
44
+ margin-bottom: 0.5rem;
45
+ font-size: 2.2rem;
46
+ }
47
+
48
+ .subtitle {
49
+ color: var(--secondary-color);
50
+ opacity: 0.8;
51
+ margin-bottom: 1.5rem;
52
+ }
53
+
54
+ .tracker-container {
55
+ display: flex;
56
+ flex-direction: column;
57
+ align-items: center;
58
+ width: 100%;
59
+ max-width: 800px;
60
+ gap: 2rem;
61
+ }
62
+
63
+ .camera-container {
64
+ position: relative;
65
+ width: 100%;
66
+ max-width: 640px;
67
+ border-radius: 12px;
68
+ overflow: hidden;
69
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
70
+ }
71
+
72
+ #video {
73
+ width: 100%;
74
+ display: block;
75
+ background-color: #000;
76
+ }
77
+
78
+ #canvas {
79
+ position: absolute;
80
+ top: 0;
81
+ left: 0;
82
+ width: 100%;
83
+ }
84
+
85
+ .metrics-container {
86
+ display: flex;
87
+ flex-wrap: wrap;
88
+ gap: 1rem;
89
+ justify-content: center;
90
+ width: 100%;
91
+ }
92
+
93
+ .metric-card {
94
+ background-color: white;
95
+ border-radius: 12px;
96
+ padding: 1.5rem;
97
+ min-width: 200px;
98
+ box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05);
99
+ flex-grow: 1;
100
+ transition: transform 0.3s ease, box-shadow 0.3s ease;
101
+ }
102
+
103
+ .metric-card:hover {
104
+ transform: translateY(-5px);
105
+ box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
106
+ }
107
+
108
+ .metric-title {
109
+ color: var(--primary-color);
110
+ font-size: 0.9rem;
111
+ text-transform: uppercase;
112
+ letter-spacing: 1px;
113
+ margin-bottom: 0.5rem;
114
+ display: flex;
115
+ align-items: center;
116
+ gap: 0.5rem;
117
+ }
118
+
119
+ .metric-value {
120
+ font-size: 1.8rem;
121
+ font-weight: bold;
122
+ color: var(--secondary-color);
123
+ }
124
+
125
+ .metric-unit {
126
+ font-size: 0.9rem;
127
+ color: #888;
128
+ margin-left: 0.3rem;
129
+ }
130
+
131
+ .chart-container {
132
+ width: 100%;
133
+ height: 200px;
134
+ background-color: white;
135
+ border-radius: 12px;
136
+ padding: 1rem;
137
+ box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05);
138
+ position: relative;
139
+ overflow: hidden;
140
+ }
141
+
142
+ canvas#gaze-chart {
143
+ width: 100%;
144
+ height: 100%;
145
+ }
146
+
147
+ .controls {
148
+ display: flex;
149
+ gap: 1rem;
150
+ margin-top: 1rem;
151
+ flex-wrap: wrap;
152
+ justify-content: center;
153
+ }
154
+
155
+ button {
156
+ padding: 0.8rem 1.5rem;
157
+ background-color: var(--primary-color);
158
+ color: white;
159
+ border: none;
160
+ border-radius: 8px;
161
+ cursor: pointer;
162
+ font-weight: 600;
163
+ transition: all 0.3s ease;
164
+ display: flex;
165
+ align-items: center;
166
+ gap: 0.5rem;
167
+ }
168
+
169
+ button:hover {
170
+ background-color: var(--secondary-color);
171
+ transform: translateY(-2px);
172
+ }
173
+
174
+ button.secondary {
175
+ background-color: white;
176
+ color: var(--primary-color);
177
+ border: 1px solid #ddd;
178
+ }
179
+
180
+ button.secondary:hover {
181
+ background-color: #f0f0f0;
182
+ }
183
+
184
+ .loading {
185
+ display: flex;
186
+ flex-direction: column;
187
+ align-items: center;
188
+ justify-content: center;
189
+ gap: 1rem;
190
+ padding: 2rem;
191
+ background-color: white;
192
+ border-radius: 12px;
193
+ box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05);
194
+ width: 100%;
195
+ }
196
+
197
+ .spinner {
198
+ border: 4px solid rgba(0, 0, 0, 0.1);
199
+ border-radius: 50%;
200
+ border-top: 4px solid var(--primary-color);
201
+ width: 40px;
202
+ height: 40px;
203
+ animation: spin 1s linear infinite;
204
+ }
205
+
206
+ @keyframes spin {
207
+ 0% { transform: rotate(0deg); }
208
+ 100% { transform: rotate(360deg); }
209
+ }
210
+
211
+ .error-message {
212
+ color: #e74c3c;
213
+ background-color: #fceae9;
214
+ padding: 1rem;
215
+ border-radius: 8px;
216
+ max-width: 100%;
217
+ text-align: center;
218
+ }
219
+
220
+ footer {
221
+ margin-top: 3rem;
222
+ text-align: center;
223
+ color: #888;
224
+ font-size: 0.9rem;
225
+ }
226
+
227
+ @media (max-width: 600px) {
228
+ .tracker-container {
229
+ gap: 1rem;
230
+ }
231
+
232
+ .metric-card {
233
+ min-width: 150px;
234
+ padding: 1rem;
235
+ }
236
+
237
+ .metric-value {
238
+ font-size: 1.5rem;
239
+ }
240
+ }
241
+ </style>
242
+ </head>
243
+ <body>
244
+ <header>
245
+ <h1><i class="fas fa-eye"></i> Webcam Eye Tracker</h1>
246
+ <p class="subtitle">Real-time eye tracking using your webcam and face detection</p>
247
+ </header>
248
+
249
+ <div class="tracker-container">
250
+ <div class="loading" id="loading">
251
+ <div class="spinner"></div>
252
+ <p>Loading face detection models...</p>
253
+ </div>
254
+
255
+ <div class="camera-container" id="camera-container" style="display: none;">
256
+ <video id="video" width="640" height="480" autoplay muted playsinline></video>
257
+ <canvas id="canvas" width="640" height="480"></canvas>
258
+ </div>
259
+
260
+ <div class="metrics-container">
261
+ <div class="metric-card">
262
+ <div class="metric-title">
263
+ <i class="fas fa-crosshairs"></i> Eye Position
264
+ </div>
265
+ <div class="metric-value" id="eye-position">
266
+ <span id="eye-x">0</span>, <span id="eye-y">0</span>
267
+ </div>
268
+ </div>
269
+
270
+ <div class="metric-card">
271
+ <div class="metric-title">
272
+ <i class="fas fa-running"></i> Movement Speed
273
+ </div>
274
+ <div class="metric-value" id="movement-speed">0<span class="metric-unit">px/s</span></div>
275
+ </div>
276
+
277
+ <div class="metric-card">
278
+ <div class="metric-title">
279
+ <i class="fas fa-history"></i> Time Tracked
280
+ </div>
281
+ <div class="metric-value" id="time-tracked">0<span class="metric-unit">s</span></div>
282
+ </div>
283
+
284
+ <div class="metric-card">
285
+ <div class="metric-title">
286
+ <i class="fas fa-bullseye"></i> Fixations
287
+ </div>
288
+ <div class="metric-value" id="fixation-count">0</div>
289
+ </div>
290
+ </div>
291
+
292
+ <div class="chart-container">
293
+ <canvas id="gaze-chart"></canvas>
294
+ </div>
295
+
296
+ <div class="controls">
297
+ <button id="start-btn"><i class="fas fa-play"></i> Start Tracking</button>
298
+ <button id="reset-btn" class="secondary"><i class="fas fa-redo"></i> Reset</button>
299
+ <button id="debug-btn" class="secondary"><i class="fas fa-bug"></i> Toggle Debug</button>
300
+ </div>
301
+
302
+ <div class="error-message" id="error-message" style="display: none;"></div>
303
+ </div>
304
+
305
+ <footer>
306
+ <p>Webcam Eye Tracker &copy; 2024 | Uses face-api.js for face and eye detection</p>
307
+ </footer>
308
+
309
+ <!-- Load face-api.js from CDN -->
310
+ <script src="https://cdn.jsdelivr.net/npm/face-api.js@0.22.2/dist/face-api.min.js"></script>
311
+ <!-- Load Chart.js from CDN -->
312
+ <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
313
+
314
+ <script>
315
+ document.addEventListener('DOMContentLoaded', async function() {
316
+ // DOM Elements
317
+ const video = document.getElementById('video');
318
+ const canvas = document.getElementById('canvas');
319
+ const ctx = canvas.getContext('2d');
320
+ const startBtn = document.getElementById('start-btn');
321
+ const resetBtn = document.getElementById('reset-btn');
322
+ const debugBtn = document.getElementById('debug-btn');
323
+ const eyeX = document.getElementById('eye-x');
324
+ const eyeY = document.getElementById('eye-y');
325
+ const movementSpeed = document.getElementById('movement-speed');
326
+ const timeTracked = document.getElementById('time-tracked');
327
+ const fixationCount = document.getElementById('fixation-count');
328
+ const loadingElement = document.getElementById('loading');
329
+ const cameraContainer = document.getElementById('camera-container');
330
+ const errorMessage = document.getElementById('error-message');
331
+
332
+ // Variables
333
+ let trackingActive = false;
334
+ let startTime = 0;
335
+ let lastPosition = { x: 0, y: 0 };
336
+ let lastTime = 0;
337
+ let currentSpeed = 0;
338
+ let fixations = 0;
339
+ let fixationStartTime = 0;
340
+ let isFixated = false;
341
+ let gazeHistory = [];
342
+ let showDebug = false;
343
+ let modelsLoaded = false;
344
+ let stream = null;
345
+
346
+ // Chart setup
347
+ const chartCtx = document.getElementById('gaze-chart').getContext('2d');
348
+ const gazeChart = new Chart(chartCtx, {
349
+ type: 'line',
350
+ data: {
351
+ labels: [],
352
+ datasets: [{
353
+ label: 'Eye Movement Speed (px/s)',
354
+ data: [],
355
+ borderColor: '#5d69b2',
356
+ backgroundColor: 'rgba(93, 105, 178, 0.1)',
357
+ tension: 0.4,
358
+ fill: true
359
+ }]
360
+ },
361
+ options: {
362
+ responsive: true,
363
+ maintainAspectRatio: false,
364
+ scales: {
365
+ y: {
366
+ beginAtZero: true
367
+ }
368
+ },
369
+ animation: {
370
+ duration: 0
371
+ }
372
+ }
373
+ });
374
+
375
+ // Load face-api.js models
376
+ async function loadModels() {
377
+ try {
378
+ await faceapi.nets.tinyFaceDetector.loadFromUri('https://justadudewhohacks.github.io/face-api.js/models');
379
+ await faceapi.nets.faceLandmark68Net.loadFromUri('https://justadudewhohacks.github.io/face-api.js/models');
380
+ modelsLoaded = true;
381
+
382
+ // Hide loading and show camera UI
383
+ loadingElement.style.display = 'none';
384
+ cameraContainer.style.display = 'block';
385
+
386
+ // Prompt for camera access
387
+ initCamera();
388
+ } catch (error) {
389
+ console.error('Error loading models:', error);
390
+ showError("Failed to load face detection models. Please check your internet connection and try again.");
391
+ }
392
+ }
393
+
394
+ // Initialize camera
395
+ async function initCamera() {
396
+ try {
397
+ stream = await navigator.mediaDevices.getUserMedia({
398
+ video: {
399
+ width: { ideal: 640 },
400
+ height: { ideal: 480 },
401
+ facingMode: 'user'
402
+ },
403
+ audio: false
404
+ });
405
+ video.srcObject = stream;
406
+ video.play();
407
+ } catch (error) {
408
+ console.error('Camera error:', error);
409
+ if (error.name === 'NotAllowedError') {
410
+ showError("Camera access was denied. Please allow camera access to use this feature.");
411
+ } else if (error.name === 'NotFoundError') {
412
+ showError("No camera found. Please connect a webcam to use this feature.");
413
+ } else {
414
+ showError("Failed to access camera. Please try again.");
415
+ }
416
+ }
417
+ }
418
+
419
+ // Show error message
420
+ function showError(message) {
421
+ errorMessage.textContent = message;
422
+ errorMessage.style.display = 'block';
423
+ loadingElement.style.display = 'none';
424
+ }
425
+
426
+ // Calculate eye position based on landmarks
427
+ function getEyePosition(landmarks) {
428
+ if (!landmarks || !landmarks.getLeftEye || !landmarks.getRightEye) {
429
+ return { x: 0, y: 0 };
430
+ }
431
+
432
+ const leftEye = landmarks.getLeftEye();
433
+ const rightEye = landmarks.getRightEye();
434
+
435
+ // Calculate center of each eye
436
+ const leftEyeCenter = leftEye.reduce((sum, point) => {
437
+ return { x: sum.x + point.x, y: sum.y + point.y };
438
+ }, { x: 0, y: 0 });
439
+
440
+ const rightEyeCenter = rightEye.reduce((sum, point) => {
441
+ return { x: sum.x + point.x, y: sum.y + point.y };
442
+ }, { x: 0, y: 0 });
443
+
444
+ leftEyeCenter.x /= leftEye.length;
445
+ leftEyeCenter.y /= leftEye.length;
446
+ rightEyeCenter.x /= rightEye.length;
447
+ rightEyeCenter.y /= rightEye.length;
448
+
449
+ // Calculate midpoint between eyes
450
+ return {
451
+ x: (leftEyeCenter.x + rightEyeCenter.x) / 2,
452
+ y: (leftEyeCenter.y + rightEyeCenter.y) / 2
453
+ };
454
+ }
455
+
456
+ // Update metrics based on eye position
457
+ function updateMetrics(position) {
458
+ const now = Date.now();
459
+ const timeElapsed = (now - lastTime) / 1000;
460
+
461
+ if (timeElapsed > 0) {
462
+ const dx = position.x - lastPosition.x;
463
+ const dy = position.y - lastPosition.y;
464
+ const distance = Math.sqrt(dx * dx + dy * dy);
465
+ currentSpeed = distance / timeElapsed;
466
+
467
+ // Check for fixation (hovering in the same area)
468
+ if (distance < 15) { // 15px threshold for fixation
469
+ if (!isFixated) {
470
+ isFixated = true;
471
+ fixationStartTime = now;
472
+ }
473
+
474
+ // If fixation lasts more than 200ms, count it
475
+ if (isFixated && now - fixationStartTime > 200) {
476
+ fixations++;
477
+ fixationCount.textContent = fixations;
478
+ isFixated = false; // Reset to count new fixations
479
+ }
480
+ } else {
481
+ isFixated = false;
482
+ }
483
+ }
484
+
485
+ // Update metrics display
486
+ movementSpeed.textContent = Math.round(currentSpeed);
487
+ timeTracked.textContent = Math.round((now - startTime) / 1000);
488
+ eyeX.textContent = Math.round(position.x);
489
+ eyeY.textContent = Math.round(position.y);
490
+
491
+ // Update chart data
492
+ if (gazeHistory.length > 50) {
493
+ gazeHistory.shift();
494
+ gazeChart.data.labels.shift();
495
+ gazeChart.data.datasets[0].data.shift();
496
+ }
497
+
498
+ gazeHistory.push(currentSpeed);
499
+ gazeChart.data.labels.push('');
500
+ gazeChart.data.datasets[0].data.push(currentSpeed);
501
+ gazeChart.update();
502
+
503
+ // Update last position and time
504
+ lastPosition = position;
505
+ lastTime = now;
506
+ }
507
+
508
+ // Process video frame for face detection
509
+ async function processVideo() {
510
+ if (!trackingActive || !modelsLoaded) {
511
+ requestAnimationFrame(processVideo);
512
+ return;
513
+ }
514
+
515
+ try {
516
+ // Detect faces with landmarks
517
+ const options = new faceapi.TinyFaceDetectorOptions({
518
+ inputSize: 128,
519
+ scoreThreshold: 0.5
520
+ });
521
+
522
+ const result = await faceapi.detectSingleFace(video, options)
523
+ .withFaceLandmarks();
524
+
525
+ // Clear canvas before drawing
526
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
527
+
528
+ if (result) {
529
+ const { landmarks, detection } = result;
530
+
531
+ // Calculate eye position
532
+ const eyePosition = getEyePosition(landmarks);
533
+
534
+ // Update metrics with eye position
535
+ updateMetrics(eyePosition);
536
+
537
+ // Draw debug information if enabled
538
+ if (showDebug) {
539
+ // Draw face detection box
540
+ const box = detection.box;
541
+ ctx.strokeStyle = '#00FF00';
542
+ ctx.lineWidth = 2;
543
+ ctx.strokeRect(box.x, box.y, box.width, box.height);
544
+
545
+ // Draw landmarks
546
+ faceapi.draw.drawFaceLandmarks(canvas, landmarks);
547
+
548
+ // Draw eye position
549
+ ctx.fillStyle = '#FF0000';
550
+ ctx.beginPath();
551
+ ctx.arc(eyePosition.x, eyePosition.y, 5, 0, 2 * Math.PI);
552
+ ctx.fill();
553
+ }
554
+ }
555
+ } catch (error) {
556
+ console.error('Detection error:', error);
557
+ }
558
+
559
+ requestAnimationFrame(processVideo);
560
+ }
561
+
562
+ // Start tracking
563
+ function startTracking() {
564
+ if (!modelsLoaded) {
565
+ showError("Face detection models not loaded yet. Please wait.");
566
+ return;
567
+ }
568
+
569
+ trackingActive = true;
570
+ startTime = Date.now();
571
+ lastTime = Date.now();
572
+ startBtn.innerHTML = '<i class="fas fa-pause"></i> Pause Tracking';
573
+ startBtn.style.backgroundColor = '#ff7e5f';
574
+
575
+ // Start processing video
576
+ processVideo();
577
+ }
578
+
579
+ // Pause tracking
580
+ function pauseTracking() {
581
+ trackingActive = false;
582
+ startBtn.innerHTML = '<i class="fas fa-play"></i> Resume Tracking';
583
+ startBtn.style.backgroundColor = '#5d69b2';
584
+ }
585
+
586
+ // Reset tracking
587
+ function resetTracking() {
588
+ pauseTracking();
589
+ startTime = 0;
590
+ currentSpeed = 0;
591
+ fixations = 0;
592
+ gazeHistory = [];
593
+
594
+ // Reset metrics display
595
+ eyeX.textContent = '0';
596
+ eyeY.textContent = '0';
597
+ movementSpeed.textContent = '0';
598
+ timeTracked.textContent = '0';
599
+ fixationCount.textContent = '0';
600
+
601
+ // Reset chart
602
+ gazeChart.data.labels = [];
603
+ gazeChart.data.datasets[0].data = [];
604
+ gazeChart.update();
605
+
606
+ // Clear canvas
607
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
608
+ }
609
+
610
+ // Event listeners
611
+ startBtn.addEventListener('click', function() {
612
+ if (trackingActive) {
613
+ pauseTracking();
614
+ } else {
615
+ startTracking();
616
+ }
617
+ });
618
+
619
+ resetBtn.addEventListener('click', resetTracking);
620
+
621
+ debugBtn.addEventListener('click', function() {
622
+ showDebug = !showDebug;
623
+ debugBtn.innerHTML = showDebug ?
624
+ '<i class="fas fa-eye-slash"></i> Hide Debug' :
625
+ '<i class="fas fa-eye"></i> Show Debug';
626
+ });
627
+
628
+ // Initialize
629
+ loadModels();
630
+ });
631
+ </script>
632
+ </body>
633
+ </html>