Mousco commited on
Commit
29667c1
·
verified ·
1 Parent(s): abf0bdd

Upload folder using huggingface_hub

Browse files
Files changed (1) hide show
  1. index.html +602 -19
index.html CHANGED
@@ -1,19 +1,602 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="fr">
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>SnapCam - Effets Photo</title>
7
+ <!-- Importation de la bibliothèque d'icônes Phosphor Icons -->
8
+ <script src="https://unpkg.com/@phosphor-icons/web"></script>
9
+
10
+ <style>
11
+ /* --- RESET & BASE --- */
12
+ :root {
13
+ --primary-color: #FFFC00; /* Jaune Snapchat */
14
+ --accent-color: #ffffff;
15
+ --bg-dark: #000000;
16
+ --overlay-bg: rgba(0, 0, 0, 0.6);
17
+ --glass-bg: rgba(20, 20, 20, 0.8);
18
+ --font-family: 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
19
+ --transition-speed: 0.3s;
20
+ }
21
+
22
+ * {
23
+ margin: 0;
24
+ padding: 0;
25
+ box-sizing: border-box;
26
+ -webkit-tap-highlight-color: transparent;
27
+ }
28
+
29
+ body {
30
+ font-family: var(--font-family);
31
+ background-color: var(--bg-dark);
32
+ color: var(--accent-color);
33
+ height: 100dvh; /* Dynamic Viewport Height */
34
+ width: 100vw;
35
+ overflow: hidden;
36
+ display: flex;
37
+ flex-direction: column;
38
+ position: relative;
39
+ }
40
+
41
+ /* --- HEADER --- */
42
+ header {
43
+ position: absolute;
44
+ top: 0;
45
+ left: 0;
46
+ width: 100%;
47
+ padding: 15px 20px;
48
+ display: flex;
49
+ justify-content: space-between;
50
+ align-items: center;
51
+ z-index: 100;
52
+ background: linear-gradient(to bottom, rgba(0,0,0,0.8), transparent);
53
+ pointer-events: none; /* Laisser passer les clics vers la vidéo si besoin */
54
+ }
55
+
56
+ .brand {
57
+ font-weight: 800;
58
+ font-size: 1.2rem;
59
+ letter-spacing: 1px;
60
+ pointer-events: auto;
61
+ text-shadow: 0 2px 4px rgba(0,0,0,0.5);
62
+ }
63
+
64
+ .anycoder-link {
65
+ font-size: 0.8rem;
66
+ color: rgba(255, 255, 255, 0.7);
67
+ text-decoration: none;
68
+ pointer-events: auto;
69
+ transition: color 0.2s;
70
+ }
71
+
72
+ .anycoder-link:hover {
73
+ color: var(--primary-color);
74
+ }
75
+
76
+ /* --- MAIN CAMERA VIEW --- */
77
+ main {
78
+ flex: 1;
79
+ position: relative;
80
+ display: flex;
81
+ justify-content: center;
82
+ align-items: center;
83
+ background: #111;
84
+ }
85
+
86
+ video {
87
+ width: 100%;
88
+ height: 100%;
89
+ object-fit: cover;
90
+ transform: scaleX(-1); /* Miroir par défaut (caméra frontale) */
91
+ transition: filter 0.2s ease;
92
+ }
93
+
94
+ /* Canvas caché pour la capture */
95
+ canvas {
96
+ display: none;
97
+ }
98
+
99
+ /* Effet Flash lors de la prise de photo */
100
+ #flash-overlay {
101
+ position: absolute;
102
+ top: 0;
103
+ left: 0;
104
+ width: 100%;
105
+ height: 100%;
106
+ background: white;
107
+ opacity: 0;
108
+ pointer-events: none;
109
+ z-index: 50;
110
+ transition: opacity 0.1s ease-out;
111
+ }
112
+
113
+ /* --- CONTROLS (BOTTOM) --- */
114
+ .controls-container {
115
+ position: absolute;
116
+ bottom: 0;
117
+ width: 100%;
118
+ padding: 20px;
119
+ background: linear-gradient(to top, rgba(0,0,0,0.9), transparent);
120
+ display: flex;
121
+ flex-direction: column;
122
+ gap: 20px;
123
+ z-index: 100;
124
+ padding-bottom: max(20px, env(safe-area-inset-bottom));
125
+ }
126
+
127
+ /* --- FILTER SCROLLER --- */
128
+ .filter-scroller {
129
+ display: flex;
130
+ gap: 15px;
131
+ overflow-x: auto;
132
+ padding-bottom: 10px;
133
+ scrollbar-width: none; /* Firefox */
134
+ -ms-overflow-style: none; /* IE 10+ */
135
+ }
136
+
137
+ .filter-scroller::-webkit-scrollbar {
138
+ display: none; /* Chrome/Safari */
139
+ }
140
+
141
+ .filter-item {
142
+ flex: 0 0 auto;
143
+ display: flex;
144
+ flex-direction: column;
145
+ align-items: center;
146
+ cursor: pointer;
147
+ opacity: 0.7;
148
+ transition: transform 0.2s, opacity 0.2s;
149
+ }
150
+
151
+ .filter-item.active {
152
+ opacity: 1;
153
+ transform: scale(1.1);
154
+ }
155
+
156
+ .filter-preview {
157
+ width: 60px;
158
+ height: 60px;
159
+ border-radius: 50%;
160
+ border: 2px solid transparent;
161
+ background-color: #333;
162
+ background-size: cover;
163
+ background-position: center;
164
+ margin-bottom: 5px;
165
+ overflow: hidden;
166
+ position: relative;
167
+ }
168
+
169
+ /* Un indicateur visuel pour le filtre actif */
170
+ .filter-item.active .filter-preview {
171
+ border-color: var(--primary-color);
172
+ }
173
+
174
+ .filter-name {
175
+ font-size: 0.75rem;
176
+ text-shadow: 0 1px 2px black;
177
+ }
178
+
179
+ /* --- ACTION BAR (Shutter, Switch, Gallery) --- */
180
+ .action-bar {
181
+ display: flex;
182
+ justify-content: space-between;
183
+ align-items: center;
184
+ padding: 0 10px;
185
+ }
186
+
187
+ .btn-icon {
188
+ background: none;
189
+ border: none;
190
+ color: white;
191
+ font-size: 2rem;
192
+ cursor: pointer;
193
+ padding: 10px;
194
+ border-radius: 50%;
195
+ transition: background 0.2s;
196
+ display: flex;
197
+ align-items: center;
198
+ justify-content: center;
199
+ }
200
+
201
+ .btn-icon:hover {
202
+ background: rgba(255, 255, 255, 0.1);
203
+ }
204
+
205
+ .btn-gallery {
206
+ position: relative;
207
+ }
208
+
209
+ .last-photo-thumb {
210
+ width: 40px;
211
+ height: 40px;
212
+ border-radius: 8px;
213
+ border: 2px solid white;
214
+ background-size: cover;
215
+ background-position: center;
216
+ display: block;
217
+ }
218
+
219
+ .placeholder-thumb {
220
+ width: 40px;
221
+ height: 40px;
222
+ border-radius: 8px;
223
+ border: 2px solid rgba(255,255,255,0.5);
224
+ background: rgba(255,255,255,0.1);
225
+ display: flex;
226
+ align-items: center;
227
+ justify-content: center;
228
+ color: rgba(255,255,255,0.5);
229
+ font-size: 1.2rem;
230
+ }
231
+
232
+ /* --- SHUTTER BUTTON --- */
233
+ .shutter-container {
234
+ position: relative;
235
+ width: 80px;
236
+ height: 80px;
237
+ display: flex;
238
+ justify-content: center;
239
+ align-items: center;
240
+ }
241
+
242
+ .shutter-outer {
243
+ position: absolute;
244
+ width: 100%;
245
+ height: 100%;
246
+ border-radius: 50%;
247
+ border: 4px solid white;
248
+ box-sizing: border-box;
249
+ transition: transform 0.1s;
250
+ }
251
+
252
+ .shutter-inner {
253
+ width: 65px;
254
+ height: 65px;
255
+ background-color: white;
256
+ border-radius: 50%;
257
+ cursor: pointer;
258
+ transition: all 0.2s;
259
+ box-shadow: 0 0 15px rgba(0,0,0,0.3);
260
+ }
261
+
262
+ .shutter-inner:active {
263
+ transform: scale(0.9);
264
+ background-color: #ddd;
265
+ }
266
+
267
+ .shutter-inner:active + .shutter-outer {
268
+ transform: scale(0.95);
269
+ border-color: #ddd;
270
+ }
271
+
272
+ /* --- TOAST NOTIFICATION --- */
273
+ .toast {
274
+ position: absolute;
275
+ top: 50%;
276
+ left: 50%;
277
+ transform: translate(-50%, -50%) scale(0.8);
278
+ background: rgba(0, 0, 0, 0.85);
279
+ color: white;
280
+ padding: 15px 25px;
281
+ border-radius: 30px;
282
+ font-size: 1rem;
283
+ opacity: 0;
284
+ pointer-events: none;
285
+ transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
286
+ z-index: 200;
287
+ text-align: center;
288
+ backdrop-filter: blur(5px);
289
+ border: 1px solid rgba(255,255,255,0.1);
290
+ display: flex;
291
+ flex-direction: column;
292
+ align-items: center;
293
+ gap: 8px;
294
+ }
295
+
296
+ .toast.show {
297
+ opacity: 1;
298
+ transform: translate(-50%, -50%) scale(1);
299
+ }
300
+
301
+ .toast i {
302
+ color: var(--primary-color);
303
+ font-size: 1.5rem;
304
+ }
305
+
306
+ /* --- PERMISSION ERROR STATE --- */
307
+ .error-message {
308
+ display: none;
309
+ position: absolute;
310
+ top: 0;
311
+ left: 0;
312
+ width: 100%;
313
+ height: 100%;
314
+ background: #111;
315
+ z-index: 300;
316
+ flex-direction: column;
317
+ justify-content: center;
318
+ align-items: center;
319
+ text-align: center;
320
+ padding: 20px;
321
+ }
322
+
323
+ .error-message h2 {
324
+ color: #ff4444;
325
+ margin-bottom: 10px;
326
+ }
327
+
328
+ .error-message p {
329
+ color: #ccc;
330
+ max-width: 400px;
331
+ }
332
+
333
+ </style>
334
+ </head>
335
+ <body>
336
+
337
+ <!-- Header -->
338
+ <header>
339
+ <div class="brand">SnapCam</div>
340
+ <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="anycoder-link">
341
+ Built with anycoder
342
+ </a>
343
+ </header>
344
+
345
+ <!-- Main Viewport -->
346
+ <main>
347
+ <video id="video" autoplay playsinline muted></video>
348
+ <div id="flash-overlay"></div>
349
+
350
+ <!-- Message d'erreur permissions -->
351
+ <div id="error-screen" class="error-message">
352
+ <i class="ph ph-camera-slash" style="font-size: 3rem; color: #666; margin-bottom: 1rem;"></i>
353
+ <h2>Accès Caméra Refusé</h2>
354
+ <p>Veuillez autoriser l'accès à la caméra dans votre navigateur pour utiliser cette application.</p>
355
+ </div>
356
+ </main>
357
+
358
+ <!-- Canvas pour le traitement image (caché) -->
359
+ <canvas id="canvas"></canvas>
360
+
361
+ <!-- Notification Toast -->
362
+ <div id="toast" class="toast">
363
+ <i class="ph ph-download-simple"></i>
364
+ <span>Photo enregistrée !</span>
365
+ </div>
366
+
367
+ <!-- Contrôles -->
368
+ <div class="controls-container">
369
+
370
+ <!-- Sélecteur de Filtres -->
371
+ <div class="filter-scroller" id="filter-list">
372
+ <!-- Les filtres seront générés par JS -->
373
+ </div>
374
+
375
+ <!-- Barre d'action (Shutter, Switch, Gallery) -->
376
+ <div class="action-bar">
377
+ <!-- Bouton Galerie (Miniature) -->
378
+ <button class="btn-icon btn-gallery" id="btn-gallery" aria-label="Galerie">
379
+ <div class="placeholder-thumb">
380
+ <i class="ph ph-image"></i>
381
+ </div>
382
+ </button>
383
+
384
+ <!-- Bouton Shutter -->
385
+ <div class="shutter-container">
386
+ <div class="shutter-inner" id="btn-capture"></div>
387
+ <div class="shutter-outer"></div>
388
+ </div>
389
+
390
+ <!-- Bouton Switch Caméra -->
391
+ <button class="btn-icon" id="btn-switch" aria-label="Changer de caméra">
392
+ <i class="ph ph-arrows-clockwise"></i>
393
+ </button>
394
+ </div>
395
+ </div>
396
+
397
+ <script>
398
+ /**
399
+ * Logique de l'application SnapCam
400
+ * Gère la caméra, les filtres CSS, la capture canvas et le téléchargement.
401
+ */
402
+
403
+ // --- Configuration & État ---
404
+ const state = {
405
+ stream: null,
406
+ facingMode: 'user', // 'user' (front) ou 'environment' (back)
407
+ currentFilter: 'none',
408
+ filters: [
409
+ { name: 'Normal', css: 'none', thumb: '' },
410
+ { name: 'Noir & Blanc', css: 'grayscale(100%) contrast(1.2)', thumb: 'filter: grayscale(100%);' },
411
+ { name: 'Sépia', css: 'sepia(100%)', thumb: 'filter: sepia(100%);' },
412
+ { name: 'Vintage', css: 'sepia(50%) contrast(1.2) brightness(0.9)', thumb: 'filter: sepia(50%) contrast(1.2);' },
413
+ { name: 'Froid', css: 'hue-rotate(180deg) saturate(1.5)', thumb: 'filter: hue-rotate(180deg) saturate(1.5);' },
414
+ { name: 'Chaud', css: 'sepia(30%) saturate(1.4) contrast(1.1)', thumb: 'filter: sepia(30%) saturate(1.4);' },
415
+ { name: 'Cyber', css: 'saturate(2) hue-rotate(20deg) contrast(1.2)', thumb: 'filter: saturate(2) hue-rotate(20deg);' },
416
+ { name: 'Inversé', css: 'invert(100%)', thumb: 'filter: invert(100%);' },
417
+ { name: 'Flou', css: 'blur(2px)', thumb: 'filter: blur(2px);' },
418
+ { name: 'Contraste', css: 'contrast(200%)', thumb: 'filter: contrast(200%);' }
419
+ ]
420
+ };
421
+
422
+ // --- Éléments DOM ---
423
+ const video = document.getElementById('video');
424
+ const canvas = document.getElementById('canvas');
425
+ const ctx = canvas.getContext('2d');
426
+ const filterList = document.getElementById('filter-list');
427
+ const btnCapture = document.getElementById('btn-capture');
428
+ const btnSwitch = document.getElementById('btn-switch');
429
+ const btnGallery = document.getElementById('btn-gallery');
430
+ const flashOverlay = document.getElementById('flash-overlay');
431
+ const toast = document.getElementById('toast');
432
+ const errorScreen = document.getElementById('error-screen');
433
+
434
+ // --- Initialisation ---
435
+ async function initCamera() {
436
+ try {
437
+ if (state.stream) {
438
+ state.stream.getTracks().forEach(track => track.stop());
439
+ }
440
+
441
+ const constraints = {
442
+ video: {
443
+ facingMode: state.facingMode,
444
+ width: { ideal: 1920 }, // Essaye d'avoir une bonne résolution
445
+ height: { ideal: 1080 }
446
+ },
447
+ audio: false
448
+ };
449
+
450
+ state.stream = await navigator.mediaDevices.getUserMedia(constraints);
451
+ video.srcObject = state.stream;
452
+
453
+ // Gérer le miroir (seulement pour la caméra frontale)
454
+ if (state.facingMode === 'user') {
455
+ video.style.transform = 'scaleX(-1)';
456
+ } else {
457
+ video.style.transform = 'scaleX(1)';
458
+ }
459
+
460
+ errorScreen.style.display = 'none';
461
+
462
+ } catch (err) {
463
+ console.error("Erreur caméra:", err);
464
+ errorScreen.style.display = 'flex';
465
+ }
466
+ }
467
+
468
+ // --- Génération des Filtres UI ---
469
+ function renderFilters() {
470
+ filterList.innerHTML = '';
471
+
472
+ state.filters.forEach((filter, index) => {
473
+ const item = document.createElement('div');
474
+ item.className = `filter-item ${filter.css === state.currentFilter ? 'active' : ''}`;
475
+
476
+ // Création de la miniature (placeholder coloré ou icon)
477
+ const preview = document.createElement('div');
478
+ preview.className = 'filter-preview';
479
+ // On simule le filtre sur un fond dégradé pour la preview
480
+ preview.style.background = 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)';
481
+ // On applique le style CSS du filtre sur l'élément preview
482
+ if (filter.thumb) {
483
+ preview.style.cssText += filter.thumb;
484
+ }
485
+
486
+ const name = document.createElement('span');
487
+ name.className = 'filter-name';
488
+ name.innerText = filter.name;
489
+
490
+ item.appendChild(preview);
491
+ item.appendChild(name);
492
+
493
+ // Clic pour changer le filtre
494
+ item.addEventListener('click', () => {
495
+ // Mise à jour UI
496
+ document.querySelectorAll('.filter-item').forEach(el => el.classList.remove('active'));
497
+ item.classList.add('active');
498
+
499
+ // Mise à jour État & Vidéo
500
+ state.currentFilter = filter.css;
501
+ video.style.filter = state.currentFilter;
502
+ });
503
+
504
+ filterList.appendChild(item);
505
+ });
506
+ }
507
+
508
+ // --- Prise de Photo ---
509
+ function takePhoto() {
510
+ if (!state.stream) return;
511
+
512
+ // 1. Effet Flash
513
+ flashOverlay.style.opacity = 1;
514
+ setTimeout(() => { flashOverlay.style.opacity = 0; }, 100);
515
+
516
+ // 2. Configuration Canvas
517
+ // On utilise les dimensions réelles de la vidéo
518
+ const width = video.videoWidth;
519
+ const height = video.videoHeight;
520
+
521
+ canvas.width = width;
522
+ canvas.height = height;
523
+
524
+ // 3. Dessin
525
+ // Si on est en mode "selfie" (front), on doit flipper le canvas horizontalement pour que le texte soit lisible
526
+ // car la vidéo est flippée par CSS pour l'utilisateur, mais le stream raw n'est pas flippé.
527
+ if (state.facingMode === 'user') {
528
+ ctx.translate(width, 0);
529
+ ctx.scale(-1, 1);
530
+ }
531
+
532
+ // Appliquer le filtre sur le contexte
533
+ if (state.currentFilter !== 'none') {
534
+ ctx.filter = state.currentFilter;
535
+ }
536
+
537
+ ctx.drawImage(video, 0, 0, width, height);
538
+
539
+ // Reset transform pour éviter les bugs prochains dessins
540
+ ctx.setTransform(1, 0, 0, 1, 0, 0);
541
+ ctx.filter = 'none';
542
+
543
+ // 4. Export & Téléchargement
544
+ const dataURL = canvas.toDataURL('image/jpeg', 0.95); // Qualité 95%
545
+
546
+ // Création d'un lien temporaire
547
+ const link = document.createElement('a');
548
+ const timestamp = new Date().getTime();
549
+ link.download = `snapcam_${timestamp}.jpg`;
550
+ link.href = dataURL;
551
+ document.body.appendChild(link);
552
+ link.click();
553
+ document.body.removeChild(link);
554
+
555
+ // 5. Mise à jour Miniature Galerie
556
+ updateGalleryThumbnail(dataURL);
557
+
558
+ // 6. Feedback Toast
559
+ showToast();
560
+ }
561
+
562
+ // --- Mise à jour Miniature ---
563
+ function updateGalleryThumbnail(dataUrl) {
564
+ // Supprimer l'icône placeholder si elle existe
565
+ if (btnGallery.querySelector('.placeholder-thumb')) {
566
+ btnGallery.innerHTML = '';
567
+ }
568
+
569
+ // Créer l'image
570
+ const img = document.createElement('div');
571
+ img.className = 'last-photo-thumb';
572
+ img.style.backgroundImage = `url(${dataUrl})`;
573
+ btnGallery.appendChild(img);
574
+ }
575
+
576
+ // --- Afficher Toast ---
577
+ function showToast() {
578
+ toast.classList.add('show');
579
+ setTimeout(() => {
580
+ toast.classList.remove('show');
581
+ }, 2500);
582
+ }
583
+
584
+ // --- Changement de Caméra ---
585
+ function switchCamera() {
586
+ state.facingMode = state.facingMode === 'user' ? 'environment' : 'user';
587
+ initCamera();
588
+ }
589
+
590
+ // --- Écouteurs d'événements ---
591
+ btnCapture.addEventListener('click', takePhoto);
592
+ btnSwitch.addEventListener('click', switchCamera);
593
+
594
+ // Démarrage
595
+ window.addEventListener('DOMContentLoaded', () => {
596
+ renderFilters();
597
+ initCamera();
598
+ });
599
+
600
+ </script>
601
+ </body>
602
+ </html>