samlax12 commited on
Commit
c6bf4be
·
verified ·
1 Parent(s): bc9d8e1

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +623 -1483
index.html CHANGED
@@ -1,1504 +1,644 @@
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>AR Nail Polish Simulator</title>
7
- <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
8
- <script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
9
- <!-- MediaPipe dependencies -->
10
- <script src="https://cdn.jsdelivr.net/npm/@mediapipe/camera_utils@0.3.1640029074/camera_utils.js"></script>
11
- <script src="https://cdn.jsdelivr.net/npm/@mediapipe/hands@0.4.1646424915/hands.js"></script>
12
- <script src="https://cdn.jsdelivr.net/npm/@mediapipe/drawing_utils@0.3.1620248257/drawing_utils.js"></script>
13
- <style>
14
- * {
15
- margin: 0;
16
- padding: 0;
17
- box-sizing: border-box;
18
- font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
19
- }
20
-
21
- body {
22
- background-color: #f5f5f5;
23
- display: flex;
24
- flex-direction: column;
25
- min-height: 100vh;
26
- overflow-x: hidden;
27
- }
28
-
29
- .container {
30
- position: relative;
31
- display: flex;
32
- flex-direction: column;
33
- align-items: center;
34
- width: 100%;
35
- flex: 1;
36
- padding: 20px;
37
- }
38
-
39
- .header {
40
- width: 100%;
41
- text-align: center;
42
- background-color: #ffffff;
43
- padding: 15px;
44
- box-shadow: 0 2px 10px rgba(0,0,0,0.1);
45
- margin-bottom: 20px;
46
- border-radius: 12px;
47
- }
48
-
49
- .header h1 {
50
- color: #333;
51
- font-size: 1.5rem;
52
- margin-bottom: 5px;
53
- }
54
-
55
- .header p {
56
- color: #777;
57
- font-size: 0.9rem;
58
- }
59
-
60
- .video-container {
61
- position: relative;
62
- width: 100%;
63
- max-width: 480px;
64
- height: 0;
65
- padding-bottom: 100%;
66
- overflow: hidden;
67
- border-radius: 12px;
68
- box-shadow: 0 4px 20px rgba(0,0,0,0.15);
69
- background-color: #333;
70
- }
71
-
72
- #video {
73
- position: absolute;
74
- top: 0;
75
- left: 0;
76
- width: 100%;
77
- height: 100%;
78
- object-fit: cover;
79
- }
80
-
81
- #nail-overlay {
82
- position: absolute;
83
- top: 0;
84
- left: 0;
85
- width: 100%;
86
- height: 100%;
87
- pointer-events: none;
88
- }
89
-
90
- .loading-message {
91
- position: absolute;
92
- top: 50%;
93
- left: 50%;
94
- transform: translate(-50%, -50%);
95
- color: white;
96
- background-color: rgba(0,0,0,0.7);
97
- padding: 15px 30px;
98
- border-radius: 30px;
99
- font-size: 1.1rem;
100
- z-index: 10;
101
- }
102
-
103
- .controls {
104
- width: 100%;
105
- max-width: 480px;
106
- background-color: #ffffff;
107
- border-radius: 12px;
108
- padding: 20px;
109
- margin-top: 20px;
110
- box-shadow: 0 4px 15px rgba(0,0,0,0.1);
111
- }
112
-
113
- .tabs {
114
- display: flex;
115
- border-bottom: 1px solid #eee;
116
- margin-bottom: 15px;
117
- }
118
-
119
- .tab {
120
- padding: 10px 15px;
121
- cursor: pointer;
122
- border-bottom: 2px solid transparent;
123
- color: #777;
124
- font-weight: 500;
125
- transition: all 0.3s ease;
126
- }
127
-
128
- .tab.active {
129
- color: #333;
130
- border-bottom-color: #f06292;
131
- }
132
-
133
- .color-selector {
134
- display: grid;
135
- grid-template-columns: repeat(5, 1fr);
136
- gap: 10px;
137
- margin-bottom: 20px;
138
- }
139
-
140
- .color-option {
141
- width: 100%;
142
- aspect-ratio: 1;
143
- border-radius: 50%;
144
- cursor: pointer;
145
- border: 3px solid transparent;
146
- transition: transform 0.2s ease, border-color 0.2s ease;
147
- box-shadow: 0 3px 10px rgba(0,0,0,0.1);
148
- }
149
-
150
- .color-option.selected {
151
- border-color: #333;
152
- transform: scale(1.1);
153
- }
154
-
155
- .texture-selector {
156
- display: grid;
157
- grid-template-columns: repeat(3, 1fr);
158
- gap: 15px;
159
- margin-bottom: 20px;
160
- }
161
-
162
- .texture-option {
163
- width: 100%;
164
- aspect-ratio: 1;
165
- border-radius: 12px;
166
- cursor: pointer;
167
- background-size: cover;
168
- box-shadow: 0 3px 10px rgba(0,0,0,0.1);
169
- border: 3px solid transparent;
170
- transition: transform 0.2s ease, border-color 0.2s ease;
171
- }
172
-
173
- .texture-option.selected {
174
- border-color: #333;
175
- transform: scale(1.05);
176
- }
177
-
178
- .opacity-control {
179
- width: 100%;
180
- margin-bottom: 20px;
181
- }
182
-
183
- .opacity-control p {
184
- margin-bottom: 8px;
185
- color: #555;
186
- font-weight: 500;
187
- }
188
-
189
- .slider-container {
190
- margin-bottom: 15px;
191
- }
192
-
193
- .slider-container p {
194
- margin-bottom: 8px;
195
- color: #555;
196
- font-weight: 500;
197
- }
198
-
199
- input[type="range"] {
200
- width: 100%;
201
- height: 8px;
202
- border-radius: 5px;
203
- background: #ddd;
204
- outline: none;
205
- -webkit-appearance: none;
206
- }
207
-
208
- input[type="range"]::-webkit-slider-thumb {
209
- -webkit-appearance: none;
210
- width: 20px;
211
- height: 20px;
212
- border-radius: 50%;
213
- background: #f06292;
214
- cursor: pointer;
215
- }
216
-
217
- .action-buttons {
218
- display: flex;
219
- justify-content: space-between;
220
- gap: 10px;
221
- }
222
-
223
- .btn {
224
- flex: 1;
225
- padding: 12px;
226
- border-radius: 30px;
227
- border: none;
228
- font-weight: 600;
229
- cursor: pointer;
230
- transition: all 0.3s ease;
231
- font-size: 1rem;
232
- }
233
-
234
- .btn-primary {
235
- background-color: #f06292;
236
- color: white;
237
- box-shadow: 0 4px 15px rgba(240, 98, 146, 0.3);
238
- }
239
-
240
- .btn-secondary {
241
- background-color: #fff;
242
- color: #333;
243
- border: 1px solid #ddd;
244
- }
245
-
246
- .btn:hover {
247
- transform: translateY(-2px);
248
- }
249
-
250
- .btn-primary:hover {
251
- box-shadow: 0 6px 20px rgba(240, 98, 146, 0.4);
252
- }
253
-
254
- .camera-switch {
255
- position: absolute;
256
- right: 15px;
257
- top: 15px;
258
- z-index: 100;
259
- background-color: rgba(255, 255, 255, 0.8);
260
- border-radius: 50%;
261
- width: 40px;
262
- height: 40px;
263
- display: flex;
264
- align-items: center;
265
- justify-content: center;
266
- cursor: pointer;
267
- box-shadow: 0 3px 10px rgba(0,0,0,0.2);
268
- }
269
-
270
- .camera-switch svg {
271
- width: 24px;
272
- height: 24px;
273
- color: #333;
274
- }
275
-
276
- .tutorial-overlay {
277
- position: fixed;
278
- top: 0;
279
- left: 0;
280
- width: 100%;
281
- height: 100%;
282
- background-color: rgba(0,0,0,0.7);
283
- z-index: 1000;
284
- display: flex;
285
- align-items: center;
286
- justify-content: center;
287
- }
288
-
289
- .tutorial-content {
290
- background-color: white;
291
- border-radius: 15px;
292
- padding: 25px;
293
- width: 90%;
294
- max-width: 400px;
295
- text-align: center;
296
- }
297
-
298
- .tutorial-content h2 {
299
- margin-bottom: 15px;
300
- color: #333;
301
- }
302
-
303
- .tutorial-content p {
304
- margin-bottom: 20px;
305
- color: #666;
306
- line-height: 1.5;
307
- }
308
-
309
- .tutorial-btn {
310
- padding: 12px 25px;
311
- background-color: #f06292;
312
- color: white;
313
- border: none;
314
- border-radius: 30px;
315
- font-weight: 600;
316
- cursor: pointer;
317
- transition: all 0.3s ease;
318
- }
319
-
320
- .tutorial-btn:hover {
321
- background-color: #e91e63;
322
- }
323
-
324
- .gallery-container {
325
- width: 100%;
326
- max-width: 480px;
327
- display: grid;
328
- grid-template-columns: repeat(3, 1fr);
329
- gap: 10px;
330
- margin-top: 20px;
331
- }
332
-
333
- .gallery-item {
334
- width: 100%;
335
- aspect-ratio: 1;
336
- border-radius: 8px;
337
- overflow: hidden;
338
- box-shadow: 0 3px 10px rgba(0,0,0,0.1);
339
- }
340
-
341
- .gallery-item img {
342
- width: 100%;
343
- height: 100%;
344
- object-fit: cover;
345
- }
346
-
347
- .shape-option {
348
- width: 100%;
349
- aspect-ratio: 1;
350
- display: flex;
351
- align-items: center;
352
- justify-content: center;
353
- background-color: #f5f5f5;
354
- border-radius: 12px;
355
- cursor: pointer;
356
- box-shadow: 0 3px 10px rgba(0,0,0,0.1);
357
- border: 3px solid transparent;
358
- transition: transform 0.2s ease, border-color 0.2s ease;
359
- }
360
-
361
- .shape-option.selected {
362
- border-color: #333;
363
- transform: scale(1.05);
364
- }
365
-
366
- .shape-option svg {
367
- width: 60%;
368
- height: 60%;
369
- }
370
-
371
- .tab-content {
372
- display: none;
373
- }
374
-
375
- .tab-content.active {
376
- display: block;
377
- }
378
-
379
- .preset-selector {
380
- display: grid;
381
- grid-template-columns: repeat(3, 1fr);
382
- gap: 15px;
383
- margin-bottom: 20px;
384
- }
385
-
386
- .preset-option {
387
- width: 100%;
388
- aspect-ratio: 1;
389
- border-radius: 12px;
390
- cursor: pointer;
391
- overflow: hidden;
392
- box-shadow: 0 3px 10px rgba(0,0,0,0.1);
393
- border: 3px solid transparent;
394
- transition: transform 0.2s ease, border-color 0.2s ease;
395
- position: relative;
396
- }
397
-
398
- .preset-option img {
399
- width: 100%;
400
- height: 100%;
401
- object-fit: cover;
402
- }
403
-
404
- .preset-option.selected {
405
- border-color: #333;
406
- transform: scale(1.05);
407
- }
408
-
409
- .preset-option p {
410
- position: absolute;
411
- bottom: 0;
412
- left: 0;
413
- right: 0;
414
- background-color: rgba(0,0,0,0.7);
415
- color: white;
416
- padding: 5px;
417
- font-size: 0.8rem;
418
- text-align: center;
419
- }
420
-
421
- .share-modal {
422
- display: none;
423
- position: fixed;
424
- top: 0;
425
- left: 0;
426
- width: 100%;
427
- height: 100%;
428
- background-color: rgba(0,0,0,0.7);
429
- z-index: 1000;
430
- align-items: center;
431
- justify-content: center;
432
- }
433
-
434
- .share-content {
435
- background-color: white;
436
- border-radius: 15px;
437
- padding: 25px;
438
- width: 90%;
439
- max-width: 400px;
440
- text-align: center;
441
- }
442
-
443
- .share-content h2 {
444
- margin-bottom: 15px;
445
- color: #333;
446
- }
447
-
448
- .share-preview {
449
- width: 100%;
450
- aspect-ratio: 1;
451
- margin-bottom: 20px;
452
- border-radius: 10px;
453
- overflow: hidden;
454
- }
455
-
456
- .share-preview img {
457
- width: 100%;
458
- height: 100%;
459
- object-fit: contain;
460
- }
461
-
462
- .share-buttons {
463
- display: flex;
464
- justify-content: space-around;
465
- margin-bottom: 20px;
466
- }
467
-
468
- .share-btn {
469
- display: flex;
470
- flex-direction: column;
471
- align-items: center;
472
- cursor: pointer;
473
- }
474
-
475
- .share-btn .icon {
476
- width: 50px;
477
- height: 50px;
478
- border-radius: 50%;
479
- display: flex;
480
- align-items: center;
481
- justify-content: center;
482
- margin-bottom: 8px;
483
- }
484
-
485
- .share-btn p {
486
- font-size: 0.8rem;
487
- color: #555;
488
- }
489
-
490
- .close-share {
491
- display: block;
492
- margin: 0 auto;
493
- padding: 10px 25px;
494
- background-color: #f5f5f5;
495
- border: none;
496
- border-radius: 20px;
497
- cursor: pointer;
498
- font-weight: 500;
499
- color: #333;
500
- }
501
-
502
- .manual-mode-toggle {
503
- display: flex;
504
- align-items: center;
505
- margin-bottom: 15px;
506
- cursor: pointer;
507
- }
508
-
509
- .manual-mode-toggle input {
510
- margin-right: 10px;
511
- }
512
-
513
- #debug-canvas {
514
- display: none;
515
- position: absolute;
516
- top: 0;
517
- left: 0;
518
- width: 100%;
519
- height: 100%;
520
- pointer-events: none;
521
- }
522
- </style>
523
  </head>
 
