Greasy Fork is available in English.
Создавайте собственные стикеры в WhatsApp Web.
- // ==UserScript==// @name WhatsApp Sticker Creator with Custom Maker Enhanced// @version 1.2// @name:af Persoonlike Stickertjies// @description:af Skep persoonlike stickertjies in WhatsApp Web.// @name:ar ملصقات مخصصة// @description:ar إنشاء ملصقات مخصصة في WhatsApp Web.// @name:az Fərdi stikerlər// @description:az WhatsApp Web-də fərdi stikerlər yaradın.// @name:bg Персонализирани стикери// @description:bg Създаване на персонализирани стикери в WhatsApp Web.// @name:bn কাস্টম স্টিকার// @description:bn WhatsApp Web-এ কাস্টম স্টিকার তৈরি করুন।// @name:bs Prilagođene naljepnice// @description:bs Kreirajte prilagođene naljepnice u WhatsApp Webu.// @name:ca Gomets personalitzats// @description:ca Crea gomets personalitzats a WhatsApp Web.// @name:cs Vlastní nálepky// @description:cs Vytvářejte vlastní nálepky ve WhatsApp Webu.// @name:cy Stickeriaid addasedig// @description:cy Creu stickeriaid addasedig yn WhatsApp Web.// @name:da Brugerdefinerede stickers// @description:da Opret brugerdefinerede stickers i WhatsApp Web.// @name:de Benutzerdefinierte Aufkleber// @description:de Erstellen Sie benutzerdefinierte Aufkleber in WhatsApp Web.// @name:el Προσαρμοσμένα αυτοκόλλητα// @description:el Δημιουργήστε προσαρμοσμένα αυτοκόλλητα στο WhatsApp Web.// @name:en Custom Stickers// @description:en Create custom stickers in WhatsApp Web.// @name:eo Propraj glumarkoj// @description:eo Kreu proprajn glumarkojn en WhatsApp Web.// @name:es Stickers personalizados// @description:es Crear stickers personalizados en WhatsApp Web.// @name:et Kohandatud kleepsud// @description:et Looge WhatsApp Web-is kohandatud kleepsud.// @name:eu Pertsonalizatutako itsaskiak// @description:eu Sortu pertsonalizatutako itsaskiak WhatsApp Web-en.// @name:fa برچسبهای سفارشی// @description:fa ایجاد برچسبهای سفارشی در WhatsApp Web.// @name:fi Mukautetut tarrat// @description:fi Luo mukautettuja tarranauhoja WhatsApp Webiin.// @name:fr Autocollants personnalisés// @description:fr Créer des autocollants personnalisés dans WhatsApp Web.// @name:gl Adhesivos personalizados// @description:gl Crea adhesivos personalizados en WhatsApp Web.// @name:gu કસ્ટમ સ્ટિકર્સ// @description:gu WhatsApp Web માં કસ્ટમ સ્ટિકર્સ બનાવો.// @name:he מדבקות מותאמות אישית// @description:he צור מדבקות מותאמות אישית ב-WhatsApp Web.// @name:hi कस्टम स्टिकर// @description:hi WhatsApp Web में कस्टम स्टिकर बनाएं।// @name:hr Prilagođene naljepnice// @description:hr Stvorite prilagođene naljepnice u WhatsApp Webu.// @name:hu Egyéni matricák// @description:hu Hozzon létre egyéni matricákat a WhatsApp Webben.// @name:id Stiker kustom// @description:id Buat stiker kustom di WhatsApp Web.// @name:it Sticker personalizzati// @description:it Crea sticker personalizzati su WhatsApp Web.// @name:ja カスタムステッカー// @description:ja WhatsApp Webでカスタムステッカーを作成します。// @name:ka მორგებული სტიკერები// @description:ka შექმენით მორგებული სტიკერები WhatsApp Web-ში.// @name:kk Таңбалар// @description:kk WhatsApp Web-де тұтынушыға сәйкес таңбалар жасаңыз.// @name:km ស្លាកតាមតម្រូវការ// @description:km បង្កើតស្លាកតាមតម្រូវការនៅលើ WhatsApp Web។// @name:kn ಅನುಗುಣವಾದ ಸ್ಟಿಕರ್ಗಳು// @description:kn WhatsApp Web ನಲ್ಲಿ ಅನುಗುಣವಾದ ಸ್ಟಿಕರ್ಗಳನ್ನು ರಚಿಸಿ.// @name:ko 사용자 정의 스티커// @description:ko WhatsApp 웹에서 사용자 정의 스티커를 만듭니다.// @name:ku Stikerên xwerû// @description:ku Di WhatsApp Web de stikerên xwerû biafirîne.// @name:ky Көнүлгө ылайыктуу стикерлер// @description:ky WhatsApp Web'de кардардын көңүлүнө ылайыктуу стикерлерди түзгүлө.// @name:lt Pasirinktini lipdukai// @description:lt Sukurkite pasirinktinius lipdukus „WhatsApp Web“.// @name:lv Pielāgotas uzlīmes// @description:lv Izveidojiet pielāgotas uzlīmes WhatsApp tīmeklī.// @name:mk Прилагодени стикери// @description:mk Креирајте прилагодени стикери во WhatsApp Web.// @name:ml ആവശ്യമനുസരിച്ച് സ്റ്റിക്കർ// @description:ml WhatsApp വെബിൽ ആവശ്യമനുസരിച്ച് സ്റ്റിക്കർ സൃഷ്ടിക്കുക.// @name:mn Өөрийн хүссэн шошго// @description:mn WhatsApp Web дээр өөрийн хүссэн шошго үүсгэх.// @name:mr कस्टम स्टिकर// @description:mr WhatsApp Web मध्ये कस्टम स्टिकर तयार करा.// @name:ms Pelekat tersuai// @description:ms Cipta pelekat tersuai di WhatsApp Web.// @name:my စိတ်ကြိုက်နှိပ်ပုံများ// @description:my WhatsApp Web တွင်စိတ်ကြိုက်သတ်မှတ်ထားသော နှိပ်ပုံများဖန်တီးပါ။// @name:nb Egne klistremerker// @description:nb Lag egne klistremerker i WhatsApp Web.// @name:ne अनुकूलित स्टिकरहरू// @description:ne WhatsApp वेबमा अनुकूलित स्टिकरहरू सिर्जना गर्नुहोस्।// @name:nl Aangepaste stickers// @description:nl Maak aangepaste stickers in WhatsApp Web.// @name:nn Tilpassa klistremerke// @description:nn Lag tilpassa klistremerke i WhatsApp Web.// @name:no Egne klistremerker// @description:no Lag egne klistremerker i WhatsApp Web.// @name:pa ਕਸਟਮ ਸਟਿੱਕਰ// @description:pa WhatsApp ਵੈਬ ਵਿੱਚ ਕਸਟਮ ਸਟਿੱਕਰ ਬਣਾਓ।// @name:pl Niestandardowe naklejki// @description:pl Twórz niestandardowe naklejki w WhatsApp Web.// @name:pt Adesivos personalizados// @description:pt Criar adesivos personalizados no WhatsApp Web.// @name:ro Autocolante personalizate// @description:ro Creați autocolante personalizate în WhatsApp Web.// @name:ru Стикеры// @description:ru Создавайте собственные стикеры в WhatsApp Web.// @name:si විශේෂිත සටිකර// @description:si WhatsApp Web හි විශේෂිත සටිකර සාදන්න.// @name:sk Vlastné nálepky// @description:sk Vytvorte vlastné nálepky v službe WhatsApp Web.// @name:sl Prilagojene nalepke// @description:sl Ustvarite prilagojene nalepke v WhatsApp Spletu.// @name:sq Ngjitës të personalizuar// @description:sq Krijoni ngjitës të personalizuar në WhatsApp Web.// @name:sr Прилагодљиве налепнице// @description:sr Направите прилагодљиве налепнице у ВхатсАпп Вебу.// @name:sv Anpassade klistermärken// @description:sv Skapa anpassade klistermärken i WhatsApp Web.// @name:sw Lebo maalum// @description:sw Tengeneza lebo maalum katika WhatsApp Web.// @name:ta தனிப்பயனாக அட்டைகள்// @description:ta WhatsApp வலைதளத்தில் தனிப்பயனாக அட்டைகள் உருவாக்கவும்.// @name:te అనుకూలిత స్టికర్లు// @description:te WhatsApp వెబ్లో అనుకూలిత స్టికర్లు సృష్టించండి.// @name:th สติกเกอร์แบบกำหนดเอง// @description:th สร้างสติกเกอร์แบบกำหนดเองใน WhatsApp Web// @name:tr Özel etiketler// @description:tr WhatsApp Web'de özel etiketler oluşturun.// @name:uk Власні наклейки// @description:uk Створюйте власні наклейки в WhatsApp Web.// @name:ur کسٹم اسٹکر// @description:ur WhatsApp ویب میں کسٹم اسٹکر بنائیں۔// @name:uz Maxsus stikerlar// @description:uz WhatsApp Web-da maxsus stikerlar yarating.// @name:vi Nhãn dán tùy chỉnh// @description:vi Tạo nhãn dán tùy chỉnh trong WhatsApp Web.// @name:zh 自定义贴纸// @description:zh 在WhatsApp Web中创建自定义贴纸。// @name:zh-CN 自定义贴纸// @description:zh-CN 在WhatsApp Web中创建自定义贴纸。// @name:zh-TW 自訂貼紙// @description:zh-TW 在WhatsApp Web中建立自訂貼紙。// @author DeveloperMDCM// @match https://web.whatsapp.com/// @icon https://static-00.iconduck.com/assets.00/whatsapp-icon-1020x####-iykox85t.png// @grant GM_addStyle// @run-at document-end// @compatible chrome// @compatible firefox// @compatible opera// @compatible safari// @compatible edge// @license MIT// @namespace https://github.com/DeveloperMDCM/// @homepage https://github.com/DeveloperMDCM/// @description Create custom stickers in WhatsApp Web.// ==/UserScript==(function () {'use strict';console.log('Script en ejecución by: DeveloperMDCM');const HEADER_STYLE = 'color: #F00; font-size: 24px; font-family: sans-serif;';const MESSAGE_STYLE = 'color: #00aaff; font-size: 16px; font-family: sans-serif;';const CODE_STYLE = 'font-size: 14px; font-family: monospace;';console.log('%cStiker Maker for Whatsapp Web\n' +'%cRun %c(v1.0)\n' +'By: DeveloperMDCM.',HEADER_STYLE,CODE_STYLE,MESSAGE_STYLE);// Variables globales para rotación y hoverlet isRotating = false;let initialRotateAngle = 0;let initialElementRotation = 0;let hoveredElement = null;let currentMousePos = { x: 0, y: 0 };const colorsText = ["#000000", "#ff0000", "#00ff00", "#0000ff", "#ffff00", "#00ffff", "#ff00ff", "#ffffff", "#ff000000"];GM_addStyle(`/* Panel principal */#stickerPanel {position: fixed;top: 0;right: 0;width: 580px;height: auto;max-height: 90vh;overflow-y: auto;background-color: #111b21;border: 1px solid #202c33;padding: 10px;z-index: 10000;box-shadow: 0 2px 8px rgba(0,0,0,0.2);font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;}/* Pestañas */.tabsContainer {display: flex;justify-content: space-around;margin-bottom: 10px;}.tabsContainer button {flex: 1;padding: 8px;border: none;cursor: pointer;background-color: #005c4b;color: #fff;font-weight: bold;}.tabsContainer button:first-child { margin-right: 5px; }.tabsContainer button:last-child { margin-left: 5px; }/* Sección Clásica */.dropZone {border: 2px dashed #ccc;padding: 20px;text-align: center;margin-bottom: 10px;cursor: pointer;background-color: #111b21;}input[type="file"] { display: none; }#createSticker, #createCustomSticker {width: 100%;padding: 8px;margin-top: 5px;background-color: #005c4b;border: none;color: #fff;font-weight: bold;border-radius: 3px;}#status, #customStatus {font-size: 12px;color: #555;text-align: center;margin-top: 5px;}#previewCanvas { display: none; }/* Sección Personalizada */#customSection { display: none; }/* Toolbar y menús emergentes */#customToolbar {display: flex;flex-wrap: wrap;gap: 5px;margin-bottom: 5px;align-items: center;}#customToolbar button {padding: 5px 8px;cursor: pointer;border: none;background-color: #005c4b;color: #fff;border-radius: 3px;}#customToolbar select { padding: 4px; }/* Panel de opciones del lápiz y de formas */#pencilOptionsPanel, #shapeOptionsPanel {display: none;margin: 5px 0;padding: 5px;border: 1px solid #ddd;background-color: #005c4b;font-size: 12px;border-radius: 3px;}/* Botones de color y tamaño */.colorButton, .sizeButton {width: 15px;height: 15px;border-radius: 50%;border: 2px solid #ccc;display: inline-block;margin: 2px;cursor: pointer;}.sizeButton[data-size="2"] { width: 8px; height: 8px; }.sizeButton[data-size="4"] { width: 12px; height: 12px; }.sizeButton[data-size="6"] { width: 16px; height: 16px; }.sizeButton[data-size="8"] { width: 20px; height: 20px; }#pencilColorContainer, #pencilSizeContainer { display: inline-block; vertical-align: middle; }/* Panel para formas */#shapeOptionsPanel button {margin-right: 5px;padding: 3px 6px;border: none;color: #fff;border-radius: 3px;cursor: pointer;}/* Área de canvas con fondo ajedrezado */.canvasContainer {border: 2px dashed #ccc;width: 100%;height: 60vh;max-height: 60vh;margin: auto;position: relative;}#customCanvas {width: 100%;height: 100%;background-size: 20px 20px;background-image:linear-gradient(45deg, #ccc 25%, transparent 25%),linear-gradient(-45deg, #ccc 25%, transparent 25%),linear-gradient(45deg, transparent 75%, #ccc 75%),linear-gradient(-45deg, #fff 75%, #ccc 75%);background-size: 20px 20px;background-position: 0 0, 0 10px, 10px -10px, -10px 0px;display: block;}/* Botón flotante */#openStickerPanel {position: fixed;bottom: 20px;right: 20px;padding: 10px 15px;background-color: #005c4b;color: #fff;border: none;border-radius: 5px;cursor: pointer;z-index: 10000;box-shadow: 0 2px 8px rgba(0,0,0,0.2);}/* Panel de edición de texto */#textEditorPanel {margin-top: 10px;padding: 5px;display: none;border-radius: 3px;}#textEditorPanel label { margin: 3px 5px; }/* Panel de emojis */#emojiContainer {position: fixed;top: 0;right: 600px;width: auto;height: 400px;background-color: #111b21;border: 1px solid #ddd;box-shadow: 0 4px 12px rgba(0,0,0,0.2);z-index: 10000;display: none;flex-direction: column;border-radius: 3px;}#emojiCategoryContainer {display: flex;justify-content: space-around;padding: 5px;}#emojiCategoryContainer button {background-color: #005c4b;color: #fff;border: none;padding: 5px;cursor: pointer;flex: 1;margin: 0 2px;border-radius: 3px;}#emojiContent {overflow-y: auto;height: 350px;padding: 10px 0 30px 10px;display: grid;background-color: black;grid-template-columns: repeat(6, 1fr);gap: 5px;}#textFontSelect {width: auto;appearance: auto;}#textFontSelect:not(:invalid) {color: #fff;}.textEditorContent {display: flex;flex-direction: column;gap: 6px;}`);// =========================// Espera a que la página se cargue// =========================window.addEventListener("load", () => { setTimeout(initStickerTool, 3000); });const emojis = {faces_emotion: [{ "emoji": "😀" }, { "emoji": "😁" }, { "emoji": "😂" }, { "emoji": "🤣" },{ "emoji": "😃" }, { "emoji": "😄" }, { "emoji": "😅" }, { "emoji": "😆" },{ "emoji": "😉" }, { "emoji": "😊" }, { "emoji": "😋" }, { "emoji": "😎" },{ "emoji": "😍" }, { "emoji": "😘" }, { "emoji": "🥰" }, { "emoji": "😗" },{ "emoji": "😙" }, { "emoji": "🥲" }, { "emoji": "😚" }, { "emoji": "☺️" },{ "emoji": "🙂" }, { "emoji": "🤗" }, { "emoji": "🤩" }, { "emoji": "🤔" },{ "emoji": "🫡" }, { "emoji": "🤨" }, { "emoji": "😐" }, { "emoji": "😑" },{ "emoji": "😶" }, { "emoji": "🫥" }, { "emoji": "😶🌫️" }, { "emoji": "🙄" },{ "emoji": "😏" }, { "emoji": "😣" }, { "emoji": "😥" }, { "emoji": "😮" },{ "emoji": "🤐" }, { "emoji": "😯" }, { "emoji": "😪" }, { "emoji": "😫" },{ "emoji": "🥱" }, { "emoji": "😴" }, { "emoji": "😌" }, { "emoji": "😛" },{ "emoji": "😜" }, { "emoji": "😝" }, { "emoji": "🤤" }, { "emoji": "😒" },{ "emoji": "😓" }, { "emoji": "😔" }, { "emoji": "😕" }, { "emoji": "🫤" },{ "emoji": "🙃" }, { "emoji": "🫠" }, { "emoji": "🤑" }, { "emoji": "😲" },{ "emoji": "☹️" }, { "emoji": "🙁" }, { "emoji": "😖" }, { "emoji": "😞" },{ "emoji": "😟" }, { "emoji": "😤" }, { "emoji": "😢" }, { "emoji": "😭" },{ "emoji": "😦" }, { "emoji": "😧" }, { "emoji": "😨" }, { "emoji": "😩" },{ "emoji": "🤯" }, { "emoji": "😬" }, { "emoji": "😮💨" }, { "emoji": "😰" },{ "emoji": "😱" }, { "emoji": "🥵" }, { "emoji": "🥶" }, { "emoji": "😳" },{ "emoji": "🤪" }, { "emoji": "😵" }, { "emoji": "😵💫" }, { "emoji": "🥴" },{ "emoji": "😠" }, { "emoji": "😡" }, { "emoji": "🤬" }, { "emoji": "😷" },{ "emoji": "🤒" }, { "emoji": "🤕" }, { "emoji": "🤢" }, { "emoji": "🤮" },{ "emoji": "🤧" }, { "emoji": "😇" }, { "emoji": "🥳" }, { "emoji": "🥸" },{ "emoji": "🥺" }, { "emoji": "🥹" }, { "emoji": "🤠" }, { "emoji": "🤡" },{ "emoji": "🤥" }, { "emoji": "🫨" }, { "emoji": "🤫" }, { "emoji": "🤭" },{ "emoji": "🫢" }, { "emoji": "🫣" }, { "emoji": "🧐" }, { "emoji": "🤓" },{ "emoji": "😈" }, { "emoji": "👿" }, { "emoji": "👹" }, { "emoji": "👺" },{ "emoji": "💀" }, { "emoji": "☠️" }, { "emoji": "👻" }, { "emoji": "👽" },{ "emoji": "👾" }, { "emoji": "💩" }, { "emoji": "🤖" }],animals: [{ "emoji": "😺" }, { "emoji": "😸" }, { "emoji": "😹" }, { "emoji": "😻" },{ "emoji": "😼" }, { "emoji": "😽" }, { "emoji": "🙀" }, { "emoji": "😿" },{ "emoji": "😾" }, { "emoji": "🙈" }, { "emoji": "🙉" }, { "emoji": "🙊" },{ "emoji": "🐵" }, { "emoji": "🐶" }, { "emoji": "🐺" }, { "emoji": "🐱" },{ "emoji": "🦁" }, { "emoji": "🐯" }, { "emoji": "🦒" }, { "emoji": "🦊" },{ "emoji": "🦝" }, { "emoji": "🐮" }, { "emoji": "🐷" }, { "emoji": "🐗" },{ "emoji": "🐭" }, { "emoji": "🐹" }, { "emoji": "🐰" }, { "emoji": "🐻" },{ "emoji": "🐨" }, { "emoji": "🐼" }, { "emoji": "🐸" }, { "emoji": "🦓" },{ "emoji": "🐴" }, { "emoji": "🫎" }, { "emoji": "🫏" }, { "emoji": "🦄" },{ "emoji": "🐔" }, { "emoji": "🐲" }, { "emoji": "🐽" }, { "emoji": "🐾" },{ "emoji": "🐒" }, { "emoji": "🦍" }, { "emoji": "🦧" }, { "emoji": "🦮" },{ "emoji": "🐩" }, { "emoji": "🐕" }, { "emoji": "🐈" }, { "emoji": "🐅" },{ "emoji": "🐆" }, { "emoji": "🦌" }, { "emoji": "🦬" }, { "emoji": "🦏" },{ "emoji": "🐘" }, { "emoji": "🐁" }, { "emoji": "🐀" }, { "emoji": "🦔" },{ "emoji": "🐇" }, { "emoji": "🦎" }, { "emoji": "🐊" }, { "emoji": "🐢" },{ "emoji": "🐍" }, { "emoji": "🐉" }, { "emoji": "🦕" }, { "emoji": "🦖" },{ "emoji": "🐬" }, { "emoji": "🐳" }, { "emoji": "🐋" }, { "emoji": "🐟" },{ "emoji": "🐠" }, { "emoji": "🐡" }, { "emoji": "🦀" }, { "emoji": "🐚" }]};function initStickerTool() {if (document.getElementById("stickerPanel")) return;// Crear panel principalconst panel = document.createElement("div");panel.id = "stickerPanel";panel.style.display = "none";// Pestañasconst tabsContainer = document.createElement("div");tabsContainer.className = "tabsContainer";const btnClassic = document.createElement("button");btnClassic.textContent = "Classic Sticker";const btnCustom = document.createElement("button");btnCustom.textContent = "Custom Sticker";tabsContainer.appendChild(btnClassic);tabsContainer.appendChild(btnCustom);panel.appendChild(tabsContainer);// Sección Clásicaconst classicSection = document.createElement("div");classicSection.id = "classicSection";classicSection.innerHTML = `<div id="dropZone" class="dropZone">Drag or click to select image</div><input type="file" id="fileInput" accept="image/*" /><button id="createSticker" disabled>Create Sticker</button><p id="status"></p><canvas id="previewCanvas"></canvas>`;panel.appendChild(classicSection);// Sección Personalizadaconst customSection = document.createElement("div");customSection.id = "customSection";customSection.innerHTML = `<!-- Toolbar con íconos --><div id="customToolbar"><button id="addCustomImage"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-photo-plus"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M15 8h.01" /><path d="M12.5 21h-6.5a3 3 0 0 1 -3 -3v-12a3 3 0 0 1 3 -3h12a3 3 0 0 1 3 3v6.5" /><path d="M3 16l5 -5c.928 -.893 2.072 -.893 3 0l4 4" /><path d="M14 14l1 -1c.67 -.644 1.45 -.824 2.182 -.54" /><path d="M16 19h6" /><path d="M19 16v6" /></svg></button><button id="openEmojiPanel"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-mood-smile"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" /><path d="M9 10l.01 0" /><path d="M15 10l.01 0" /><path d="M9.5 15a3.5 3.5 0 0 0 5 0" /></svg></button><input type="file" id="customFileInput" accept="image/*" /><button id="toggleDrawing"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-pencil"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M4 20h4l10.5 -10.5a2.828 2.828 0 1 0 -4 -4l-10.5 10.5v4" /><path d="M13.5 6.5l4 4" /></svg></button><button id="toggleShapes"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-square"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M3 3m0 2a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v14a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2z" /></svg></button><button id="bringForward"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-layers-selected"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M8 10.5l6.492 -6.492" /><path d="M13.496 16l6.504 -6.504z" /><path d="M8.586 15.414l10.827 -10.827" /><path d="M8 6a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z" /><path d="M16 16v2a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2v-8a2 2 0 0 1 2 -2h2" /></svg></button><button id="sendBackward"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-layers-selected-bottom"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M4 14.5l4 -4" /><path d="M9.496 20l4.004 -4z" /><path d="M4.586 19.414l3.914 -3.914" /><path d="M8 6a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z" /><path d="M16 16v2a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2v-8a2 2 0 0 1 2 -2h2" /></svg></button><button id="deleteElement"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-trash"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M4 7l16 0" /><path d="M10 11l0 6" /><path d="M14 11l0 6" /><path d="M5 7l1 12a2 2 0 0 0 2 2h8a2 2 0 0 0 2 -2l1 -12" /><path d="M9 7v-3a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v3" /></svg></button><button id="clearCanvas"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-restore"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M3.06 13a9 9 0 1 0 .49 -4.087" /><path d="M3 4.001v5h5" /><path d="M12 12m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0" /></svg></button><button id="toggleMultiSelect"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-select-all"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M8 8m0 1a1 1 0 0 1 1 -1h6a1 1 0 0 1 1 1v6a1 1 0 0 1 -1 1h-6a1 1 0 0 1 -1 -1z" /><path d="M12 20v.01" /><path d="M16 20v.01" /><path d="M8 20v.01" /><path d="M4 20v.01" /><path d="M4 16v.01" /><path d="M4 12v.01" /><path d="M4 8v.01" /><path d="M4 4v.01" /><path d="M8 4v.01" /><path d="M12 4v.01" /><path d="M16 4v.01" /><path d="M20 4v.01" /><path d="M20 8v.01" /><path d="M20 12v.01" /><path d="M20 16v.01" /><path d="M20 20v.01" /></svg></btton><button id="addText"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-letter-t"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M6 4l12 0" /><path d="M12 4l0 16" /></svg></button><button id="downloadImage"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-photo-down"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M15 8h.01" /><path d="M12.5 21h-6.5a3 3 0 0 1 -3 -3v-12a3 3 0 0 1 3 -3h12a3 3 0 0 1 3 3v6.5" /><path d="M3 16l5 -5c.928 -.893 2.072 -.893 3 0l4 4" /><path d="M14 14l1 -1c.653 -.629 1.413 -.815 2.13 -.559" /><path d="M19 16v6" /><path d="M22 19l-3 3l-3 -3" /></svg></button><button id="undo"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-arrow-back-up"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M9 14l-4 -4l4 -4" /><path d="M5 10h11a4 4 0 1 1 0 8h-1" /></svg></button><button id="redo"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-arrow-forward-up"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M15 14l4 -4l-4 -4" /><path d="M19 10h-11a4 4 0 1 0 0 8h1" /></svg></button></div><!-- Panel emergente para opciones del lápiz --><div id="pencilOptionsPanel"><div>Pincel - Colores:</div><div id="pencilColorContainer"></div><div>Pincel - Grosor:</div><div id="pencilSizeContainer"></div></div><!-- Panel emergente para opciones de formas --><div id="shapeOptionsPanel"><button id="shapeSquare"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-square"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M3 3m0 2a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v14a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2z" /></svg></button><button id="shapeCircle"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-circle"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" /></svg></button></div><div class="canvasContainer"><canvas id="customCanvas" width="512" height="512"></canvas></div><button id="createCustomSticker">Create Custom Sticker</button><p id="customStatus"></p><!-- Panel de edición de texto --><div id="textEditorPanel"><div class="textEditorContent"><label>Text: <input type="text" id="textContentInput"></label><div><label>Color: <span id="textColorButtons"></span></label><label>Bg: <span id="textBgButtons"></span></label></div><div><label>Font:<select id="textFontSelect"><option value="" disabled selected>Select font</option><option value="Arial">Arial</option><option value="Courier New">Courier New</option><option value="Times New Roman">Times New Roman</option><option value="Verdana">Verdana</option><option value="Georgia">Georgia</option></select></label><label>Size: <input type="range" id="textFontSizeInput" min="10" max="100" value="30"></label></div></dib></div>`;panel.appendChild(customSection);document.body.appendChild(panel);// Botón flotante para abrir/cerrar el paneladdFloatingButton(panel);// Configurar seccionessetupClassicSection();setupCustomSection();// Cambio de pestañasbtnClassic.addEventListener("click", () => {document.getElementById("classicSection").style.display = "block";document.getElementById("customSection").style.display = "none";});btnCustom.addEventListener("click", () => {document.getElementById("classicSection").style.display = "none";document.getElementById("customSection").style.display = "block";});}// Botón flotantefunction addFloatingButton(panel) {const btn = document.createElement("button");btn.id = "openStickerPanel";btn.textContent = "Sticker";btn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-sticker-2"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M6 4h12a2 2 0 0 1 2 2v7h-5a2 2 0 0 0 -2 2v5h-7a2 2 0 0 1 -2 -2v-12a2 2 0 0 1 2 -2z" /><path d="M20 13v.172a2 2 0 0 1 -.586 1.414l-4.828 4.828a2 2 0 0 1 -1.414 .586h-.172" /></svg>`document.body.appendChild(btn);btn.addEventListener("click", () => {panel.style.display = panel.style.display === "none" ? "block" : "none";const emojiContainer = document.getElementById("emojiContainer");if (emojiContainer.style.display === "flex") {emojiContainer.style.display = "none";}else {emojiContainer.style.display = "none";}});}// =========================// MODO CLÁSICO// =========================function setupClassicSection() {const dropZone = document.getElementById("dropZone");const fileInput = document.getElementById("fileInput");const createStickerButton = document.getElementById("createSticker");const statusText = document.getElementById("status");const previewCanvas = document.getElementById("previewCanvas");let selectedImageCanvas = null;let createClicked = false;dropZone.addEventListener("click", () => fileInput.click());dropZone.addEventListener("dra###er", (e) => { e.preventDefault(); dropZone.style.borderColor = "#000"; });dropZone.addEventListener("dragleave", (e) => { e.preventDefault(); dropZone.style.borderColor = "#ccc"; });dropZone.addEventListener("drop", (e) => {e.preventDefault();dropZone.style.borderColor = "#ccc";if (e.dataTransfer.files && e.dataTransfer.files[0]) { handleFile(e.dataTransfer.files[0]); }});fileInput.addEventListener("change", () => { if (fileInput.files && fileInput.files[0]) { handleFile(fileInput.files[0]); } });function handleFile(file) {const reader = new FileReader();reader.onload = function (event) {const img = new Image();img.onload = function () {previewCanvas.width = previewCanvas.parentElement.clientWidth;previewCanvas.height = previewCanvas.parentElement.clientHeight;const ctx = previewCanvas.getContext("2d");ctx.clearRect(0, 0, previewCanvas.width, previewCanvas.height);const ratio = Math.min(previewCanvas.width / img.width, previewCanvas.height / img.height);const newWidth = img.width * ratio;const newHeight = img.height * ratio;const dx = (previewCanvas.width - newWidth) / 2;const dy = (previewCanvas.height - newHeight) / 2;ctx.drawImage(img, dx, dy, newWidth, newHeight);selectedImageCanvas = previewCanvas;createStickerButton.disabled = false;statusText.textContent = "Image uploaded successfully";createClicked = false;};img.src = event.target.r###lt;};reader.readAsDataURL(file);}createStickerButton.addEventListener("click", () => {if (!selectedImageCanvas || createClicked) return;createClicked = true;createStickerButton.disabled = true;statusText.textContent = "Generating sticker...";selectedImageCanvas.toBlob(function (blob) {if (!blob) { statusText.textContent = "Error al convertir la imagen."; return; }const stickerFile = new File([blob], "sticker.webp", { type: "image/webp" });simulateWhatsAppFileUpload(stickerFile, statusText);}, "image/webp");});}// =========================// MODO PERSONALIZADO (STICKER MAKER)// =========================function setupCustomSection() {const customCanvas = document.getElementById("customCanvas");const ctx = customCanvas.getContext("2d");customCanvas.width = customCanvas.offsetWidth;customCanvas.height = customCanvas.offsetHeight;const addCustomImageButton = document.getElementById("addCustomImage");const customFileInput = document.getElementById("customFileInput");const createCustomStickerButton = document.getElementById("createCustomSticker");const customStatus = document.getElementById("customStatus");const toggleDrawingButton = document.getElementById("toggleDrawing");const toggleShapesButton = document.getElementById("toggleShapes");const bringForwardButton = document.getElementById("bringForward");const sendBackwardButton = document.getElementById("sendBackward");const deleteElementButton = document.getElementById("deleteElement");const clearCanvasButton = document.getElementById("clearCanvas");const toggleMultiSelectButton = document.getElementById("toggleMultiSelect");const addTextButton = document.getElementById("addText");const fontSelect = document.getElementById("fontSelect");const downloadButton = document.getElementById("downloadImage");const undoButton = document.getElementById("undo");const redoButton = document.getElementById("redo");// Paneles emergentesconst pencilOptionsPanel = document.getElementById("pencilOptionsPanel");const pencilColorContainer = document.getElementById("pencilColorContainer");const pencilSizeContainer = document.getElementById("pencilSizeContainer");const shapeOptionsPanel = document.getElementById("shapeOptionsPanel");const shapeSquareButton = document.getElementById("shapeSquare");const shapeCircleButton = document.getElementById("shapeCircle");// Panel de edición de textoconst textEditorPanel = document.getElementById("textEditorPanel");const textContentInput = document.getElementById("textContentInput");const textColorButtons = document.getElementById("textColorButtons");const textBgButtons = document.getElementById("textBgButtons");const textFontSelect = document.getElementById("textFontSelect");const textFontSizeInput = document.getElementById("textFontSizeInput");// Variables internas para el lápizlet drawingColor = "#000000";const brushSizeInput = { value: 2 };// Variables para elementos en el canvaslet customElements = [];let selectedElement = null;let isDrawingMode = false;let drawingInProgress = false;let currentDrawing = null;let customCreateClicked = false;let offsetX = 0, offsetY = 0;let isDragging = false;let isResizing = false, resizeStartX = 0, resizeStartY = 0;let originalFontSize = 0;let originalWidth = 0, originalHeight = 0;// Variables para multi-selectlet isMultiSelectMode = false;let multiSelectedElements = [];let multiSelectRect = null;let isGroupDragging = false;let groupDragStart = null;// Variables para undo/redolet history = [];let historyIndex = -1;function resizeCanvas() {const container = customCanvas.parentElement;customCanvas.width = container.clientWidth;customCanvas.height = container.clientHeight;drawCustomCanvas();}resizeCanvas();window.addEventListener('resize', resizeCanvas);function cloneCustomElements(elements) {return elements.map(el => {let newEl = Object.assign({}, el);if (el.points) newEl.points = el.points.map(p => ({ x: p.x, y: p.y }));if (el.type === "image" && el.img && el.img.src) {const newImg = new Image();newImg.src = el.img.src;newEl.img = newImg;}return newEl;});}function saveHistory() {history = history.slice(0, historyIndex + 1);history.push(cloneCustomElements(customElements));historyIndex++;}// Helper: tamaño por defecto para imágenesfunction getDefaultImageSize(img) {let width = img.width, height = img.height;if (width > 300) {const ratio = 300 / width;width = img.width * ratio;height = img.height * ratio;}return { width, height };}// Función para detectar si un punto está en un elemento (considerando rotación)function isPointInElement(el, x, y) {if (el.rotation && el.rotation !== 0) {const cx = el.x + el.width / 2;const cy = el.y + el.height / 2;// Convertir (x,y) al sistema de coordenadas del elementoconst dx = x - cx;const dy = y - cy;const angle = -el.rotation;const rx = dx * Math.cos(angle) - dy * Math.sin(angle);const ry = dx * Math.sin(angle) + dy * Math.cos(angle);return rx >= -el.width / 2 && rx <= el.width / 2 && ry >= -el.height / 2 && ry <= el.height / 2;} else {return x >= el.x && x <= el.x + el.width && y >= el.y && y <= el.y + el.height;}}// Variable para almacenar la posición actual del mouselet currentMousePos = { x: 0, y: 0 };// Función para dibujar un elemento (aplica rotación si tiene)function drawElement(el) {if (el.rotation && el.rotation !== 0) {const cx = el.x + el.width / 2, cy = el.y + el.height / 2;ctx.save();ctx.translate(cx, cy);ctx.rotate(el.rotation);if (el.type === "image") {ctx.drawImage(el.img, -el.width / 2, -el.height / 2, el.width, el.height);} else if (el.type === "emoji") {ctx.font = el.fontSize + "px sans-serif";ctx.textBaseline = "top";ctx.fillText(el.emoji, -el.width / 2, -el.height / 2);} else if (el.type === "drawing") {ctx.beginPath();el.points.forEach((p, index) => { index === 0 ? ctx.moveTo(p.x - cx, p.y - cy) : ctx.lineTo(p.x - cx, p.y - cy); });ctx.strokeStyle = el.color; ctx.lineWidth = el.brushSize;ctx.lineJoin = "round"; ctx.lineCap = "round"; ctx.stroke();} else if (el.type === "text") {ctx.font = el.fontSize + "px " + el.fontFamily;ctx.textBaseline = "top";if (el.bgColor) {const metrics = ctx.measureText(el.text);const padding = 2;ctx.fillStyle = el.bgColor;ctx.fillRect(-el.width / 2 - padding, -el.height / 2 - padding, metrics.width + 2 * padding, el.fontSize + 2 * padding);}ctx.fillStyle = el.color;ctx.fillText(el.text, -el.width / 2, -el.height / 2);} else if (el.type === "shape") {ctx.strokeStyle = el.color; ctx.lineWidth = 2;if (el.shape === "square") {ctx.strokeRect(-el.width / 2, -el.height / 2, el.width, el.height);} else if (el.shape === "circle") {ctx.beginPath();ctx.arc(0, 0, el.width / 2, 0, Math.PI * 2);ctx.stroke();}}ctx.restore();} else {if (el.type === "image") {ctx.drawImage(el.img, el.x, el.y, el.width, el.height);} else if (el.type === "emoji") {ctx.font = el.fontSize + "px sans-serif";ctx.textBaseline = "top";ctx.fillText(el.emoji, el.x, el.y);const metrics = ctx.measureText(el.emoji);el.width = metrics.width; el.height = el.fontSize;} else if (el.type === "drawing") {ctx.beginPath();el.points.forEach((p, index) => { index === 0 ? ctx.moveTo(p.x, p.y) : ctx.lineTo(p.x, p.y); });ctx.strokeStyle = el.color; ctx.lineWidth = el.brushSize;ctx.lineJoin = "round"; ctx.lineCap = "round"; ctx.stroke();} else if (el.type === "text") {ctx.font = el.fontSize + "px " + el.fontFamily;ctx.textBaseline = "top";if (el.bgColor) {const metrics = ctx.measureText(el.text);const padding = 2;ctx.fillStyle = el.bgColor;ctx.fillRect(el.x - padding, el.y - padding, metrics.width + 2 * padding, el.fontSize + 2 * padding);}ctx.fillStyle = el.color;ctx.fillText(el.text, el.x, el.y);const metrics = ctx.measureText(el.text);el.width = metrics.width; el.height = el.fontSize;} else if (el.type === "shape") {ctx.strokeStyle = el.color; ctx.lineWidth = 2;if (el.shape === "square") ctx.strokeRect(el.x, el.y, el.width, el.height);else if (el.shape === "circle") {ctx.beginPath();ctx.arc(el.x + el.width / 2, el.y + el.height / 2, el.width / 2, 0, Math.PI * 2);ctx.stroke();}}}}// Función para dibujar el canvas completo, incluyendo handles y resaltado por hover/selecciónfunction drawCustomCanvas(forceRedraw = false) {const rect = customCanvas.getBoundingClientRect();// Verificar si hay cambio de tamañoif (customCanvas.width !== rect.width || customCanvas.height !== rect.height || forceRedraw) {customCanvas.width = rect.width;customCanvas.height = rect.height;}ctx.clearRect(0, 0, customCanvas.width, customCanvas.height);customElements.forEach(el => { drawElement(el); });// Si hay un elemento hover (y no está seleccionado) se dibuja su borde en verdeif (hoveredElement && hoveredElement !== selectedElement) {ctx.save();ctx.strokeStyle = "green";ctx.lineWidth = 2;if (hoveredElement.rotation && hoveredElement.rotation !== 0) {const cx = hoveredElement.x + hoveredElement.width / 2;const cy = hoveredElement.y + hoveredElement.height / 2;ctx.translate(cx, cy);ctx.rotate(hoveredElement.rotation);ctx.strokeRect(-hoveredElement.width / 2, -hoveredElement.height / 2, hoveredElement.width, hoveredElement.height);} else {ctx.strokeRect(hoveredElement.x, hoveredElement.y, hoveredElement.width, hoveredElement.height);}ctx.restore();}// Si hay un elemento seleccionado, dibujar borde verde, fondo semitransparente e indicadoresif (selectedElement) {ctx.save();if (selectedElement.rotation && selectedElement.rotation !== 0) {const cx = selectedElement.x + selectedElement.width / 2;const cy = selectedElement.y + selectedElement.height / 2;ctx.translate(cx, cy);ctx.rotate(selectedElement.rotation);// Fondo verde semitransparente y bordectx.fillStyle = "rgba(0,255,0,0.2)";ctx.fillRect(-selectedElement.width / 2, -selectedElement.height / 2, selectedElement.width, selectedElement.height);ctx.strokeStyle = "green";ctx.lineWidth = 2;ctx.strokeRect(-selectedElement.width / 2, -selectedElement.height / 2, selectedElement.width, selectedElement.height);// Indicador de rotación (círculo verde con flecha)ctx.beginPath();ctx.arc(0, -selectedElement.height / 2 - 20, 8, 0, Math.PI * 2);ctx.fillStyle = "green";ctx.fill();ctx.strokeStyle = "white";ctx.lineWidth = 2;// Dibujar flecha circularctx.beginPath();ctx.arc(0, -selectedElement.height / 2 - 20, 12, -Math.PI / 2, Math.PI / 2, false);ctx.stroke();// Punta de la flechactx.beginPath();ctx.moveTo(4, -selectedElement.height / 2 - 20);ctx.lineTo(8, -selectedElement.height / 2 - 24);ctx.lineTo(12, -selectedElement.height / 2 - 20);ctx.stroke();// Indicador de redimensión (cuadrado verde)ctx.fillStyle = "green";ctx.fillRect(selectedElement.width / 2 - 8, selectedElement.height / 2 - 8, 16, 16);ctx.strokeStyle = "white";ctx.strokeRect(selectedElement.width / 2 - 8, selectedElement.height / 2 - 8, 16, 16);} else {// Fondo verde semitransparente y bordectx.fillStyle = "rgba(0,255,0,0.2)";ctx.fillRect(selectedElement.x, selectedElement.y, selectedElement.width, selectedElement.height);ctx.strokeStyle = "green";ctx.lineWidth = 2;ctx.strokeRect(selectedElement.x, selectedElement.y, selectedElement.width, selectedElement.height);// Indicador de rotaciónctx.beginPath();ctx.arc(selectedElement.x + selectedElement.width / 2, selectedElement.y - 20, 8, 0, Math.PI * 2);ctx.fillStyle = "green";ctx.fill();ctx.strokeStyle = "white";ctx.lineWidth = 2;// Dibujar flecha circularctx.beginPath();ctx.arc(selectedElement.x + selectedElement.width / 2, selectedElement.y - 20, 12, -Math.PI / 2, Math.PI / 2, false);ctx.stroke();// Punta de la flechactx.beginPath();ctx.moveTo(selectedElement.x + selectedElement.width / 2 + 4, selectedElement.y - 20);ctx.lineTo(selectedElement.x + selectedElement.width / 2 + 8, selectedElement.y - 24);ctx.lineTo(selectedElement.x + selectedElement.width / 2 + 12, selectedElement.y - 20);ctx.stroke();// Indicador de redimensiónctx.fillStyle = "green";ctx.fillRect(selectedElement.x + selectedElement.width - 8, selectedElement.y + selectedElement.height - 8, 16, 16);ctx.strokeStyle = "white";ctx.strokeRect(selectedElement.x + selectedElement.width - 8, selectedElement.y + selectedElement.height - 8, 16, 16);}ctx.restore();}// Dibujar bordes azul dashed para multi-selecciónmultiSelectedElements.forEach(el => {ctx.save();ctx.strokeStyle = "blue";ctx.lineWidth = 1;ctx.setLineDash([5, 5]);ctx.strokeRect(el.x, el.y, el.width, el.height);ctx.restore();});// Dibujar rectángulo de selección múltiple si está activoif (multiSelectRect) {ctx.save();ctx.strokeStyle = "blue";ctx.lineWidth = 1;ctx.setLineDash([5, 5]);const rx = Math.min(multiSelectRect.startX, multiSelectRect.currentX);const ry = Math.min(multiSelectRect.startY, multiSelectRect.currentY);const rw = Math.abs(multiSelectRect.currentX - multiSelectRect.startX);const rh = Math.abs(multiSelectRect.currentY - multiSelectRect.startY);ctx.strokeRect(rx, ry, rw, rh);ctx.restore();}}// Actualizar variable hoveredElement según la posición del mousecustomCanvas.addEventListener("mousemove", (e) => {const rect = customCanvas.getBoundingClientRect();currentMousePos = { x: e.clientX - rect.left, y: e.clientY - rect.top };// Si no se está arrastrando, redimensionando, rotando o dibujando, detectar hoverif (!isDragging && !isResizing && !isRotating && !drawingInProgress) {hoveredElement = null;for (let i = customElements.length - 1; i >= 0; i--) {const el = customElements[i];if (isPointInElement(el, currentMousePos.x, currentMousePos.y)) {hoveredElement = el;break;}}drawCustomCanvas();}});// =============================// Eventos del canvas// =============================customCanvas.addEventListener("mousedown", (e) => {const rect = customCanvas.getBoundingClientRect();const x = e.clientX - rect.left, y = e.clientY - rect.top;// Si hay un elemento seleccionado, comprobar handle de rotaciónif (selectedElement) {const cx = selectedElement.x + selectedElement.width / 2;const cy = selectedElement.y + selectedElement.height / 2;const rot = selectedElement.rotation || 0;const handleOffset = { x: 0, y: -(selectedElement.height / 2 + 20) };const rx = cx + handleOffset.x * Math.cos(rot) - handleOffset.y * Math.sin(rot);const ry = cy + handleOffset.x * Math.sin(rot) + handleOffset.y * Math.cos(rot);if (distance({ x, y }, { x: rx, y: ry }) < 15) {isRotating = true;initialRotateAngle = Math.atan2(y - cy, x - cx);initialElementRotation = selectedElement.rotation || 0;return;}// Comprobar handle de resizeconst vectorBR = { x: selectedElement.width / 2, y: selectedElement.height / 2 };const brx = cx + vectorBR.x * Math.cos(rot) - vectorBR.y * Math.sin(rot);const bry = cy + vectorBR.x * Math.sin(rot) + vectorBR.y * Math.cos(rot);if (distance({ x, y }, { x: brx, y: bry }) < 15) {isResizing = true;resizeStartX = x; resizeStartY = y;if (selectedElement.type === "text" || selectedElement.type === "emoji") {originalWidth = selectedElement.width;originalFontSize = selectedElement.fontSize;} else {originalWidth = selectedElement.width;originalHeight = selectedElement.height;}return;}}if (isDrawingMode) {drawingInProgress = true;currentDrawing = { type: "drawing", points: [{ x, y }], color: drawingColor, brushSize: brushSizeInput.value, rotation: 0 };selectedElement = null;} else if (isMultiSelectMode) {let inSelected = multiSelectedElements.some(el => x >= el.x && x <= el.x + el.width && y >= el.y && y <= el.y + el.height);if (inSelected && multiSelectedElements.length > 0) {isGroupDragging = true;groupDragStart = { startX: x, startY: y, positions: multiSelectedElements.map(el => ({ x: el.x, y: el.y })) };} else {multiSelectRect = { startX: x, startY: y, currentX: x, currentY: y };multiSelectedElements = [];}selectedElement = null;} else {let found = false;for (let i = customElements.length - 1; i >= 0; i--) {const el = customElements[i];if (el.type === "drawing") {const xs = el.points.map(p => p.x), ys = el.points.map(p => p.y);const minX = Math.min(...xs), maxX = Math.max(...xs);const minY = Math.min(...ys), maxY = Math.max(...ys);if (x >= minX && x <= maxX && y >= minY && y <= maxY) { selectedElement = el; offsetX = x - minX; offsetY = y - minY; found = true; break; }} else {if (isPointInElement(el, x, y)) {selectedElement = el;if (x >= el.x + el.width - 15 && x <= el.x + el.width + 15 && y >= el.y + el.height - 15 && y <= el.y + el.height + 15) {isResizing = true;resizeStartX = x; resizeStartY = y;if (el.type === "text" || el.type === "emoji") {originalWidth = el.width; originalFontSize = el.fontSize;} else {originalWidth = el.width; originalHeight = el.height;}} else {isDragging = true;offsetX = x - el.x; offsetY = y - el.y;}found = true; break;}}}if (!found) { selectedElement = null; }drawCustomCanvas();updateTextEditorPanel();}});customCanvas.addEventListener("mousemove", (e) => {const rect = customCanvas.getBoundingClientRect();const x = e.clientX - rect.left, y = e.clientY - rect.top;currentMousePos = { x, y };// Si se está en modo rotaciónif (isRotating && selectedElement) {const cx = selectedElement.x + selectedElement.width / 2;const cy = selectedElement.y + selectedElement.height / 2;const currentAngle = Math.atan2(y - cy, x - cx);selectedElement.rotation = initialElementRotation + (currentAngle - initialRotateAngle);drawCustomCanvas();return;}if (isDrawingMode && drawingInProgress && currentDrawing) {currentDrawing.points.push({ x, y });drawCustomCanvas();ctx.strokeStyle = currentDrawing.color;ctx.lineWidth = currentDrawing.brushSize;ctx.lineJoin = "round"; ctx.lineCap = "round";ctx.beginPath();currentDrawing.points.forEach((point, index) => { index === 0 ? ctx.moveTo(point.x, point.y) : ctx.lineTo(point.x, point.y); });ctx.stroke();} else if (isMultiSelectMode) {if (isGroupDragging && groupDragStart) {const deltaX = x - groupDragStart.startX, deltaY = y - groupDragStart.startY;multiSelectedElements.forEach((el, idx) => {const initPos = groupDragStart.positions[idx];el.x = initPos.x + deltaX; el.y = initPos.y + deltaY;});drawCustomCanvas();} else if (multiSelectRect) {multiSelectRect.currentX = x; multiSelectRect.currentY = y;drawCustomCanvas();} else if (isDragging && selectedElement && !isDrawingMode && !isResizing && !isRotating) {selectedElement.x = x - offsetX; selectedElement.y = y - offsetY;drawCustomCanvas();}} else {if (isResizing && selectedElement) {let newWidth = originalWidth + (x - resizeStartX);if (newWidth < 20) newWidth = 20;if (selectedElement.type === "text" || selectedElement.type === "emoji") {let scale = newWidth / originalWidth;selectedElement.fontSize = originalFontSize * scale;selectedElement.width = newWidth;selectedElement.height = originalFontSize * scale;} else {let newHeight = originalHeight + (y - resizeStartY);if (newHeight < 20) newHeight = 20;selectedElement.width = newWidth; selectedElement.height = newHeight;}drawCustomCanvas();} else if (isDragging && selectedElement && !isDrawingMode && !isResizing && !isRotating) {selectedElement.x = x - offsetX; selectedElement.y = y - offsetY;drawCustomCanvas();}}// Actualizar cursor sobre handles (para rotación y resize)if (!isDragging && !isResizing && !isRotating && selectedElement) {const cx = selectedElement.x + selectedElement.width / 2;const cy = selectedElement.y + selectedElement.height / 2;const rot = selectedElement.rotation || 0;const handleOffset = { x: 0, y: -(selectedElement.height / 2 + 20) };const rx = cx + handleOffset.x * Math.cos(rot) - handleOffset.y * Math.sin(rot);const ry = cy + handleOffset.x * Math.sin(rot) + handleOffset.y * Math.cos(rot);const vectorBR = { x: selectedElement.width / 2, y: selectedElement.height / 2 };const brx = cx + vectorBR.x * Math.cos(rot) - vectorBR.y * Math.sin(rot);const bry = cy + vectorBR.x * Math.sin(rot) + vectorBR.y * Math.cos(rot);if (distance({ x, y }, { x: rx, y: ry }) < 15) customCanvas.style.cursor = "grab";else if (distance({ x, y }, { x: brx, y: bry }) < 15) customCanvas.style.cursor = "nwse-resize";else customCanvas.style.cursor = "default";}});customCanvas.addEventListener("mouseup", () => {if (isDrawingMode && drawingInProgress && currentDrawing) {customElements.push(currentDrawing);saveHistory();currentDrawing = null; drawingInProgress = false;}if (isMultiSelectMode) {if (isGroupDragging) { isGroupDragging = false; groupDragStart = null; saveHistory(); }else if (multiSelectRect) {const rx = Math.min(multiSelectRect.startX, multiSelectRect.currentX);const ry = Math.min(multiSelectRect.startY, multiSelectRect.currentY);const rw = Math.abs(multiSelectRect.currentX - multiSelectRect.startX);const rh = Math.abs(multiSelectRect.currentY - multiSelectRect.startY);multiSelectedElements = customElements.filter(el => (el.x >= rx && el.y >= ry && (el.x + el.width) <= (rx + rw) && (el.y + el.height) <= (ry + rh)));multiSelectRect = null; drawCustomCanvas();}} else { if (isDragging || isResizing || isRotating) saveHistory(); }isResizing = false; isDragging = false; isRotating = false;drawCustomCanvas();});customCanvas.addEventListener("mouseleave", () => {if (isDrawingMode && drawingInProgress && currentDrawing) {customElements.push(currentDrawing); saveHistory();currentDrawing = null; drawingInProgress = false;}isResizing = false; isDragging = false; isGroupDragging = false; multiSelectRect = null; isRotating = false;drawCustomCanvas();});// =============================// Agregar imagen personalizada// =============================addCustomImageButton.addEventListener("click", () => customFileInput.click());customFileInput.addEventListener("change", () => {if (customFileInput.files && customFileInput.files[0]) {const file = customFileInput.files[0];const reader = new FileReader();reader.onload = function (event) {const img = new Image();img.onload = function () {const size = getDefaultImageSize(img);if (file.type === "image/gif") {const element = { type: "image", isGif: true, originalBlob: file, img: new Image(), x: 50, y: 50, width: size.width, height: size.height, rotation: 0 };element.img.src = URL.createObjectURL(file);customElements.push(element);} else {const element = { type: "image", img: img, x: 50, y: 50, width: size.width, height: size.height, rotation: 0 };customElements.push(element);}saveHistory(); drawCustomCanvas();};img.src = event.target.r###lt;};reader.readAsDataURL(file);}});// =============================// Panel de emojis// =============================let currentCategory = 'faces_emotion';const emojiContainer = document.createElement("div");emojiContainer.id = "emojiContainer";const emojiCategoryContainer = document.createElement("div");emojiCategoryContainer.id = "emojiCategoryContainer";const btnEmotions = document.createElement("button");btnEmotions.textContent = "Emociones";const btnAnimals = document.createElement("button");btnAnimals.textContent = "Animales";btnEmotions.addEventListener("click", () => { currentCategory = 'faces_emotion'; loadEmojis(currentCategory); });btnAnimals.addEventListener("click", () => { currentCategory = 'animals'; loadEmojis(currentCategory); });emojiCategoryContainer.appendChild(btnEmotions);emojiCategoryContainer.appendChild(btnAnimals);const emojiContent = document.createElement("div");emojiContent.id = "emojiContent";emojiContainer.appendChild(emojiCategoryContainer);emojiContainer.appendChild(emojiContent);document.body.appendChild(emojiContainer);const emojiToggleButton = document.getElementById("openEmojiPanel");emojiToggleButton.addEventListener("click", () => {if (emojiContainer.style.display === "none" || emojiContainer.style.display === "") { emojiContainer.style.display = "flex"; loadEmojis(currentCategory); }else { emojiContainer.style.display = "none"; }});function loadEmojis(category) {emojiContent.innerHTML = "";const data = emojis[category];data.forEach(emojiData => {const btn = document.createElement("button");btn.textContent = emojiData.emoji;btn.style.fontSize = "24px"; btn.style.padding = "5px";btn.classList.add("addEmoji");btn.style.border = "1px solid #ccc"; btn.style.borderRadius = "5px";btn.style.cursor = "pointer"; btn.style.backgroundColor = "#f9f9f9";btn.addEventListener("click", () => {const element = { type: "emoji", emoji: emojiData.emoji, x: 50, y: 50, fontSize: 70, width: 0, height: 0, rotation: 0 };customElements.push(element); saveHistory(); drawCustomCanvas();});emojiContent.appendChild(btn);});}loadEmojis(currentCategory);// =============================// Configurar menú emergente del lápiz// =============================pencilColorContainer.innerHTML = "";const pencilColors = colorsText;pencilColors.forEach(color => {const btn = document.createElement("div");btn.className = "colorButton";btn.style.backgroundColor = color;btn.addEventListener("click", () => {drawingColor = color;Array.from(pencilColorContainer.children).forEach(b => b.style.borderColor = "#ccc");btn.style.borderColor = "#000";});pencilColorContainer.appendChild(btn);});pencilSizeContainer.innerHTML = "";const pencilSizes = [2, 4, 6, 8];pencilSizes.forEach(size => {const btn = document.createElement("div");btn.className = "sizeButton";btn.setAttribute("data-size", size);btn.style.backgroundColor = "#777";btn.addEventListener("click", () => {brushSizeInput.value = size;if (selectedElement && selectedElement.type === "drawing") {selectedElement.brushSize = size; drawCustomCanvas();}Array.from(pencilSizeContainer.children).forEach(b => b.style.borderColor = "#ccc");btn.style.borderColor = "#000";});pencilSizeContainer.appendChild(btn);});// Toggle lápiz: ahora alterna entre activar y desactivar el modo dibujotoggleDrawingButton.addEventListener("click", () => {if (isDrawingMode) {isDrawingMode = false;pencilOptionsPanel.style.display = "none";toggleDrawingButton.style.backgroundColor = "#005c4b";} else {isDrawingMode = true;pencilOptionsPanel.style.display = "block";toggleDrawingButton.style.backgroundColor = "#ddd";// Si se activa el lápiz, desactivar modo formasshapeOptionsPanel.style.display = "none";selectedElement = null;}});// =============================// Configurar menú emergente para formas// =============================toggleShapesButton.addEventListener("click", () => {pencilOptionsPanel.style.display = "none";shapeOptionsPanel.style.display = (shapeOptionsPanel.style.display === "none" || shapeOptionsPanel.style.display === "") ? "block" : "none";toggleShapesButton.style.backgroundColor = shapeOptionsPanel.style.display === "block" ? "#ddd" : "#005c4b";});shapeSquareButton.addEventListener("click", () => {const element = { type: "shape", shape: "square", x: 50, y: 50, width: 100, height: 100, color: "#000000", rotation: 0 };customElements.push(element); saveHistory(); drawCustomCanvas();});shapeCircleButton.addEventListener("click", () => {const element = { type: "shape", shape: "circle", x: 50, y: 50, width: 100, height: 100, color: "#000000", rotation: 0 };customElements.push(element); saveHistory(); drawCustomCanvas();});// =============================// Configurar botón multi-select// =============================toggleMultiSelectButton.addEventListener("click", () => {isMultiSelectMode = !isMultiSelectMode;toggleMultiSelectButton.style.backgroundColor = isMultiSelectMode ? "#ddd" : "#005c4b";if (!isMultiSelectMode) { multiSelectedElements = []; drawCustomCanvas(); }});// =============================// Configurar edición de texto (botones de color)// =============================const textColors = colorsText;function populateColorButtons(container, callback) {container.innerHTML = "";textColors.forEach(color => {const btn = document.createElement("div");btn.className = "colorButton";btn.style.backgroundColor = color;btn.addEventListener("click", () => { callback(color); });container.appendChild(btn);});}populateColorButtons(textColorButtons, (color) => { if (selectedElement && selectedElement.type === "text") { selectedElement.color = color; drawCustomCanvas(); } });populateColorButtons(textBgButtons, (color) => { if (selectedElement && selectedElement.type === "text") { selectedElement.bgColor = color; drawCustomCanvas(); } });addTextButton.addEventListener("click", () => {const text = prompt("Enter the text:");if (text) {const element = { type: "text", text: text, x: 50, y: 50, fontSize: 30, width: 0, height: 0, color: "#000000", bgColor: "", rotation: 0 };customElements.push(element); saveHistory(); drawCustomCanvas();}});// =============================// Resto de controles: bringForward, sendBackward, etc.// =============================bringForwardButton.addEventListener("click", () => {if (selectedElement) {const idx = customElements.indexOf(selectedElement);if (idx !== -1 && idx < customElements.length - 1) { customElements.splice(idx, 1); customElements.push(selectedElement); saveHistory(); drawCustomCanvas(); }}});sendBackwardButton.addEventListener("click", () => {if (selectedElement) {const idx = customElements.indexOf(selectedElement);if (idx > 0) { customElements.splice(idx, 1); customElements.unshift(selectedElement); saveHistory(); drawCustomCanvas(); }}});deleteElementButton.addEventListener("click", () => {if (selectedElement) {const idx = customElements.indexOf(selectedElement);if (idx !== -1) { customElements.splice(idx, 1); selectedElement = null; saveHistory(); drawCustomCanvas(); }}});clearCanvasButton.addEventListener("click", () => { customElements = []; selectedElement = null; saveHistory(); drawCustomCanvas(); });downloadButton.addEventListener("click", () => {const dataURL = customCanvas.toDataURL("image/png");const a = document.createElement("a");a.href = dataURL; a.download = "sticker.png"; a.click();});undoButton.addEventListener("click", () => {if (historyIndex > 0) { historyIndex--; customElements = cloneCustomElements(history[historyIndex]); drawCustomCanvas(); }});redoButton.addEventListener("click", () => {if (historyIndex < history.length - 1) { historyIndex++; customElements = cloneCustomElements(history[historyIndex]); drawCustomCanvas(); }});// =============================// Panel de edición de texto: actualización en tiempo real// =============================function updateTextEditorPanel() {if (selectedElement && selectedElement.type === "text") {textEditorPanel.style.display = "block";textContentInput.value = selectedElement.text;textFontSelect.value = selectedElement.fontFamily || "";textFontSizeInput.value = selectedElement.fontSize;} else { textEditorPanel.style.display = "none"; }}textContentInput.addEventListener("input", () => { if (selectedElement && selectedElement.type === "text") { selectedElement.text = textContentInput.value; drawCustomCanvas(); } });textFontSelect.addEventListener("change", () => { if (selectedElement && selectedElement.type === "text") { selectedElement.fontFamily = textFontSelect.value; drawCustomCanvas(); } });textFontSizeInput.addEventListener("input", () => { if (selectedElement && selectedElement.type === "text") { selectedElement.fontSize = parseInt(textFontSizeInput.value); drawCustomCanvas(); } });// =============================// Animación: Si hay algún GIF, redibujar continuamente// =============================function animateCanvas() {if (customElements.some(el => el.type === "image" && el.isGif)) { drawCustomCanvas(); }requestAnimationFrame(animateCanvas);}animateCanvas();// =============================// Crear sticker personalizado// =============================createCustomStickerButton.addEventListener("click", () => {if (customCreateClicked) return;customCreateClicked = true;createCustomStickerButton.disabled = true;customStatus.textContent = "Generating custom sticker...";selectedElement = null;hoveredElement = null;multiSelectedElements = [];drawCustomCanvas();const gifElements = customElements.filter(el => el.type === "image" && el.isGif);if (gifElements.length === 1 && customElements.length === 1) {simulateWhatsAppFileUpload(gifElements[0].originalBlob, customStatus);customElements = []; selectedElement = null; drawCustomCanvas();createCustomStickerButton.disabled = false; customCreateClicked = false;} else {customCanvas.toBlob(function (blob) {if (!blob) { customStatus.textContent = "Error generating sticker."; return; }const stickerFile = new File([blob], "sticker_personalizado.webp", { type: "image/webp" });simulateWhatsAppFileUpload(stickerFile, customStatus);customElements = []; selectedElement = null; drawCustomCanvas();createCustomStickerButton.disabled = false; customCreateClicked = false;}, "image/webp");}});}// =============================// Simulación de subida a WhatsApp// =============================function simulateWhatsAppFileUpload(file, statusElement) {openAttachmentMenu();setTimeout(() => {const waFileInput = document.querySelector("input[type='file'][accept*='image']");if (!waFileInput) { statusElement.textContent = "WhatsApp file input not found"; return; }const dt = new DataTransfer();dt.items.add(file);waFileInput.files = dt.files;const event = new Event("change", { bubbles: true });waFileInput.dispatchEvent(event);statusElement.textContent = "Sticker loaded. Sending...";setTimeout(() => {const sendButton = document.querySelector("span[data-icon='send']");if (sendButton) { sendButton.click(); statusElement.textContent = "Sticker send"; }else { statusElement.textContent = "Submit button not found"; }}, 1000);}, 500);}// =============================// Abrir menú de adjuntos de WhatsApp// =============================function openAttachmentMenu() {const attachmentButton = document.querySelector("div[title='Adjuntar']") || document.querySelector("span[data-icon='clip']");if (attachmentButton) { attachmentButton.click(); }}// Función auxiliar: distancia entre dos puntosfunction distance(p1, p2) {return Math.sqrt((p1.x - p2.x) ** 2 + (p1.y - p2.y) ** 2);}})();