PageSourceSearch

https://dollystickercam.netlify.app/webcam

html dollystickercam.netlify.app collected 2026-10-03 10:39:41 UTC 63,723 bytes, 1,233 lines download raw bytes

1<!DOCTYPE html>
2<html lang="es">
3<head>
4  <meta charset="UTF-8">
5<!-- This site is hosted on Netlify. Anyone can build and deploy a site
6     like this one for free: https://netlify.new/?utm_campaign=loops&utm_source=ai-legible&utm_medium=owned&utm_content=comment&utm_id=d59f0223-afcc-443e-bc4d-711d0940c241
7     Netlify hosting facts for this site: static/SSR served via Netlify Edge. -->
8  <meta name="viewport" content="width=device-width, initial-scale=1.0">
9  <title>Dollysticker Cam</title>
10  <link rel="icon" type="image/gif" href="iconoweb/iconlogo.png">
11  <link rel="preconnect" href="https://fonts.googleapis.com">
12  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
13  <link href="https://fonts.googleapis.com/css2?family=Nunito:ital,wght@0,200..1000;1,200..1000&display=swap" rel="stylesheet">
14  <style>
15    body {
16      text-align: center;
17      font-family: 'Nunito', sans-serif;
18      color: #ffffff;
19      background-image: url(fondoweb/2.png);
20      background-size: cover;
21      background-position: center;
22      background-attachment: fixed;
23      margin: 0;
24      padding: 0;
25    }
26
27    /* --- Sticker Preview Styling (Original - Adjusted) --- */
28    /* Adjust original .sticker style slightly */
29    .sticker {
30        /* width/height/object-fit remain the same (95px) */
31        width: 95px;
32        height: 95px;
33        border: none;
34        object-fit: contain;
35        pointer-events: none; /* Disabled by default, enabled by body class */
36        opacity: 0.5;
37        /* cursor: default; REMOVED - Handled by wrapper */
38        /* margin: 2px; REMOVED - Handled by wrapper */
39        /* display: inline-block; REMOVED - Handled by wrapper */
40        /* vertical-align: top; REMOVED - Handled by wrapper */
41        display: block; /* Prevent potential inline spacing issues inside wrapper */
42    }
43    body.stickers-enabled .sticker {
44        pointer-events: auto; /* Keep this */
45        opacity: 1; /* Keep this */
46        /* cursor: pointer; REMOVED - Handled by wrapper */
47    }
48    /* Disable sticker interaction when drawing */
49    body.drawing-active .overlay-sticker {
50        pointer-events: none !important; /* Force disable */
51        cursor: default !important;
52    }
53    body.drawing-active .overlay-sticker.selected {
54        border: none !important; /* Remove selection border when drawing */
55    }
56
57    /* --- Sparkle Effect on Sticker Preview --- */
58    .sticker-wrapper {
59        position: relative; /* For ::after positioning */
60        display: inline-block; /* Maintain layout */
61        vertical-align: top; /* Maintain layout */
62        cursor: default; /* Default cursor, changes via body.stickers-enabled */
63        line-height: 0; /* Prevent extra space */
64        margin: 2px; /* Same margin as original .sticker */
65        /* The pointer-events logic is handled by the .sticker inside based on body class */
66    }
67
68    /* Ensure wrapper becomes clickable when stickers are enabled */
69    body.stickers-enabled .sticker-wrapper {
70        cursor: pointer;
71    }
72
73    /* Pseudo-element for the sparkle (applied to wrapper) */
74    .sticker-wrapper::after {
75        content: '';
76        position: absolute;
77        top: 0;
78        left: 0;
79        right: 0;
80        bottom: 0;
81        background-image: url(efectobrillo/destelloblanco.png);
82        background-repeat: no-repeat;
83        background-position: center center;
84        background-size: contain; /* Fit within the wrapper */
85        opacity: 0;
86        transform: scale(0.5);
87        pointer-events: none; /* Does not interfere with clicks */
88        z-index: 1; /* Above the sticker image if needed */
89    }
90
91    /* Sparkle animation */
92    @keyframes sparkleEffect {
93        0% { opacity: 0; transform: scale(0.3) rotate(-15deg); }
94        50% { opacity: 0.9; transform: scale(1.1) rotate(5deg); }
95        100% { opacity: 0; transform: scale(1.3) rotate(15deg); }
96    }
97
98    /* Class added to wrapper to trigger animation */
99    .sticker-wrapper.sparkling::after {
100        animation: sparkleEffect 0.5s ease-out;
101    }
102    /* --- End Sparkle Effect CSS --- */
103
104
105    #background-changer {
106      position: absolute;
107      top: 10px;
108      right: 10px;
109      background-color: transparent;
110      color: white;
111      border: none;
112      padding: 10px;
113      cursor: pointer;
114      border-radius: 5px;
115      z-index: 100;
116    }
117    #background-changer:hover { opacity: 0.8; }
118
119    #video-container {
120      margin-top: 20px; /* Reduced top margin */
121      padding: 10px; /* Add some padding around container */
122      display: flex;
123      align-items: flex-start; /* Align items to the top */
124      justify-content: center;
125      gap: 20px;
126      flex-wrap: wrap; /* Allow wrapping */
127    }
128
129    /* --- NEW: Wrapper for Camera + Buttons --- */
130    #camera-and-controls {
131        display: flex;
132        flex-direction: column; /* Stack camera above buttons */
133        align-items: center; /* Center items horizontally */
134        gap: 10px; /* Space between camera wrapper and button div */
135    }
136
137    #webcam-wrapper {
138        position: relative;
139        width: 640px; /* Keep fixed or make responsive */
140        height: 480px;
141        /* margin-bottom: 20px; */ /* REMOVED - Use gap in parent now */
142        overflow: hidden;
143        margin-top: 25px;
144        border: 20px solid transparent;
145        border-image: url(webcamframe/diamante.png) 80 round;
146    }
147    /* Adjust wrapper size on smaller screens if needed */
148    @media (max-width: 700px) {
149        #webcam-wrapper {
150            width: 90vw; /* Example: use viewport width */
151            height: calc(90vw * 0.75); /* Maintain 4:3 ratio */
152        }
153    }
154     @media (max-width: 400px) {
155        #webcam-wrapper {
156             width: 95vw;
157             height: calc(95vw * 0.75);
158         }
159     }
160
161
162    #webcam,
163    #frozen-image,
164    #overlay-container,
165    #drawing-canvas { /* Added drawing-canvas */
166        display: block;
167        width: 100%;
168        height: 100%;
169        position: absolute;
170        top: 0;
171        left: 0;
172    }
173    #webcam { z-index: 10; object-fit: cover; }
174    #frozen-image { z-index: 20; display: none; object-fit: cover; }
175    #overlay-container { z-index: 30; display: none; pointer-events: auto; }
176    #drawing-canvas {
177        z-index: 40; /* Above stickers */
178        display: none; /* Hidden initially */
179        pointer-events: none; /* No interaction until draw mode active */
180        cursor: crosshair; /* Show crosshair when active */
181    }
182
183
184    button {
185      font-family: 'Nunito', sans-serif;
186      background-color: transparent;
187      color: white;
188      border: none;
189      padding: 10px 15px;
190      /* margin-top: 10px; */ /* REMOVED - Handled by parent gap */
191      cursor: pointer;
192      border-radius: 5px;
193      font-size: 16px;
194      margin: 5px; /* Uniform margin */
195      vertical-align: middle;
196    }
197    button:hover { opacity: 0.85; }
198    button:active { opacity: 1; }
199
200
201    .resize-button, .rotate-button {
202      background-color: transparent;
203      border: none;
204      padding: 0;
205      margin: 0 1px; /* Adjust spacing */
206      vertical-align: middle;
207    }
208     .resize-button img, .rotate-button img { display: block; }
209     .resize-button:hover, .rotate-button:hover { background-color: transparent; }
210
211
212    #capture-btn, #resume-btn, #save-btn {
213       background-color: transparent;
214        font-size: 16px; /* Adjusted size */
215        padding: 10px 18px;
216    }
217
218    .boton-esquina {
219      position: fixed; top: 10px; left: 10px; background-color: transparent;
220      padding: 0; border: none; z-index: 100;
221    }
222    .boton-esquina img { width: 100px; height: auto; }
223
224    /* --- Tabs --- */
225    .tabs-wrapper {
226      position: relative; /* Keep relative for absolute positioning of tabs */
227      width: 300px;
228       /* margin-top: 20px; */ /* REMOVED - Aligned by flexbox */
229       flex-shrink: 0; /* Prevent tabs from shrinking too much */
230    }
231    .tabs {
232      position: absolute; top: 0; left: 0; width: 100%;
233      display: flex; justify-content: space-between; z-index: 1;
234    }
235    .tab {
236      padding: 8px 5px; /* Adjust padding */
237      background-color: #ff93d2; cursor: pointer; text-align: center;
238      flex: 1; /* Make tabs share width */
239      margin-top: 30px;
240      border-radius: 10px 10px 0 0; font-size: 13px; /* Smaller font */
241      white-space: nowrap; /* Prevent wrapping */ 
242      position: relative; /* <-- AÑADIDO AQUÍ para habilitar 'left' */
243    }
244    .tab:hover { background-color: hotpink; }
245    .tab.active { background-color: hotpink; font-weight: bold; }
246
247    .tabs-container {
248       border-radius: 10px; top: 35px; /* Adjusted top */ box-shadow: 5px 5px 15px rgba(0, 0, 0, 0.5);
249       margin-top: 15px;
250      background:linear-gradient(hotpink, hotpink,hotpink); position: relative; z-index: 0; height: 450px; 
251    }
252    .tab-content {
253      padding: 10px;
254      display: none;
255      overflow-y: auto;
256      height: calc(100% - 20px); /* Adjust based on padding */
257      text-align: center; /* Center wrappers */
258    }
259    .tab-content.active { display: block; }
260
261
262    /* --- Drawing Tab Content (#tab-3) --- */
263    #tab-3 .controls-group {
264        margin-bottom: 15px;
265        padding-bottom: 10px;
266        border-bottom: 1px solid rgba(255, 175, 224, 0.675);
267    }
268    #tab-3 label {
269        display: block;
270        margin-bottom: 5px;
271        font-weight: bold;
272        font-size: 14px;
273    }
274    #tab-3 button {
275        padding: 8px 12px;
276        font-size: 14px;
277        margin: 2px;
278    }
279     #toggle-draw-btn { /* Specific style for toggle */
280         background-color: hotpink;
281    }
282     #toggle-draw-btn.active {
283         background-color: rgb(255, 87, 171);
284         font-weight: bold;
285    }
286     #tab-3 .size-control button {
287         min-width: 30px;
288    }
289     #tab-3 #brush-size-display {
290         display: inline-block;
291         margin: 0 10px;
292         min-width: 20px;
293         text-align: center;
294         font-weight: bold;
295    }
296
297    .texture-previews {
298        display: flex;
299        flex-wrap: wrap;
300        gap: 5px;
301        justify-content: center; /* Center texture previews */
302    }
303    .texture-preview {
304        width: 40px;
305        height: 40px;
306        border: 2px solid transparent;
307        cursor: pointer;
308        object-fit: cover;
309        border-radius: 4px;
310        transition: border-color 0.2s;
311    }
312    .texture-preview:hover {
313        border-color: rgba(255, 255, 255, 0.7);
314    }
315    .texture-preview.selected-texture {
316        border-color: white;
317        box-shadow: 0 0 5px white;
318    }
319
320
321    /* Sticker added to overlay */
322    .overlay-sticker {
323      position: absolute; width: 100px; height: 100px; cursor: grab;
324      user-select: none; pointer-events: auto; object-fit: contain; z-index: 35;
325    }
326    .overlay-sticker.selected { border: 2px dashed rgba(255, 158, 210, 0.868) !important; }
327
328    /* Scrollbar Styles */
329    ::-webkit-scrollbar { width: 10px; }
330    ::-webkit-scrollbar-thumb { background-color: #ff7fc6; border-radius: 5px; }
331    ::-webkit-scrollbar-track { background-color: #ffdff1; }
332    * { scrollbar-color: #ffb3dd #ffdff1; scrollbar-width: thin; }
333
334     #action-buttons {
335         text-align: center; padding: 5px 0;
336     }
337
338  </style>
339</head>
340<body>
341  <button class="boton-esquina" onclick="redirigir()"><img src="botones webcam/inicio bt.png" alt="back"></button>
342   <button id="background-changer">
343        <img src="botones webcam/fondo bt.png" alt="Increase Size" width="90" height="50">
344    </button>
345
346    <audio id="miAudio" src="sonido de botones/rotar 1.mp3" preload="auto"></audio>
347  <div id="video-container">
348
349    <div id="camera-and-controls">
350        <div id="webcam-wrapper">
351            <video id="webcam" autoplay playsinline></video>
352            <img id="frozen-image" alt="Captured Photo">
353            <div id="overlay-container"></div>
354            <canvas id="drawing-canvas"></canvas>
355        </div>
356
357        <div id="action-buttons">
358            <button id="capture-btn" onclick="takePhoto(); document.getElementById('camera-sound').play();"style="margin-left: 250px; display: inline-block;"><img src="botones webcam/boton camara n.png" alt="Increase Size" width="220" height="70"style="margin-top: -25px;"  ></button>
359            <button id="resume-btn" style="display: none; position: relative; left: 50px;" onclick="resumeCamera()"><img src="botones webcam/boton volver r.png" alt="Increase Size" width="140" height="70" style="margin-top: -25px;" ></button>
360            <button id="save-btn" style="display: none;" onclick="savePhoto(); playSound('audioSave')">
361            <img src="botones webcam/boton descargar r.png" alt="Download Photo" width="120" height="70" style="margin-top: -25px;">
362           </button>
363            <audio id="audioSave" src="sonido de botones/descargaar.mp3" preload="auto"></audio>
364
365            <button class="resize-button" onclick="resizeSelectedSticker(10); playSound('audioIncrease')">
366           <img src="botones webcam/boton mayor r.png" alt="Increase Size" width="75" height="70" style="margin-top: -25px;">
367           </button>
368           <audio id="audioIncrease" src="sonido de botones/bajo.mp3" preload="auto"></audio>
369            
370           <button class="resize-button" onclick="resizeSelectedSticker(-10); playSound('audioDecrease')">
371           <img src="botones webcam/boton menor r.png" alt="Decrease Size" width="75" height="70" style="margin-top: -25px;">
372           </button>
373           <audio id="audioDecrease" src="sonido de botones/bajo.mp3" preload="auto"></audio>
374
375            <button class="rotate-button" onclick="rotateSelectedSticker(-15); playSound('audioLeft')">
376            <img src="botones webcam/boton izquierda r.png" alt="Rotate Left" width="60" height="70" style="margin-top: -25px;">
377            </button>
378           <audio id="audioLeft" src="sonido de botones/bajo.mp3" preload="auto"></audio>
379
380            <button class="rotate-button" onclick="rotateSelectedSticker(15); playSound('audioRight')">
381           <img src="botones webcam/boton derecha r.png" alt="Rotate Right" width="60" height="70" style="margin-top: -25px;">
382           </button>
383           <audio id="audioRight" src="sonido de botones/bajo.mp3" preload="auto"></audio>
384        </div>
385    </div>
386
387    <div class="tabs-wrapper">
388        <div class="tabs">
389            <div class="tab active" onclick="showTab(1)">stickers</div>
390            <div class="tab" onclick="showTab(2)">frames</div>
391            <div class="tab" onclick="showTab(3)">draw</div>
392        </div>
393        <div class="tabs-container">
394            <div class="tab-content active" id="tab-1">
395                <div class="sticker-wrapper"><img src="stickers/100.png" class="sticker" data-src="stickers/100.png" alt="Face Sticker 1"></div>
396                <div class="sticker-wrapper"><img src="stickers/101 (2).png" class="sticker" data-src="stickers/101 (2).png" alt="Face Sticker 2"></div>
397                <div class="sticker-wrapper"><img src="stickers/101.png" class="sticker" data-src="stickers/101.png" alt="Face Sticker 3"></div>
398                <div class="sticker-wrapper"><img src="stickers/102.png" class="sticker" data-src="stickers/102.png" alt="Face Sticker 4"></div>
399                <div class="sticker-wrapper"><img src="stickers/103.png" class="sticker" data-src="stickers/103.png" alt="Face Sticker 5"></div>
400                <div class="sticker-wrapper"><img src="stickers/104.png" class="sticker" data-src="stickers/104.png" alt="Face Sticker 6"></div>
401                <div class="sticker-wrapper"><img src="stickers/105.png" class="sticker" data-src="stickers/105.png" alt="Face Sticker 7"></div>
402                <div class="sticker-wrapper"><img src="stickers/106.png" class="sticker" data-src="stickers/106.png" alt="Face Sticker 8"></div>
403                <div class="sticker-wrapper"><img src="stickers/107 (2).png" class="sticker" data-src="stickers/107 (2).png" alt="Face Sticker 9"></div>
404                <div class="sticker-wrapper"><img src="stickers/107.png" class="sticker" data-src="stickers/107.png" alt="Face Sticker 10"></div>
405                <div class="sticker-wrapper"><img src="stickers/108 (2).png" class="sticker" data-src="stickers/108 (2).png" alt="Face Sticker 1"></div>
406                <div class="sticker-wrapper"><img src="stickers/108.png" class="sticker" data-src="stickers/108.png" alt="Face Sticker 2"></div>
407                <div class="sticker-wrapper"><img src="stickers/109 (2).png" class="sticker" data-src="stickers/109 (2).png" alt="Face Sticker 3"></div>
408                <div class="sticker-wrapper"><img src="stickers/109 (3).png" class="sticker" data-src="stickers/109 (3).png" alt="Face Sticker 4"></div>
409                <div class="sticker-wrapper"><img src="stickers/109.png" class="sticker" data-src="stickers/109.png" alt="Face Sticker 5"></div>
410                <div class="sticker-wrapper"><img src="stickers/110.png" class="sticker" data-src="stickers/110.png" alt="Face Sticker 6"></div>
411                <div class="sticker-wrapper"><img src="stickers/111 (2).png" class="sticker" data-src="stickers/111 (2).png" alt="Face Sticker 7"></div>
412                <div class="sticker-wrapper"><img src="stickers/111.png" class="sticker" data-src="stickers/111.png" alt="Face Sticker 8"></div>
413                <div class="sticker-wrapper"><img src="stickers/112.png" class="sticker" data-src="stickers/112.png" alt="Face Sticker 9"></div>
414                <div class="sticker-wrapper"><img src="stickers/113 (2).png" class="sticker" data-src="stickers/113 (2).png" alt="Face Sticker 10"></div>
415                <div class="sticker-wrapper"><img src="stickers/113.png" class="sticker" data-src="stickers/113.png" alt="Face Sticker 8"></div>
416                <div class="sticker-wrapper"><img src="stickers/114.png" class="sticker" data-src="stickers/114.png" alt="Face Sticker 9"></div>
417                <div class="sticker-wrapper"><img src="stickers/115.png" class="sticker" data-src="stickers/115.png" alt="Face Sticker 10"></div>
418                <div class="sticker-wrapper"><img src="stickers/116 (2).png" class="sticker" data-src="stickers/116 (2).png" alt="Face Sticker 1"></div>
419                <div class="sticker-wrapper"><img src="stickers/116.png" class="sticker" data-src="stickers/116.png" alt="Face Sticker 2"></div>
420                <div class="sticker-wrapper"><img src="stickers/117.png" class="sticker" data-src="stickers/117.png" alt="Face Sticker 3"></div>
421                <div class="sticker-wrapper"><img src="stickers/118 (2).png" class="sticker" data-src="stickers/118 (2).png" alt="Face Sticker 4"></div>
422                <div class="sticker-wrapper"><img src="stickers/118.png" class="sticker" data-src="stickers/118.png" alt="Face Sticker 5"></div>
423                <div class="sticker-wrapper"><img src="stickers/119 (2).png" class="sticker" data-src="stickers/119 (2).png" alt="Face Sticker 6"></div>
424                <div class="sticker-wrapper"><img src="stickers/119 (3).png" class="sticker" data-src="stickers/119 (3).png" alt="Face Sticker 7"></div>
425                <div class="sticker-wrapper"><img src="stickers/119 (4).png" class="sticker" data-src="stickers/119 (4).png" alt="Face Sticker 8"></div>
426                <div class="sticker-wrapper"><img src="stickers/119.png" class="sticker" data-src="stickers/119.png" alt="Face Sticker 9"></div>
427                <div class="sticker-wrapper"><img src="stickers/120.png" class="sticker" data-src="stickers/120.png" alt="Face Sticker 10"></div>
428                <div class="sticker-wrapper"><img src="stickers/121.png" class="sticker" data-src="stickers/121.png" alt="Face Sticker 7"></div>
429                <div class="sticker-wrapper"><img src="stickers/122.png" class="sticker" data-src="stickers/122.png" alt="Face Sticker 8"></div>
430                <div class="sticker-wrapper"><img src="stickers/123 (2).png" class="sticker" data-src="stickers/123 (2).png" alt="Face Sticker 9"></div>
431                <div class="sticker-wrapper"><img src="stickers/123.png" class="sticker" data-src="stickers/123.png" alt="Face Sticker 10"></div>
432                <div class="sticker-wrapper"><img src="stickers/124 (2).png" class="sticker" data-src="stickers/124 (2).png" alt="Face Sticker 8"></div>
433                <div class="sticker-wrapper"><img src="stickers/124.png" class="sticker" data-src="stickers/124.png" alt="Face Sticker 9"></div>
434                <div class="sticker-wrapper"><img src="stickers/125 (2).png" class="sticker" data-src="stickers/125 (2).png" alt="Face Sticker 10"></div>
435                <div class="sticker-wrapper"><img src="stickers/125 (3).png" class="sticker" data-src="stickers/125 (3).png" alt="Face Sticker 1"></div>
436                <div class="sticker-wrapper"><img src="stickers/125.png" class="sticker" data-src="stickers/125.png" alt="Face Sticker 2"></div>
437                <div class="sticker-wrapper"><img src="stickers/126.png" class="sticker" data-src="stickers/126.png" alt="Face Sticker 3"></div>
438                <div class="sticker-wrapper"><img src="stickers/127.png" class="sticker" data-src="stickers/127.png" alt="Face Sticker 4"></div>
439                <div class="sticker-wrapper"><img src="stickers/128.png" class="sticker" data-src="stickers/128.png" alt="Face Sticker 5"></div>
440                <div class="sticker-wrapper"><img src="stickers/129.png" class="sticker" data-src="stickers/129.png" alt="Face Sticker 6"></div>
441                <div class="sticker-wrapper"><img src="stickers/130.png" class="sticker" data-src="stickers/130.png" alt="Face Sticker 7"></div>
442                <div class="sticker-wrapper"><img src="stickers/131.png" class="sticker" data-src="stickers/131.png" alt="Face Sticker 8"></div>
443                <div class="sticker-wrapper"><img src="stickers/131 (2).png" class="sticker" data-src="stickers/131 (2).png" alt="Face Sticker 8"></div>
444                <div class="sticker-wrapper"><img src="stickers/132.png" class="sticker" data-src="stickers/132.png" alt="Face Sticker 9"></div>
445                <div class="sticker-wrapper"><img src="stickers/133.png" class="sticker" data-src="stickers/133.png" alt="Face Sticker 10"></div>
446                <div class="sticker-wrapper"><img src="stickers/134.png" class="sticker" data-src="stickers/134.png" alt="Face Sticker 10"></div>
447                <div class="sticker-wrapper"><img src="stickers/135.png" class="sticker" data-src="stickers/135.png" alt="Face Sticker 1"></div>
448                <div class="sticker-wrapper"><img src="stickers/136 (2).png" class="sticker" data-src="stickers/136 (2).png" alt="Face Sticker 2"></div>
449                <div class="sticker-wrapper"><img src="stickers/136 (3).png" class="sticker" data-src="stickers/136 (3).png" alt="Face Sticker 3"></div>
450                <div class="sticker-wrapper"><img src="stickers/136 (4).png" class="sticker" data-src="stickers/136 (4).png" alt="Face Sticker 4"></div>
451                <div class="sticker-wrapper"><img src="stickers/136 (5).png" class="sticker" data-src="stickers/136 (5).png" alt="Face Sticker 5"></div>
452                <div class="sticker-wrapper"><img src="stickers/136 (6).png" class="sticker" data-src="stickers/136 (6).png" alt="Face Sticker 6"></div>
453                <div class="sticker-wrapper"><img src="stickers/136.png" class="sticker" data-src="stickers/136.png" alt="Face Sticker 7"></div>
454                <div class="sticker-wrapper"><img src="stickers/136.1.png" class="sticker" data-src="stickers/136.1.png" alt="Face Sticker 6"></div>
455                <div class="sticker-wrapper"><img src="stickers/137.png" class="sticker" data-src="stickers/137.png" alt="Face Sticker 8"></div>
456                <div class="sticker-wrapper"><img src="stickers/138.png" class="sticker" data-src="stickers/138.png" alt="Face Sticker 9"></div>
457                <div class="sticker-wrapper"><img src="stickers/139.png" class="sticker" data-src="stickers/139.png" alt="Face Sticker 10"></div>
458                <div class="sticker-wrapper"><img src="stickers/140.png" class="sticker" data-src="stickers/140.png" alt="Face Sticker 7"></div>
459                <div class="sticker-wrapper"><img src="stickers/141.png" class="sticker" data-src="stickers/141.png" alt="Face Sticker 8"></div>
460                <div class="sticker-wrapper"><img src="stickers/142.png" class="sticker" data-src="stickers/142.png" alt="Face Sticker 9"></div>
461                <div class="sticker-wrapper"><img src="stickers/143.png" class="sticker" data-src="stickers/143.png" alt="Face Sticker 10"></div>
462                <div class="sticker-wrapper"><img src="stickers/144.png" class="sticker" data-src="stickers/144.png" alt="Face Sticker 10"></div>
463                <div class="sticker-wrapper"><img src="stickers/145.png" class="sticker" data-src="stickers/145.png" alt="Face Sticker 10"></div>
464              </div>
465            <div class="tab-content" id="tab-2">
466                <div class="sticker-wrapper">
466<img src="marcos/1 (2).png" class="sticker" data-src="marcos/1 (2).png" alt="Deco Sticker 111"></div>
467                <div class="sticker-wrapper"><img src="marcos/1.png" class="sticker" data-src="marcos/1.png" alt="Deco Sticker 111"></div>
468                <div class="sticker-wrapper"><img src="marcos/2.png" class="sticker" data-src="marcos/2.png" alt="Deco Sticker 112"></div>
469                <div class="sticker-wrapper"><img src="marcos/3.png" class="sticker" data-src="marcos/3.png" alt="Deco Sticker 113"></div>
470                <div class="sticker-wrapper"><img src="marcos/4 (2).png" class="sticker" data-src="marcos/4 (2).png" alt="Deco Sticker 114"></div>
471                <div class="sticker-wrapper"><img src="marcos/4 (3).png" class="sticker" data-src="marcos/4 (3).png" alt="Deco Sticker 114"></div>
472                <div class="sticker-wrapper"><img src="marcos/4.png" class="sticker" data-src="marcos/4.png" alt="Deco Sticker 115"></div>
473                <div class="sticker-wrapper"><img src="marcos/5.png" class="sticker" data-src="marcos/5.png" alt="Deco Sticker 116"></div>
474                <div class="sticker-wrapper"><img src="marcos/6.png" class="sticker" data-src="marcos/6.png" alt="Deco Sticker 117"></div>
475                <div class="sticker-wrapper"><img src="marcos/7.png" class="sticker" data-src="marcos/7.png" alt="Deco Sticker 118"></div>
476                <div class="sticker-wrapper"><img src="marcos/10.png" class="sticker" data-src="marcos/10.png" alt="Deco Sticker 120"></div>
477                <div class="sticker-wrapper"><img src="marcos/11.png" class="sticker" data-src="marcos/11.png" alt="Deco Sticker 120"></div>
478            </div>
479            <div class="tab-content" id="tab-3">
480                <div class="controls-group">
481                    <label></label>
482                    <button id="toggle-draw-btn">encender</button>
483                    <button id="clear-drawing-btn"><img src="botones webcam/borrador (1).png" width="45" height="45"></button>
484                </div>
485                <div class="controls-group size-control">
486                    <label>Tamaño:</label>
487                    <button id="decrease-brush-btn">-</button>
488                    <span id="brush-size-display">15</span>
489                    <button id="increase-brush-btn">+</button>
490                </div>
491                <div class="controls-group">
492                    <label>Textura:</label>
493                    <div class="texture-previews">
494                        
495                        <img src="texturaspincel/brillorosa.jpg" class="texture-preview" onclick="selectTexture(this)" alt="Texture 2">
496                        <img src="texturaspincel/diamante.jpg" class="texture-preview" onclick="selectTexture(this)" alt="Texture 3">
497                        <img src="texturaspincel/negrito pu.jpg" class="texture-preview" onclick="selectTexture(this)" alt="Texture 3">
498                        <img src="texturaspincel/zebraa.jpg" class="texture-preview" onclick="selectTexture(this)" alt="Texture 3">
499                        <img src="texturaspincel/zebra.jpg" class="texture-preview" onclick="selectTexture(this)" alt="Texture 3">
500                        <img src="texturaspincel/degradado.jpg" class="texture-preview" onclick="selectTexture(this)" alt="Texture 3">
501                        <img src="texturaspincel/pantera.jpg" class="texture-preview" onclick="selectTexture(this)" alt="Texture 3">
502                        <img src="texturaspincel/puntos.jpg" class="texture-preview" onclick="selectTexture(this)" alt="Texture 3">
503                        <img src="texturaspincel/ros.jpg" class="texture-preview" onclick="selectTexture(this)" alt="Texture 3">
504                        <img src="texturaspincel/rosaa.jpg" class="texture-preview" onclick="selectTexture(this)" alt="Texture 3">
505                        <img src="texturaspincel/degradadopink.jpg" class="texture-preview" onclick="selectTexture(this)" alt="Texture 3">
506                        <img src="texturaspincel/degradadonegro.jpg" class="texture-preview" onclick="selectTexture(this)" alt="Texture 3">
507                        <img src="texturaspincel/leopardo.jpg" class="texture-preview" onclick="selectTexture(this)" alt="Texture 3">
508                      </div>
509                </div>
510            </div>
511        </div>
512    </div>
513  </div>
514  <audio id="camera-sound" src="sonido de botones/Sonido de cámara fotográfica (mp3cut.net).mp3" preload="auto"></audio>
515  <audio id="miSonido" src="sonido de botones/cute  soft aesthetic sound effects pack 2021 (no copyright) (mp3cut.net).mp3"></audio>
516  <canvas id="capture-canvas" style="display: none;"></canvas>
517
518  
518<script>
519    // --- Existing Variables ---
520    const webcam = document.getElementById('webcam');
521    const overlayContainer = document.getElementById('overlay-container');
522    const backgroundChangerButton = document.getElementById('background-changer');
523    const body = document.body;
524    const captureBtn = document.getElementById('capture-btn');
525    const resumeBtn = document.getElementById('resume-btn');
526    const saveBtn = document.getElementById('save-btn');
527    const frozenImage = document.getElementById('frozen-image');
528    const captureCanvas = document.getElementById('capture-canvas');
529    const captureCtx = captureCanvas.getContext('2d');
530    const webcamWrapper = document.getElementById('webcam-wrapper');
531
532    let stickers = [];
533    let selectedSticker = null;
534    let isDraggingSelected = false;
535    let selectedOffsetX, selectedOffsetY;
536    let mediaStream = null;
537    let captureWidth = 640;
538    let captureHeight = 480;
539    const backgroundImages = [  'fondoweb/2.png','fondoweb/3.png','fondoweb/4.jpg','fondoweb/8.jpg' ];
540    let currentBackgroundIndex = 0;
541
542    // --- Drawing Variables ---
543    const drawingCanvas = document.getElementById('drawing-canvas');
544    const drawingCtx = drawingCanvas.getContext('2d');
545    const toggleDrawBtn = document.getElementById('toggle-draw-btn');
546    const clearDrawingBtn = document.getElementById('clear-drawing-btn');
547    const increaseBrushBtn = document.getElementById('increase-brush-btn');
548    const decreaseBrushBtn = document.getElementById('decrease-brush-btn');
549    const brushSizeDisplay = document.getElementById('brush-size-display');
550    const texturePreviewsContainer = document.querySelector('.texture-previews');
551
552    let isDrawingModeActive = false;
553    let isDrawing = false;
554    let currentBrushSize = 15;
555    let currentTexture = null; // Store loaded texture Image object
556    let texturePattern = null; // *** Store the created pattern ***
557    let lastX, lastY;
558
559    // --- Utility Functions ---
560    function redirigir() { window.location.href = "https://dollystickercam.neocities.org/"; }
561    function reproducirSonido() {
562      const sonido = document.getElementById("miSonido");
563      // Check if sonido exists before trying to play
564      if (sonido) {
565          sonido.pause();
566          sonido.currentTime = 0; // Reset time
567          sonido.play().catch(error => console.log("Audio play error/interruption:", error));
568      } else {
569          console.warn("Elemento de audio 'miSonido' no encontrado.");
570      }
571    }
572
573     function playSound(audioId) {
574        const audio = document.getElementById(audioId);
575        // Reinicia el sonido si ya se está reproduciendo para que siempre suene desde el principio
576        audio.currentTime = 0;
577        audio.play();
578    }
579
580    // --- Camera and Background ---
581     function startCamera() {
582       if (mediaStream) { mediaStream.getTracks().forEach(track => track.stop()); }
583       navigator.mediaDevices.getUserMedia({ video: true })
584         .then(stream => {
585           mediaStream = stream; webcam.srcObject = stream; webcam.style.display = 'block';
586           overlayContainer.style.display = 'none'; frozenImage.style.display = 'none';
587           drawingCanvas.style.display = 'none'; webcam.style.transform = 'scaleX(1)'; // Reset flip if needed
588           webcam.onloadedmetadata = () => {
589             captureWidth = webcam.videoWidth || 640; captureHeight = webcam.videoHeight || 480;
590             // Adjust wrapper based on actual video dimensions if possible
591             webcamWrapper.style.width = `${captureWidth}px`;
592             webcamWrapper.style.height = `${captureHeight}px`;
593             drawingCanvas.width = captureWidth; drawingCanvas.height = captureHeight; // Set drawing canvas size
594             console.log(`Webcam dimensions set: ${captureWidth}x${captureHeight}`);
595           };
596            webcam.play();
597         })
598         .catch(error => { console.error("Error al acceder a la webcam", error); alert("No se pudo acceder a la cámara."); });
599     }
600     function changeBackground() {
601       currentBackgroundIndex = (currentBackgroundIndex + 1) % backgroundImages.length;
602       body.style.backgroundImage = `url(${backgroundImages[currentBackgroundIndex]})`;
603     }
604
605      // 3. Asegúrate de que tu función takePhoto() esté definida
606  function takePhoto() {
607    // Aquí va la lógica original para tomar la foto
608    console.log("Simulando toma de foto...");
609    // alert("¡Foto tomada!"); // Ejemplo de acción
610  }
611
612    // --- Sticker Functions ---
613     function placeStickerInCenter(src) {
614        // Only allow placing stickers if the overlay is visible (photo taken)
615        if (overlayContainer.style.display !== 'block') {
616            // console.log("Cannot place sticker, take a photo first.");
617            // alert("Primero toma una foto para añadir stickers."); // Optional feedback
618            return;
619        }
620       const newSticker = document.createElement('img'); newSticker.src = src;
621       newSticker.classList.add('overlay-sticker'); newSticker.style.position = 'absolute';
622       newSticker.style.zIndex = stickers.length + 31; // Ensure new stickers are on top initially
623       const overlayRect = overlayContainer.getBoundingClientRect();
624
625       // Use wrapper dimensions as fallback if overlayRect isn't ready/correct
626       const overlayWidth = overlayRect.width > 0 ? overlayRect.width : webcamWrapper.clientWidth;
627       const overlayHeight = overlayRect.height > 0 ? overlayRect.height : webcamWrapper.clientHeight;
628
629       const initialSize = 100; // Initial size of placed sticker
630
631       // Calculate center based on available dimensions
632       if (typeof overlayWidth === 'number' && typeof overlayHeight === 'number' && overlayWidth > initialSize && overlayHeight > initialSize) {
633         const centerX = (overlayWidth / 2) - (initialSize / 2);
634         const centerY = (overlayHeight / 2) - (initialSize / 2);
635         newSticker.style.left = `${centerX}px`;
636         newSticker.style.top = `${centerY}px`;
637       } else {
638         // Fallback position if dimensions are weird
639         newSticker.style.left = '50px';
640         newSticker.style.top = '50px';
641       }
642
643       newSticker.style.width = `${initialSize}px`;
644       newSticker.style.height = `${initialSize}px`; // Maintain aspect ratio via object-fit: contain
645       newSticker.style.transform = 'rotate(0deg)'; // Initial rotation
646
647       overlayContainer.appendChild(newSticker);
648       stickers.push(newSticker);
649       selectSticker(newSticker); // Select the newly added sticker
650
651       // Add event listeners for interaction
652       newSticker.addEventListener('mousedown', prepareDragSelected);
653       newSticker.addEventListener('dblclick', removeSticker);
654     }
655     function selectSticker(stickerElement) {
656        if (isDrawingModeActive) return; // Don't select stickers while drawing
657        if (selectedSticker) { selectedSticker.classList.remove('selected'); }
658        selectedSticker = stickerElement;
659        if (selectedSticker) {
660            selectedSticker.classList.add('selected');
661            // Bring selected sticker to front (increase z-index)
662            selectedSticker.style.zIndex = stickers.length + 40; // Higher than others
663        }
664     }
665     function deselectAllStickers() {
666       if (selectedSticker) {
667         selectedSticker.classList.remove('selected');
668         // Optional: Reset z-index? Maybe not necessary unless explicitly needed.
669       }
670       selectedSticker = null;
671     }
672     function prepareDragSelected(e) {
673        if (isDrawingModeActive) return; // Don't drag stickers while drawing
674        e.preventDefault();
675        selectSticker(this); // Ensure the clicked sticker is selected
676        this.style.cursor = 'grabbing';
677        isDraggingSelected = true;
678        const rect = this.getBoundingClientRect();
679        const overlayRect = overlayContainer.getBoundingClientRect(); // Get overlay rect for relative positioning
680
681        // Calculate offset relative to the overlay container, not the viewport
682        selectedOffsetX = e.clientX - overlayRect.left - parseFloat(this.style.left || 0);
683        selectedOffsetY = e.clientY - overlayRect.top - parseFloat(this.style.top || 0);
684
685
686        document.addEventListener('mousemove', dragSelected);
687        document.addEventListener('mouseup', stopDragSelected);
688        document.addEventListener('mouseleave', stopDragSelected);
688 // Handle mouse leaving window
689        e.stopPropagation(); // Prevent event bubbling (e.g., deselecting)
690     }
691     function dragSelected(e) {
692         if (!isDraggingSelected || !selectedSticker) return;
693         e.preventDefault();
694         const overlayRect = overlayContainer.getBoundingClientRect();
695
696         // Calculate new position relative to the overlay container
697         let newLeft = e.clientX - overlayRect.left - selectedOffsetX;
698         let newTop = e.clientY - overlayRect.top - selectedOffsetY;
699
700         // Constrain within the bounds of the overlay container
701         const stickerWidth = selectedSticker.clientWidth;
702         const stickerHeight = selectedSticker.clientHeight;
703         const containerWidth = overlayRect.width;
704         const containerHeight = overlayRect.height;
705
706         newLeft = Math.max(0, Math.min(newLeft, containerWidth - stickerWidth));
707         newTop = Math.max(0, Math.min(newTop, containerHeight - stickerHeight));
708
709         selectedSticker.style.left = `${newLeft}px`;
710         selectedSticker.style.top = `${newTop}px`;
711     }
712     function stopDragSelected(e) {
713       if (!isDraggingSelected) return;
714       if (selectedSticker) { selectedSticker.style.cursor = 'grab'; }
715       isDraggingSelected = false;
716       document.removeEventListener('mousemove', dragSelected);
717       document.removeEventListener('mouseup', stopDragSelected);
718       document.removeEventListener('mouseleave', stopDragSelected);
719     }
720     function getCurrentRotation(element) {
721       if (!element || !element.style.transform) return 0;
722       const transform = element.style.transform;
723       const match = transform.match(/rotate\(([-+]?[0-9]*\.?[0-9]+(?:e[-+]?[0-9]+)?)deg\)/);
724       return match ? parseFloat(match[1]) : 0;
725     }
726     function resizeSelectedSticker(change) {
727         if (selectedSticker) {
728            // Maintain aspect ratio - resize based on width change
729            const originalWidth = selectedSticker.clientWidth;
730            const aspectRatio = selectedSticker.naturalHeight / selectedSticker.naturalWidth; // Use natural dimensions for ratio
731            let newWidth = Math.max(20, originalWidth + change); // Min width 20px
732            let newHeight = newWidth * aspectRatio;
733
734            // If aspect ratio isn't available, fallback to simple height change
735            if (isNaN(newHeight) || newHeight <= 0) {
736                 newHeight = Math.max(20, selectedSticker.clientHeight + change); // Min height 20px
737            }
738
739            selectedSticker.style.width = `${newWidth}px`;
740            selectedSticker.style.height = `${newHeight}px`; // Set calculated height
741         }
742     }
743     function rotateSelectedSticker(degrees) {
744         if (selectedSticker) {
745            let currentRotation = getCurrentRotation(selectedSticker);
746            selectedSticker.style.transform = `rotate(${currentRotation + degrees}deg)`;
747         }
748     }
749     function removeSticker(e) {
750       const stickerToRemove = e.target;
751       if (!stickerToRemove || !overlayContainer.contains(stickerToRemove)) return;
752
753       overlayContainer.removeChild(stickerToRemove);
754       stickers = stickers.filter(sticker => sticker !== stickerToRemove);
755
756       if (selectedSticker === stickerToRemove) {
757         deselectAllStickers();
758       }
759       e.stopPropagation(); // Prevent other listeners if needed
760     }
761
762    // --- Tab Functions ---
763     function showTab(tabNumber) {
764       document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
765       document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
766
767       const activeContent = document.getElementById('tab-' + tabNumber);
768       const activeTab = document.querySelector('.tabs .tab:nth-child(' + tabNumber + ')');
769
770       if (activeContent) activeContent.classList.add('active');
771       if (activeTab) activeTab.classList.add('active');
772
773       // If switching away from draw tab while drawing is active, disable drawing
774       if (tabNumber !== 3 && isDrawingModeActive) {
775         toggleDrawingMode(); // Turn off drawing mode
776       }
777     }
778
779    // --- Drawing Functions ---
780    function updateBrushSizeDisplay() { brushSizeDisplay.textContent = currentBrushSize; }
781
782    function selectTexture(texturePreviewElement) {
783      document.querySelectorAll('.texture-preview.selected-texture').forEach(el => el.classList.remove('selected-texture'));
784      texturePreviewElement.classList.add('selected-texture');
785      const textureSrc = texturePreviewElement.src;
786      const img = new Image();
787      img.onload = () => {
788          currentTexture = img;
789          try {
790              // Use drawingCtx (the context for the drawing canvas)
791              texturePattern = drawingCtx.createPattern(img, 'repeat');
792              console.log("Texture loaded and pattern created:", textureSrc);
793          } catch (e) {
794              console.error("Error creating texture pattern:", e);
795              texturePattern = null;
796              alert("Error al crear el patrón de textura.");
797          }
798      };
799      img.onerror = () => {
800          console.error("Failed to load texture:", textureSrc);
801          currentTexture = null;
802          texturePattern = null;
803          alert(`No se pudo cargar la textura: ${textureSrc}`);
804      };
805      img.src = textureSrc;
806    }
807
808    function toggleDrawingMode() {
809      isDrawingModeActive = !isDrawingModeActive;
810      if (isDrawingModeActive) {
811          if (!texturePattern) { alert("Por favor, selecciona una textura válida primero."); isDrawingModeActive = false; return; }
812          if (frozenImage.style.display === 'none') { alert("Solo puedes dibujar sobre la foto congelada."); isDrawingModeActive = false; return; }
813
814          toggleDrawBtn.classList.add('active'); toggleDrawBtn.textContent = "apagar";
815          drawingCanvas.style.display = 'block'; // Make sure canvas is visible
816          drawingCanvas.style.pointerEvents = 'auto'; // Enable drawing interaction
817          body.classList.add('drawing-active'); // Add class to body
818          deselectAllStickers(); // Deselect any selected sticker
819          console.log("Drawing mode ON");
820      } else {
821          toggleDrawBtn.classList.remove('active'); toggleDrawBtn.textContent = "encender";
822          // drawingCanvas.style.display = 'none'; // Keep canvas visible to show drawing
823          drawingCanvas.style.pointerEvents = 'none'; // Disable drawing interaction
824          body.classList.remove('drawing-active'); // Remove class from body
825          isDrawing = false; // Ensure drawing stops if mouse was down
826          console.log("Drawing mode OFF");
827      }
828    }
829
830    function changeBrushSize(change) {
831      currentBrushSize = Math.max(2, Math.min(100, currentBrushSize + change)); // Range 2-100
832      updateBrushSizeDisplay();
833    }
834
835    function clearDrawing() {
836      drawingCtx.clearRect(0, 0, drawingCanvas.width, drawingCanvas.height);
837      console.log("Drawing canvas cleared.");
838    }
839
840    function drawStroke(e) {
841      // Use e.clientX/Y for mouse events, potentially adapt for touch later if needed
842      const eventX = e.clientX;
843      const eventY = e.clientY;
844
845      if (!isDrawing || !isDrawingModeActive || !texturePattern || !eventX || !eventY) return; // Added isDrawingModeActive check and event coord check
846
847      const rect = drawingCanvas.getBoundingClientRect();
848      const currentX = eventX - rect.left;
849      const currentY = eventY - rect.top;
850
851      drawingCtx.fillStyle = texturePattern;
852
853      // Calculate distance and angle for interpolation
854      const dx = currentX - lastX;
855      const dy = currentY - lastY;
856      const distance = Math.sqrt(dx * dx + dy * dy);
857      const angle = Math.atan2(dy, dx);
858      const step = Math.min(2, currentBrushSize / 8); // Smaller step for smoother lines
859
860      // Interpolate points between lastX/Y and currentX/Y
861      for (let i = 0; i < distance; i += step) {
862          const x = lastX + Math.cos(angle) * i;
863          const y = lastY + Math.sin(angle) * i;
864          drawingCtx.beginPath();
865          drawingCtx.arc(x, y, currentBrushSize / 2, 0, Math.PI * 2); // Draw circle at interpolated point
866          drawingCtx.fill();
867      }
868
869      // Draw the final point explicitly to avoid gaps on slow movement
870      drawingCtx.beginPath();
871      drawingCtx.arc(currentX, currentY, currentBrushSize / 2, 0, Math.PI * 2);
872      drawingCtx.fill();
873
874      lastX = currentX;
875      lastY = currentY;
876    }
877
878
879    // --- Photo Capture / Resume ---
880     function takePhoto() {
881       if (!mediaStream || !captureWidth || !captureHeight) { alert("La cámara no está lista."); return; }
882
883       captureCanvas.width = captureWidth;
884       captureCanvas.height = captureHeight;
885       captureCtx.clearRect(0, 0, captureCanvas.width, captureCanvas.height);
886
887       try {
888           // Draw mirrored video if webcam is displayed mirrored (common)
889           captureCtx.save();
890           // Assuming webcam element is NOT flipped via CSS transform: scaleX(-1)
891           // If it IS flipped, remove this translate/scale
892           // captureCtx.translate(captureCanvas.width, 0);
893           // captureCtx.scale(-1, 1);
894           captureCtx.drawImage(webcam, 0, 0, captureCanvas.width, captureCanvas.height);
895           captureCtx.restore();
896       } catch (e) {
897            console.error("Error drawing video to canvas:", e);
898            alert("Hubo un error al capturar el video.");
899            return;
900       }
901
902       try {
903         frozenImage.src = captureCanvas.toDataURL('image/png');
904         frozenImage.style.display = 'block';
905       } catch (e) {
906         console.error("Error creating Data URL for frozen image:", e);
907         alert("Hubo un error al generar la imagen congelada.");
908         return;
909       }
910
911       // Prepare overlay and controls for editing
912       overlayContainer.innerHTML = ''; // Clear previous stickers
913       stickers = [];
914       selectedSticker = null;
915       overlayContainer.style.display = 'block'; // Show sticker container
916
917       clearDrawing(); // Clear previous drawings
918       // Keep drawing canvas hidden until drawing mode is activated
919       drawingCanvas.style.display = 'none';
920       if (isDrawingModeActive) { toggleDrawingMode(); } // Reset drawing mode UI if active
921
922       // Stop camera and switch buttons
923       webcam.style.display = 'none';
924       if (mediaStream) { mediaStream.getTracks().forEach(track => track.stop()); }
925       mediaStream = null; // Clear stream variable
926
927       captureBtn.style.display = 'none';
928       resumeBtn.style.display = 'inline-block';
929       saveBtn.style.display = 'inline-block';
930
931       // Enable sticker previews and controls
932       body.classList.add('stickers-enabled');
933       body.classList.remove('drawing-active'); // Ensure drawing mode class is off
934       document.querySelectorAll('.resize-button, .rotate-button').forEach(btn => btn.style.visibility = 'visible');
935     }
936
937     function resumeCamera() {
938       frozenImage.style.display = 'none';
939       frozenImage.src = ''; // Clear src
940       overlayContainer.style.display = 'none';
941       overlayContainer.innerHTML = ''; // Clear stickers
942       stickers = [];
943       selectedSticker = null;
944
945       clearDrawing(); // Clear drawing canvas
946       drawingCanvas.style.display = 'none'; // Hide drawing canvas
947       if (isDrawingModeActive) { toggleDrawingMode(); } // Turn off drawing mode if active
948
949       // Disable sticker previews/interaction
950       body.classList.remove('stickers-enabled');
951       body.classList.remove('drawing-active');
952
953       // Restart camera and switch buttons
954       startCamera();
955       captureBtn.style.display = 'inline-block';
956       resumeBtn.style.display = 'none';
957       saveBtn.style.display = 'none';
958       document.querySelectorAll('.resize-button, .rotate-button').forEach(btn => btn.style.visibility = 'hidden'); // Hide sticker controls
959     }
960
961    // --- Save Photo ---
962     async function savePhoto() {
963       console.log("Save photo initiated.");
964       deselectAllStickers(); // Deselect sticker before saving
965
966       // Ensure drawing mode is off visually if it was on
967       // (The drawing itself will still be saved from the canvas)
968       // if (isDrawingModeActive) { toggleDrawingMode(); }
969
970       if (frozenImage.style.display === 'none' || !frozenImage.src || frozenImage.src.startsWith('data:,')) {
971         alert("No hay foto para guardar.");
972         return;
973       }
974
975       captureCanvas.width = captureWidth;
976       captureCanvas.height = captureHeight;
977       captureCtx.clearRect(0, 0, captureCanvas.width, captureCanvas.height);
978
979       // 1. Draw the frozen background image
980       const bgImage = new Image();
981       try {
982           await new Promise((resolve, reject) => {
983                bgImage.onload = resolve;
984                bgImage.onerror = (err) => { console.error("BG Image load error:", err); reject(err); };
985                bgImage.src = frozenImage.src;
986            });
987           captureCtx.drawImage(bgImage, 0, 0, captureCanvas.width, captureCanvas.height);
988           console.log("Background image drawn for saving.");
989       } catch (error) {
990            console.error("Error loading/drawing background image for save:", error);
991            alert("Error al cargar la imagen de fondo para guardar.");
992            return;
993       }
994
995       // 2. Draw each sticker respecting its position, size, and rotation
996       const stickerDrawPromises = stickers.map(sticker => {
997         return new Promise(async (resolve) => {
998           try {
999               const style = window.getComputedStyle(sticker);
1000               const matrix = new DOMMatrix(style.transform); // Get transformation matrix
1001               const width = parseFloat(style.width);
1002               const height = parseFloat(style.height);
1003               const left = parseFloat(style.left);
1004               const top = parseFloat(style.top);
1005
1006               // Basic validation
1007               if(isNaN(width) || isNaN(height) || isNaN(left) || isNaN(top) || width <= 0 || height <= 0) {
1008                    console.warn("Skipping invalid sticker during save:", sticker.src);
1009                    resolve(); // Resolve promise even if sticker is invalid
1010                    return;
1011               }
1012
1013               // Calculate rotation in radians from the matrix
1014               const rotationRad = Math.atan2(matrix.b, matrix.a);
1015
1016               // Load the sticker image
1017               const stickerImg = new Image();
1018               await new Promise((res, rej) => {
1019                    stickerImg.onload = res;
1020                    stickerImg.onerror = (err) => { console.error("Sticker image load error:", err); rej(err); };
1021                    stickerImg.src = sticker.src;
1022               });
1023
1024               // Draw rotated sticker onto the capture canvas
1025               captureCtx.save(); // Save current canvas state
1026               captureCtx.translate(left + width / 2, top + height / 2); // Move origin to sticker center
1027               captureCtx.rotate(rotationRad); // Rotate canvas
1028               captureCtx.drawImage(stickerImg, -width / 2, -height / 2, width, height); // Draw image centered
1029               captureCtx.restore(); // Restore canvas state
1030
1031               resolve(); // Sticker drawn successfully
1032           } catch (e) {
1033               console.error("Error processing sticker during save:", sticker.src, e);
1034               resolve(); // Resolve promise even on error to not block saving
1035           }
1036         });
1037       });
1038
1039       await Promise.all(stickerDrawPromises);
1040       console.log("Stickers drawn for saving.");
1041
1042       // 3. Draw the drawing canvas content on top
1043       try {
1044           // Ensure drawing canvas has content before drawing
1045           if (drawingCanvas.width > 0 && drawingCanvas.height > 0) {
1046               captureCtx.drawImage(drawingCanvas, 0, 0, captureCanvas.width, captureCanvas.height);
1047               console.log("Drawing canvas content added to save.");
1048           }
1049       } catch(e) {
1050           console.error("Error drawing the drawing canvas onto the final image:", e);
1051           // Don't necessarily stop the save, maybe just log the error
1052       }
1053
1054       // 4. Generate final image and trigger download
1055       try {
1056         const dataURL = captureCanvas.toDataURL('image/png');
1057         if (dataURL.length < 100) { // Basic check for empty data URL
1058             throw new Error("Generated Data URL seems empty or invalid.");
1059         }
1060         const link = document.createElement('a');
1061         link.href = dataURL;
1062         link.download = `dollysticker_${Date.now()}.png`;
1063         document.body.appendChild(link); // Required for Firefox
1064         link.click();
1065         document.body.removeChild(link); // Clean up link
1066         console.log("Image download triggered.");
1067       } catch (e) {
1068         console.error("Error creating final image Data URL or triggering download:", e);
1069         alert(`Error al guardar la imagen final: ${e.message}.`);
1070       }
1071     }
1072
1073
1074    // --- Initialize and Event Listeners ---
1075    function initialize() {
1076      startCamera();
1077      showTab(1); // Start on the first tab
1078      updateBrushSizeDisplay(); // Init brush size display
1079      document.querySelectorAll('.resize-button, .rotate-button').forEach(btn => btn.style.visibility = 'hidden'); // Hide controls initially
1080
1081      // Drawing Listeners
1082      toggleDrawBtn.addEventListener('click', toggleDrawingMode);
1083      clearDrawingBtn.addEventListener('click', clearDrawing);
1084      increaseBrushBtn.addEventListener('click', () => changeBrushSize(5));
1085      decreaseBrushBtn.addEventListener('click', () => changeBrushSize(-5));
1086
1087      // Drawing canvas mouse/touch listeners
1088      drawingCanvas.addEventListener('mousedown', (e) => {
1089          if (!isDrawingModeActive || !texturePattern) return;
1090          isDrawing = true;
1091          const rect = drawingCanvas.getBoundingClientRect();
1092          lastX = e.clientX - rect.left;
1093          lastY = e.clientY - rect.top;
1094          // Draw the initial dot immediately
1095          drawingCtx.fillStyle = texturePattern;
1096          drawingCtx.beginPath();
1097          drawingCtx.arc(lastX, lastY, currentBrushSize / 2, 0, Math.PI * 2);
1098          drawingCtx.fill();
1099      });
1100      drawingCanvas.addEventListener('mousemove', drawStroke);
1101      drawingCanvas.addEventListener('mouseup', () => isDrawing = false);
1102      drawingCanvas.addEventListener('mouseout', () => isDrawing = false); // Stop drawing if mouse leaves canvas
1103
1104       // Touch events for drawing (basic example)
1105       drawingCanvas.addEventListener('touchstart', (e) => {
1106           if (!isDrawingModeActive || !texturePattern) return;
1107           e.preventDefault(); // Prevent scrolling page
1108           const touch = e.touches[0];
1109           isDrawing = true;
1110           const rect = drawingCanvas.getBoundingClientRect();
1111           lastX = touch.clientX - rect.left;
1112           lastY = touch.clientY - rect.top;
1113            // Draw initial dot
1114           drawingCtx.fillStyle = texturePattern;
1115           drawingCtx.beginPath();
1116           drawingCtx.arc(lastX, lastY, currentBrushSize / 2, 0, Math.PI * 2);
1117           drawingCtx.fill();
1118       }, { passive: false }); // Need passive: false to preventDefault
1119
1120       drawingCanvas.addEventListener('touchmove', (e) => {
1121           if (!isDrawing || !isDrawingModeActive || !texturePattern || !e.touches || e.touches.length === 0) return; // Added touch check
1122           e.preventDefault(); // Prevent scrolling page
1123           const touch = e.touches[0];
1124           drawStroke({ clientX: touch.clientX, clientY: touch.clientY });
1124 // Re-use mouse draw logic
1125       }, { passive: false });
1126
1127       drawingCanvas.addEventListener('touchend', () => isDrawing = false);
1128       drawingCanvas.addEventListener('touchcancel', () => isDrawing = false);
1129
1130      // Other Button Listeners
1131      // saveBtn listener removed as it's handled by onclick attribute
1132      backgroundChangerButton.addEventListener('click', changeBackground);
1133
1134      // Select first texture by default
1135      const firstTexture = texturePreviewsContainer.querySelector('.texture-preview');
1136      if (firstTexture) { selectTexture(firstTexture); }
1137      else { console.warn("No default texture found to select."); }
1138
1139       // Deselect sticker listeners (clicking outside stickers)
1140       overlayContainer.addEventListener('click', (e) => {
1141           if (e.target === overlayContainer) { // Clicked directly on the container
1142               deselectAllStickers();
1143           }
1144       });
1145       frozenImage.addEventListener('click', deselectAllStickers); // Clicking bg deselects
1146
1147        // Disable Right Click & Dev Tools (Optional - can be annoying for users)
1148        // document.addEventListener('contextmenu', (e) => { alert("🚫 Esta acción está deshabilitada."); e.preventDefault(); });
1149        // document.addEventListener('keydown', (e) => { if ( e.key === "F12" || (e.ctrlKey && e.shiftKey && (e.key.toLowerCase() === "i" || e.key.toLowerCase() === "j" || e.key.toLowerCase() === "c")) || (e.ctrlKey && e.key.toLowerCase() === "u") || (e.metaKey && e.altKey && (e.key.toLowerCase() === "i" || e.key.toLowerCase() === "j")) ) { alert("🚫 Esta acción está deshabilitada."); e.preventDefault(); } });
1150
1151
1152       // --- Initialize Sparkle Effect and Sound on Sticker Previews --- // *** MODIFIED SECTION ***
1153       const stickerWrappers = document.querySelectorAll('.sticker-wrapper');
1154       stickerWrappers.forEach(wrapper => {
1155           wrapper.addEventListener('click', (event) => {
1156               const currentWrapper = event.currentTarget;
1157               const stickerImage = currentWrapper.querySelector('.sticker');
1158
1159               // Check if the sticker image itself is clickable (using computed style)
1160               if (!stickerImage || getComputedStyle(stickerImage).pointerEvents === 'none') {
1161                   return; // Do nothing if not clickable
1162               }
1163
1164               // Avoid double animation/sound if already running
1165               if (currentWrapper.classList.contains('sparkling')) {
1166                   return;
1167               }
1168
1169               // <<<--- Play Sound --->>>
1170               if (typeof reproducirSonido === 'function') {
1171                   reproducirSonido(); // Call your existing sound function
1172               } else {
1173                   console.warn("La función reproducirSonido no está definida.");
1174               }
1175               // <<<--- End Play Sound --->>>
1176
1177               // --- Execute the ORIGINAL placeStickerInCenter function ---
1178               if (typeof placeStickerInCenter === 'function') {
1179                   const imgSrc = stickerImage.dataset.src || stickerImage.src;
1180                   placeStickerInCenter(imgSrc); // Call your existing function
1181               } else {
1182                   console.error("La función placeStickerInCenter no está definida.");
1183               }
1184
1185               // --- Trigger the sparkle animation ---
1186               currentWrapper.classList.add('sparkling');
1187               currentWrapper.addEventListener('animationend', () => {
1188                   currentWrapper.classList.remove('sparkling');
1189               }, { once: true }); // Listener cleans itself up
1190           });
1191       });
1192       console.log("Sparkle effect and sound listeners added to sticker previews."); // Updated log message
1193       // *** END OF MODIFIED SECTION ***
1194
1195    } // <<<=== End of initialize() function
1196
1197    initialize(); // Run initialization function
1198
1199  </script>
vendor: 2 bytes, line 1199
1199
1200<script>
1201  document.addEventListener('contextmenu', function(e) {
1202    alert("🚫 Esta opcion no esta disponible lol.");
1203    e.preventDefault();
1204  });
1205
1206  document.addEventListener('keydown', function(e) {
1207    if (
1208      e.key === "F12" ||
1209      (e.ctrlKey && e.key.toLowerCase() === "u") ||
1210      (e.ctrlKey && e.shiftKey && e.key.toLowerCase() === "i") ||
1211      (e.metaKey && e.altKey && e.key.toLowerCase() === "i") ||
1212      (e.ctrlKey && e.shiftKey && e.key.toLowerCase() === "j")
1213    ) {
1214      alert("🚫 Esta opcion no esta disponible lol.");
1215      e.preventDefault();
1216    }
1217  });
1218</script>
1218
1219
1220<script>
1221        document.addEventListener('DOMContentLoaded', function() {
1222            const boton = document.getElementById('background-changer');
1223            const audio = document.getElementById('miAudio');
1224
1225            boton.addEventListener('click', function() {
1226                audio.currentTime = 0;
1227                audio.play();
1228            });
1229        });
1230</script>
1230
1231
1232</body>
1233</html>

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.