524
  <body>
525
- <div class="container">
526
- <div class="header">
527
- <h1>AR Nail Polish Simulator</h1>
528
- <p>Try on virtual nail polish designs in real-time</p>
529
- </div>
530
-
531
- <div class="video-container">
532
- <video id="video" autoplay playsinline></video>
533
- <canvas id="nail-overlay"></canvas>
534
- <canvas id="debug-canvas"></canvas>
535
- <div class="camera-switch">
536
- <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
537
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4" />
538
- </svg>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
539
  </div>
540
- <div class="loading-message">Initializing camera...</div>
541
- </div>
542
-
543
- <div class="controls">
544
- <div class="manual-mode-toggle">
545
- <input type="checkbox" id="manual-mode-checkbox">
546
- <label for="manual-mode-checkbox">Manual Placement Mode</label>
547
  </div>
548
-
549
- <div class="tabs">
550
- <div class="tab active" data-tab="color">Colors</div>
551
- <div class="tab" data-tab="texture">Textures</div>
552
- <div class="tab" data-tab="shape">Shape</div>
553
- <div class="tab" data-tab="preset">Presets</div>
554
  </div>
555
-
556
- <div class="tab-content active" id="color-tab">
557
- <div class="color-selector">
558
- <div class="color-option selected" style="background-color: #f06292;" data-color="#f06292"></div>
559
- <div class="color-option" style="background-color: #ec407a;" data-color="#ec407a"></div>
560
- <div class="color-option" style="background-color: #d81b60;" data-color="#d81b60"></div>
561
- <div class="color-option" style="background-color: #e91e63;" data-color="#e91e63"></div>
562
- <div class="color-option" style="background-color: #ad1457;" data-color="#ad1457"></div>
563
- <div class="color-option" style="background-color: #880e4f;" data-color="#880e4f"></div>
564
- <div class="color-option" style="background-color: #c2185b;" data-color="#c2185b"></div>
565
- <div class="color-option" style="background-color: #ff80ab;" data-color="#ff80ab"></div>
566
- <div class="color-option" style="background-color: #ff4081;" data-color="#ff4081"></div>
567
- <div class="color-option" style="background-color: #f50057;" data-color="#f50057"></div>
568
- <div class="color-option" style="background-color: #c51162;" data-color="#c51162"></div>
569
- <div class="color-option" style="background-color: #ff1744;" data-color="#ff1744"></div>
570
- <div class="color-option" style="background-color: #d50000;" data-color="#d50000"></div>
571
- <div class="color-option" style="background-color: #ff5252;" data-color="#ff5252"></div>
572
- <div class="color-option" style="background-color: #ff8a80;" data-color="#ff8a80"></div>
573
- </div>
574
-
575
- <div class="opacity-control">
576
- <p>Color Opacity: <span id="opacity-value">100%</span></p>
577
- <input type="range" id="opacity-slider" min="10" max="100" value="100">
578
- </div>
579
  </div>
580
-
581
- <div class="tab-content" id="texture-tab">
582
- <div class="texture-selector">
583
- <div class="texture-option" style="background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAiIGhlaWdodD0iNDAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PHBhdHRlcm4gaWQ9ImdyaWQiIHdpZHRoPSI0MCIgaGVpZ2h0PSI0MCIgcGF0dGVyblVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHBhdGggZD0iTSAwIDEwIEwgNDAgMTAgTSAxMCAwIEwgMTAgNDAgTSAwIDIwIEwgNDAgMjAgTSAyMCAwIEwgMjAgNDAgTSAwIDMwIEwgNDAgMzAgTSAzMCAwIEwgMzAgNDAiIGZpbGw9Im5vbmUiIHN0cm9rZT0iYmxhY2siIHN0cm9rZS13aWR0aD0iMSIvPjwvcGF0dGVybj48L2RlZnM+PHJlY3Qgd2lkdGg9IjQwIiBoZWlnaHQ9IjQwIiBmaWxsPSJ1cmwoI2dyaWQpIiAvPjwvc3ZnPg==')" data-texture="grid"></div>
584
- <div class="texture-option" style="background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAiIGhlaWdodD0iMjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PHBhdHRlcm4gaWQ9InBvbGthIiB3aWR0aD0iMjAiIGhlaWdodD0iMjAiIHBhdHRlcm5Vbml0cz0idXNlclNwYWNlT25Vc2UiIHBhdHRlcm5UcmFuc2Zvcm09InJvdGF0ZSgwKSI+PGNpcmNsZSBjeD0iMTAiIGN5PSIxMCIgcj0iMiIgZmlsbD0iYmxhY2siIC8+PC9wYXR0ZXJuPjwvZGVmcz48cmVjdCB3aWR0aD0iMjAiIGhlaWdodD0iMjAiIGZpbGw9InVybCgjcG9sa2EpIiAvPjwvc3ZnPg==')" data-texture="polka"></div>
585
- <div class="texture-option" style="background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAiIGhlaWdodD0iNDAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PHBhdHRlcm4gaWQ9InN0cmlwZXMiIHdpZHRoPSI0MCIgaGVpZ2h0PSI0MCIgcGF0dGVyblVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgcGF0dGVyblRyYW5zZm9ybT0icm90YXRlKDQ1KSI+PHJlY3Qgd2lkdGg9IjEwIiBoZWlnaHQ9IjQwIiBmaWxsPSJibGFjayIgLz48cmVjdCB4PSIxMCIgd2lkdGg9IjEwIiBoZWlnaHQ9IjQwIiBmaWxsPSJ0cmFuc3BhcmVudCIgLz48L3BhdHRlcm4+PC9kZWZzPjxyZWN0IHdpZHRoPSI0MCIgaGVpZ2h0PSI0MCIgZmlsbD0idXJsKCNzdHJpcGVzKSIgLz48L3N2Zz4=')" data-texture="stripes"></div>
586
- <div class="texture-option" style="background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAiIGhlaWdodD0iNDAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PHBhdHRlcm4gaWQ9ImNoZXZyb24iIHdpZHRoPSI0MCIgaGVpZ2h0PSI0MCIgcGF0dGVyblVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHBhdGggZD0iTTAgMjAgbDIwIDIwIGwyMC0yMCBNLTIwIDAgbDIwIDIwIGwyMC0yMCBNMjAgMCBsLTIwIDIwIGw0MCA0MCBNNjAgMCBsLTIwIDIwIGwyMCAyMCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSJibGFjayIgc3Ryb2tlLXdpZHRoPSIyIi8+PC9wYXR0ZXJuPjwvZGVmcz48cmVjdCB3aWR0aD0iNDAiIGhlaWdodD0iNDAiIGZpbGw9InVybCgjY2hldnJvbikiIC8+PC9zdmc+')" data-texture="chevron"></div>
587
- <div class="texture-option" style="background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAiIGhlaWdodD0iMjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PHBhdHRlcm4gaWQ9InN0YXJzIiB3aWR0aD0iMjAiIGhlaWdodD0iMjAiIHBhdHRlcm5Vbml0cz0idXNlclNwYWNlT25Vc2UiPjxwYXRoIGQ9Ik0xMCA1IGwxIDMgaDMgbC0yLjUgMiAxIDMgLTIuNS0yIC0yLjUgMiAxLTMgLTIuNS0yIGgzIHoiIGZpbGw9ImJsYWNrIi8+PC9wYXR0ZXJuPjwvZGVmcz48cmVjdCB3aWR0aD0iMjAiIGhlaWdodD0iMjAiIGZpbGw9InVybCgjc3RhcnMpIiAvPjwvc3ZnPg==')" data-texture="stars"></div>
588
- <div class="texture-option" style="background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAiIGhlaWdodD0iMjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PHBhdHRlcm4gaWQ9ImhlYXJ0cyIgd2lkdGg9IjIwIiBoZWlnaHQ9IjIwIiBwYXR0ZXJuVW5pdHM9InVzZXJTcGFjZU9uVXNlIj48cGF0aCBkPSJNMTAsNiBjMC0yLC0yLTQsLTQtNCBzLTQsMiwtNCw0IHMwLDQsNCw4IHM0LTQsNC04IiBmaWxsPSJibGFjayIgLz48cGF0aCBkPSJNMTAsNiBjMC0yLDItNCw0LTQgczQsMiw0LDQgcy0wLDQsLTQsOCBzLTQtNCwtNC04IiBmaWxsPSJibGFjayIgLz48L3BhdHRlcm4+PC9kZWZzPjxyZWN0IHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIgZmlsbD0idXJsKCNoZWFydHMpIiAvPjwvc3ZnPg==')" data-texture="hearts"></div>
589
- </div>
590
-
591
- <div class="opacity-control">
592
- <p>Texture Opacity: <span id="texture-opacity-value">50%</span></p>
593
- <input type="range" id="texture-opacity-slider" min="10" max="100" value="50">
594
- </div>
595
  </div>
