manmohanai commited on
Commit
9a35b42
·
verified ·
1 Parent(s): 1d590c3

add functionality capture video and detect water container. check how much ml of water it can have do measurement automatically. - Follow Up Deployment

Browse files
Files changed (1) hide show
  1. index.html +346 -465
index.html CHANGED
@@ -3,521 +3,402 @@
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Emotion Detection Camera</title>
7
  <script src="https://cdn.tailwindcss.com"></script>
8
- <script src="https://cdn.jsdelivr.net/npm/face-api.js@0.22.2/dist/face-api.min.js"></script>
 
 
9
  <style>
10
- .camera-container {
11
  position: relative;
12
- width: 100%;
13
- max-width: 640px;
14
- margin: 0 auto;
15
- border-radius: 12px;
16
  overflow: hidden;
17
- box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
18
  }
19
-
20
- .loading-spinner {
21
- border: 3px solid rgba(255, 255, 255, 0.3);
22
- border-radius: 50%;
23
- border-top: 3px solid #4f46e5;
24
- width: 30px;
25
- height: 30px;
26
- animation: spin 1s linear infinite;
27
- margin: 0 auto;
28
- }
29
-
30
- @keyframes spin {
31
- 0% { transform: rotate(0deg); }
32
- 100% { transform: rotate(360deg); }
33
  }
34
-
35
- .face-box {
36
  position: absolute;
37
- border: 2px solid #4f46e5;
38
- border-radius: 4px;
39
- background-color: rgba(79, 70, 229, 0.1);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
  }
41
  </style>
42
  </head>
43
- <body class="bg-gray-100 min-h-screen">
44
  <div class="container mx-auto px-4 py-8">
45
- <div class="flex flex-col lg:flex-row gap-8">
46
- <div class="lg:w-2/3">
47
- <div class="text-center mb-8">
48
- <h1 class="text-3xl md:text-4xl font-bold text-indigo-700 mb-2">Emotion Detection Camera</h1>
49
- <p class="text-gray-600 max-w-lg mx-auto">Take a photo and our AI will detect your facial expressions and emotions in real-time.</p>
50
- </div>
51
-
52
- <div class="bg-white rounded-xl shadow-lg p-6 max-w-4xl mx-auto">
53
- <div id="status" class="text-center py-4 hidden">
54
- <div class="loading-spinner"></div>
55
- <p class="mt-2 text-gray-600">Loading models...</p>
56
- </div>
57
-
58
- <div class="camera-container bg-gray-200 aspect-video relative" id="cameraView">
59
- <video id="video" autoplay muted playsinline class="w-full h-full object-cover"></video>
60
- <canvas id="canvas" class="absolute top-0 left-0 w-full h-full hidden"></canvas>
61
- </div>
62
-
63
- <div class="flex flex-wrap justify-center gap-4 mt-6">
64
- <button id="captureBtn" class="bg-indigo-600 hover:bg-indigo-700 text-white px-6 py-3 rounded-lg font-medium flex items-center gap-2 transition-all">
65
- <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
66
- <path fill-rule="evenodd" d="M4 5a2 2 0 00-2 2v8a2 2 0 002 2h12a2 2 0 002-2V7a2 2 0 00-2-2h-1.586a1 1 0 01-.707-.293l-1.121-1.121A2 2 0 0011.172 3H8.828a2 2 0 00-1.414.586L6.293 4.707A1 1 0 015.586 5H4zm6 9a3 3 0 100-6 3 3 0 000 6z" clip-rule="evenodd" />
67
- </svg>
68
- Capture Photo
69
- </button>
70
-
71
- <button id="switchCameraBtn" class="bg-gray-200 hover:bg-gray-300 text-gray-800 px-6 py-3 rounded-lg font-medium flex items-center gap-2 transition-all">
72
- <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
73
- <path d="M2 6a2 2 0 012-2h6a2 2 0 012 2v8a2 2 0 01-2 2H4a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v8a2 2 0 01-2 2h-2a2 2 0 01-2-2V6z" />
74
- </svg>
75
- Switch Camera
76
- </button>
77
-
78
- <button id="resetBtn" class="bg-gray-200 hover:bg-gray-300 text-gray-800 px-6 py-3 rounded-lg font-medium flex items-center gap-2 transition-all hidden">
79
- <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
80
- <path fill-rule="evenodd" d="M4 2a1 1 0 011 1v2.101a7.002 7.002 0 0111.601 2.566 1 1 0 11-1.885.666A5.002 5.002 0 005.999 7H9a1 1 0 010 2H4a1 1 0 01-1-1V3a1 1 0 011-1zm.008 9.057a1 1 0 011.276.61A5.002 5.002 0 0014.001 13H11a1 1 0 110-2h5a1 1 0 011 1v5a1 1 0 11-2 0v-2.101a7.002 7.002 0 01-11.601-2.566 1 1 0 01.61-1.276z" clip-rule="evenodd" />
81
- </svg>
82
- Reset
83
- </button>
84
- </div>
85
-
86
- <div id="results" class="mt-8 hidden">
87
- <h3 class="text-xl font-semibold text-gray-800 mb-4">Emotion Analysis</h3>
88
- <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
89
- <div>
90
- <h4 class="font-medium text-gray-700 mb-2">Primary Emotion</h4>
91
- <div id="primaryEmotion" class="bg-indigo-50 text-indigo-800 px-4 py-3 rounded-lg font-medium"></div>
92
  </div>
