Sa-m commited on
Commit
d7318a6
·
verified ·
1 Parent(s): 5b6c2a2

Update assets/js/script.js

Browse files
Files changed (1) hide show
  1. assets/js/script.js +514 -14
assets/js/script.js CHANGED
@@ -1,4 +1,6 @@
 
1
  // Sample Email Data
 
2
  const emails = [
3
  {
4
  id: 1,
@@ -14,9 +16,81 @@ const emails = [
14
  snippet: "Top companies are hiring right now — don't miss your shot at these exclusive opportunities!",
15
  timestamp: "12:35 PM"
16
  },
17
- // ... other emails ...
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  ];
19
 
 
 
 
20
  class DebugManager {
21
  constructor() {
22
  this.statusEl = document.getElementById('debugStatus');
@@ -27,15 +101,91 @@ class DebugManager {
27
  this.cameraStatusEl = document.getElementById('debugCameraStatus');
28
  this.lastErrorEl = document.getElementById('debugLastError');
29
  this.statusIndicator = document.getElementById('statusIndicator');
 
 
30
  this.toggleButton = document.getElementById('debugToggle');
31
  this.debugPanel = document.getElementById('debugPanel');
32
- this.toggleButton.addEventListener('click', () => this.toggleDebugPanel());
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  }
34
 
35
- // ... rest of DebugManager methods ...
 
 
 
 
 
 
36
  }
37
 
 
 
 
38
  class UIManager {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  renderEmails() {
40
  this.emailList.innerHTML = '';
41
  this.emailElements = [];
@@ -62,12 +212,137 @@ class UIManager {
62
  });
63
  });
64
 
 
65
  this.updateEmailPositions();
66
  }
67
 
68
- // ... rest of UIManager methods ...
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
  }