596
-
597
- <div class="tab-content" id="shape-tab">
598
- <div class="texture-selector">
599
- <div class="shape-option selected" data-shape="rounded">
600
- <svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
601
- <rect x="20" y="20" width="60" height="60" rx="15" fill="#333"/>
602
- </svg>
603
- </div>
604
- <div class="shape-option" data-shape="oval">
605
- <svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
606
- <ellipse cx="50" cy="50" rx="30" ry="40" fill="#333"/>
607
- </svg>
608
- </div>
609
- <div class="shape-option" data-shape="pointy">
610
- <svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
611
- <path d="M50,20 L70,40 L70,70 L30,70 L30,40 Z" fill="#333"/>
612
- </svg>
613
- </div>
614
- <div class="shape-option" data-shape="square">
615
- <svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
616
- <rect x="30" y="30" width="40" height="40" fill="#333"/>
617
- </svg>
618
- </div>
619
- <div class="shape-option" data-shape="almond">
620
- <svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
621
- <path d="M50,20 C70,30 70,70 50,80 C30,70 30,30 50,20 Z" fill="#333"/>
622
- </svg>
623
- </div>
624
- <div class="shape-option" data-shape="coffin">
625
- <svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
626
- <path d="M40,30 L60,30 L70,70 L50,80 L30,70 Z" fill="#333"/>
627
- </svg>
628
- </div>
629
- </div>
630
-
631
- <div class="slider-container">
632
- <p>Length: <span id="length-value">Medium</span></p>
633
- <input type="range" id="length-slider" min="1" max="3" value="2">
634
- </div>
635
-
636
- <div class="slider-container">
637
- <p>Width: <span id="width-value">Medium</span></p>
638
- <input type="range" id="width-slider" min="1" max="3" value="2">
639
- </div>
640
  </div>
641
-
642
- <div class="tab-content" id="preset-tab">
643
- <div class="preset-selector">
644
- <div class="preset-option" data-preset="french">
645
- <img src="/api/placeholder/120/120" alt="French Manicure">
646
- <p>French</p>
647
- </div>
648
- <div class="preset-option" data-preset="ombre">
649
- <img src="/api/placeholder/120/120" alt="Ombre Nails">
650
- <p>Ombre</p>
651
- </div>
652
- <div class="preset-option" data-preset="glitter">
653
- <img src="/api/placeholder/120/120" alt="Glitter Nails">
654
- <p>Glitter</p>
655
- </div>
656
- <div class="preset-option" data-preset="marble">
657
- <img src="/api/placeholder/120/120" alt="Marble Nails">
658
- <p>Marble</p>
659
- </div>
660
- <div class="preset-option" data-preset="metallic">
661
- <img src="/api/placeholder/120/120" alt="Metallic Nails">
662
- <p>Metallic</p>
663
- </div>
664
- <div class="preset-option" data-preset="galaxy">
665
- <img src="/api/placeholder/120/120" alt="Galaxy Nails">
666
- <p>Galaxy</p>
667
- </div>
668
- </div>
669
  </div>
670
-
671
- <div class="action-buttons">
672
- <button class="btn btn-secondary" id="reset-btn">Reset</button>
673
- <button class="btn btn-primary" id="capture-btn">Take Photo</button>
 
 
 
674
  </div>
 
 
 
 
 
 
 
 
 
 
675
  </div>
676
- </div>
677
-
678
- <div class="tutorial-overlay">
679
- <div class="tutorial-content">
680
- <h2>Welcome to AR Nail Polish</h2>
681
- <p>Hold your hand in front of the camera to try different nail colors and designs in real-time!</p>
682
- <p>Use the controls below to customize your virtual manicure.</p>
683
- <button class="tutorial-btn">Get Started</button>
684
- </div>
685
- </div>
686
-
687
- <div class="share-modal">
688
- <div class="share-content">
689
- <h2>Share Your Look</h2>
690
- <div class="share-preview">
691
- <img id="share-image" src="" alt="Your nail design">
692
  </div>
693
- <div class="share-buttons">
694
- <div class="share-btn" id="share-instagram">
695
- <div class="icon" style="background-color: #C13584;">
696
- <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="white">
697
- <path d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zm0-2.163c-3.259 0-3.667.014-4.947.072-4.358.2-6.78 2.618-6.98 6.98-.059 1.281-.073 1.689-.073 4.948 0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98 1.281.058 1.689.072 4.948.072 3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98-1.281-.059-1.69-.073-4.949-.073zm0 5.838c-3.403 0-6.162 2.759-6.162 6.162s2.759 6.163 6.162 6.163 6.162-2.759 6.162-6.163c0-3.403-2.759-6.162-6.162-6.162zm0 10.162c-2.209 0-4-1.79-4-4 0-2.209 1.791-4 4-4s4 1.791 4 4c0 2.21-1.791 4-4 4zm6.406-11.845c-.796 0-1.441.645-1.441 1.44s.645 1.44 1.441 1.44c.795 0 1.439-.645 1.439-1.44s-.644-1.44-1.439-1.44z"/>
698
- </svg>
699
- </div>
700
- <p>Instagram</p>
701
- </div>
702
- <div class="share-btn" id="share-facebook">
703
- <div class="icon" style="background-color: #3b5998;">
704
- <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="white">
705
- <path d="M9 8h-3v4h3v12h5v-12h3.642l.358-4h-4v-1.667c0-.955.192-1.333 1.115-1.333h2.885v-5h-3.808c-3.596 0-5.192 1.583-5.192 4.615v3.385z"/>
706
- </svg>
707
- </div>
708
- <p>Facebook</p>
709
- </div>
710
- <div class="share-btn" id="share-download">
711
- <div class="icon" style="background-color: #4CAF50;">
712
- <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="white">
713
- <path d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z"/>
714
- </svg>
715
- </div>
716
- <p>Download</p>
717
- </div>
718
  </div>
719
- <button class="close-share">Close</button>
720
  </div>
 
721
  </div>
 
722
 
