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.