93
- <div>
94
- <h4 class="font-medium text-gray-700 mb-2">Confidence Level</h4>
95
- <div id="confidenceLevel" class="bg-indigo-50 text-indigo-800 px-4 py-3 rounded-lg font-medium"></div>
96
  </div>
97
  </div>
98
-
99
- <div class="mt-6">
100
- <h4 class="font-medium text-gray-700 mb-2">Detailed Breakdown</h4>
101
- <div id="emotionDetails" class="space-y-2"></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
102
  </div>
103
- </div>
104
- </div>
105
-
106
- </div> <!-- End of video content -->
107
 
108
- <!-- Emotion Suggestions Sidebar -->
109
- <div class="lg:w-1/3 bg-white rounded-xl shadow-lg p-6 sticky top-8">
110
- <div class="flex justify-between items-center mb-4">
111
- <h3 class="text-xl font-semibold text-indigo-700">Emotion Tips</h3>
112
- <button id="muteBtn" class="text-gray-500 hover:text-indigo-600">
113
- <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
114
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.536 8.464a5 5 0 010 7.072m2.828-9.9a9 9 0 010 12.728M5.586 15H4a1 1 0 01-1-1v-4a1 1 0 011-1h1.586l4.707-4.707C10.923 3.663 12 4.109 12 5v14c0 .891-1.077 1.337-1.707.707L5.586 15z" />
115
- </svg>
116
- </button>
 
 
 
 
 
 
 
 
 
 
117
  </div>
118
- <div id="emotionSuggestions" class="space-y-4">
119
- <div class="text-center py-8 text-gray-400">
120
- <svg xmlns="http://www.w3.org/2000/svg" class="h-12 w-12 mx-auto mb-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
121
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M14.828 14.828a4 4 0 01-5.656 0M9 10h.01M15 10h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
122
- </svg>
123
- <p>Face the camera to get personalized suggestions!</p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
124
  </div>
125
  </div>
126
  </div>
127
- </div> <!-- End of container -->
128
-
129
- </div> <!-- End of flex container -->
130
- </div> <!-- End of main container -->
131
-
132
- <div class="mt-8 text-center text-gray-500 text-sm">
133
- <p>This app uses face-api.js for emotion detection. All processing happens in your browser.</p>
134
  </div>
135
 
136
  <script>