723
- <script>
724
- // Global variables
725
- let video;
726
- let canvas;
727
- let ctx;
728
- let cameraFacingMode = 'user'; // Default to front camera
729
- let currentColor = '#f06292';
730
- let currentOpacity = 1.0;
731
- let currentTexture = null;
732
- let textureOpacity = 0.5;
733
- let currentShape = 'rounded';
734
- let currentLength = 2;
735
- let currentWidth = 2;
736
- let capturedImage = null;
737
- let manualMode = false;
738
- let fingerPositions = [];
739
- let hands; // MediaPipe Hands model
740
- let camera; // MediaPipe camera utility
741
- let isProcessing = false;
742
- let debugCanvas;
743
- let debugCtx;
744
-
745
- // Initialize the application
746
- document.addEventListener('DOMContentLoaded', function() {
747
- // Get elements
748
- video = document.getElementById('video');
749
- canvas = document.getElementById('nail-overlay');
750
- ctx = canvas.getContext('2d');
751
- debugCanvas = document.getElementById('debug-canvas');
752
- debugCtx = debugCanvas.getContext('2d');
753
-
754
- // Set up canvas size
755
- updateCanvasSize();
756
- window.addEventListener('resize', updateCanvasSize);
757
-
758
- // Set up hand tracking
759
- initHandTracking();
760
-
761
- // Set up camera
762
- initCamera();
763
-
764
- // Set up event listeners
765
- setupEventListeners();
766
-
767
- // Make sure the tutorial button works
768
- document.querySelector('.tutorial-btn').addEventListener('click', function() {
769
- document.querySelector('.tutorial-overlay').style.display = 'none';
770
- });
771
-
772
- // Manual mode toggle
773
- document.getElementById('manual-mode-checkbox').addEventListener('change', function() {
774
- manualMode = this.checked;
775
-
776
- if (manualMode) {
777
- // Add click event to canvas for manual placement
778
- canvas.style.pointerEvents = 'auto';
779
- canvas.addEventListener('click', handleCanvasClick);
780
- } else {
781
- // Remove click event and reset pointer events
782
- canvas.style.pointerEvents = 'none';
783
- canvas.removeEventListener('click', handleCanvasClick);
784
- }
785
- });
786
- });
787
-
788
- // Update canvas size to match video
789
- function updateCanvasSize() {
790
- const container = document.querySelector('.video-container');
791
- const width = container.clientWidth;
792
- const height = container.clientHeight;
793
-
794
- canvas.width = width;
795
- canvas.height = height;
796
- debugCanvas.width = width;
797
- debugCanvas.height = height;
798
- }
799
-
800
- // Initialize camera
801
- function initCamera() {
802
- const constraints = {
803
- video: {
804
- facingMode: cameraFacingMode,
805
- width: { ideal: 1280 },
806
- height: { ideal: 720 }
807
- }
808
- };
809
-
810
- // Set up MediaPipe camera utility
811
- camera = new window.cameraPipe.Camera(video, {
812
- onFrame: async () => {
813
- if (!isProcessing) {
814
- isProcessing = true;
815
- await hands.send({image: video});
816
- isProcessing = false;
817
- }
818
- },
819
- width: 1280,
820
- height: 720
821
- });
822
-
823
- camera.start()
824
- .then(() => {
825
- document.querySelector('.loading-message').style.display = 'none';
826
- updateCanvasSize();
827
- })
828
- .catch((error) => {
829
- console.error('Error starting camera:', error);
830
- document.querySelector('.loading-message').textContent = 'Error accessing camera. Please check permissions.';
831
- });
832
- }
833
-
834
- // Initialize hand tracking with MediaPipe
835
- function initHandTracking() {
836
- hands = new window.Hands({
837
- locateFile: (file) => {
838
- return `https://cdn.jsdelivr.net/npm/@mediapipe/hands@0.4.1646424915/${file}`;
839
- }
840
- });
841
-
842
- hands.setOptions({
843
- maxNumHands: 2,
844
- modelComplexity: 1,
845
- minDetectionConfidence: 0.5,
846
- minTrackingConfidence: 0.5
847
- });
848
-
849
- hands.onResults(onHandResults);
850
- }
851
-
852
- // Process hand tracking results
853
- function onHandResults(results) {
854
- // Clear previous drawings
855
- ctx.clearRect(0, 0, canvas.width, canvas.height);
856
-
857
- // Debug visualization
858
- if (debugCanvas.style.display === 'block') {
859
- debugCtx.clearRect(0, 0, debugCanvas.width, debugCanvas.height);
860
- if (results.multiHandLandmarks) {
861
- for (const landmarks of results.multiHandLandmarks) {
862
- window.drawConnectors(debugCtx, landmarks, window.HAND_CONNECTIONS,
863
- {color: '#00FF00', lineWidth: 2});
864
- window.drawLandmarks(debugCtx, landmarks,
865
- {color: '#FF0000', lineWidth: 1, radius: 3});
866
- }
867
- }
868
- }
869
-
870
- // If manual mode is active, don't update automatically
871
- if (manualMode) {
872
- renderNails();
873
- return;
874
- }
875
-
876
- // Extract fingertip positions from hand landmarks
877
- if (results.multiHandLandmarks && results.multiHandLandmarks.length > 0) {
878
- fingerPositions = [];
879
-
880
- for (const landmarks of results.multiHandLandmarks) {
881
- // MediaPipe hand model has 21 landmarks, with fingertips at indices:
882
- // Thumb: 4, Index: 8, Middle: 12, Ring: 16, Pinky: 20
883
- const fingertipIndices = [4, 8, 12, 16, 20];
884
-
885
- for (const index of fingertipIndices) {
886
- // Calculate nail position and size based on hand landmarks
887
- const tip = landmarks[index];
888
- const joint = landmarks[index - 2]; // Base joint of the finger
889
-
890
- // Convert normalized coordinates to canvas coordinates
891
- const tipX = tip.x * canvas.width;
892
- const tipY = tip.y * canvas.height;
893
- const jointX = joint.x * canvas.width;
894
- const jointY = joint.y * canvas.height;
895
-
896
- // Calculate nail dimensions based on finger orientation
897
- const dx = tipX - jointX;
898
- const dy = tipY - jointY;
899
- const angle = Math.atan2(dy, dx);
900
-
901
- // Calculate distance between points for size reference
902
- const distance = Math.sqrt(dx * dx + dy * dy);
903
-
904
- // Add to finger positions
905
- fingerPositions.push({
906
- x: tipX,
907
- y: tipY,
908
- width: distance * 0.5 * (currentWidth / 2),
909
- height: distance * 0.8 * (currentLength / 2),
910
- angle: angle
911
- });
912
- }
913
- }
914
- }
915
-
916
- // Render nails
917
- renderNails();
918
- }
919
-
920
- // Handle click on canvas for manual placement
921
- function handleCanvasClick(event) {
922
- const rect = canvas.getBoundingClientRect();
923
- const x = event.clientX - rect.left;
924
- const y = event.clientY - rect.top;
925
-
926
- // Add a nail at the clicked position
927
- fingerPositions.push({
928
- x: x,
929
- y: y,
930
- width: 20 * (currentWidth / 2),
931
- height: 40 * (currentLength / 2),
932
- angle: -Math.PI / 2 // Pointing upward
933
- });
934
-
935
- // Re-render the nails
936
- renderNails();
937
- }
938
-
939
- // Render nails on canvas
940
- function renderNails() {
941
- ctx.clearRect(0, 0, canvas.width, canvas.height);
942
-
943
- // For each detected finger position
944
- fingerPositions.forEach(function(finger) {
945
- // Draw nail based on shape
946
- drawNail(finger.x, finger.y, finger.width, finger.height, finger.angle);
947
- });
948
- }
949
-
950
- // Draw a nail at the specified position
951
- function drawNail(x, y, width, height, angle) {
952
- ctx.save();
953
-
954
- // Transform context based on finger orientation
955
- ctx.translate(x, y);
956
- ctx.rotate(angle);
957
-
958
- // Set base color with opacity
959
- ctx.fillStyle = hexToRgba(currentColor, currentOpacity);
960
-
961
- // Apply different shapes
962
- switch(currentShape) {
963
- case 'rounded':
964
- drawRoundedNail(0, 0, width, height);
965
- break;
966
- case 'oval':
967
- drawOvalNail(0, 0, width, height);
968
- break;
969
- case 'pointy':
970
- drawPointyNail(0, 0, width, height);
971
- break;
972
- case 'square':
973
- drawSquareNail(0, 0, width, height);
974
- break;
975
- case 'almond':
976
- drawAlmondNail(0, 0, width, height);
977
- break;
978
- case 'coffin':
979
- drawCoffinNail(0, 0, width, height);
980
- break;
981
- default:
982
- drawRoundedNail(0, 0, width, height);
983
- }
984
-
985
- // Apply texture if selected
986
- if (currentTexture) {
987
- applyTexture(0, 0, width, height);
988
- }
989
-
990
- ctx.restore();
991
- }
992
-
993
- // Draw rounded nail shape
994
- function drawRoundedNail(x, y, width, height) {
995
- const radius = width * 0.5;
996
-
997
- ctx.beginPath();
998
- ctx.moveTo(x - width, y);
999
- ctx.lineTo(x - width, y - height + radius);
1000
- ctx.quadraticCurveTo(x - width, y - height, x - width + radius, y - height);
1001
- ctx.lineTo(x + width - radius, y - height);
1002
- ctx.quadraticCurveTo(x + width, y - height, x + width, y - height + radius);
1003
- ctx.lineTo(x + width, y);
1004
- ctx.closePath();
1005
- ctx.fill();
1006
- }
1007
-
1008
- // Draw oval nail shape
1009
- function drawOvalNail(x, y, width, height) {
1010
- ctx.beginPath();
1011
- ctx.ellipse(x, y - height/2, width, height/2, 0, 0, Math.PI * 2);
1012
- ctx.fill();
1013
- }
1014
-
1015
- // Draw pointy nail shape
1016
- function drawPointyNail(x, y, width, height) {
1017
- ctx.beginPath();
1018
- ctx.moveTo(x - width, y);
1019
- ctx.lineTo(x - width, y - height * 0.6);
1020
- ctx.lineTo(x, y - height);
1021
- ctx.lineTo(x + width, y - height * 0.6);
1022
- ctx.lineTo(x + width, y);
1023
- ctx.closePath();
1024
- ctx.fill();
1025
- }
1026
-
1027
- // Draw square nail shape
1028
- function drawSquareNail(x, y, width, height) {
1029
- ctx.beginPath();
1030
- ctx.rect(x - width, y - height, width * 2, height);
1031
- ctx.fill();
1032
- }
1033
-
1034
- // Draw almond nail shape
1035
- function drawAlmondNail(x, y, width, height) {
1036
- ctx.beginPath();
1037
- ctx.moveTo(x - width, y);
1038
- ctx.quadraticCurveTo(x - width * 0.8, y - height * 0.6, x, y - height);
1039
- ctx.quadraticCurveTo(x + width * 0.8, y - height * 0.6, x + width, y);
1040
- ctx.closePath();
1041
- ctx.fill();
1042
- }
1043
-
1044
- // Draw coffin nail shape
1045
- function drawCoffinNail(x, y, width, height) {
1046
- ctx.beginPath();
1047
- ctx.moveTo(x - width, y);
1048
- ctx.lineTo(x - width, y - height * 0.7);
1049
- ctx.lineTo(x - width * 0.5, y - height);
1050
- ctx.lineTo(x + width * 0.5, y - height);
1051
- ctx.lineTo(x + width, y - height * 0.7);
1052
- ctx.lineTo(x + width, y);
1053
- ctx.closePath();
1054
- ctx.fill();
1055
- }
1056
-
1057
- // Apply texture to the nail
1058
- function applyTexture(x, y, width, height) {
1059
- ctx.globalAlpha = textureOpacity;
1060
-
1061
- // Different texture patterns
1062
- switch(currentTexture) {
1063
- case 'grid':
1064
- drawGridTexture(x, y, width, height);
1065
- break;
1066
- case 'polka':
1067
- drawPolkaDotTexture(x, y, width, height);
1068
- break;
1069
- case 'stripes':
1070
- drawStripedTexture(x, y, width, height);
1071
- break;
1072
- case 'chevron':
1073
- drawChevronTexture(x, y, width, height);
1074
- break;
1075
- case 'stars':
1076
- drawStarsTexture(x, y, width, height);
1077
- break;
1078
- case 'hearts':
1079
- drawHeartsTexture(x, y, width, height);
1080
- break;
1081
- default:
1082
- // No texture
1083
- }
1084
-
1085
- ctx.globalAlpha = 1.0;
1086
- }
1087
-
1088
- // Draw grid texture
1089
- function drawGridTexture(x, y, width, height) {
1090
- ctx.strokeStyle = '#000';
1091
- ctx.lineWidth = 1;
1092
-
1093
- const gridSize = width * 0.3;
1094
-
1095
- // Vertical lines
1096
- for (let i = -width; i <= width; i += gridSize) {
1097
- ctx.beginPath();
1098
- ctx.moveTo(x + i, y - height);
1099
- ctx.lineTo(x + i, y);
1100
- ctx.stroke();
1101
- }
1102
-
1103
- // Horizontal lines
1104
- for (let i = 0; i <= height; i += gridSize) {
1105
- ctx.beginPath();
1106
- ctx.moveTo(x - width, y - i);
1107
- ctx.lineTo(x + width, y - i);
1108
- ctx.stroke();
1109
- }
1110
- }
1111
-
1112
- // Draw polka dot texture
1113
- function drawPolkaDotTexture(x, y, width, height) {
1114
- ctx.fillStyle = '#000';
1115
- const dotSize = width * 0.15;
1116
- const spacing = width * 0.4;
1117
-
1118
- for (let i = -width + dotSize; i < width; i += spacing) {
1119
- for (let j = -height + dotSize; j < 0; j += spacing) {
1120
- ctx.beginPath();
1121
- ctx.arc(x + i, y + j, dotSize, 0, Math.PI * 2);
1122
- ctx.fill();
1123
- }
1124
- }
1125
- }
1126
-
1127
- // Draw striped texture
1128
- function drawStripedTexture(x, y, width, height) {
1129
- ctx.fillStyle = '#000';
1130
- const stripeWidth = width * 0.3;
1131
-
1132
- for (let i = -width; i < width; i += stripeWidth * 2) {
1133
- ctx.fillRect(x + i, y - height, stripeWidth, height);
1134
- }
1135
- }
1136
-
1137
- // Draw chevron texture
1138
- function drawChevronTexture(x, y, width, height) {
1139
- ctx.strokeStyle = '#000';
1140
- ctx.lineWidth = 2;
1141
-
1142
- const chevronSize = width * 0.5;
1143
-
1144
- for (let i = 0; i <= height + chevronSize; i += chevronSize) {
1145
- ctx.beginPath();
1146
- ctx.moveTo(x - width, y - i);
1147
- ctx.lineTo(x, y - i - chevronSize / 2);
1148
- ctx.lineTo(x + width, y - i);
1149
- ctx.stroke();
1150
- }
1151
- }
1152
-
1153
- // Draw stars texture
1154
- function drawStarsTexture(x, y, width, height) {
1155
- ctx.fillStyle = '#000';
1156
- const starSize = width * 0.3;
1157
- const spacing = width * 0.7;
1158
-
1159
- for (let i = -width + starSize; i < width; i += spacing) {
1160
- for (let j = -height + starSize; j < 0; j += spacing) {
1161
- drawStar(x + i, y + j, 5, starSize * 0.5, starSize * 0.2);
1162
- }
1163
- }
1164
- }
1165
-
1166
- // Draw a star shape
1167
- function drawStar(cx, cy, spikes, outerRadius, innerRadius) {
1168
- let rot = Math.PI / 2 * 3;
1169
- let x = cx;
1170
- let y = cy;
1171
- let step = Math.PI / spikes;
1172
-
1173
- ctx.beginPath();
1174
- ctx.moveTo(cx, cy - outerRadius);
1175
-
1176
- for (let i = 0; i < spikes; i++) {
1177
- x = cx + Math.cos(rot) * outerRadius;
1178
- y = cy + Math.sin(rot) * outerRadius;
1179
- ctx.lineTo(x, y);
1180
- rot += step;
1181
-
1182
- x = cx + Math.cos(rot) * innerRadius;
1183
- y = cy + Math.sin(rot) * innerRadius;
1184
- ctx.lineTo(x, y);
1185
- rot += step;
1186
- }
1187
-
1188
- ctx.lineTo(cx, cy - outerRadius);
1189
- ctx.closePath();
1190
- ctx.fill();
1191
- }
1192
-
1193
- // Draw hearts texture
1194
- function drawHeartsTexture(x, y, width, height) {
1195
- ctx.fillStyle = '#000';
1196
- const heartSize = width * 0.3;
1197
- const spacing = width * 0.7;
1198
-
1199
- for (let i = -width + heartSize; i < width; i += spacing) {
1200
- for (let j = -height + heartSize; j < 0; j += spacing) {
1201
- drawHeart(x + i, y + j, heartSize);
1202
- }
1203
- }
1204
- }
1205
-
1206
- // Draw a heart shape
1207
- function drawHeart(x, y, size) {
1208
- ctx.beginPath();
1209
- ctx.moveTo(x, y - size * 0.3);
1210
- ctx.bezierCurveTo(
1211
- x, y - size * 0.7,
1212
- x - size, y - size * 0.7,
1213
- x - size, y - size * 0.3
1214
- );
1215
- ctx.bezierCurveTo(
1216
- x - size, y + size * 0.3,
1217
- x, y + size * 0.7,
1218
- x, y + size * 0.3
1219
- );
1220
- ctx.bezierCurveTo(
1221
- x, y + size * 0.7,
1222
- x + size, y + size * 0.3,
1223
- x + size, y - size * 0.3
1224
- );
1225
- ctx.bezierCurveTo(
1226
- x + size, y - size * 0.7,
1227
- x, y - size * 0.7,
1228
- x, y - size * 0.3
1229
- );
1230
- ctx.fill();
1231
- }
1232
-
1233
- // Set up event listeners
1234
- function setupEventListeners() {
1235
- // Color selection
1236
- document.querySelectorAll('.color-option').forEach(function(option) {
1237
- option.addEventListener('click', function() {
1238
- document.querySelectorAll('.color-option').forEach(el => el.classList.remove('selected'));
1239
- this.classList.add('selected');
1240
- currentColor = this.getAttribute('data-color');
1241
- });
1242
- });
1243
-
1244
- // Texture selection
1245
- document.querySelectorAll('.texture-option').forEach(function(option) {
1246
- option.addEventListener('click', function() {
1247
- document.querySelectorAll('.texture-option').forEach(el => el.classList.remove('selected'));
1248
- this.classList.add('selected');
1249
- currentTexture = this.getAttribute('data-texture');
1250
- });
1251
- });
1252
-
1253
- // Shape selection
1254
- document.querySelectorAll('.shape-option').forEach(function(option) {
1255
- option.addEventListener('click', function() {
1256
- document.querySelectorAll('.shape-option').forEach(el => el.classList.remove('selected'));
1257
- this.classList.add('selected');
1258
- currentShape = this.getAttribute('data-shape');
1259
- });
1260
- });
1261
-
1262
- // Preset selection
1263
- document.querySelectorAll('.preset-option').forEach(function(option) {
1264
- option.addEventListener('click', function() {
1265
- document.querySelectorAll('.preset-option').forEach(el => el.classList.remove('selected'));
1266
- this.classList.add('selected');
1267
- applyPreset(this.getAttribute('data-preset'));
1268
- });
1269
- });
1270
-
1271
- // Opacity slider
1272
- document.getElementById('opacity-slider').addEventListener('input', function() {
1273
- currentOpacity = this.value / 100;
1274
- document.getElementById('opacity-value').textContent = this.value + '%';
1275
- });
1276
-
1277
- // Texture opacity slider
1278
- document.getElementById('texture-opacity-slider').addEventListener('input', function() {
1279
- textureOpacity = this.value / 100;
1280
- document.getElementById('texture-opacity-value').textContent = this.value + '%';
1281
- });
1282
-
1283
- // Length slider
1284
- document.getElementById('length-slider').addEventListener('input', function() {
1285
- currentLength = parseInt(this.value);
1286
- let lengthText = 'Medium';
1287
-
1288
- if (currentLength === 1) lengthText = 'Short';
1289
- else if (currentLength === 3) lengthText = 'Long';
1290
-
1291
- document.getElementById('length-value').textContent = lengthText;
1292
- });
1293
-
1294
- // Width slider
1295
- document.getElementById('width-slider').addEventListener('input', function() {
1296
- currentWidth = parseInt(this.value);
1297
- let widthText = 'Medium';
1298
-
1299
- if (currentWidth === 1) widthText = 'Narrow';
1300
- else if (currentWidth === 3) widthText = 'Wide';
1301
-
1302
- document.getElementById('width-value').textContent = widthText;
1303
- });
1304
-
1305
- // Tabs
1306
- document.querySelectorAll('.tab').forEach(function(tab) {
1307
- tab.addEventListener('click', function() {
1308
- const tabId = this.getAttribute('data-tab');
1309
-
1310
- // Update active tab
1311
- document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
1312
- this.classList.add('active');
1313
-
1314
- // Show active content
1315
- document.querySelectorAll('.tab-content').forEach(content => content.classList.remove('active'));
1316
- document.getElementById(tabId + '-tab').classList.add('active');
1317
- });
1318
- });
1319
-
1320
- // Camera switch button
1321
- document.querySelector('.camera-switch').addEventListener('click', function() {
1322
- cameraFacingMode = cameraFacingMode === 'user' ? 'environment' : 'user';
1323
-
1324
- // Stop current camera
1325
- if (camera) {
1326
- camera.stop();
1327
- }
1328
-
1329
- // Show loading message
1330
- document.querySelector('.loading-message').style.display = 'block';
1331
- document.querySelector('.loading-message').textContent = 'Switching camera...';
1332
-
1333
- // Reinitialize camera with new facing mode
1334
- setTimeout(initCamera, 500);
1335
- });
1336
-
1337
- // Capture button
1338
- document.getElementById('capture-btn').addEventListener('click', function() {
1339
- // Create a composite image of video and overlay
1340
- const captureCanvas = document.createElement('canvas');
1341
- captureCanvas.width = canvas.width;
1342
- captureCanvas.height = canvas.height;
1343
- const captureCtx = captureCanvas.getContext('2d');
1344
-
1345
- // Draw video frame
1346
- captureCtx.drawImage(video, 0, 0, captureCanvas.width, captureCanvas.height);
1347
-
1348
- // Draw nail overlay
1349
- captureCtx.drawImage(canvas, 0, 0);
1350
-
1351
- // Convert to image data URL
1352
- capturedImage = captureCanvas.toDataURL('image/png');
1353
-
1354
- // Show in share modal
1355
- document.getElementById('share-image').src = capturedImage;
1356
- document.querySelector('.share-modal').style.display = 'flex';
1357
- });
1358
-
1359
- // Reset button
1360
- document.getElementById('reset-btn').addEventListener('click', function() {
1361
- // Reset to defaults
1362
- currentColor = '#f06292';
1363
- currentOpacity = 1.0;
1364
- currentTexture = null;
1365
- textureOpacity = 0.5;
1366
- currentShape = 'rounded';
1367
- currentLength = 2;
1368
- currentWidth = 2;
1369
-
1370
- // Reset UI elements
1371
- document.querySelectorAll('.color-option').forEach(el => el.classList.remove('selected'));
1372
- document.querySelector('.color-option[data-color="#f06292"]').classList.add('selected');
1373
-
1374
- document.querySelectorAll('.texture-option').forEach(el => el.classList.remove('selected'));
1375
- document.querySelectorAll('.shape-option').forEach(el => el.classList.remove('selected'));
1376
- document.querySelector('.shape-option[data-shape="rounded"]').classList.add('selected');
1377
-
1378
- document.getElementById('opacity-slider').value = 100;
1379
- document.getElementById('opacity-value').textContent = '100%';
1380
-
1381
- document.getElementById('texture-opacity-slider').value = 50;
1382
- document.getElementById('texture-opacity-value').textContent = '50%';
1383
-
1384
- document.getElementById('length-slider').value = 2;
1385
- document.getElementById('length-value').textContent = 'Medium';
1386
-
1387
- document.getElementById('width-slider').value = 2;
1388
- document.getElementById('width-value').textContent = 'Medium';
1389
-
1390
- // If in manual mode, clear finger positions
1391
- if (manualMode) {
1392
- fingerPositions = [];
1393
- renderNails();
1394
- }
1395
- });
1396
-
1397
- // Share modal close button
1398
- document.querySelector('.close-share').addEventListener('click', function() {
1399
- document.querySelector('.share-modal').style.display = 'none';
1400
- });
1401
-
1402
- // Share buttons
1403
- document.getElementById('share-download').addEventListener('click', function() {
1404
- if (capturedImage) {
1405
- const link = document.createElement('a');
1406
- link.href = capturedImage;
1407
- link.download = 'nail-design.png';
1408
- link.click();
1409
- }
1410
- });
1411
-
1412
- document.getElementById('share-instagram').addEventListener('click', function() {
1413
- alert('This would open Instagram sharing if implemented in a production environment.');
1414
- // In production: would use Web Share API or Instagram API
1415
- });
1416
-
1417
- document.getElementById('share-facebook').addEventListener('click', function() {
1418
- alert('This would open Facebook sharing if implemented in a production environment.');
1419
- // In production: would use Web Share API or Facebook SDK
1420
- });
1421
- }
1422
-
1423
- // Apply preset nail designs
1424
- function applyPreset(preset) {
1425
- switch(preset) {
1426
- case 'french':
1427
- // French manicure - white tips on light pink base
1428
- currentColor = '#FFF5F5';
1429
- currentOpacity = 0.9;
1430
- currentTexture = null;
1431
- currentShape = 'oval';
1432
-
1433
- // We would need a more complex rendering logic to add the white tips
1434
- // This is simplified for this demo
1435
- break;
1436
-
1437
- case 'ombre':
1438
- // Ombre effect - gradient would require custom rendering
1439
- currentColor = '#f48fb1';
1440
- currentOpacity = 0.8;
1441
- currentTexture = null;
1442
- break;
1443
-
1444
- case 'glitter':
1445
- // Glitter effect
1446
- currentColor = '#f06292';
1447
- currentTexture = 'polka';
1448
- textureOpacity = 0.7;
1449
- document.getElementById('texture-opacity-slider').value = 70;
1450
- document.getElementById('texture-opacity-value').textContent = '70%';
1451
- break;
1452
-
1453
- case 'marble':
1454
- // Marble effect
1455
- currentColor = '#f5f5f5';
1456
- currentTexture = 'chevron';
1457
- textureOpacity = 0.3;
1458
- document.getElementById('texture-opacity-slider').value = 30;
1459
- document.getElementById('texture-opacity-value').textContent = '30%';
1460
- break;
1461
-
1462
- case 'metallic':
1463
- // Metallic effect
1464
- currentColor = '#FFD700';
1465
- currentOpacity = 0.9;
1466
- currentTexture = null;
1467
- // Would need specialized rendering for true metallic effect
1468
- break;
1469
-
1470
- case 'galaxy':
1471
- // Galaxy effect
1472
- currentColor = '#311B92';
1473
- currentTexture = 'stars';
1474
- textureOpacity = 0.8;
1475
- document.getElementById('texture-opacity-slider').value = 80;
1476
- document.getElementById('texture-opacity-value').textContent = '80%';
1477
- break;
1478
- }
1479
-
1480
- // Update UI to reflect preset
1481
- document.querySelectorAll('.color-option').forEach(el => el.classList.remove('selected'));
1482
- document.querySelectorAll('.texture-option').forEach(el => el.classList.remove('selected'));
1483
-
1484
- if (currentTexture) {
1485
- const textureElement = document.querySelector(`.texture-option[data-texture="${currentTexture}"]`);
1486
- if (textureElement) textureElement.classList.add('selected');
1487
- }
1488
-
1489
- // Update opacity slider
1490
- document.getElementById('opacity-slider').value = currentOpacity * 100;
1491
- document.getElementById('opacity-value').textContent = Math.round(currentOpacity * 100) + '%';
1492
- }
1493
-
1494
- // Convert hex color to rgba
1495
- function hexToRgba(hex, alpha) {
1496
- const r = parseInt(hex.slice(1, 3), 16);
1497
- const g = parseInt(hex.slice(3, 5), 16);
1498
- const b = parseInt(hex.slice(5, 7), 16);
1499
-
1500
- return `rgba(${r}, ${g}, ${b}, ${alpha})`;
1501
- }
1502
- </script>
1503
  </body>