70
 
 
 
 
71
  class GestureDetector {
72
  constructor(uiManager, debugManager) {
73
  this.uiManager = uiManager;
@@ -75,24 +350,121 @@ class GestureDetector {
75
  this.selectedEmailId = null;
76
  this.gestureBuffer = [];
77
  this.circlePoints = [];
78
- this.circleThreshold = 8; // Reduced from 10
79
- this.swipeThreshold = 30; // Reduced from 50
80
- this.gestureCooldown = 1500;
81
  this.lastGestureTime = 0;
 
 
 
82
  this.setupMediaPipe();
83
  }
84
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
85
  detectGesture(landmarks) {
86
  try {
 
87
  const indexTip = landmarks[8];
88
  const middleTip = landmarks[12];
89
  const wrist = landmarks[0];
90
 
91
- // Fix Y-axis inversion
 
92
  const screenX = indexTip.x * window.innerWidth;
93
- const screenY = (1 - indexTip.y) * window.innerHeight; // Fixed
94
 
95
- if (screenY < middleTip.y * window.innerHeight) { // Pointing detection
96
  this.checkEmailSelection(screenX, screenY);
97
  } else {
98
  this.uiManager.clearSelection();
@@ -103,14 +475,20 @@ class GestureDetector {
103
  this.debugManager.updateCircleCount(0);
104
  }
105
 
106
- if (this.selectedEmailId === null) return;
 
 
 
107
 
108
- // Improved palm center calculation
 
109
  const palmCenterX = (wrist.x + landmarks[9].x) / 2;
110
  const palmCenterY = (wrist.y + landmarks[9].y) / 2;
111
 
 
112
  this.gestureBuffer.push({x: palmCenterX, y: palmCenterY});
113
 
 
114
  if (this.gestureBuffer.length > 2) {
115
  const prev = this.gestureBuffer[this.gestureBuffer.length - 2];
116
  const current = this.gestureBuffer[this.gestureBuffer.length - 1];
@@ -118,6 +496,7 @@ class GestureDetector {
118
  const dx = (current.x - prev.x) * window.innerWidth;
119
  const dy = (current.y - prev.y) * window.innerHeight;
120
 
 
121
  if (Math.abs(dx) > this.swipeThreshold && Math.abs(dx) > Math.abs(dy) * 2) {
122
  if (Date.now() - this.lastGestureTime > this.gestureCooldown) {
123
  this.lastGestureTime = Date.now();
@@ -136,6 +515,7 @@ class GestureDetector {
136
  }
137
  }
138
 
 
139
  this.circlePoints.push({x: palmCenterX, y: palmCenterY});
140
  this.debugManager.updateCircleCount(this.circlePoints.length);
141
 
@@ -147,6 +527,7 @@ class GestureDetector {
147
  const dy = endPoint.y - startPoint.y;
148
  const distance = Math.sqrt(dx*dx + dy*dy) * window.innerWidth;
149
 
 
150
  if (distance < 40 && Date.now() - this.lastGestureTime > this.gestureCooldown) {
151
  this.lastGestureTime = Date.now();
152
  this.debugManager.updateGestureType('Circle');
@@ -162,5 +543,124 @@ class GestureDetector {
162
  }
163
  }
164
 
165
- // ... rest of GestureDetector methods ...
166
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // ----------------------------
2
  // Sample Email Data
3
+ // ----------------------------
4
  const emails = [
5
  {
6
  id: 1,
 
16
  snippet: "Top companies are hiring right now — don't miss your shot at these exclusive opportunities!",
17
  timestamp: "12:35 PM"
18
  },
19
+ {
20
+ id: 3,
21
+ sender: "Professor Smith",
22
+ subject: "Research Collaboration Proposal",
23
+ snippet: "I've reviewed your proposal draft. Let's schedule a call next week to discuss next steps.",
24
+ timestamp: "10:22 AM"
25
+ },
26
+ {
27
+ id: 4,
28
+ sender: "Friend",
29
+ subject: "Weekend Plans",
30
+ snippet: "Are we still on for the hiking trip this weekend? Let me know so I can book the gear.",
31
+ timestamp: "Yesterday"
32
+ },
33
+ {
34
+ id: 5,
35
+ sender: "Bank Support",
36
+ subject: "Monthly Statement",
37
+ snippet: "Your statement is ready. Minimum due: $120. Avoid penalties by paying before the 15th.",
38
+ timestamp: "Sep 5"
39
+ },
40
+ {
41
+ id: 6,
42
+ sender: "Netflix",
43
+ subject: "New Releases This Week!",
44
+ snippet: "Check out the hottest new shows and movies added to your watchlist this week.",
45
+ timestamp: "Sep 4"
46
+ },
47
+ {
48
+ id: 7,
49
+ sender: "Amazon",
50
+ subject: "Your Order #12345 Has Shipped",
51
+ snippet: "Your package is on the way! Track your delivery using the link below.",
52
+ timestamp: "Sep 3"
53
+ },
54
+ {
55
+ id: 8,
56
+ sender: "LinkedIn",
57
+ subject: "You have 5 new notifications",
58
+ snippet: "See who viewed your profile and new job recommendations.",
59
+ timestamp: "Sep 2"
60
+ },
61
+ {
62
+ id: 9,
63
+ sender: "Microsoft",
64
+ subject: "Your Office 365 Subscription Renewal",
65
+ snippet: "Your Office 365 subscription will expire in 7 days. Renew now to avoid service interruption.",
66
+ timestamp: "Sep 1"
67
+ },
68
+ {
69
+ id: 10,
70
+ sender: "TripAdvisor",
71
+ subject: "Your upcoming trip to Paris",
72
+ snippet: "Don't forget to check-in for your flight tomorrow. Here's your itinerary and hotel information.",
73
+ timestamp: "Aug 30"
74
+ },
75
+ {
76
+ id: 11,
77
+ sender: "GitHub",
78
+ subject: "New login to your account",
79
+ snippet: "We noticed a new login to your GitHub account from a Windows device in New York.",
80
+ timestamp: "Aug 29"
81
+ },
82
+ {
83
+ id: 12,
84
+ sender: "Spotify",
85
+ subject: "Your Weekly Discover Mix is ready",
86
+ snippet: "We've curated 30 songs just for you based on your listening habits. Listen now!",
87
+ timestamp: "Aug 28"
88
+ }
89
  ];
90
 
91
+ // ----------------------------
92
+ // Debug Utilities
93
+ // ----------------------------
94
  class DebugManager {
95
  constructor() {
96
  this.statusEl = document.getElementById('debugStatus');
 
101
  this.cameraStatusEl = document.getElementById('debugCameraStatus');
102
  this.lastErrorEl = document.getElementById('debugLastError');
103
  this.statusIndicator = document.getElementById('statusIndicator');
104
+
105
+ // Get debug toggle button
106
  this.toggleButton = document.getElementById('debugToggle');
107
  this.debugPanel = document.getElementById('debugPanel');
108
+
109
+ // Add click event listener
110
+ this.toggleButton.addEventListener('click', () => {
111
+ this.toggleDebugPanel();
112
+ });
113
+ }
114
+
115
+ updateStatus(status) {
116
+ this.statusEl.textContent = status;
117
+ }
118
+
119
+ updateSelectedEmail(emailId) {
120
+ if (emailId) {
121
+ const email = emails.find(e => e.id === emailId);
122
+ this.selectedEmailEl.textContent = email ? email.subject.substring(0, 20) + '...' : 'Unknown';
123
+ } else {
124
+ this.selectedEmailEl.textContent = 'None';
125
+ }
126
+ }
127
+
128
+ updateGestureType(gestureType) {
129
+ this.gestureTypeEl.textContent = gestureType;
130
+ }
131
+
132
+ updateBufferCount(count) {
133
+ this.bufferCountEl.textContent = `${count} points`;
134
+ }
135
+
136
+ updateCircleCount(count) {
137
+ this.circleCountEl.textContent = `${count} points`;
138
+ }
139
+
140
+ updateCameraStatus(status) {
141
+ this.cameraStatusEl.textContent = status;
142
+ }
143
+
144
+ logError(error) {
145
+ console.error("Gesture Detection Error:", error);
146
+ this.lastErrorEl.textContent = error.message.substring(0, 50) + (error.message.length > 50 ? '...' : '');
147
+
148
+ // Update status indicator to red
149
+ this.statusIndicator.className = 'status-indicator error';
150
+ }
151
+
152
+ setReady() {
153
+ this.updateStatus('Ready');
154
+ this.statusIndicator.className = 'status-indicator ready';
155
+ }
156
+
157
+ setProcessing() {
158
+ this.updateStatus('Processing...');
159
+ this.statusIndicator.className = 'status-indicator processing';
160
  }
161
 
162
+ toggleDebugPanel() {
163
+ if (this.debugPanel.classList.contains('visible')) {
164
+ this.debugPanel.classList.remove('visible');
165
+ } else {
166
+ this.debugPanel.classList.add('visible');
167
+ }
168
+ }
169
  }
170
 
171
+ // ----------------------------
172
+ // UI Management
173
+ // ----------------------------
174
  class UIManager {
175
+ constructor() {
176
+ this.emailList = document.getElementById('emailList');
177
+ this.actionFeedback = document.getElementById('actionFeedback');
178
+ this.selectionHighlight = document.getElementById('selectionHighlight');
179
+ this.handLandmarks = document.getElementById('handLandmarks');
180
+ this.gesturePath = document.getElementById('gesturePath');
181
+
182
+ this.selectedEmail = null;
183
+ this.emailElements = [];
184
+
185
+ this.renderEmails();
186
+ this.setupEventListeners();
187
+ }
188
+
189
  renderEmails() {
190
  this.emailList.innerHTML = '';
191
  this.emailElements = [];
 
212
  });
213
  });
214
 
215
+ // Update email positions
216
  this.updateEmailPositions();
217
  }
218
 
219
+ updateEmailPositions() {
220
+ this.emailElements.forEach(item => {
221
+ const rect = item.element.getBoundingClientRect();
222
+ item.rect = {
223
+ left: rect.left,
224
+ top: rect.top,
225
+ right: rect.right,
226
+ bottom: rect.bottom,
227
+ width: rect.width,
228
+ height: rect.height
229
+ };
230
+ });
231
+ }
232
+
233
+ selectEmail(emailId) {
234
+ // Remove previous selection
235
+ if (this.selectedEmail) {
236
+ const prevElement = this.emailElements.find(e => e.id === this.selectedEmail);
237
+ if (prevElement) {
238
+ prevElement.element.classList.remove('selected');
239
+ }
240
+ }
241
+
242
+ // Set new selection
243
+ this.selectedEmail = emailId;
244
+ const newElement = this.emailElements.find(e => e.id === emailId);
245
+
246
+ if (newElement) {
247
+ newElement.element.classList.add('selected');
248
+ this.showSelectionHighlight(newElement.rect);
249
+ }
250
+
251
+ return newElement ? newElement.rect : null;
252
+ }
253
+
254
+ showSelectionHighlight(rect) {
255
+ this.selectionHighlight.style.display = 'block';
256
+ this.selectionHighlight.style.left = `${rect.left}px`;
257
+ this.selectionHighlight.style.top = `${rect.top}px`;
258
+ this.selectionHighlight.style.width = `${rect.width}px`;
259
+ this.selectionHighlight.style.height = `${rect.height}px`;
260
+ }
261
+
262
+ hideSelectionHighlight() {
263
+ this.selectionHighlight.style.display = 'none';
264
+ }
265
+
266
+ showActionFeedback(message, type) {
267
+ this.actionFeedback.textContent = message;
268
+ this.actionFeedback.className = 'action-feedback';
269
+
270
+ if (type === 'delete') {
271
+ this.actionFeedback.classList.add('delete');
272
+ } else if (type === 'archive') {
273
+ this.actionFeedback.classList.add('archive');
274
+ } else {
275
+ this.actionFeedback.classList.add('summary');
276
+ }
277
+
278
+ this.actionFeedback.classList.add('show');
279
+
280
+ setTimeout(() => {
281
+ this.actionFeedback.classList.remove('show');
282
+ }, 2000);
283
+ }
284
+
285
+ clearSelection() {
286
+ if (this.selectedEmail) {
287
+ const element = this.emailElements.find(e => e.id === this.selectedEmail);
288
+ if (element) {
289
+ element.element.classList.remove('selected');
290
+ }
291
+ this.selectedEmail = null;
292
+ this.hideSelectionHighlight();
293
+ }
294
+ }
295
+
296
+ setupEventListeners() {
297
+ window.addEventListener('resize', () => {
298
+ this.updateEmailPositions();
299
+ if (this.selectedEmail) {
300
+ const element = this.emailElements.find(e => e.id === this.selectedEmail);
301
+ if (element) {
302
+ this.showSelectionHighlight(element.rect);
303
+ }
304
+ }
305
+ });
306
+ }
307
+
308
+ // For gesture visualization
309
+ updateHandLandmarks(landmarks) {
310
+ this.handLandmarks.innerHTML = '';
311
+
312
+ if (!landmarks || landmarks.length === 0) return;
313
+
314
+ landmarks.forEach((landmark, i) => {
315
+ const landmarkEl = document.createElement('div');
316
+ landmarkEl.className = 'landmark';
317
+ if (i === 8) { // Index finger tip
318
+ landmarkEl.classList.add('index-tip');
319
+ }
320
+
321
+ landmarkEl.style.left = `${landmark.x * 100}%`;
322
+ landmarkEl.style.top = `${landmark.y * 100}%`;
323
+
324
+ this.handLandmarks.appendChild(landmarkEl);
325
+ });
326
+ }
327
+
328
+ updateGesturePath(points) {
329
+ this.gesturePath.innerHTML = '';
330
+
331
+ if (!points || points.length === 0) return;
332
+
333
+ points.forEach(point => {
334
+ const pointEl = document.createElement('div');
335
+ pointEl.className = 'point';
336
+ pointEl.style.left = `${point.x * 100}%`;
337
+ pointEl.style.top = `${point.y * 100}%`;
338
+ this.gesturePath.appendChild(pointEl);
339
+ });
340
+ }
341
  }
342
 
343
+ // ----------------------------
344
+ // Gesture Detection
345
+ // ----------------------------
346
  class GestureDetector {
347
  constructor(uiManager, debugManager) {
348
  this.uiManager = uiManager;
 
350
  this.selectedEmailId = null;
351
  this.gestureBuffer = [];
352
  this.circlePoints = [];
353
+ this.circleThreshold = 8; // Reduced from 10 for better circle detection
354
+ this.swipeThreshold = 30; // Reduced from 50 for more sensitive swipe detection
355
+ this.gestureCooldown = 1500; // 1.5 seconds
356
  this.lastGestureTime = 0;
357
+ this.camera = null;
358
+
359
+ this.debugManager.updateStatus('Setting up MediaPipe...');
360
  this.setupMediaPipe();
361
  }
362
 
363
+ setupMediaPipe() {
364
+ try {
365
+ const hands = new Hands({locateFile: (file) => {
366
+ return `https://cdn.jsdelivr.net/npm/@mediapipe/[email protected]/${file}`;
367
+ }});
368
+
369
+ hands.setOptions({
370
+ maxNumHands: 1,
371
+ modelComplexity: 1,
372
+ minDetectionConfidence: 0.7,
373
+ minTrackingConfidence: 0.7
374
+ });
375
+
376
+ hands.onResults(results => {
377
+ this.processResults(results);
378
+ });
379
+
380
+ const videoElement = document.getElementById('webcam');
381
+
382
+ // Check if Camera is available
383
+ if (typeof Camera === 'undefined') {
384
+ this.debugManager.logError(new Error("Camera utils not loaded. Make sure camera_utils.js is included."));
385
+ return;
386
+ }
387
+
388
+ this.camera = new Camera(videoElement, {
389
+ onFrame: async () => {
390
+ try {
391
+ this.debugManager.setProcessing();
392
+ await hands.send({image: videoElement});
393
+ } catch (error) {
394
+ this.debugManager.logError(error);
395
+ }
396
+ },
397
+ width: 320,
398
+ height: 240
399
+ });
400
+
401
+ this.startCamera();
402
+ } catch (error) {
403
+ this.debugManager.logError(error);
404
+ console.error("MediaPipe setup error:", error);
405
+ }
406
+ }
407
+
408
+ async startCamera() {
409
+ try {
410
+ this.debugManager.updateCameraStatus('Starting...');
411
+ await this.camera.start();
412
+ this.debugManager.updateCameraStatus('Active');
413
+ this.debugManager.setReady();
414
+ console.log("Camera initialized successfully");
415
+ } catch (error) {
416
+ this.debugManager.updateCameraStatus('Error');
417
+ this.debugManager.logError(error);
418
+
419
+ // Try to get more specific error information
420
+ if (error.name === 'NotAllowedError') {
421
+ alert("Camera access denied. Please allow camera access in your browser settings.");
422
+ } else if (error.name === 'NotFoundError') {
423
+ alert("No camera found. Please connect a camera device.");
424
+ } else {
425
+ alert("Failed to start camera: " + error.message);
426
+ }
427
+ }
428
+ }
429
+
430
+ processResults(results) {
431
+ try {
432
+ // Update hand landmarks visualization
433
+ if (results.multiHandLandmarks && results.multiHandLandmarks.length > 0) {
434
+ this.uiManager.updateHandLandmarks(results.multiHandLandmarks[0]);
435
+
436
+ // Update gesture path for debugging
437
+ if (this.gestureBuffer.length > 0) {
438
+ this.uiManager.updateGesturePath(this.gestureBuffer);
439
+ }
440
+
441
+ this.detectGesture(results.multiHandLandmarks[0]);
442
+ } else {
443
+ this.uiManager.clearSelection();
444
+ this.gestureBuffer = [];
445
+ this.circlePoints = [];
446
+ this.debugManager.updateGestureType('None');
447
+ this.debugManager.updateBufferCount(0);
448
+ this.debugManager.updateCircleCount(0);
449
+ }
450
+ } catch (error) {
451
+ this.debugManager.logError(error);
452
+ }
453
+ }
454
+
455
  detectGesture(landmarks) {
456
  try {
457
+ // Convert to pixel coordinates for easier calculation
458
  const indexTip = landmarks[8];
459
  const middleTip = landmarks[12];
460
  const wrist = landmarks[0];
461
 
462
+ // Pointing detection (index finger higher than middle)
463
+ // Fix: Proper Y-axis calculation (inverted in MediaPipe)
464
  const screenX = indexTip.x * window.innerWidth;
465
+ const screenY = (1 - indexTip.y) * window.innerHeight;
466
 
467
+ if (indexTip.y < middleTip.y) {
468
  this.checkEmailSelection(screenX, screenY);
469
  } else {
470
  this.uiManager.clearSelection();
 
475
  this.debugManager.updateCircleCount(0);
476
  }
477
 
478
+ // Only process gestures if an email is selected
479
+ if (this.selectedEmailId === null) {
480
+ return;
481
+ }
482
 
483
+ // Get palm center for gesture detection
484
+ // Fix: Better palm center calculation
485
  const palmCenterX = (wrist.x + landmarks[9].x) / 2;
486
  const palmCenterY = (wrist.y + landmarks[9].y) / 2;
487
 
488
+ // Add to gesture buffer
489
  this.gestureBuffer.push({x: palmCenterX, y: palmCenterY});
490
 
491
+ // Check for swipe
492
  if (this.gestureBuffer.length > 2) {
493
  const prev = this.gestureBuffer[this.gestureBuffer.length - 2];
494
  const current = this.gestureBuffer[this.gestureBuffer.length - 1];
 
496
  const dx = (current.x - prev.x) * window.innerWidth;
497
  const dy = (current.y - prev.y) * window.innerHeight;
498
 
499
+ // Check if it's a horizontal swipe
500
  if (Math.abs(dx) > this.swipeThreshold && Math.abs(dx) > Math.abs(dy) * 2) {
501
  if (Date.now() - this.lastGestureTime > this.gestureCooldown) {
502
  this.lastGestureTime = Date.now();
 
515
  }
516
  }
517
 
518
+ // Check for circle
519
  this.circlePoints.push({x: palmCenterX, y: palmCenterY});
520
  this.debugManager.updateCircleCount(this.circlePoints.length);
521
 
 
527
  const dy = endPoint.y - startPoint.y;
528
  const distance = Math.sqrt(dx*dx + dy*dy) * window.innerWidth;
529
 
530
+ // Fix: Better circle detection with radius check
531
  if (distance < 40 && Date.now() - this.lastGestureTime > this.gestureCooldown) {
532
  this.lastGestureTime = Date.now();
533
  this.debugManager.updateGestureType('Circle');
 
543
  }
544
  }
545
 
546
+ checkEmailSelection(x, y) {
547
+ try {
548
+ // Check which email is under the finger
549
+ for (let i = this.uiManager.emailElements.length - 1; i >= 0; i--) {
550
+ const email = this.uiManager.emailElements[i];
551
+ if (email.rect &&
552
+ x >= email.rect.left &&
553
+ x <= email.rect.right &&
554
+ y >= email.rect.top &&
555
+ y <= email.rect.bottom) {
556
+
557
+ // Only select if it's a different email
558
+ if (this.selectedEmailId !== email.id) {
559
+ this.selectedEmailId = email.id;
560
+ this.uiManager.selectEmail(email.id);
561
+ this.debugManager.updateSelectedEmail(email.id);
562
+ }
563
+ return;
564
+ }
565
+ }
566
+
567
+ // If no email is selected, clear selection
568
+ if (!this.uiManager.emailElements.some(email =>
569
+ email.rect &&
570
+ x >= email.rect.left &&
571
+ x <= email.rect.right &&
572
+ y >= email.rect.top &&
573
+ y <= email.rect.bottom)) {
574
+ this.uiManager.clearSelection();
575
+ this.selectedEmailId = null;
576
+ this.debugManager.updateSelectedEmail(null);
577
+ }
578
+ } catch (error) {
579
+ this.debugManager.logError(error);
580
+ }
581
+ }
582
+
583
+ handleGesture(gesture) {
584
+ try {
585
+ if (!this.selectedEmailId) return;
586
+
587
+ const email = emails.find(e => e.id === this.selectedEmailId);
588
+ if (!email) return;
589
+
590
+ switch (gesture) {
591
+ case 'swipe_left':
592
+ this.uiManager.showActionFeedback(`🗑️ Deleted: ${email.subject}`, 'delete');
593
+ // Remove from UI
594
+ const index = emails.findIndex(e => e.id === this.selectedEmailId);
595
+ if (index !== -1) emails.splice(index, 1);
596
+ this.uiManager.renderEmails();
597
+ this.selectedEmailId = null;
598
+ this.debugManager.updateSelectedEmail(null);
599
+ break;
600
+
601
+ case 'swipe_right':
602
+ this.uiManager.showActionFeedback(`✅ Archived: ${email.subject}`, 'archive');
603
+ // In a real app, we'd move to archive
604
+ const archiveIndex = emails.findIndex(e => e.id === this.selectedEmailId);
605
+ if (archiveIndex !== -1) emails.splice(archiveIndex, 1);
606
+ this.uiManager.renderEmails();
607
+ this.selectedEmailId = null;
608
+ this.debugManager.updateSelectedEmail(null);
609
+ break;
610
+
611
+ case 'circle':
612
+ const summary = `This email discusses ${email.subject.toLowerCase()}.`;
613
+ this.uiManager.showActionFeedback(`📝 Summary: ${summary}`, 'summary');
614
+ break;
615
+ }
616
+ } catch (error) {
617
+ this.debugManager.logError(error);
618
+ }
619
+ }
620
+ }
621
+
622
+ // ----------------------------
623
+ // Initialize App
624
+ // ----------------------------
625
+ document.addEventListener('DOMContentLoaded', () => {
626
+ // Initialize debug manager first
627
+ const debugManager = new DebugManager();
628
+ debugManager.updateStatus('Initializing UI...');
629
+
630
+ // Initialize UI
631
+ const uiManager = new UIManager();
632
+ debugManager.setReady();
633
+
634
+ // Request camera access
635
+ const videoElement = document.getElementById('webcam');
636
+
637
+ navigator.mediaDevices.getUserMedia({ video: true })
638
+ .then(stream => {
639
+ videoElement.srcObject = stream;
640
+ debugManager.updateCameraStatus('Initializing...');
641
+
642
+ // Initialize gesture detection after a short delay to ensure UI is ready
643
+ setTimeout(() => {
644
+ debugManager.updateStatus('Setting up gesture detection...');
645
+ try {
646
+ new GestureDetector(uiManager, debugManager);
647
+ } catch (error) {
648
+ debugManager.logError(error);
649
+ }
650
+ }, 1500);
651
+ })
652
+ .catch(err => {
653
+ debugManager.updateCameraStatus('Denied');
654
+ debugManager.logError(err);
655
+ console.error("Error accessing camera:", err);
656
+
657
+ // More specific error handling
658
+ if (err.name === 'NotAllowedError') {
659
+ alert("Camera access denied. Please allow camera access in your browser settings.");
660
+ } else if (err.name === 'NotFoundError') {
661
+ alert("No camera found. Please connect a camera device.");
662
+ } else {
663
+ alert("Camera error: " + err.message);
664
+ }
665
+ });
666
+ });