137
- // DOM Elements
138
- const video = document.getElementById('video');
139
- const canvas = document.getElementById('canvas');
140
- const captureBtn = document.getElementById('captureBtn');
141
- const switchCameraBtn = document.getElementById('switchCameraBtn');
142
- const resetBtn = document.getElementById('resetBtn');
143
- const statusElement = document.getElementById('status');
144
- const resultsElement = document.getElementById('results');
145
- const primaryEmotionElement = document.getElementById('primaryEmotion');
146
- const confidenceLevelElement = document.getElementById('confidenceLevel');
147
- const emotionDetailsElement = document.getElementById('emotionDetails');
148
-
149
- let stream = null;
150
- let currentFacingMode = 'user'; // 'user' for front camera, 'environment' for back
151
- let isCaptured = false;
152
- let isCooldown = false;
153
- let cooldownEndTime = 0;
154
-
155
- // Load models and start camera
156
- async function init() {
157
- statusElement.classList.remove('hidden');
158
 
 
 
159
  try {
160
- await Promise.all([
161
- faceapi.nets.tinyFaceDetector.loadFromUri('https://justadudewhohacks.github.io/face-api.js/models'),
162
- faceapi.nets.faceLandmark68Net.loadFromUri('https://justadudewhohacks.github.io/face-api.js/models'),
163
- faceapi.nets.faceRecognitionNet.loadFromUri('https://justadudewhohacks.github.io/face-api.js/models'),
164
- faceapi.nets.faceExpressionNet.loadFromUri('https://justadudewhohacks.github.io/face-api.js/models')
165
- ]);
166
-
167
- statusElement.classList.add('hidden');
168
- startCamera();
169
- } catch (error) {
170
- statusElement.innerHTML = `
171
- <div class="bg-red-50 text-red-600 p-4 rounded-lg">
172
- <p>Failed to load models. Please refresh the page or check your internet connection.</p>
173
- </div>
174
- `;
175
- console.error(error);
176
- }
177
- }
178
-
179
- // Start camera with selected facing mode
180
- async function startCamera() {
181
- if (stream) {
182
- stream.getTracks().forEach(track => track.stop());
183
  }
184
 
185
- try {
186
- stream = await navigator.mediaDevices.getUserMedia({
187
- video: {
188
- facingMode: currentFacingMode,
189
- width: { ideal: 1280 },
190
- height: { ideal: 720 }
191
- },
192
- audio: false
193
- });
194
-
195
- video.srcObject = stream;
196
- video.onloadedmetadata = () => {
197
- video.play();
198
- if (!isCaptured) {
199
- detectEmotions();
200
- }
201
- };
202
- } catch (error) {
203
- console.error("Camera error:", error);
204
- alert("Could not access the camera. Please make sure you've granted camera permissions.");
205
- }
206
- }
207
-
208
- // Detect emotions in real-time
209
- async function detectEmotions() {
210
- if (isCaptured || isCooldown) {
211
- // Show remaining cooldown time if active
212
- if (isCooldown) {
213
- const remaining = Math.ceil((cooldownEndTime - Date.now()) / 1000);
214
- if (remaining > 0) {
215
- console.log(`Cooldown: ${remaining}s remaining`);
216
- }
217
- }
218
- return;
219
  }
220
 
221
- const detection = await faceapi.detectSingleFace(video, new faceapi.TinyFaceDetectorOptions())
222
- .withFaceLandmarks()
223
- .withFaceExpressions();
224
-
225
- // Clear previous drawings
226
- const canvas = faceapi.createCanvasFromMedia(video);
227
- const context = canvas.getContext('2d');
228
- context.clearRect(0, 0, canvas.width, canvas.height);
229
-
230
- // Resize canvas to match video dimensions
231
- faceapi.matchDimensions(canvas, {
232
- width: video.clientWidth,
233
- height: video.clientHeight
234
- });
235
-
236
- if (detection) {
237
- // Draw face detection box
238
- const resizedDetections = faceapi.resizeResults(detection, {
239
- width: video.clientWidth,
240
- height: video.clientHeight
241
- });
242
 
243
- // Draw face box
244
- const box = resizedDetections.detection.box;
245
- const drawBox = new faceapi.draw.DrawBox(box, {
246
- label: `Confidence: ${Math.round(detection.detection.score * 100)}%`
247
- });
248
- drawBox.draw(canvas);
249
 
250
- // Draw face landmarks
251
- faceapi.draw.drawFaceLandmarks(canvas, resizedDetections);
 
252
 
253
- // Get dominant emotion
254
- const expressions = detection.expressions;
255
- const primaryEmotion = Object.entries(expressions).reduce((a, b) => a[1] > b[1] ? a : b);
 
256
 
257
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
258
 
259
- // Update emotion suggestions
260
- if (detection) {
261
- updateEmotionSuggestions(detection.expressions);
262
- // Start cooldown period
263
- isCooldown = true;
264
- cooldownEndTime = Date.now() + 10000; // 10 seconds from now
265
- setTimeout(() => {
266
- isCooldown = false;
267
- detectEmotions();
268
- }, 10000);
269
- } else {
270
- // Schedule next detection normally if no face detected
271
- setTimeout(() => detectEmotions(), 300);
272
- }
273
- }
274
-
275
- // Capture photo and analyze emotions
276
- captureBtn.addEventListener('click', async () => {
277
- if (isCaptured) return;
278
 
279
- isCaptured = true;
280
- captureBtn.disabled = true;
 
281
 
282
- // Create canvas from video
283
- canvas.width = video.clientWidth;
284
- canvas.height = video.clientHeight;
285
- const context = canvas.getContext('2d');
286
- context.drawImage(video, 0, 0, canvas.width, canvas.height);
287
 
288
- // Hide video and show canvas
289
- video.classList.add('hidden');
290
- canvas.classList.remove('hidden');
 
 
 
 
 
291
 
292
- // Analyze the captured image
293
- const detection = await faceapi.detectSingleFace(canvas, new faceapi.TinyFaceDetectorOptions())
294
- .withFaceLandmarks()
295
- .withFaceExpressions();
 
 
 
296
 
297
- if (detection) {
298
- const expressions = detection.expressions;
299
- const primaryEmotion = Object.entries(expressions).reduce((a, b) => a[1] > b[1] ? a : b);
 
300
 
301
- // Display results
302
- primaryEmotionElement.textContent = `${capitalizeFirstLetter(primaryEmotion[0])} (${Math.round(primaryEmotion[1] * 100)}%)`;
303
- confidenceLevelElement.textContent = getConfidenceLevel(primaryEmotion[1]);
304
 
305
- // Create detailed breakdown
306
- emotionDetailsElement.innerHTML = '';
307
- Object.entries(expressions)
308
- .sort((a, b) => b[1] - a[1])
309
- .forEach(([emotion, value]) => {
310
- const percentage = Math.round(value * 100);
311
- const emotionItem = document.createElement('div');
312
- emotionItem.className = 'bg-gray-50 p-3 rounded-lg';
313
- emotionItem.innerHTML = `
314
- <div class="flex justify-between items-center">
315
- <span class="font-medium">${capitalizeFirstLetter(emotion)}</span>
316
- <span class="text-gray-600">${percentage}%</span>
317
- </div>
318
- <div class="w-full bg-gray-200 rounded-full h-2 mt-2">
319
- <div class="bg-indigo-500 h-2 rounded-full" style="width: ${percentage}%"></div>
320
- </div>
321
- `;
322
- emotionDetailsElement.appendChild(emotionItem);
323
- });
324
 
325
- resultsElement.classList.remove('hidden');
326
- resetBtn.classList.remove('hidden');
327
- } else {
328
- alert("No face detected in the captured image. Please try again.");
329
- resetApp();
330
- }
331
-
332
- captureBtn.disabled = false;
333
- });
334
-
335
- // Switch between front and back camera
336
- switchCameraBtn.addEventListener('click', () => {
337
- currentFacingMode = currentFacingMode === 'user' ? 'environment' : 'user';
338
- startCamera();
339
- });
340
-
341
- // Reset the app to initial state
342
- resetBtn.addEventListener('click', resetApp);
343
-
344
- function resetApp() {
345
- isCaptured = false;
346
- video.classList.remove('hidden');
347
- canvas.classList.add('hidden');
348
- resultsElement.classList.add('hidden');
349
- resetBtn.classList.add('hidden');
350
-
351
- // Clear canvas
352
- const context = canvas.getContext('2d');
353
- context.clearRect(0, 0, canvas.width, canvas.height);
354
-
355
- // Restart emotion detection
356
- detectEmotions();
357
- }
358
-
359
- // Speech synthesis
360
- function speak(text) {
361
- if ('speechSynthesis' in window && !isMuted) {
362
- speechSynthesis.cancel(); // Cancel any previous speech
363
- const utterance = new SpeechSynthesisUtterance(text);
364
- utterance.rate = 0.9;
365
- speechSynthesis.speak(utterance);
366
- }
367
- }
368
-
369
- // Helper functions
370
- function capitalizeFirstLetter(string) {
371
- return string.charAt(0).toUpperCase() + string.slice(1);
372
- }
373
-
374
- function getConfidenceLevel(confidence) {
375
- if (confidence > 0.8) return "Very High";
376
- if (confidence > 0.6) return "High";
377
- if (confidence > 0.4) return "Moderate";
378
- if (confidence > 0.2) return "Low";
379
- return "Very Low";
380
- }
381
-
382
- // Track last emotion and values
383
- let lastEmotion = null;
384
- let lastEmotionValues = {};
385
- const EMOTION_CHANGE_THRESHOLD = 0.2; // Minimum change to consider significant
386
-
387
- // Emotion-based suggestions
388
- const emotionTips = {
389
- happy: [
390
- "Your smile is contagious! Keep spreading joy!",
391
- "Try doing something creative today - paint, write, or dance!",
392
- "Share your happiness with someone who needs it today."
393
- ],
394
- sad: [
395
- "Why did the sad computer apply for a job? It had too many bytes of emotional baggage!",
396
- "Remember: This too shall pass. You've got this!",
397
- "Try listening to uplifting music or calling a friend."
398
- ],
399
- angry: [
400
- "Take 5 deep breaths - in through the nose, out through the mouth.",
401
- "Try squeezing a stress ball or going for a quick walk.",
402
- "Write down what's bothering you, then tear it up!"
403
- ],
404
- surprised: [
405
- "Did you know? The average person is surprised 3 times a day!",
406
- "Embrace the unexpected - it keeps life interesting!",
407
- "Try something new today to keep that surprised look going!"
408
- ],
409
- fearful: [
410
- "Face your fears one small step at a time - you're stronger than you think!",
411
- "Try the 5-4-3-2-1 grounding technique: Name 5 things you can see, 4 you can touch, etc.",
412
- "Remember: Courage isn't the absence of fear, but acting despite it."
413
- ],
414
- disgusted: [
415
- "Eww, what's that face for? Try thinking of your favorite food instead!",
416
- "When life gives you lemons... make sure they're not rotten first!",
417
- "Change your environment - go somewhere pleasant to reset your mood."
418
- ],
419
- neutral: [
420
- "Feeling balanced? Perfect time for mindfulness meditation!",
421
- "Try a new facial expression - how about a smile?",
422
- "Your calm demeanor is your superpower today."
423
- ]
424
- };
425
-
426
- function updateEmotionSuggestions(expressions) {
427
- const primaryEmotion = Object.entries(expressions).reduce((a, b) => a[1] > b[1] ? a : b)[0];
428
-
429
- // Check if emotion values have changed significantly
430
- let hasSignificantChange = false;
431
- if (lastEmotionValues) {
432
- for (const [emotion, value] of Object.entries(expressions)) {
433
- if (Math.abs(value - (lastEmotionValues[emotion] || 0)) > EMOTION_CHANGE_THRESHOLD) {
434
- hasSignificantChange = true;
435
- break;
436
- }
437
  }
438
- } else {
439
- hasSignificantChange = true; // First detection
440
  }
441
 
442
- // Only update if emotion changed significantly
443
- if (!hasSignificantChange && primaryEmotion === lastEmotion) {
444
- return;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
445
  }
446
 
447
- lastEmotion = primaryEmotion;
448
- lastEmotionValues = {...expressions};
449
-
450
- const suggestionsContainer = document.getElementById('emotionSuggestions');
451
- const tips = emotionTips[primaryEmotion] || [
452
- "Your emotions are valid. Take a moment to acknowledge how you feel."
453
- ];
454
-
455
- const randomTip = tips[Math.floor(Math.random() * tips.length)];
456
- speak(`You seem ${primaryEmotion}. ${randomTip}`);
457
-
458
- suggestionsContainer.innerHTML = `
459
- <div class="bg-indigo-50 rounded-lg p-4">
460
- <div class="flex items-center gap-3 mb-3">
461
- <div class="bg-indigo-100 p-2 rounded-full">
462
- <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-indigo-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
463
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
464
- </svg>
465
- </div>
466
- <h4 class="font-medium text-indigo-700">${capitalizeFirstLetter(primaryEmotion)} Detected</h4>
467
- </div>
468
- <p class="text-gray-700">${randomTip}</p>
469
- </div>
470
- <div class="bg-gray-50 rounded-lg p-4">
471
- <h4 class="font-medium text-gray-700 mb-2">Quick Mood Boosters</h4>
472
- <ul class="space-y-2 text-sm text-gray-600">
473
- <li class="flex items-center gap-2">
474
- <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-green-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
475
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
476
- </svg>
477
- Take 3 deep breaths
478
- </li>
479
- <li class="flex items-center gap-2">
480
- <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-green-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
481
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
482
- </svg>
483
- Drink some water
484
- </li>
485
- <li class="flex items-center gap-2">
486
- <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-green-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
487
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
488
- </svg>
489
- Stretch your body
490
- </li>
491
- </ul>
492
- </div>
493
- `;
494
- }
495
-
496
- // Mute button functionality
497
- const muteBtn = document.getElementById('muteBtn');
498
- let isMuted = false;
499
-
500
- muteBtn.addEventListener('click', () => {
501
- isMuted = !isMuted;
502
- if (isMuted) {
503
- muteBtn.innerHTML = `
504
- <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
505
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5.586 15H4a1 1 0 01-1-1v-4a1 1 0 011-1h1.586l4.707-4.707C10.923 3.663 12 4.109 12 5v14c0 .891-1.077 1.337-1.707.707L5.586 15z" />
506
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2" />
507
- </svg>
508
- `;
509
- speechSynthesis.cancel();
510
- } else {
511
- muteBtn.innerHTML = `
512
- <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
513
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.536 8.464a5 5 0 010 7.072m2.828-9.9a9 9 0 010 12.728M5.586 15H4a1 1 0 01-1-1v-4a1 1 0 011-1h1.586l4.707-4.707C10.923 3.663 12 4.109 12 5v14c0 .891-1.077 1.337-1.707.707L5.586 15z" />
514
- </svg>
515
  `;
 
 
 
 
 
 
 
 
516
  }
517
  });
518
-
519
- // Initialize the app
520
- document.addEventListener('DOMContentLoaded', init);
521
  </script>
522
  <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=manmohanai/faceimotion" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
523
  </html>
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Hydration Tracker</title>
7
  <script src="https://cdn.tailwindcss.com"></script>
8
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
9
+ <script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@3.18.0/dist/tf.min.js"></script>
10
+ <script src="https://cdn.jsdelivr.net/npm/@tensorflow-models/coco-ssd@2.2.2/dist/coco-ssd.min.js"></script>
11
  <style>
12
+ .wave {
13
  position: relative;
 
 
 
 
14
  overflow: hidden;
 
15
  }
16
+ .wave::before {
17
+ content: "";
18
+ position: absolute;
19
+ left: 0;
20
+ bottom: 0;
21
+ right: 0;
22
+ background-repeat: repeat;
23
+ height: 10px;
24
+ background-size: 20px 20px;
25
+ background-image: radial-gradient(circle at 10px -5px, transparent 12px, #3b82f6 13px);
 
 
 
 
26
  }
27
+ .wave::after {
28
+ content: "";
29
  position: absolute;
30
+ left: 0;
31
+ bottom: 0;
32
+ right: 0;
33
+ background-repeat: repeat;
34
+ height: 15px;
35
+ background-size: 40px 20px;
36
+ background-image: radial-gradient(circle at 10px 15px, #3b82f6 12px, transparent 13px);
37
+ }
38
+ .glass {
39
+ transition: all 0.5s ease;
40
+ }
41
+ .glass:hover {
42
+ transform: translateY(-5px);
43
+ }
44
+ .progress-ring__circle {
45
+ transition: stroke-dashoffset 0.5s;
46
+ transform: rotate(-90deg);
47
+ transform-origin: 50% 50%;
48
  }
49
  </style>
50
  </head>
51
+ <body class="bg-blue-50 min-h-screen">
52
  <div class="container mx-auto px-4 py-8">
53
+ <div class="max-w-3xl mx-auto">
54
+ <!-- Header -->
55
+ <header class="text-center mb-8">
56
+ <h1 class="text-4xl font-bold text-blue-800 mb-2">Hydration Tracker</h1>
57
+ <p class="text-blue-600">Track your daily water intake and stay hydrated</p>
58
+ </header>
59
+
60
+ <!-- Main Content -->
61
+ <div class="bg-white rounded-xl shadow-lg p-6 mb-8">
62
+ <!-- Progress Circle -->
63
+ <div class="flex flex-col items-center mb-8">
64
+ <div class="relative w-48 h-48 mb-4">
65
+ <svg class="w-full h-full" viewBox="0 0 100 100">
66
+ <!-- Background circle -->
67
+ <circle cx="50" cy="50" r="45" fill="none" stroke="#e2e8f0" stroke-width="8"/>
68
+ <!-- Progress circle -->
69
+ <circle id="progress-circle" cx="50" cy="50" r="45" fill="none" stroke="#3b82f6"
70
+ stroke-width="8" stroke-dasharray="283" stroke-dashoffset="283"/>
71
+ </svg>
72
+ <div class="absolute inset-0 flex flex-col items-center justify-center">
73
+ <span id="progress-percent" class="text-3xl font-bold text-blue-800">0%</span>
74
+ <span class="text-blue-600">Daily Goal</span>
75
+ </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
  </div>
77
+ <div id="progress-text" class="text-center">
78
+ <p class="text-lg font-semibold text-blue-800">Start drinking water!</p>
79
+ <p class="text-blue-600">0 of 8 glasses (2L)</p>
80
  </div>
81
  </div>
82
+
83
+ <!-- Video Capture -->
84
+ <div class="mb-8 bg-blue-50 rounded-lg p-6">
85
+ <h2 class="text-xl font-semibold text-blue-800 mb-4">Measure Container Automatically</h2>
86
+ <div class="flex flex-col items-center">
87
+ <video id="video" width="320" height="240" autoplay class="mb-4 rounded-lg bg-gray-200"></video>
88
+ <canvas id="canvas" width="320" height="240" class="hidden"></canvas>
89
+ <button id="capture-btn" class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition">
90
+ <i class="fas fa-camera mr-2"></i>Capture & Measure
91
+ </button>
92
+ <div id="measure-result" class="mt-4 text-center hidden">
93
+ <p class="font-medium text-blue-800">Detected Container:</p>
94
+ <p id="detected-container" class="text-lg font-bold text-blue-800">-</p>
95
+ <p id="detected-size" class="text-blue-600">- ml</p>
96
+ </div>
97
+ </div>
98
+ </div>
99
+
100
+ <!-- Water Container Selection -->
101
+ <div class="mb-8">
102
+ <h2 class="text-xl font-semibold text-blue-800 mb-4">Select Your Container</h2>
103
+ <div class="grid grid-cols-2 sm:grid-cols-4 gap-4">
104
+ <div class="glass cursor-pointer bg-blue-50 rounded-lg p-4 flex flex-col items-center"
105
+ data-size="250" data-name="Small Glass">
106
+ <div class="w-16 h-24 bg-blue-100 rounded-b-lg rounded-t-full relative wave mb-2"
107
+ style="height: 40%;"></div>
108
+ <span class="text-sm font-medium text-blue-800">Small Glass</span>
109
+ <span class="text-xs text-blue-600">250ml</span>
110
+ </div>
111
+ <div class="glass cursor-pointer bg-blue-50 rounded-lg p-4 flex flex-col items-center"
112
+ data-size="350" data-name="Medium Glass">
113
+ <div class="w-16 h-24 bg-blue-100 rounded-b-lg rounded-t-full relative wave mb-2"
114
+ style="height: 60%;"></div>
115
+ <span class="text-sm font-medium text-blue-800">Medium Glass</span>
116
+ <span class="text-xs text-blue-600">350ml</span>
117
+ </div>
118
+ <div class="glass cursor-pointer bg-blue-50 rounded-lg p-4 flex flex-col items-center"
119
+ data-size="500" data-name="Large Glass">
120
+ <div class="w-16 h-24 bg-blue-100 rounded-b-lg rounded-t-full relative wave mb-2"
121
+ style="height: 80%;"></div>
122
+ <span class="text-sm font-medium text-blue-800">Large Glass</span>
123
+ <span class="text-xs text-blue-600">500ml</span>
124
+ </div>
125
+ <div class="glass cursor-pointer bg-blue-50 rounded-lg p-4 flex flex-col items-center"
126
+ data-size="1000" data-name="Bottle">
127
+ <div class="w-16 h-24 bg-blue-100 rounded-b-lg rounded-t-full relative wave mb-2"
128
+ style="height: 100%;"></div>
129
+ <span class="text-sm font-medium text-blue-800">Bottle</span>
130
+ <span class="text-xs text-blue-600">1L</span>
131
+ </div>
132
+ </div>
133
+ </div>
134
+
135
+ <!-- Add Water Intake -->
136
+ <div class="mb-8">
137
+ <h2 class="text-xl font-semibold text-blue-800 mb-4">Add Water Intake</h2>
138
+ <div class="flex flex-col sm:flex-row gap-4">
139
+ <div class="flex-1">
140
+ <label for="custom-amount" class="block text-sm font-medium text-blue-700 mb-1">Custom Amount (ml)</label>
141
+ <input type="number" id="custom-amount"
142
+ class="w-full px-4 py-2 border border-blue-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
143
+ </div>
144
+ <div class="flex items-end">
145
+ <button id="add-water"
146
+ class="w-full sm:w-auto px-6 py-2 bg-blue-600 text-white font-medium rounded-lg hover:bg-blue-700 transition">
147
+ Add Water
148
+ </button>
149
+ </div>
150
+ </div>
151
  </div>
 
 
 
 
152
 
153
+ <!-- Today's Intake -->
154
+ <div>
155
+ <h2 class="text-xl font-semibold text-blue-800 mb-4">Today's Intake</h2>
156
+ <div id="intake-list" class="space-y-3">
157
+ <div class="text-center py-4 text-blue-600" id="empty-state">
158
+ No water intake recorded yet. Add your first glass!
159
+ </div>
160
+ </div>
161
+ <div class="mt-4 pt-4 border-t border-blue-100">
162
+ <div class="flex justify-between items-center">
163
+ <span class="font-medium text-blue-800">Total:</span>
164
+ <span id="total-intake" class="font-bold text-blue-800">0 ml</span>
165
+ </div>
166
+ <div class="flex justify-between items-center mt-1">
167
+ <span class="font-medium text-blue-800">Glasses:</span>
168
+ <span id="total-glasses" class="font-bold text-blue-800">0</span>
169
+ </div>
170
+ </div>
171
+ </div>
172
  </div>
173
+
174
+ <!-- Hydration Tips -->
175
+ <div class="bg-white rounded-xl shadow-lg p-6">
176
+ <h2 class="text-xl font-semibold text-blue-800 mb-4">Hydration Tips</h2>
177
+ <div class="space-y-4">
178
+ <div class="flex items-start">
179
+ <div class="flex-shrink-0 mt-1 mr-3 text-blue-500">
180
+ <i class="fas fa-check-circle"></i>
181
+ </div>
182
+ <p class="text-blue-700">Aim for 8 glasses (2 liters) of water per day as a general guideline.</p>
183
+ </div>
184
+ <div class="flex items-start">
185
+ <div class="flex-shrink-0 mt-1 mr-3 text-blue-500">
186
+ <i class="fas fa-check-circle"></i>
187
+ </div>
188
+ <p class="text-blue-700">Drink more if you're active, in hot weather, or pregnant/breastfeeding.</p>
189
+ </div>
190
+ <div class="flex items-start">
191
+ <div class="flex-shrink-0 mt-1 mr-3 text-blue-500">
192
+ <i class="fas fa-check-circle"></i>
193
+ </div>
194
+ <p class="text-blue-700">Your urine color is a good indicator - aim for pale yellow.</p>
195
+ </div>
196
  </div>
197
  </div>
198
  </div>
 
 
 
 
 
 
 
199
  </div>
200
 
201
  <script>
202
+ document.addEventListener('DOMContentLoaded', async function() {
203
+ // Video Capture Elements
204
+ const video = document.getElementById('video');
205
+ const canvas = document.getElementById('canvas');
206
+ const captureBtn = document.getElementById('capture-btn');
207
+ const measureResult = document.getElementById('measure-result');
208
+ const detectedContainer = document.getElementById('detected-container');
209
+ const detectedSize = document.getElementById('detected-size');
 
 
 
 
 
 
 
 
 
 
 
 
 
210
 
211
+ // Load COCO-SSD model
212
+ let model;
213
  try {
214
+ model = await cocoSsd.load();
215
+ console.log('Model loaded successfully');
216
+ } catch (err) {
217
+ console.error('Failed to load model:', err);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
218
  }
219
 
220
+ // Start video stream
221
+ if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
222
+ navigator.mediaDevices.getUserMedia({ video: true })
223
+ .then(function(stream) {
224
+ video.srcObject = stream;
225
+ })
226
+ .catch(function(err) {
227
+ console.error("Error accessing camera:", err);
228
+ });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
229
  }
230
 
231
+ // Capture and detect button
232
+ captureBtn.addEventListener('click', async function() {
233
+ if (!model) {
234
+ alert('Model not loaded yet. Please wait...');
235
+ return;
236
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
237
 
238
+ // Draw video frame to canvas
239
+ const context = canvas.getContext('2d');
240
+ context.drawImage(video, 0, 0, canvas.width, canvas.height);
 
 
 
241
 
242
+ // Detect objects
243
+ const predictions = await model.detect(canvas);
244
+ console.log('Predictions:', predictions);
245
 
246
+ // Find drinking glasses or bottles
247
+ const container = predictions.find(p =>
248
+ ['bottle', 'cup', 'wine glass'].includes(p.class)
249
+ );
250
 
251
+ if (container) {
252
+ // Estimate size based on bounding box dimensions
253
+ const area = container.bbox[2] * container.bbox[3];
254
+ let size;
255
+
256
+ if (container.class === 'bottle') {
257
+ size = Math.round(area * 0.15); // Bottle size estimation
258
+ if (size > 1500) size = 1000;
259
+ else if (size > 800) size = 750;
260
+ else size = 500;
261
+ } else {
262
+ size = Math.round(area * 0.3); // Glass size estimation
263
+ if (size > 400) size = 500;
264
+ else if (size > 300) size = 350;
265
+ else size = 250;
266
+ }
267
+
268
+ detectedContainer.textContent = container.class.charAt(0).toUpperCase() + container.class.slice(1);
269
+ detectedSize.textContent = `${size} ml`;
270
+ measureResult.classList.remove('hidden');
271
+
272
+ // Auto-fill the custom amount field
273
+ customAmount.value = size;
274
+ } else {
275
+ detectedContainer.textContent = 'No container detected';
276
+ detectedSize.textContent = '';
277
+ measureResult.classList.remove('hidden');
278
+ }
279
+ });
280
+ // Constants
281
+ const DAILY_GOAL_ML = 2000; // 2 liters
282
+ const GLASS_SIZE_ML = 250; // Standard glass size
283
 
284
+ // DOM Elements
285
+ const progressCircle = document.getElementById('progress-circle');
286
+ const progressPercent = document.getElementById('progress-percent');
287
+ const progressText = document.getElementById('progress-text');
288
+ const totalIntake = document.getElementById('total-intake');
289
+ const totalGlasses = document.getElementById('total-glasses');
290
+ const intakeList = document.getElementById('intake-list');
291
+ const emptyState = document.getElementById('empty-state');
292
+ const addWaterBtn = document.getElementById('add-water');
293
+ const customAmount = document.getElementById('custom-amount');
294
+ const glassContainers = document.querySelectorAll('.glass');
 
 
 
 
 
 
 
 
295
 
296
+ // State
297
+ let currentIntake = 0;
298
+ let glassesCount = 0;
299
 
300
+ // Initialize
301
+ updateUI();
 
 
 
302
 
303
+ // Event Listeners
304
+ glassContainers.forEach(glass => {
305
+ glass.addEventListener('click', function() {
306
+ const size = parseInt(this.dataset.size);
307
+ const name = this.dataset.name;
308
+ addWater(size, name);
309
+ });
310
+ });
311
 
312
+ addWaterBtn.addEventListener('click', function() {
313
+ const amount = parseInt(customAmount.value);
314
+ if (amount && amount > 0) {
315
+ addWater(amount, 'Custom Amount');
316
+ customAmount.value = '';
317
+ }
318
+ });
319
 
320
+ // Functions
321
+ function addWater(amount, containerName) {
322
+ currentIntake += amount;
323
+ glassesCount = Math.round(currentIntake / GLASS_SIZE_ML);
324
 
325
+ // Add to history
326
+ if (emptyState) emptyState.style.display = 'none';
 
327
 
328
+ const now = new Date();
329
+ const timeString = now.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
330
 
331
+ const intakeItem = document.createElement('div');
332
+ intakeItem.className = 'flex justify-between items-center bg-blue-50 p-3 rounded-lg';
333
+ intakeItem.innerHTML = `
334
+ <div class="flex items-center">
335
+ <div class="w-8 h-8 bg-blue-100 rounded-full flex items-center justify-center mr-3">
336
+ <i class="fas fa-tint text-blue-500"></i>
337
+ </div>
338
+ <div>
339
+ <p class="font-medium text-blue-800">${containerName}</p>
340
+ <p class="text-xs text-blue-600">${timeString}</p>
341
+ </div>
342
+ </div>
343
+ <span class="font-bold text-blue-800">${amount} ml</span>
344
+ `;
345
+
346
+ intakeList.prepend(intakeItem);
347
+ updateUI();
348
+
349
+ // Show feedback based on progress
350
+ const percent = Math.min(Math.round((currentIntake / DAILY_GOAL_ML) * 100), 100);
351
+ if (percent >= 100) {
352
+ showFeedback("Great job! You've reached your daily goal!", "text-green-600");
353
+ } else if (percent >= 75) {
354
+ showFeedback("Almost there! Keep going!", "text-blue-600");
355
+ } else if (percent >= 50) {
356
+ showFeedback("Halfway there! You're doing well.", "text-blue-600");
357
+ } else if (percent >= 25) {
358
+ showFeedback("Good start! Keep hydrating.", "text-blue-600");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
359
  }
 
 
360
  }
361
 
362
+ function updateUI() {
363
+ const percent = Math.min((currentIntake / DAILY_GOAL_ML) * 100, 100);
364
+ const dashoffset = 283 - (283 * percent / 100);
365
+
366
+ progressCircle.style.strokeDashoffset = dashoffset;
367
+ progressPercent.textContent = `${Math.round(percent)}%`;
368
+ totalIntake.textContent = `${currentIntake} ml`;
369
+ totalGlasses.textContent = glassesCount;
370
+
371
+ // Update progress text
372
+ if (percent >= 100) {
373
+ progressText.innerHTML = `
374
+ <p class="text-lg font-semibold text-green-600">Goal Achieved!</p>
375
+ <p class="text-green-600">${glassesCount} of 8 glasses (${(currentIntake/1000).toFixed(1)}L of 2L)</p>
376
+ `;
377
+ } else {
378
+ progressText.innerHTML = `
379
+ <p class="text-lg font-semibold text-blue-800">${glassesCount >= 4 ? 'Good progress!' : 'Keep drinking!'}</p>
380
+ <p class="text-blue-600">${glassesCount} of 8 glasses (${(currentIntake/1000).toFixed(1)}L of 2L)</p>
381
+ `;
382
+ }
383
  }
384
 
385
+ function showFeedback(message, colorClass) {
386
+ const feedback = document.createElement('div');
387
+ feedback.className = `fixed bottom-4 left-1/2 transform -translate-x-1/2 bg-white shadow-lg rounded-lg px-6 py-3 flex items-center ${colorClass} animate-bounce`;
388
+ feedback.innerHTML = `
389
+ <i class="fas fa-check-circle mr-2"></i>
390
+ <span>${message}</span>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
391
  `;
392
+
393
+ document.body.appendChild(feedback);
394
+
395
+ setTimeout(() => {
396
+ feedback.classList.remove('animate-bounce');
397
+ feedback.classList.add('opacity-0', 'transition-opacity', 'duration-300');
398
+ setTimeout(() => feedback.remove(), 300);
399
+ }, 3000);
400
  }
401
  });
 
 
 
402
  </script>
403
  <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=manmohanai/faceimotion" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
404
  </html>