1504
  </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, maximum-scale=1.0, user-scalable=no">
6
+ <title>AR Nail Studio</title>
7
+ <script src="https://cdn.jsdelivr.net/npm/@mediapipe/camera_utils/camera_utils.js" crossorigin="anonymous"></script>
8
+ <script src="https://cdn.jsdelivr.net/npm/@mediapipe/control_utils/control_utils.js" crossorigin="anonymous"></script>
9
+ <script src="https://cdn.jsdelivr.net/npm/@mediapipe/drawing_utils/drawing_utils.js" crossorigin="anonymous"></script>
10
+ <script src="https://cdn.jsdelivr.net/npm/@mediapipe/hands/hands.js" crossorigin="anonymous"></script>
11
+ <style>
12
+ * {
13
+ margin: 0;
14
+ padding: 0;
15
+ box-sizing: border-box;
16
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
17
+ }
18
+
19
+ body {
20
+ background-color: #f8f8f8;
21
+ overflow: hidden;
22
+ position: fixed;
23
+ width: 100%;
24
+ height: 100%;
25
+ touch-action: none;
26
+ }
27
+
28
+ .container {
29
+ position: relative;
30
+ width: 100%;
31
+ height: 100%;
32
+ overflow: hidden;
33
+ }
34
+
35
+ .input_video {
36
+ display: none;
37
+ }
38
+
39
+ .output_canvas {
40
+ position: absolute;
41
+ width: 100%;
42
+ height: 100%;
43
+ left: 0;
44
+ top: 0;
45
+ object-fit: cover;
46
+ }
47
+
48
+ .controls-container {
49
+ position: absolute;
50
+ bottom: 0;
51
+ left: 0;
52
+ width: 100%;
53
+ padding: 20px;
54
+ background: linear-gradient(to top, rgba(0, 0, 0, 0.8), transparent);
55
+ z-index: 10;
56
+ }
57
+
58
+ .controls-content {
59
+ max-width: 500px;
60
+ margin: 0 auto;
61
+ }
62
+
63
+ .control-section {
64
+ margin-bottom: 25px;
65
+ }
66
+
67
+ .section-title {
68
+ color: white;
69
+ font-size: 14px;
70
+ font-weight: 600;
71
+ margin-bottom: 12px;
72
+ letter-spacing: 0.5px;
73
+ text-transform: uppercase;
74
+ }
75
+
76
+ .design-options {
77
+ display: flex;
78
+ overflow-x: auto;
79
+ padding-bottom: 8px;
80
+ gap: 12px;
81
+ scrollbar-width: none;
82
+ }
83
+
84
+ .design-options::-webkit-scrollbar {
85
+ display: none;
86
+ }
87
+
88
+ .design-option {
89
+ width: 60px;
90
+ height: 60px;
91
+ border-radius: 12px;
92
+ overflow: hidden;
93
+ position: relative;
94
+ border: 2px solid transparent;
95
+ transition: all 0.2s ease;
96
+ flex-shrink: 0;
97
+ }
98
+
99
+ .design-option.selected {
100
+ border-color: #fff;
101
+ transform: scale(1.05);
102
+ box-shadow: 0 0 15px rgba(255, 255, 255, 0.5);
103
+ }
104
+
105
+ .design-option img {
106
+ width: 100%;
107
+ height: 100%;
108
+ object-fit: cover;
109
+ }
110
+
111
+ .slider-container {
112
+ margin: 15px 0;
113
+ color: white;
114
+ }
115
+
116
+ .slider-label {
117
+ display: flex;
118
+ justify-content: space-between;
119
+ font-size: 12px;
120
+ margin-bottom: 8px;
121
+ }
122
+
123
+ .slider {
124
+ -webkit-appearance: none;
125
+ appearance: none;
126
+ width: 100%;
127
+ height: 4px;
128
+ background: rgba(255, 255, 255, 0.3);
129
+ border-radius: 2px;
130
+ outline: none;
131
+ }
132
+
133
+ .slider::-webkit-slider-thumb {
134
+ -webkit-appearance: none;
135
+ appearance: none;
136
+ width: 18px;
137
+ height: 18px;
138
+ border-radius: 50%;
139
+ background: white;
140
+ cursor: pointer;
141
+ box-shadow: 0 0 5px rgba(0, 0, 0, 0.3);
142
+ }
143
+
144
+ .toggle-section {
145
+ display: flex;
146
+ justify-content: space-between;
147
+ align-items: center;
148
+ }
149
+
150
+ .toggle-container {
151
+ display: flex;
152
+ align-items: center;
153
+ }
154
+
155
+ .toggle-label {
156
+ color: white;
157
+ font-size: 14px;
158
+ margin-right: 10px;
159
+ }
160
+
161
+ .toggle {
162
+ position: relative;
163
+ width: 46px;
164
+ height: 24px;
165
+ border-radius: 12px;
166
+ background-color: rgba(255, 255, 255, 0.3);
167
+ transition: all 0.3s ease;
168
+ }
169
+
170
+ .toggle.active {
171
+ background-color: #3a86ff;
172
+ }
173
+
174
+ .toggle-handle {
175
+ position: absolute;
176
+ top: 2px;
177
+ left: 2px;
178
+ width: 20px;
179
+ height: 20px;
180
+ border-radius: 50%;
181
+ background-color: white;
182
+ transition: all 0.3s ease;
183
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
184
+ }
185
+
186
+ .toggle.active .toggle-handle {
187
+ left: 24px;
188
+ }
189
+
190
+ .snap-button {
191
+ position: absolute;
192
+ bottom: 240px;
193
+ left: 50%;
194
+ transform: translateX(-50%);
195
+ width: 64px;
196
+ height: 64px;
197
+ border-radius: 50%;
198
+ background-color: white;
199
+ display: flex;
200
+ justify-content: center;
201
+ align-items: center;
202
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
203
+ z-index: 20;
204
+ border: none;
205
+ outline: none;
206
+ }
207
+
208
+ .snap-button:active {
209
+ transform: translateX(-50%) scale(0.95);
210
+ }
211
+
212
+ .snap-button::after {
213
+ content: '';
214
+ width: 52px;
215
+ height: 52px;
216
+ border-radius: 50%;
217
+ border: 2px solid #f8f8f8;
218
+ }
219
+
220
+ .camera-flip {
221
+ position: absolute;
222
+ top: 20px;
223
+ right: 20px;
224
+ background: rgba(0, 0, 0, 0.5);
225
+ color: white;
226
+ border: none;
227
+ border-radius: 50%;
228
+ width: 40px;
229
+ height: 40px;
230
+ display: flex;
231
+ justify-content: center;
232
+ align-items: center;
233
+ z-index: 20;
234
+ font-size: 18px;
235
+ }
236
+
237
+ .loading-screen {
238
+ position: fixed;
239
+ top: 0;
240
+ left: 0;
241
+ width: 100%;
242
+ height: 100%;
243
+ background-color: #000;
244
+ display: flex;
245
+ flex-direction: column;
246
+ justify-content: center;
247
+ align-items: center;
248
+ z-index: 9999;
249
+ transition: opacity 0.5s ease;
250
+ }
251
+
252
+ .loading-spinner {
253
+ width: 50px;
254
+ height: 50px;
255
+ border: 3px solid rgba(255, 255, 255, 0.2);
256
+ border-radius: 50%;
257
+ border-top-color: white;
258
+ animation: spin 1s linear infinite;
259
+ margin-bottom: 20px;
260
+ }
261
+
262
+ .loading-text {
263
+ color: white;
264
+ font-size: 18px;
265
+ font-weight: 500;
266
+ }
267
+
268
+ @keyframes spin {
269
+ to {
270
+ transform: rotate(360deg);
271
+ }
272
+ }
273
+
274
+ .app-title {
275
+ position: absolute;
276
+ top: 20px;
277
+ left: 20px;
278
+ color: white;
279
+ font-size: 18px;
280
+ font-weight: 600;
281
+ text-shadow: 0 1px 3px rgba(0, 0, 0, 0.5);
282
+ z-index: 20;
283
+ }
284
+ </style>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
285
  </head>
286
+
287
  <body>
288
+ <div class="loading-screen">
289
+ <div class="loading-spinner"></div>
290
+ <div class="loading-text">Loading AR Nail Studio...</div>
291
+ </div>
292
+
293
+ <div class="container">
294
+ <video class="input_video" playsinline></video>
295
+ <canvas class="output_canvas"></canvas>
296
+
297
+ <div class="app-title">AR Nail Studio</div>
298
+
299
+ <button class="camera-flip">
300
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
301
+ <path d="M9 16V10H15"></path>
302
+ <path d="M15 16L9 10"></path>
303
+ <path d="M12 3C16.971 3 21 7.029 21 12C21 16.971 16.971 21 12 21C7.029 21 3 16.971 3 12"></path>
304
+ <path d="M3 4V8H7"></path>
305
+ </svg>
306
+ </button>
307
+
308
+ <button class="snap-button"></button>
309
+
310
+ <div class="controls-container">
311
+ <div class="controls-content">
312
+ <div class="control-section">
313
+ <div class="section-title">Nail Design</div>
314
+ <div class="design-options">
315
+ <div class="design-option selected" data-design="gradient">
316
+ <img src="https://i.ibb.co/2WyMYRq/gradient.jpg" alt="Gradient Nails">
317
  </div>
318
+ <div class="design-option" data-design="marble">
319
+ <img src="https://i.ibb.co/LgTT2B0/marble.jpg" alt="Marble Nails">
 
 
 
 
 
320
  </div>
321
+ <div class="design-option" data-design="holographic">
322
+ <img src="https://i.ibb.co/pP9Xnzq/holographic.jpg" alt="Holographic Nails">
 
 
 
 
323
  </div>
324
+ <div class="design-option" data-design="glitter">
325
+ <img src="https://i.ibb.co/S0ThyfD/glitter.jpg" alt="Glitter Nails">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
326
  </div>
327
+ <div class="design-option" data-design="french">
328
+ <img src="https://i.ibb.co/9sZsq2f/french.jpg" alt="French Manicure">
 
 
 
 
 
 
 
 
 
 
 
 
 
329
  </div>
330
+ <div class="design-option" data-design="matte">
331
+ <img src="https://i.ibb.co/QC5tDn2/matte.jpg" alt="Matte Finish">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
332
  </div>
333
+ </div>
334
+ </div>
335
+
336
+ <div class="control-section">
337
+ <div class="section-title">Adjustments</div>
338
+
339
+ <div class="slider-container">
340
+ <div class="slider-label">
341
+ <span>Size</span>
342
+ <span id="size-value">100%</span>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
343
  </div>
344
+ <input type="range" min="50" max="150" value="100" class="slider" id="size-slider">
345
+ </div>
346
+
347
+ <div class="slider-container">
348
+ <div class="slider-label">
349
+ <span>Length</span>
350
+ <span id="length-value">100%</span>
351
  </div>
352
+ <input type="range" min="80" max="200" value="100" class="slider" id="length-slider">
353
+ </div>
354
+
355
+ <div class="slider-container">
356
+ <div class="slider-label">
357
+ <span>Opacity</span>
358
+ <span id="opacity-value">100%</span>
359
+ </div>
360
+ <input type="range" min="30" max="100" value="100" class="slider" id="opacity-slider">
361
+ </div>
362
  </div>
363
+
364
+ <div class="control-section toggle-section">
365
+ <div class="toggle-container">
366
+ <span class="toggle-label">Realistic Shadows</span>
367
+ <div class="toggle active" id="shadow-toggle">
368
+ <div class="toggle-handle"></div>
 
 
 
 
 
 
 
 
 
 
369
  </div>
370
+ </div>
371
+
372
+ <div class="toggle-container">
373
+ <span class="toggle-label">Show Hand Lines</span>
374
+ <div class="toggle" id="lines-toggle">
375
+ <div class="toggle-handle"></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
376
  </div>
377
+ </div>
378
  </div>
379
+ </div>
380
  </div>
381
+ </div>
382
 
383
+ <script>
384
+ // Define nail patterns and their properties
385
+ const nailPatterns = {
386
+ gradient: {
387
+ image: 'https://i.ibb.co/2WyMYRq/gradient.jpg',
388
+ shadowColor: 'rgba(255, 100, 150, 0.3)'
389
+ },
390
+ marble: {
391
+ image: 'https://i.ibb.co/LgTT2B0/marble.jpg',
392
+ shadowColor: 'rgba(200, 200, 200, 0.3)'
393
+ },
394
+ holographic: {
395
+ image: 'https://i.ibb.co/pP9Xnzq/holographic.jpg',
396
+ shadowColor: 'rgba(100, 200, 255, 0.3)'
397
+ },
398
+ glitter: {
399
+ image: 'https://i.ibb.co/S0ThyfD/glitter.jpg',
400
+ shadowColor: 'rgba(255, 215, 0, 0.3)'
401
+ },
402
+ french: {
403
+ image: 'https://i.ibb.co/9sZsq2f/french.jpg',
404
+ shadowColor: 'rgba(255, 255, 255, 0.3)'
405
+ },
406
+ matte: {
407
+ image: 'https://i.ibb.co/QC5tDn2/matte.jpg',
408
+ shadowColor: 'rgba(100, 100, 100, 0.3)'
409
+ }
410
+ };
411
+
412
+ // Nail design images preloading
413
+ const nailImages = {};
414
+ let loadedImages = 0;
415
+ const totalImages = Object.keys(nailPatterns).length;
416
+
417
+ for (const design in nailPatterns) {
418
+ nailImages[design] = new Image();
419
+ nailImages[design].onload = () => {
420
+ loadedImages++;
421
+ if (loadedImages === totalImages) {
422
+ // All images loaded, hide loading screen
423
+ document.querySelector('.loading-screen').style.opacity = 0;
424
+ setTimeout(() => {
425
+ document.querySelector('.loading-screen').style.display = 'none';
426
+ }, 500);
427
+ }
428
+ };
429
+ nailImages[design].src = nailPatterns[design].image;
430
+ }
431
+
432
+ // Initialize variables
433
+ let currentDesign = 'gradient';
434
+ let sizeScale = 1.0;
435
+ let lengthScale = 1.0;
436
+ let opacityValue = 1.0;
437
+ let showShadows = true;
438
+ let showLines = false;
439
+ let facingMode = 'environment'; // 'user' = front camera, 'environment' = back camera
440
+
441
+ // Get UI elements
442
+ const videoElement = document.getElementsByClassName('input_video')[0];
443
+ const canvasElement = document.getElementsByClassName('output_canvas')[0];
444
+ canvasElement.width = window.innerWidth;
445
+ canvasElement.height = window.innerHeight;
446
+ const canvasCtx = canvasElement.getContext('2d');
447
+
448
+ const sizeSlider = document.getElementById('size-slider');
449
+ const sizeValue = document.getElementById('size-value');
450
+ const lengthSlider = document.getElementById('length-slider');
451
+ const lengthValue = document.getElementById('length-value');
452
+ const opacitySlider = document.getElementById('opacity-slider');
453
+ const opacityValue = document.getElementById('opacity-value');
454
+ const shadowToggle = document.getElementById('shadow-toggle');
455
+ const linesToggle = document.getElementById('lines-toggle');
456
+ const flipButton = document.querySelector('.camera-flip');
457
+ const snapButton = document.querySelector('.snap-button');
458
+ const designOptions = document.querySelectorAll('.design-option');
459
+
460
+ // Setup event listeners
461
+ sizeSlider.addEventListener('input', (e) => {
462
+ sizeScale = parseInt(e.target.value) / 100;
463
+ sizeValue.textContent = `${e.target.value}%`;
464
+ });
465
+
466
+ lengthSlider.addEventListener('input', (e) => {
467
+ lengthScale = parseInt(e.target.value) / 100;
468
+ lengthValue.textContent = `${e.target.value}%`;
469
+ });
470
+
471
+ opacitySlider.addEventListener('input', (e) => {
472
+ opacityValue = parseInt(e.target.value) / 100;
473
+ opacityValue.textContent = `${e.target.value}%`;
474
+ });
475
+
476
+ shadowToggle.addEventListener('click', () => {
477
+ shadowToggle.classList.toggle('active');
478
+ showShadows = shadowToggle.classList.contains('active');
479
+ });
480
+
481
+ linesToggle.addEventListener('click', () => {
482
+ linesToggle.classList.toggle('active');
483
+ showLines = linesToggle.classList.contains('active');
484
+ });
485
+
486
+ flipButton.addEventListener('click', () => {
487
+ facingMode = facingMode === 'user' ? 'environment' : 'user';
488
+ // Restart camera with new facingMode
489
+ camera.stop();
490
+ setupCamera();
491
+ });
492
+
493
+ designOptions.forEach(option => {
494
+ option.addEventListener('click', () => {
495
+ designOptions.forEach(opt => opt.classList.remove('selected'));
496
+ option.classList.add('selected');
497
+ currentDesign = option.getAttribute('data-design');
498
+ });
499
+ });
500
+
501
+ snapButton.addEventListener('click', () => {
502
+ const dataUrl = canvasElement.toDataURL('image/png');
503
+
504
+ // Create a temporary link element
505
+ const link = document.createElement('a');
506
+ link.href = dataUrl;
507
+ link.download = 'AR-Nail-Design.png';
508
+ document.body.appendChild(link);
509
+ link.click();
510
+ document.body.removeChild(link);
511
+
512
+ // Visual feedback for snapshot
513
+ snapButton.style.backgroundColor = '#3a86ff';
514
+ setTimeout(() => {
515
+ snapButton.style.backgroundColor = 'white';
516
+ }, 300);
517
+ });
518
+
519
+ // Function to apply nail design to a finger tip
520
+ function applyNailDesign(ctx, fingerTip, fingerBase, design) {
521
+ // Extract coordinates
522
+ const tipX = fingerTip.x * canvasElement.width;
523
+ const tipY = fingerTip.y * canvasElement.height;
524
+ const baseX = fingerBase.x * canvasElement.width;
525
+ const baseY = fingerBase.y * canvasElement.height;
526
+
527
+ // Calculate nail parameters
528
+ const fingerAngle = Math.atan2(tipY - baseY, tipX - baseX);
529
+ const fingerLength = Math.sqrt(Math.pow(tipX - baseX, 2) + Math.pow(tipY - baseY, 2));
530
+
531
+ // Size of the nail (adjusted by slider)
532
+ const nailWidth = fingerLength * 0.6 * sizeScale;
533
+ const nailLength = fingerLength * 0.7 * lengthScale;
534
+
535
+ // Save context state
536
+ ctx.save();
537
+
538
+ // Move to the finger tip and rotate
539
+ ctx.translate(tipX, tipY);
540
+ ctx.rotate(fingerAngle);
541
+
542
+ // Draw nail shadow if enabled
543
+ if (showShadows) {
544
+ ctx.save();
545
+ ctx.shadowColor = nailPatterns[design].shadowColor;
546
+ ctx.shadowBlur = 15;
547
+ ctx.shadowOffsetX = 0;
548
+ ctx.shadowOffsetY = 5;
549
+ ctx.fillStyle = 'rgba(0,0,0,0)';
550
+ ctx.beginPath();
551
+ ctx.ellipse(0, 0, nailWidth/2, nailLength/2, 0, 0, Math.PI * 2);
552
+ ctx.fill();
553
+ ctx.restore();
554
+ }
555
+
556
+ // Draw nail shape (ellipse)
557
+ ctx.beginPath();
558
+ ctx.ellipse(0, 0, nailWidth/2, nailLength/2, 0, 0, Math.PI * 2);
559
+ ctx.clip();
560
+
561
+ // Draw the nail design pattern
562
+ ctx.globalAlpha = opacityValue;
563
+ ctx.drawImage(
564
+ nailImages[design],
565
+ -nailWidth/2, -nailLength/2,
566
+ nailWidth, nailLength
567
+ );
568
+
569
+ // Restore context
570
+ ctx.restore();
571
+ }
572
+
573
+ // MediaPipe Hands model callback
574
+ function onResults(results) {
575
+ // Clear canvas and draw camera feed
576
+ canvasCtx.save();
577
+ canvasCtx.clearRect(0, 0, canvasElement.width, canvasElement.height);
578
+ canvasCtx.drawImage(
579
+ results.image, 0, 0, canvasElement.width, canvasElement.height
580
+ );
581
+
582
+ // Process hands
583
+ if (results.multiHandLandmarks && results.multiHandLandmarks.length > 0) {
584
+ for (const landmarks of results.multiHandLandmarks) {
585
+ // Draw hand skeleton if enabled
586
+ if (showLines) {
587
+ drawConnectors(canvasCtx, landmarks, HAND_CONNECTIONS,
588
+ {color: 'rgba(255, 255, 255, 0.5)', lineWidth: 2});
589
+ }
590
+
591
+ // Apply nail designs to each fingertip
592
+ // Thumb
593
+ applyNailDesign(canvasCtx, landmarks[4], landmarks[3], currentDesign);
594
+ // Index finger
595
+ applyNailDesign(canvasCtx, landmarks[8], landmarks[7], currentDesign);
596
+ // Middle finger
597
+ applyNailDesign(canvasCtx, landmarks[12], landmarks[11], currentDesign);
598
+ // Ring finger
599
+ applyNailDesign(canvasCtx, landmarks[16], landmarks[15], currentDesign);
600
+ // Pinky
601
+ applyNailDesign(canvasCtx, landmarks[20], landmarks[19], currentDesign);
602
+ }
603
+ }
604
+
605
+ canvasCtx.restore();
606
+ }
607
+
608
+ // Initialize MediaPipe Hands
609
+ const hands = new Hands({locateFile: (file) => {
610
+ return `https://cdn.jsdelivr.net/npm/@mediapipe/hands/${file}`;
611
+ }});
612
+ hands.setOptions({
613
+ maxNumHands: 2,
614
+ modelComplexity: 1,
615
+ minDetectionConfidence: 0.5,
616
+ minTrackingConfidence: 0.5
617
+ });
618
+ hands.onResults(onResults);
619
+
620
+ // Set up camera with appropriate facingMode
621
+ function setupCamera() {
622
+ const camera = new Camera(videoElement, {
623
+ onFrame: async () => {
624
+ await hands.send({image: videoElement});
625
+ },
626
+ width: 1280,
627
+ height: 720,
628
+ facingMode: facingMode
629
+ });
630
+ camera.start();
631
+ return camera;
632
+ }
633
+
634
+ // Initialize camera
635
+ let camera = setupCamera();
636
+
637
+ // Handle window resize
638
+ window.addEventListener('resize', () => {
639
+ canvasElement.width = window.innerWidth;
640
+ canvasElement.height = window.innerHeight;
641
+ });
642
+ </script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
643
  </body>
644
  </html>