WhatsApp Sticker Creator with Custom Maker Enhanced

在WhatsApp Web中创建自定义贴纸。

目前为 2025-02-24 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name WhatsApp Sticker Creator with Custom Maker Enhanced
  3. // @version 1.0
  4. // @description Create custom stickers in WhatsApp Web.
  5. // @description:af Skep persoonlike stickertjies in WhatsApp Web.
  6. // @description:ar إنشاء ملصقات مخصصة في WhatsApp Web.
  7. // @description:az WhatsApp Web-də fərdi stikerlər yaradın.
  8. // @description:bg Създаване на персонализирани стикери в WhatsApp Web.
  9. // @description:bn WhatsApp Web-এ কাস্টম স্টিকার তৈরি করুন।
  10. // @description:bs Kreirajte prilagođene naljepnice u WhatsApp Webu.
  11. // @description:ca Crea gomets personalitzats a WhatsApp Web.
  12. // @description:cs Vytvářejte vlastní nálepky ve WhatsApp Webu.
  13. // @description:cy Creu stickeriaid addasedig yn WhatsApp Web.
  14. // @description:da Opret brugerdefinerede stickers i WhatsApp Web.
  15. // @description:de Erstellen Sie benutzerdefinierte Aufkleber in WhatsApp Web.
  16. // @description:el Δημιουργήστε προσαρμοσμένα αυτοκόλλητα στο WhatsApp Web.
  17. // @description:en Create custom stickers in WhatsApp Web.
  18. // @description:eo Kreu proprajn glumarkojn en WhatsApp Web.
  19. // @description:es Crear stickers personalizados en WhatsApp Web.
  20. // @description:et Looge WhatsApp Web-is kohandatud kleepsud.
  21. // @description:eu Sortu pertsonalizatutako itsaskiak WhatsApp Web-en.
  22. // @description:fa ایجاد برچسب‌های سفارشی در WhatsApp Web.
  23. // @description:fi Luo mukautettuja tarranauhoja WhatsApp Webiin.
  24. // @description:fr Créer des autocollants personnalisés dans WhatsApp Web.
  25. // @description:gl Crea adhesivos personalizados en WhatsApp Web.
  26. // @description:gu WhatsApp Web માં કસ્ટમ સ્ટિકર્સ બનાવો.
  27. // @description:he צור מדבקות מותאמות אישית ב-WhatsApp Web.
  28. // @description:hi WhatsApp Web में कस्टम स्टिकर बनाएं।
  29. // @description:hr Stvorite prilagođene naljepnice u WhatsApp Webu.
  30. // @description:hu Hozzon létre egyéni matricákat a WhatsApp Webben.
  31. // @description:id Buat stiker kustom di WhatsApp Web.
  32. // @description:it Crea sticker personalizzati su WhatsApp Web.
  33. // @description:ja WhatsApp Webでカスタムステッカーを作成します。
  34. // @description:ka შექმენით მორგებული სტიკერები WhatsApp Web-ში.
  35. // @description:kk WhatsApp Web-де тұтынушыға сәйкес таңбалар жасаңыз.
  36. // @description:km បង្កើតស្លាកតាមតម្រូវការនៅលើ WhatsApp Web។
  37. // @description:kn WhatsApp Web ನಲ್ಲಿ ಅನುಗುಣವಾದ ಸ್ಟಿಕರ್‌ಗಳನ್ನು ರಚಿಸಿ.
  38. // @description:ko WhatsApp 웹에서 사용자 정의 스티커를 만듭니다.
  39. // @description:ku Di WhatsApp Web de stikerên xwerû biafirîne.
  40. // @description:ky WhatsApp Web'de кардардын көңүлүнө ылайыктуу стикерлерди түзгүлө.
  41. // @description:lt Sukurkite pasirinktinius lipdukus „WhatsApp Web“.
  42. // @description:lv Izveidojiet pielāgotas uzlīmes WhatsApp tīmeklī.
  43. // @description:mk Креирајте прилагодени стикери во WhatsApp Web.
  44. // @description:ml WhatsApp വെബിൽ ആവശ്യമനുസരിച്ച് സ്റ്റിക്കർ സൃഷ്ടിക്കുക.
  45. // @description:mn WhatsApp Web дээр өөрийн хүссэн шошго үүсгэх.
  46. // @description:mr WhatsApp Web मध्ये कस्टम स्टिकर तयार करा.
  47. // @description:ms Cipta pelekat tersuai di WhatsApp Web.
  48. // @description:my WhatsApp Web တွင်စိတ်ကြိုက်သတ်မှတ်ထားသော နှိပ်ပုံများဖန်တီးပါ။
  49. // @description:nb Lag egne klistremerker i WhatsApp Web.
  50. // @description:ne WhatsApp वेबमा अनुकूलित स्टिकरहरू सिर्जना गर्नुहोस्।
  51. // @description:nl Maak aangepaste stickers in WhatsApp Web.
  52. // @description:nn Lag tilpassa klistremerke i WhatsApp Web.
  53. // @description:no Lag egne klistremerker i WhatsApp Web.
  54. // @description:pa WhatsApp ਵੈਬ ਵਿੱਚ ਕਸਟਮ ਸਟਿੱਕਰ ਬਣਾਓ।
  55. // @description:pl Twórz niestandardowe naklejki w WhatsApp Web.
  56. // @description:pt Criar adesivos personalizados no WhatsApp Web.
  57. // @description:ro Creați autocolante personalizate în WhatsApp Web.
  58. // @description:ru Создавайте собственные стикеры в WhatsApp Web.
  59. // @description:si WhatsApp Web හි විශේෂිත සටිකර සාදන්න.
  60. // @description:sk Vytvorte vlastné nálepky v službe WhatsApp Web.
  61. // @description:sl Ustvarite prilagojene nalepke v WhatsApp Spletu.
  62. // @description:sq Krijoni ngjitës të personalizuar në WhatsApp Web.
  63. // @description:sr Направите прилагодљиве налепнице у ВхатсАпп Вебу.
  64. // @description:sv Skapa anpassade klistermärken i WhatsApp Web.
  65. // @description:sw Tengeneza lebo maalum katika WhatsApp Web.
  66. // @description:ta WhatsApp வலைதளத்தில் தனிப்பயனாக அட்டைகள் உருவாக்கவும்.
  67. // @description:te WhatsApp వెబ్‌లో అనుకూలిత స్టికర్లు సృష్టించండి.
  68. // @description:th สร้างสติกเกอร์แบบกำหนดเองใน WhatsApp Web
  69. // @description:tr WhatsApp Web'de özel etiketler oluşturun.
  70. // @description:uk Створюйте власні наклейки в WhatsApp Web.
  71. // @description:ur WhatsApp ویب میں کسٹم اسٹکر بنائیں۔
  72. // @description:uz WhatsApp Web-da maxsus stikerlar yarating.
  73. // @description:vi Tạo nhãn dán tùy chỉnh trong WhatsApp Web.
  74. // @description:zh 在WhatsApp Web中创建自定义贴纸。
  75. // @description:zh-CN 在WhatsApp Web中创建自定义贴纸。
  76. // @description:zh-TW 在WhatsApp Web中建立自訂貼紙。
  77. // @author DeveloperMDCM
  78. // @match https://web.whatsapp.com/
  79. // @icon https://www.google.com/s2/favicons?sz=64&domain=youtube.com
  80. // @grant GM_addStyle
  81. // @run-at document-end
  82. // @compatible chrome
  83. // @compatible firefox
  84. // @compatible opera
  85. // @compatible safari
  86. // @compatible edge
  87. // @license MIT
  88. // @namespace https://github.com/DeveloperMDCM/
  89. // @homepage https://github.com/DeveloperMDCM/
  90. // ==/UserScript==
  91.  
  92. (function () {
  93. 'use strict';
  94. console.log('Scrip en ejecución by: DeveloperMDCM');
  95. const HEADER_STYLE = 'color: #F00; font-size: 24px; font-family: sans-serif;';
  96. const MESSAGE_STYLE = 'color: #00aaff; font-size: 16px; font-family: sans-serif;';
  97. const CODE_STYLE = 'font-size: 14px; font-family: monospace;';
  98. console.log(
  99. '%cYoutube Tools Extension NEW UI\n' +
  100. '%cRun %c(v2.3.2)\n' +
  101. 'By: DeveloperMDCM.',
  102. HEADER_STYLE,
  103. CODE_STYLE,
  104. MESSAGE_STYLE
  105. );
  106. // Variables globales para rotación y hover
  107. let isRotating = false;
  108. let initialRotateAngle = 0;
  109. let initialElementRotation = 0;
  110. let hoveredElement = null;
  111. let currentMousePos = { x: 0, y: 0 };
  112. const colorsText = ["#000000", "#ff0000", "#00ff00", "#0000ff", "#ffff00", "#00ffff", "#ff00ff", "#ffffff", "#ff000000"];
  113.  
  114. GM_addStyle(`
  115. /* Panel principal */
  116. #stickerPanel {
  117. position: fixed;
  118. top: 0;
  119. right: 0;
  120. width: 580px;
  121. height: auto;
  122. max-height: 90vh;
  123. overflow-y: auto;
  124. background-color: #111b21;
  125. border: 1px solid #202c33;
  126. padding: 10px;
  127. z-index: 10000;
  128. box-shadow: 0 2px 8px rgba(0,0,0,0.2);
  129. font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
  130. }
  131. /* Pestañas */
  132. .tabsContainer {
  133. display: flex;
  134. justify-content: space-around;
  135. margin-bottom: 10px;
  136. }
  137. .tabsContainer button {
  138. flex: 1;
  139. padding: 8px;
  140. border: none;
  141. cursor: pointer;
  142. background-color: #005c4b;
  143. color: #fff;
  144. font-weight: bold;
  145. }
  146. .tabsContainer button:first-child { margin-right: 5px; }
  147. .tabsContainer button:last-child { margin-left: 5px; }
  148. /* Sección Clásica */
  149. .dropZone {
  150. border: 2px dashed #ccc;
  151. padding: 20px;
  152. text-align: center;
  153. margin-bottom: 10px;
  154. cursor: pointer;
  155. background-color: #111b21;
  156. }
  157. input[type="file"] { display: none; }
  158. #createSticker, #createCustomSticker {
  159. width: 100%;
  160. padding: 8px;
  161. margin-top: 5px;
  162. background-color: #005c4b;
  163. border: none;
  164. color: #fff;
  165. font-weight: bold;
  166. border-radius: 3px;
  167. }
  168. #status, #customStatus {
  169. font-size: 12px;
  170. color: #555;
  171. text-align: center;
  172. margin-top: 5px;
  173. }
  174. #previewCanvas { display: none; }
  175. /* Sección Personalizada */
  176. #customSection { display: none; }
  177. /* Toolbar y menús emergentes */
  178. #customToolbar {
  179. display: flex;
  180. flex-wrap: wrap;
  181. gap: 5px;
  182. margin-bottom: 5px;
  183. align-items: center;
  184. }
  185. #customToolbar button {
  186. padding: 5px 8px;
  187. cursor: pointer;
  188. border: none;
  189. background-color: #005c4b;
  190. color: #fff;
  191. border-radius: 3px;
  192. }
  193. #customToolbar select { padding: 4px; }
  194. /* Panel de opciones del lápiz y de formas */
  195. #pencilOptionsPanel, #shapeOptionsPanel {
  196. display: none;
  197. margin: 5px 0;
  198. padding: 5px;
  199. border: 1px solid #ddd;
  200. background-color: #005c4b;
  201. font-size: 12px;
  202. border-radius: 3px;
  203. }
  204. /* Botones de color y tamaño */
  205. .colorButton, .sizeButton {
  206. width: 15px;
  207. height: 15px;
  208. border-radius: 50%;
  209. border: 2px solid #ccc;
  210. display: inline-block;
  211. margin: 2px;
  212. cursor: pointer;
  213. }
  214. .sizeButton[data-size="2"] { width: 8px; height: 8px; }
  215. .sizeButton[data-size="4"] { width: 12px; height: 12px; }
  216. .sizeButton[data-size="6"] { width: 16px; height: 16px; }
  217. .sizeButton[data-size="8"] { width: 20px; height: 20px; }
  218. #pencilColorContainer, #pencilSizeContainer { display: inline-block; vertical-align: middle; }
  219. /* Panel para formas */
  220. #shapeOptionsPanel button {
  221. margin-right: 5px;
  222. padding: 3px 6px;
  223. border: none;
  224. color: #fff;
  225. border-radius: 3px;
  226. cursor: pointer;
  227. }
  228. /* Área de canvas con fondo ajedrezado */
  229. .canvasContainer {
  230. border: 2px dashed #ccc;
  231. width: 100%;
  232. height: 60vh;
  233. max-height: 60vh;
  234. margin: auto;
  235. position: relative;
  236. }
  237. #customCanvas {
  238. width: 100%;
  239. height: 100%;
  240. background-size: 20px 20px;
  241. background-image:
  242. linear-gradient(45deg, #ccc 25%, transparent 25%),
  243. linear-gradient(-45deg, #ccc 25%, transparent 25%),
  244. linear-gradient(45deg, transparent 75%, #ccc 75%),
  245. linear-gradient(-45deg, #fff 75%, #ccc 75%);
  246. background-size: 20px 20px;
  247. background-position: 0 0, 0 10px, 10px -10px, -10px 0px;
  248. display: block;
  249. }
  250. /* Botón flotante */
  251. #openStickerPanel {
  252. position: fixed;
  253. bottom: 20px;
  254. right: 20px;
  255. padding: 10px 15px;
  256. background-color: #005c4b;
  257. color: #fff;
  258. border: none;
  259. border-radius: 5px;
  260. cursor: pointer;
  261. z-index: 10000;
  262. box-shadow: 0 2px 8px rgba(0,0,0,0.2);
  263. }
  264. /* Panel de edición de texto */
  265. #textEditorPanel {
  266. margin-top: 10px;
  267. padding: 5px;
  268. display: none;
  269. border-radius: 3px;
  270. }
  271. #textEditorPanel label { margin: 3px 5px; }
  272. /* Panel de emojis */
  273. #emojiContainer {
  274. position: fixed;
  275. top: 0;
  276. right: 600px;
  277. width: auto;
  278. height: 400px;
  279. background-color: #111b21;
  280. border: 1px solid #ddd;
  281. box-shadow: 0 4px 12px rgba(0,0,0,0.2);
  282. z-index: 10000;
  283. display: none;
  284. flex-direction: column;
  285. border-radius: 3px;
  286. }
  287. #emojiCategoryContainer {
  288. display: flex;
  289. justify-content: space-around;
  290. padding: 5px;
  291. }
  292. #emojiCategoryContainer button {
  293. background-color: #005c4b;
  294. color: #fff;
  295. border: none;
  296. padding: 5px;
  297. cursor: pointer;
  298. flex: 1;
  299. margin: 0 2px;
  300. border-radius: 3px;
  301. }
  302. #emojiContent {
  303. overflow-y: auto;
  304. height: 350px;
  305. padding: 10px 0 30px 10px;
  306. display: grid;
  307. background-color: black;
  308. grid-template-columns: repeat(6, 1fr);
  309. gap: 5px;
  310. }
  311. #textFontSelect {
  312. width: auto;
  313. appearance: auto;
  314. }
  315. #textFontSelect:not(:invalid) {
  316. color: #fff;
  317. }
  318. .textEditorContent {
  319. display: flex;
  320. flex-direction: column;
  321. gap: 6px;
  322. }
  323. `);
  324.  
  325. // =========================
  326. // Espera a que la página se cargue
  327. // =========================
  328. window.addEventListener("load", () => { setTimeout(initStickerTool, 3000); });
  329.  
  330.  
  331. const emojis = {
  332. faces_emotion: [
  333. { "emoji": "😀" }, { "emoji": "😁" }, { "emoji": "😂" }, { "emoji": "🤣" },
  334. { "emoji": "😃" }, { "emoji": "😄" }, { "emoji": "😅" }, { "emoji": "😆" },
  335. { "emoji": "😉" }, { "emoji": "😊" }, { "emoji": "😋" }, { "emoji": "😎" },
  336. { "emoji": "😍" }, { "emoji": "😘" }, { "emoji": "🥰" }, { "emoji": "😗" },
  337. { "emoji": "😙" }, { "emoji": "🥲" }, { "emoji": "😚" }, { "emoji": "☺️" },
  338. { "emoji": "🙂" }, { "emoji": "🤗" }, { "emoji": "🤩" }, { "emoji": "🤔" },
  339. { "emoji": "🫡" }, { "emoji": "🤨" }, { "emoji": "😐" }, { "emoji": "😑" },
  340. { "emoji": "😶" }, { "emoji": "🫥" }, { "emoji": "😶‍🌫️" }, { "emoji": "🙄" },
  341. { "emoji": "😏" }, { "emoji": "😣" }, { "emoji": "😥" }, { "emoji": "😮" },
  342. { "emoji": "🤐" }, { "emoji": "😯" }, { "emoji": "😪" }, { "emoji": "😫" },
  343. { "emoji": "🥱" }, { "emoji": "😴" }, { "emoji": "😌" }, { "emoji": "😛" },
  344. { "emoji": "😜" }, { "emoji": "😝" }, { "emoji": "🤤" }, { "emoji": "😒" },
  345. { "emoji": "😓" }, { "emoji": "😔" }, { "emoji": "😕" }, { "emoji": "🫤" },
  346. { "emoji": "🙃" }, { "emoji": "🫠" }, { "emoji": "🤑" }, { "emoji": "😲" },
  347. { "emoji": "☹️" }, { "emoji": "🙁" }, { "emoji": "😖" }, { "emoji": "😞" },
  348. { "emoji": "😟" }, { "emoji": "😤" }, { "emoji": "😢" }, { "emoji": "😭" },
  349. { "emoji": "😦" }, { "emoji": "😧" }, { "emoji": "😨" }, { "emoji": "😩" },
  350. { "emoji": "🤯" }, { "emoji": "😬" }, { "emoji": "😮‍💨" }, { "emoji": "😰" },
  351. { "emoji": "😱" }, { "emoji": "🥵" }, { "emoji": "🥶" }, { "emoji": "😳" },
  352. { "emoji": "🤪" }, { "emoji": "😵" }, { "emoji": "😵‍💫" }, { "emoji": "🥴" },
  353. { "emoji": "😠" }, { "emoji": "😡" }, { "emoji": "🤬" }, { "emoji": "😷" },
  354. { "emoji": "🤒" }, { "emoji": "🤕" }, { "emoji": "🤢" }, { "emoji": "🤮" },
  355. { "emoji": "🤧" }, { "emoji": "😇" }, { "emoji": "🥳" }, { "emoji": "🥸" },
  356. { "emoji": "🥺" }, { "emoji": "🥹" }, { "emoji": "🤠" }, { "emoji": "🤡" },
  357. { "emoji": "🤥" }, { "emoji": "🫨" }, { "emoji": "🤫" }, { "emoji": "🤭" },
  358. { "emoji": "🫢" }, { "emoji": "🫣" }, { "emoji": "🧐" }, { "emoji": "🤓" },
  359. { "emoji": "😈" }, { "emoji": "👿" }, { "emoji": "👹" }, { "emoji": "👺" },
  360. { "emoji": "💀" }, { "emoji": "☠️" }, { "emoji": "👻" }, { "emoji": "👽" },
  361. { "emoji": "👾" }, { "emoji": "💩" }, { "emoji": "🤖" }
  362. ],
  363. animals: [
  364. { "emoji": "😺" }, { "emoji": "😸" }, { "emoji": "😹" }, { "emoji": "😻" },
  365. { "emoji": "😼" }, { "emoji": "😽" }, { "emoji": "🙀" }, { "emoji": "😿" },
  366. { "emoji": "😾" }, { "emoji": "🙈" }, { "emoji": "🙉" }, { "emoji": "🙊" },
  367. { "emoji": "🐵" }, { "emoji": "🐶" }, { "emoji": "🐺" }, { "emoji": "🐱" },
  368. { "emoji": "🦁" }, { "emoji": "🐯" }, { "emoji": "🦒" }, { "emoji": "🦊" },
  369. { "emoji": "🦝" }, { "emoji": "🐮" }, { "emoji": "🐷" }, { "emoji": "🐗" },
  370. { "emoji": "🐭" }, { "emoji": "🐹" }, { "emoji": "🐰" }, { "emoji": "🐻" },
  371. { "emoji": "🐨" }, { "emoji": "🐼" }, { "emoji": "🐸" }, { "emoji": "🦓" },
  372. { "emoji": "🐴" }, { "emoji": "🫎" }, { "emoji": "🫏" }, { "emoji": "🦄" },
  373. { "emoji": "🐔" }, { "emoji": "🐲" }, { "emoji": "🐽" }, { "emoji": "🐾" },
  374. { "emoji": "🐒" }, { "emoji": "🦍" }, { "emoji": "🦧" }, { "emoji": "🦮" },
  375. { "emoji": "🐩" }, { "emoji": "🐕" }, { "emoji": "🐈" }, { "emoji": "🐅" },
  376. { "emoji": "🐆" }, { "emoji": "🦌" }, { "emoji": "🦬" }, { "emoji": "🦏" },
  377. { "emoji": "🐘" }, { "emoji": "🐁" }, { "emoji": "🐀" }, { "emoji": "🦔" },
  378. { "emoji": "🐇" }, { "emoji": "🦎" }, { "emoji": "🐊" }, { "emoji": "🐢" },
  379. { "emoji": "🐍" }, { "emoji": "🐉" }, { "emoji": "🦕" }, { "emoji": "🦖" },
  380. { "emoji": "🐬" }, { "emoji": "🐳" }, { "emoji": "🐋" }, { "emoji": "🐟" },
  381. { "emoji": "🐠" }, { "emoji": "🐡" }, { "emoji": "🦀" }, { "emoji": "🐚" }
  382. ]
  383. };
  384.  
  385. function initStickerTool() {
  386. if (document.getElementById("stickerPanel")) return;
  387.  
  388. // Crear panel principal
  389. const panel = document.createElement("div");
  390. panel.id = "stickerPanel";
  391. panel.style.display = "none";
  392. // Pestañas
  393. const tabsContainer = document.createElement("div");
  394. tabsContainer.className = "tabsContainer";
  395. const btnClassic = document.createElement("button");
  396. btnClassic.textContent = "Classic Sticker";
  397. const btnCustom = document.createElement("button");
  398. btnCustom.textContent = "Custom Sticker";
  399. tabsContainer.appendChild(btnClassic);
  400. tabsContainer.appendChild(btnCustom);
  401. panel.appendChild(tabsContainer);
  402.  
  403. // Sección Clásica
  404. const classicSection = document.createElement("div");
  405. classicSection.id = "classicSection";
  406. classicSection.innerHTML = `
  407. <div id="dropZone" class="dropZone">Drag or click to select image</div>
  408. <input type="file" id="fileInput" accept="image/*" />
  409. <button id="createSticker" disabled>Create Sticker</button>
  410. <p id="status"></p>
  411. <canvas id="previewCanvas"></canvas>
  412. `;
  413. panel.appendChild(classicSection);
  414.  
  415. // Sección Personalizada
  416. const customSection = document.createElement("div");
  417. customSection.id = "customSection";
  418. customSection.innerHTML = `
  419.  
  420. <!-- Toolbar con íconos -->
  421. <div id="customToolbar">
  422. <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>
  423. <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>
  424. <input type="file" id="customFileInput" accept="image/*" />
  425. <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>
  426. <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>
  427. <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>
  428. <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>
  429. <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>
  430. <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>
  431. <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>
  432. <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>
  433. <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>
  434. <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>
  435. <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>
  436. </div>
  437. <!-- Panel emergente para opciones del lápiz -->
  438. <div id="pencilOptionsPanel">
  439. <div>Pincel - Colores:</div>
  440. <div id="pencilColorContainer"></div>
  441. <div>Pincel - Grosor:</div>
  442. <div id="pencilSizeContainer"></div>
  443. </div>
  444. <!-- Panel emergente para opciones de formas -->
  445. <div id="shapeOptionsPanel">
  446. <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>
  447. <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>
  448. </div>
  449. <div class="canvasContainer">
  450. <canvas id="customCanvas" width="512" height="512"></canvas>
  451. </div>
  452. <button id="createCustomSticker">Create Custom Sticker</button>
  453. <p id="customStatus"></p>
  454. <!-- Panel de edición de texto -->
  455. <div id="textEditorPanel">
  456. <div class="textEditorContent">
  457. <label>Text: <input type="text" id="textContentInput"></label>
  458. <div>
  459. <label>Color: <span id="textColorButtons"></span></label>
  460. <label>Bg: <span id="textBgButtons"></span></label>
  461. </div>
  462. <div>
  463. <label>Font:
  464. <select id="textFontSelect">
  465. <option value="" disabled selected>Select font</option>
  466. <option value="Arial">Arial</option>
  467. <option value="Courier New">Courier New</option>
  468. <option value="Times New Roman">Times New Roman</option>
  469. <option value="Verdana">Verdana</option>
  470. <option value="Georgia">Georgia</option>
  471. </select>
  472. </label>
  473. <label>Size: <input type="range" id="textFontSizeInput" min="10" max="100" value="30"></label>
  474. </div>
  475. </dib>
  476. </div>
  477. `;
  478. panel.appendChild(customSection);
  479.  
  480. document.body.appendChild(panel);
  481.  
  482. // Botón flotante para abrir/cerrar el panel
  483. addFloatingButton(panel);
  484.  
  485. // Configurar secciones
  486. setupClassicSection();
  487. setupCustomSection();
  488.  
  489. // Cambio de pestañas
  490. btnClassic.addEventListener("click", () => {
  491. document.getElementById("classicSection").style.display = "block";
  492. document.getElementById("customSection").style.display = "none";
  493. });
  494. btnCustom.addEventListener("click", () => {
  495. document.getElementById("classicSection").style.display = "none";
  496. document.getElementById("customSection").style.display = "block";
  497. });
  498. }
  499.  
  500.  
  501.  
  502. // Botón flotante
  503. function addFloatingButton(panel) {
  504. const btn = document.createElement("button");
  505. btn.id = "openStickerPanel";
  506. btn.textContent = "Sticker";
  507. 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>`
  508. document.body.appendChild(btn);
  509. btn.addEventListener("click", () => {
  510. panel.style.display = panel.style.display === "none" ? "block" : "none";
  511. const emojiContainer = document.getElementById("emojiContainer");
  512.  
  513. if (emojiContainer.style.display === "flex") {
  514. emojiContainer.style.display = "none";
  515. }
  516. else {
  517. emojiContainer.style.display = "none";
  518. }
  519.  
  520. });
  521. }
  522.  
  523. // =========================
  524. // MODO CLÁSICO
  525. // =========================
  526. function setupClassicSection() {
  527. const dropZone = document.getElementById("dropZone");
  528. const fileInput = document.getElementById("fileInput");
  529. const createStickerButton = document.getElementById("createSticker");
  530. const statusText = document.getElementById("status");
  531. const previewCanvas = document.getElementById("previewCanvas");
  532. let selectedImageCanvas = null;
  533. let createClicked = false;
  534.  
  535. dropZone.addEventListener("click", () => fileInput.click());
  536. dropZone.addEventListener("dragover", (e) => { e.preventDefault(); dropZone.style.borderColor = "#000"; });
  537. dropZone.addEventListener("dragleave", (e) => { e.preventDefault(); dropZone.style.borderColor = "#ccc"; });
  538. dropZone.addEventListener("drop", (e) => {
  539. e.preventDefault();
  540. dropZone.style.borderColor = "#ccc";
  541. if (e.dataTransfer.files && e.dataTransfer.files[0]) { handleFile(e.dataTransfer.files[0]); }
  542. });
  543. fileInput.addEventListener("change", () => { if (fileInput.files && fileInput.files[0]) { handleFile(fileInput.files[0]); } });
  544.  
  545. function handleFile(file) {
  546.  
  547. const reader = new FileReader();
  548. reader.onload = function (event) {
  549. const img = new Image();
  550. img.onload = function () {
  551. previewCanvas.width = previewCanvas.parentElement.clientWidth;
  552. previewCanvas.height = previewCanvas.parentElement.clientHeight;
  553. const ctx = previewCanvas.getContext("2d");
  554. ctx.clearRect(0, 0, previewCanvas.width, previewCanvas.height);
  555. const ratio = Math.min(previewCanvas.width / img.width, previewCanvas.height / img.height);
  556. const newWidth = img.width * ratio;
  557. const newHeight = img.height * ratio;
  558. const dx = (previewCanvas.width - newWidth) / 2;
  559. const dy = (previewCanvas.height - newHeight) / 2;
  560. ctx.drawImage(img, dx, dy, newWidth, newHeight);
  561. selectedImageCanvas = previewCanvas;
  562. createStickerButton.disabled = false;
  563. statusText.textContent = "Image uploaded successfully";
  564. createClicked = false;
  565. };
  566. img.src = event.target.result;
  567. };
  568. reader.readAsDataURL(file);
  569. }
  570.  
  571. createStickerButton.addEventListener("click", () => {
  572. if (!selectedImageCanvas || createClicked) return;
  573. createClicked = true;
  574. createStickerButton.disabled = true;
  575. statusText.textContent = "Generating sticker...";
  576. selectedImageCanvas.toBlob(function (blob) {
  577. if (!blob) { statusText.textContent = "Error al convertir la imagen."; return; }
  578. const stickerFile = new File([blob], "sticker.webp", { type: "image/webp" });
  579. simulateWhatsAppFileUpload(stickerFile, statusText);
  580. }, "image/webp");
  581. });
  582. }
  583.  
  584. // =========================
  585. // MODO PERSONALIZADO (STICKER MAKER)
  586. // =========================
  587. function setupCustomSection() {
  588. const customCanvas = document.getElementById("customCanvas");
  589. const ctx = customCanvas.getContext("2d");
  590. customCanvas.width = customCanvas.offsetWidth;
  591. customCanvas.height = customCanvas.offsetHeight;
  592. const addCustomImageButton = document.getElementById("addCustomImage");
  593. const customFileInput = document.getElementById("customFileInput");
  594. const createCustomStickerButton = document.getElementById("createCustomSticker");
  595. const customStatus = document.getElementById("customStatus");
  596. const toggleDrawingButton = document.getElementById("toggleDrawing");
  597. const toggleShapesButton = document.getElementById("toggleShapes");
  598. const bringForwardButton = document.getElementById("bringForward");
  599. const sendBackwardButton = document.getElementById("sendBackward");
  600. const deleteElementButton = document.getElementById("deleteElement");
  601. const clearCanvasButton = document.getElementById("clearCanvas");
  602. const toggleMultiSelectButton = document.getElementById("toggleMultiSelect");
  603. const addTextButton = document.getElementById("addText");
  604. const fontSelect = document.getElementById("fontSelect");
  605. const downloadButton = document.getElementById("downloadImage");
  606. const undoButton = document.getElementById("undo");
  607. const redoButton = document.getElementById("redo");
  608.  
  609. // Paneles emergentes
  610. const pencilOptionsPanel = document.getElementById("pencilOptionsPanel");
  611. const pencilColorContainer = document.getElementById("pencilColorContainer");
  612. const pencilSizeContainer = document.getElementById("pencilSizeContainer");
  613. const shapeOptionsPanel = document.getElementById("shapeOptionsPanel");
  614. const shapeSquareButton = document.getElementById("shapeSquare");
  615. const shapeCircleButton = document.getElementById("shapeCircle");
  616.  
  617. // Panel de edición de texto
  618. const textEditorPanel = document.getElementById("textEditorPanel");
  619. const textContentInput = document.getElementById("textContentInput");
  620. const textColorButtons = document.getElementById("textColorButtons");
  621. const textBgButtons = document.getElementById("textBgButtons");
  622. const textFontSelect = document.getElementById("textFontSelect");
  623. const textFontSizeInput = document.getElementById("textFontSizeInput");
  624.  
  625. // Variables internas para el lápiz
  626. let drawingColor = "#000000";
  627. const brushSizeInput = { value: 2 };
  628.  
  629. // Variables para elementos en el canvas
  630. let customElements = [];
  631. let selectedElement = null;
  632. let isDrawingMode = false;
  633. let drawingInProgress = false;
  634. let currentDrawing = null;
  635. let customCreateClicked = false;
  636. let offsetX = 0, offsetY = 0;
  637. let isDragging = false;
  638. let isResizing = false, resizeStartX = 0, resizeStartY = 0;
  639. let originalFontSize = 0;
  640. let originalWidth = 0, originalHeight = 0;
  641.  
  642. // Variables para multi-select
  643. let isMultiSelectMode = false;
  644. let multiSelectedElements = [];
  645. let multiSelectRect = null;
  646. let isGroupDragging = false;
  647. let groupDragStart = null;
  648.  
  649. // Variables para undo/redo
  650. let history = [];
  651. let historyIndex = -1;
  652.  
  653. function resizeCanvas() {
  654. const container = customCanvas.parentElement;
  655. customCanvas.width = container.clientWidth;
  656. customCanvas.height = container.clientHeight;
  657. drawCustomCanvas();
  658. }
  659. resizeCanvas();
  660. window.addEventListener('resize', resizeCanvas);
  661.  
  662. function cloneCustomElements(elements) {
  663. return elements.map(el => {
  664. let newEl = Object.assign({}, el);
  665. if (el.points) newEl.points = el.points.map(p => ({ x: p.x, y: p.y }));
  666. if (el.type === "image" && el.img && el.img.src) {
  667. const newImg = new Image();
  668. newImg.src = el.img.src;
  669. newEl.img = newImg;
  670. }
  671. return newEl;
  672. });
  673. }
  674. function saveHistory() {
  675. history = history.slice(0, historyIndex + 1);
  676. history.push(cloneCustomElements(customElements));
  677. historyIndex++;
  678. }
  679.  
  680. // Helper: tamaño por defecto para imágenes
  681. function getDefaultImageSize(img) {
  682. let width = img.width, height = img.height;
  683. if (width > 300) {
  684. const ratio = 300 / width;
  685. width = img.width * ratio;
  686. height = img.height * ratio;
  687. }
  688. return { width, height };
  689. }
  690.  
  691. // Función para detectar si un punto está en un elemento (considerando rotación)
  692. function isPointInElement(el, x, y) {
  693. if (el.rotation && el.rotation !== 0) {
  694. const cx = el.x + el.width / 2;
  695. const cy = el.y + el.height / 2;
  696. // Convertir (x,y) al sistema de coordenadas del elemento
  697. const dx = x - cx;
  698. const dy = y - cy;
  699. const angle = -el.rotation;
  700. const rx = dx * Math.cos(angle) - dy * Math.sin(angle);
  701. const ry = dx * Math.sin(angle) + dy * Math.cos(angle);
  702. return rx >= -el.width / 2 && rx <= el.width / 2 && ry >= -el.height / 2 && ry <= el.height / 2;
  703. } else {
  704. return x >= el.x && x <= el.x + el.width && y >= el.y && y <= el.y + el.height;
  705. }
  706. }
  707.  
  708. // Variable para almacenar la posición actual del mouse
  709. let currentMousePos = { x: 0, y: 0 };
  710.  
  711. // Función para dibujar un elemento (aplica rotación si tiene)
  712. function drawElement(el) {
  713. if (el.rotation && el.rotation !== 0) {
  714. const cx = el.x + el.width / 2, cy = el.y + el.height / 2;
  715. ctx.save();
  716. ctx.translate(cx, cy);
  717. ctx.rotate(el.rotation);
  718. if (el.type === "image") {
  719. ctx.drawImage(el.img, -el.width / 2, -el.height / 2, el.width, el.height);
  720. } else if (el.type === "emoji") {
  721. ctx.font = el.fontSize + "px sans-serif";
  722. ctx.textBaseline = "top";
  723. ctx.fillText(el.emoji, -el.width / 2, -el.height / 2);
  724. } else if (el.type === "drawing") {
  725. ctx.beginPath();
  726. el.points.forEach((p, index) => { index === 0 ? ctx.moveTo(p.x - cx, p.y - cy) : ctx.lineTo(p.x - cx, p.y - cy); });
  727. ctx.strokeStyle = el.color; ctx.lineWidth = el.brushSize;
  728. ctx.lineJoin = "round"; ctx.lineCap = "round"; ctx.stroke();
  729. } else if (el.type === "text") {
  730. ctx.font = el.fontSize + "px " + el.fontFamily;
  731. ctx.textBaseline = "top";
  732. if (el.bgColor) {
  733. const metrics = ctx.measureText(el.text);
  734. const padding = 2;
  735. ctx.fillStyle = el.bgColor;
  736. ctx.fillRect(-el.width / 2 - padding, -el.height / 2 - padding, metrics.width + 2 * padding, el.fontSize + 2 * padding);
  737. }
  738. ctx.fillStyle = el.color;
  739. ctx.fillText(el.text, -el.width / 2, -el.height / 2);
  740. } else if (el.type === "shape") {
  741. ctx.strokeStyle = el.color; ctx.lineWidth = 2;
  742. if (el.shape === "square") {
  743. ctx.strokeRect(-el.width / 2, -el.height / 2, el.width, el.height);
  744. } else if (el.shape === "circle") {
  745. ctx.beginPath();
  746. ctx.arc(0, 0, el.width / 2, 0, Math.PI * 2);
  747. ctx.stroke();
  748. }
  749. }
  750. ctx.restore();
  751. } else {
  752. if (el.type === "image") {
  753. ctx.drawImage(el.img, el.x, el.y, el.width, el.height);
  754. } else if (el.type === "emoji") {
  755. ctx.font = el.fontSize + "px sans-serif";
  756. ctx.textBaseline = "top";
  757. ctx.fillText(el.emoji, el.x, el.y);
  758. const metrics = ctx.measureText(el.emoji);
  759. el.width = metrics.width; el.height = el.fontSize;
  760. } else if (el.type === "drawing") {
  761. ctx.beginPath();
  762. el.points.forEach((p, index) => { index === 0 ? ctx.moveTo(p.x, p.y) : ctx.lineTo(p.x, p.y); });
  763. ctx.strokeStyle = el.color; ctx.lineWidth = el.brushSize;
  764. ctx.lineJoin = "round"; ctx.lineCap = "round"; ctx.stroke();
  765. } else if (el.type === "text") {
  766. ctx.font = el.fontSize + "px " + el.fontFamily;
  767. ctx.textBaseline = "top";
  768. if (el.bgColor) {
  769. const metrics = ctx.measureText(el.text);
  770. const padding = 2;
  771. ctx.fillStyle = el.bgColor;
  772. ctx.fillRect(el.x - padding, el.y - padding, metrics.width + 2 * padding, el.fontSize + 2 * padding);
  773. }
  774. ctx.fillStyle = el.color;
  775. ctx.fillText(el.text, el.x, el.y);
  776. const metrics = ctx.measureText(el.text);
  777. el.width = metrics.width; el.height = el.fontSize;
  778. } else if (el.type === "shape") {
  779. ctx.strokeStyle = el.color; ctx.lineWidth = 2;
  780. if (el.shape === "square") ctx.strokeRect(el.x, el.y, el.width, el.height);
  781. else if (el.shape === "circle") {
  782. ctx.beginPath();
  783. ctx.arc(el.x + el.width / 2, el.y + el.height / 2, el.width / 2, 0, Math.PI * 2);
  784. ctx.stroke();
  785. }
  786. }
  787. }
  788. }
  789. // Función para dibujar el canvas completo, incluyendo handles y resaltado por hover/selección
  790. function drawCustomCanvas(forceRedraw = false) {
  791. const rect = customCanvas.getBoundingClientRect();
  792.  
  793. // Verificar si hay cambio de tamaño
  794. if (customCanvas.width !== rect.width || customCanvas.height !== rect.height || forceRedraw) {
  795. customCanvas.width = rect.width;
  796. customCanvas.height = rect.height;
  797. }
  798.  
  799. ctx.clearRect(0, 0, customCanvas.width, customCanvas.height);
  800. customElements.forEach(el => { drawElement(el); });
  801.  
  802. // Si hay un elemento hover (y no está seleccionado) se dibuja su borde en verde
  803. if (hoveredElement && hoveredElement !== selectedElement) {
  804. ctx.save();
  805. ctx.strokeStyle = "green";
  806. ctx.lineWidth = 2;
  807. if (hoveredElement.rotation && hoveredElement.rotation !== 0) {
  808. const cx = hoveredElement.x + hoveredElement.width / 2;
  809. const cy = hoveredElement.y + hoveredElement.height / 2;
  810. ctx.translate(cx, cy);
  811. ctx.rotate(hoveredElement.rotation);
  812. ctx.strokeRect(-hoveredElement.width / 2, -hoveredElement.height / 2, hoveredElement.width, hoveredElement.height);
  813. } else {
  814. ctx.strokeRect(hoveredElement.x, hoveredElement.y, hoveredElement.width, hoveredElement.height);
  815. }
  816. ctx.restore();
  817. }
  818.  
  819. // Si hay un elemento seleccionado, dibujar borde verde, fondo semitransparente e indicadores
  820. if (selectedElement) {
  821. ctx.save();
  822. if (selectedElement.rotation && selectedElement.rotation !== 0) {
  823. const cx = selectedElement.x + selectedElement.width / 2;
  824. const cy = selectedElement.y + selectedElement.height / 2;
  825. ctx.translate(cx, cy);
  826. ctx.rotate(selectedElement.rotation);
  827.  
  828. // Fondo verde semitransparente y borde
  829. ctx.fillStyle = "rgba(0,255,0,0.2)";
  830. ctx.fillRect(-selectedElement.width / 2, -selectedElement.height / 2, selectedElement.width, selectedElement.height);
  831. ctx.strokeStyle = "green";
  832. ctx.lineWidth = 2;
  833. ctx.strokeRect(-selectedElement.width / 2, -selectedElement.height / 2, selectedElement.width, selectedElement.height);
  834.  
  835. // Indicador de rotación (círculo verde con flecha)
  836. ctx.beginPath();
  837. ctx.arc(0, -selectedElement.height / 2 - 20, 8, 0, Math.PI * 2);
  838. ctx.fillStyle = "green";
  839. ctx.fill();
  840. ctx.strokeStyle = "white";
  841. ctx.lineWidth = 2;
  842. // Dibujar flecha circular
  843. ctx.beginPath();
  844. ctx.arc(0, -selectedElement.height / 2 - 20, 12, -Math.PI / 2, Math.PI / 2, false);
  845. ctx.stroke();
  846. // Punta de la flecha
  847. ctx.beginPath();
  848. ctx.moveTo(4, -selectedElement.height / 2 - 20);
  849. ctx.lineTo(8, -selectedElement.height / 2 - 24);
  850. ctx.lineTo(12, -selectedElement.height / 2 - 20);
  851. ctx.stroke();
  852.  
  853. // Indicador de redimensión (cuadrado verde)
  854. ctx.fillStyle = "green";
  855. ctx.fillRect(selectedElement.width / 2 - 8, selectedElement.height / 2 - 8, 16, 16);
  856. ctx.strokeStyle = "white";
  857. ctx.strokeRect(selectedElement.width / 2 - 8, selectedElement.height / 2 - 8, 16, 16);
  858.  
  859. } else {
  860. // Fondo verde semitransparente y borde
  861. ctx.fillStyle = "rgba(0,255,0,0.2)";
  862. ctx.fillRect(selectedElement.x, selectedElement.y, selectedElement.width, selectedElement.height);
  863. ctx.strokeStyle = "green";
  864. ctx.lineWidth = 2;
  865. ctx.strokeRect(selectedElement.x, selectedElement.y, selectedElement.width, selectedElement.height);
  866.  
  867. // Indicador de rotación
  868. ctx.beginPath();
  869. ctx.arc(selectedElement.x + selectedElement.width / 2, selectedElement.y - 20, 8, 0, Math.PI * 2);
  870. ctx.fillStyle = "green";
  871. ctx.fill();
  872. ctx.strokeStyle = "white";
  873. ctx.lineWidth = 2;
  874. // Dibujar flecha circular
  875. ctx.beginPath();
  876. ctx.arc(selectedElement.x + selectedElement.width / 2, selectedElement.y - 20, 12, -Math.PI / 2, Math.PI / 2, false);
  877. ctx.stroke();
  878. // Punta de la flecha
  879. ctx.beginPath();
  880. ctx.moveTo(selectedElement.x + selectedElement.width / 2 + 4, selectedElement.y - 20);
  881. ctx.lineTo(selectedElement.x + selectedElement.width / 2 + 8, selectedElement.y - 24);
  882. ctx.lineTo(selectedElement.x + selectedElement.width / 2 + 12, selectedElement.y - 20);
  883. ctx.stroke();
  884.  
  885. // Indicador de redimensión
  886. ctx.fillStyle = "green";
  887. ctx.fillRect(selectedElement.x + selectedElement.width - 8, selectedElement.y + selectedElement.height - 8, 16, 16);
  888. ctx.strokeStyle = "white";
  889. ctx.strokeRect(selectedElement.x + selectedElement.width - 8, selectedElement.y + selectedElement.height - 8, 16, 16);
  890. }
  891. ctx.restore();
  892. }
  893.  
  894. // Dibujar bordes azul dashed para multi-selección
  895. multiSelectedElements.forEach(el => {
  896. ctx.save();
  897. ctx.strokeStyle = "blue";
  898. ctx.lineWidth = 1;
  899. ctx.setLineDash([5, 5]);
  900. ctx.strokeRect(el.x, el.y, el.width, el.height);
  901. ctx.restore();
  902. });
  903.  
  904. // Dibujar rectángulo de selección múltiple si está activo
  905. if (multiSelectRect) {
  906. ctx.save();
  907. ctx.strokeStyle = "blue";
  908. ctx.lineWidth = 1;
  909. ctx.setLineDash([5, 5]);
  910. const rx = Math.min(multiSelectRect.startX, multiSelectRect.currentX);
  911. const ry = Math.min(multiSelectRect.startY, multiSelectRect.currentY);
  912. const rw = Math.abs(multiSelectRect.currentX - multiSelectRect.startX);
  913. const rh = Math.abs(multiSelectRect.currentY - multiSelectRect.startY);
  914. ctx.strokeRect(rx, ry, rw, rh);
  915. ctx.restore();
  916. }
  917. }
  918.  
  919.  
  920. // Actualizar variable hoveredElement según la posición del mouse
  921. customCanvas.addEventListener("mousemove", (e) => {
  922. const rect = customCanvas.getBoundingClientRect();
  923. currentMousePos = { x: e.clientX - rect.left, y: e.clientY - rect.top };
  924. // Si no se está arrastrando, redimensionando, rotando o dibujando, detectar hover
  925. if (!isDragging && !isResizing && !isRotating && !drawingInProgress) {
  926. hoveredElement = null;
  927. for (let i = customElements.length - 1; i >= 0; i--) {
  928. const el = customElements[i];
  929. if (isPointInElement(el, currentMousePos.x, currentMousePos.y)) {
  930. hoveredElement = el;
  931. break;
  932. }
  933. }
  934. drawCustomCanvas();
  935. }
  936. });
  937.  
  938. // =============================
  939. // Eventos del canvas
  940. // =============================
  941. customCanvas.addEventListener("mousedown", (e) => {
  942. const rect = customCanvas.getBoundingClientRect();
  943. const x = e.clientX - rect.left, y = e.clientY - rect.top;
  944. // Si hay un elemento seleccionado, comprobar handle de rotación
  945. if (selectedElement) {
  946. const cx = selectedElement.x + selectedElement.width / 2;
  947. const cy = selectedElement.y + selectedElement.height / 2;
  948. const rot = selectedElement.rotation || 0;
  949. const handleOffset = { x: 0, y: -(selectedElement.height / 2 + 20) };
  950. const rx = cx + handleOffset.x * Math.cos(rot) - handleOffset.y * Math.sin(rot);
  951. const ry = cy + handleOffset.x * Math.sin(rot) + handleOffset.y * Math.cos(rot);
  952. if (distance({ x, y }, { x: rx, y: ry }) < 15) {
  953. isRotating = true;
  954. initialRotateAngle = Math.atan2(y - cy, x - cx);
  955. initialElementRotation = selectedElement.rotation || 0;
  956. return;
  957. }
  958. // Comprobar handle de resize
  959. const vectorBR = { x: selectedElement.width / 2, y: selectedElement.height / 2 };
  960. const brx = cx + vectorBR.x * Math.cos(rot) - vectorBR.y * Math.sin(rot);
  961. const bry = cy + vectorBR.x * Math.sin(rot) + vectorBR.y * Math.cos(rot);
  962. if (distance({ x, y }, { x: brx, y: bry }) < 15) {
  963. isResizing = true;
  964. resizeStartX = x; resizeStartY = y;
  965. if (selectedElement.type === "text" || selectedElement.type === "emoji") {
  966. originalWidth = selectedElement.width;
  967. originalFontSize = selectedElement.fontSize;
  968. } else {
  969. originalWidth = selectedElement.width;
  970. originalHeight = selectedElement.height;
  971. }
  972. return;
  973. }
  974. }
  975. if (isDrawingMode) {
  976. drawingInProgress = true;
  977. currentDrawing = { type: "drawing", points: [{ x, y }], color: drawingColor, brushSize: brushSizeInput.value, rotation: 0 };
  978. selectedElement = null;
  979. } else if (isMultiSelectMode) {
  980. let inSelected = multiSelectedElements.some(el => x >= el.x && x <= el.x + el.width && y >= el.y && y <= el.y + el.height);
  981. if (inSelected && multiSelectedElements.length > 0) {
  982. isGroupDragging = true;
  983. groupDragStart = { startX: x, startY: y, positions: multiSelectedElements.map(el => ({ x: el.x, y: el.y })) };
  984. } else {
  985. multiSelectRect = { startX: x, startY: y, currentX: x, currentY: y };
  986. multiSelectedElements = [];
  987. }
  988. selectedElement = null;
  989. } else {
  990. let found = false;
  991. for (let i = customElements.length - 1; i >= 0; i--) {
  992. const el = customElements[i];
  993. if (el.type === "drawing") {
  994. const xs = el.points.map(p => p.x), ys = el.points.map(p => p.y);
  995. const minX = Math.min(...xs), maxX = Math.max(...xs);
  996. const minY = Math.min(...ys), maxY = Math.max(...ys);
  997. if (x >= minX && x <= maxX && y >= minY && y <= maxY) { selectedElement = el; offsetX = x - minX; offsetY = y - minY; found = true; break; }
  998. } else {
  999. if (isPointInElement(el, x, y)) {
  1000. selectedElement = el;
  1001. 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) {
  1002. isResizing = true;
  1003. resizeStartX = x; resizeStartY = y;
  1004. if (el.type === "text" || el.type === "emoji") {
  1005. originalWidth = el.width; originalFontSize = el.fontSize;
  1006. } else {
  1007. originalWidth = el.width; originalHeight = el.height;
  1008. }
  1009. } else {
  1010. isDragging = true;
  1011. offsetX = x - el.x; offsetY = y - el.y;
  1012. }
  1013. found = true; break;
  1014. }
  1015. }
  1016. }
  1017. if (!found) { selectedElement = null; }
  1018. drawCustomCanvas();
  1019. updateTextEditorPanel();
  1020. }
  1021. });
  1022.  
  1023. customCanvas.addEventListener("mousemove", (e) => {
  1024. const rect = customCanvas.getBoundingClientRect();
  1025. const x = e.clientX - rect.left, y = e.clientY - rect.top;
  1026. currentMousePos = { x, y };
  1027. // Si se está en modo rotación
  1028. if (isRotating && selectedElement) {
  1029. const cx = selectedElement.x + selectedElement.width / 2;
  1030. const cy = selectedElement.y + selectedElement.height / 2;
  1031. const currentAngle = Math.atan2(y - cy, x - cx);
  1032. selectedElement.rotation = initialElementRotation + (currentAngle - initialRotateAngle);
  1033. drawCustomCanvas();
  1034. return;
  1035. }
  1036. if (isDrawingMode && drawingInProgress && currentDrawing) {
  1037. currentDrawing.points.push({ x, y });
  1038. drawCustomCanvas();
  1039. ctx.strokeStyle = currentDrawing.color;
  1040. ctx.lineWidth = currentDrawing.brushSize;
  1041. ctx.lineJoin = "round"; ctx.lineCap = "round";
  1042. ctx.beginPath();
  1043. currentDrawing.points.forEach((point, index) => { index === 0 ? ctx.moveTo(point.x, point.y) : ctx.lineTo(point.x, point.y); });
  1044. ctx.stroke();
  1045. } else if (isMultiSelectMode) {
  1046. if (isGroupDragging && groupDragStart) {
  1047. const deltaX = x - groupDragStart.startX, deltaY = y - groupDragStart.startY;
  1048. multiSelectedElements.forEach((el, idx) => {
  1049. const initPos = groupDragStart.positions[idx];
  1050. el.x = initPos.x + deltaX; el.y = initPos.y + deltaY;
  1051. });
  1052. drawCustomCanvas();
  1053. } else if (multiSelectRect) {
  1054. multiSelectRect.currentX = x; multiSelectRect.currentY = y;
  1055. drawCustomCanvas();
  1056. } else if (isDragging && selectedElement && !isDrawingMode && !isResizing && !isRotating) {
  1057. selectedElement.x = x - offsetX; selectedElement.y = y - offsetY;
  1058. drawCustomCanvas();
  1059. }
  1060. } else {
  1061. if (isResizing && selectedElement) {
  1062. let newWidth = originalWidth + (x - resizeStartX);
  1063. if (newWidth < 20) newWidth = 20;
  1064. if (selectedElement.type === "text" || selectedElement.type === "emoji") {
  1065. let scale = newWidth / originalWidth;
  1066. selectedElement.fontSize = originalFontSize * scale;
  1067. selectedElement.width = newWidth;
  1068. selectedElement.height = originalFontSize * scale;
  1069. } else {
  1070. let newHeight = originalHeight + (y - resizeStartY);
  1071. if (newHeight < 20) newHeight = 20;
  1072. selectedElement.width = newWidth; selectedElement.height = newHeight;
  1073. }
  1074. drawCustomCanvas();
  1075. } else if (isDragging && selectedElement && !isDrawingMode && !isResizing && !isRotating) {
  1076. selectedElement.x = x - offsetX; selectedElement.y = y - offsetY;
  1077. drawCustomCanvas();
  1078. }
  1079. }
  1080. // Actualizar cursor sobre handles (para rotación y resize)
  1081. if (!isDragging && !isResizing && !isRotating && selectedElement) {
  1082. const cx = selectedElement.x + selectedElement.width / 2;
  1083. const cy = selectedElement.y + selectedElement.height / 2;
  1084. const rot = selectedElement.rotation || 0;
  1085. const handleOffset = { x: 0, y: -(selectedElement.height / 2 + 20) };
  1086. const rx = cx + handleOffset.x * Math.cos(rot) - handleOffset.y * Math.sin(rot);
  1087. const ry = cy + handleOffset.x * Math.sin(rot) + handleOffset.y * Math.cos(rot);
  1088. const vectorBR = { x: selectedElement.width / 2, y: selectedElement.height / 2 };
  1089. const brx = cx + vectorBR.x * Math.cos(rot) - vectorBR.y * Math.sin(rot);
  1090. const bry = cy + vectorBR.x * Math.sin(rot) + vectorBR.y * Math.cos(rot);
  1091. if (distance({ x, y }, { x: rx, y: ry }) < 15) customCanvas.style.cursor = "grab";
  1092. else if (distance({ x, y }, { x: brx, y: bry }) < 15) customCanvas.style.cursor = "nwse-resize";
  1093. else customCanvas.style.cursor = "default";
  1094. }
  1095. });
  1096.  
  1097. customCanvas.addEventListener("mouseup", () => {
  1098. if (isDrawingMode && drawingInProgress && currentDrawing) {
  1099. customElements.push(currentDrawing);
  1100. saveHistory();
  1101. currentDrawing = null; drawingInProgress = false;
  1102. }
  1103. if (isMultiSelectMode) {
  1104. if (isGroupDragging) { isGroupDragging = false; groupDragStart = null; saveHistory(); }
  1105. else if (multiSelectRect) {
  1106. const rx = Math.min(multiSelectRect.startX, multiSelectRect.currentX);
  1107. const ry = Math.min(multiSelectRect.startY, multiSelectRect.currentY);
  1108. const rw = Math.abs(multiSelectRect.currentX - multiSelectRect.startX);
  1109. const rh = Math.abs(multiSelectRect.currentY - multiSelectRect.startY);
  1110. multiSelectedElements = customElements.filter(el => (el.x >= rx && el.y >= ry && (el.x + el.width) <= (rx + rw) && (el.y + el.height) <= (ry + rh)));
  1111. multiSelectRect = null; drawCustomCanvas();
  1112. }
  1113. } else { if (isDragging || isResizing || isRotating) saveHistory(); }
  1114. isResizing = false; isDragging = false; isRotating = false;
  1115. drawCustomCanvas();
  1116. });
  1117. customCanvas.addEventListener("mouseleave", () => {
  1118. if (isDrawingMode && drawingInProgress && currentDrawing) {
  1119. customElements.push(currentDrawing); saveHistory();
  1120. currentDrawing = null; drawingInProgress = false;
  1121. }
  1122. isResizing = false; isDragging = false; isGroupDragging = false; multiSelectRect = null; isRotating = false;
  1123. drawCustomCanvas();
  1124. });
  1125.  
  1126. // =============================
  1127. // Agregar imagen personalizada
  1128. // =============================
  1129. addCustomImageButton.addEventListener("click", () => customFileInput.click());
  1130. customFileInput.addEventListener("change", () => {
  1131. if (customFileInput.files && customFileInput.files[0]) {
  1132. const file = customFileInput.files[0];
  1133. const reader = new FileReader();
  1134. reader.onload = function (event) {
  1135. const img = new Image();
  1136. img.onload = function () {
  1137. const size = getDefaultImageSize(img);
  1138. if (file.type === "image/gif") {
  1139. const element = { type: "image", isGif: true, originalBlob: file, img: new Image(), x: 50, y: 50, width: size.width, height: size.height, rotation: 0 };
  1140. element.img.src = URL.createObjectURL(file);
  1141. customElements.push(element);
  1142. } else {
  1143. const element = { type: "image", img: img, x: 50, y: 50, width: size.width, height: size.height, rotation: 0 };
  1144. customElements.push(element);
  1145. }
  1146. saveHistory(); drawCustomCanvas();
  1147. };
  1148. img.src = event.target.result;
  1149. };
  1150. reader.readAsDataURL(file);
  1151. }
  1152. });
  1153.  
  1154. // =============================
  1155. // Panel de emojis
  1156. // =============================
  1157. let currentCategory = 'faces_emotion';
  1158. const emojiContainer = document.createElement("div");
  1159. emojiContainer.id = "emojiContainer";
  1160. const emojiCategoryContainer = document.createElement("div");
  1161. emojiCategoryContainer.id = "emojiCategoryContainer";
  1162. const btnEmotions = document.createElement("button");
  1163. btnEmotions.textContent = "Emociones";
  1164. const btnAnimals = document.createElement("button");
  1165. btnAnimals.textContent = "Animales";
  1166. btnEmotions.addEventListener("click", () => { currentCategory = 'faces_emotion'; loadEmojis(currentCategory); });
  1167. btnAnimals.addEventListener("click", () => { currentCategory = 'animals'; loadEmojis(currentCategory); });
  1168. emojiCategoryContainer.appendChild(btnEmotions);
  1169. emojiCategoryContainer.appendChild(btnAnimals);
  1170. const emojiContent = document.createElement("div");
  1171. emojiContent.id = "emojiContent";
  1172. emojiContainer.appendChild(emojiCategoryContainer);
  1173. emojiContainer.appendChild(emojiContent);
  1174. document.body.appendChild(emojiContainer);
  1175. const emojiToggleButton = document.getElementById("openEmojiPanel");
  1176. emojiToggleButton.addEventListener("click", () => {
  1177. if (emojiContainer.style.display === "none" || emojiContainer.style.display === "") { emojiContainer.style.display = "flex"; loadEmojis(currentCategory); }
  1178. else { emojiContainer.style.display = "none"; }
  1179. });
  1180. function loadEmojis(category) {
  1181. emojiContent.innerHTML = "";
  1182. const data = emojis[category];
  1183. data.forEach(emojiData => {
  1184. const btn = document.createElement("button");
  1185. btn.textContent = emojiData.emoji;
  1186. btn.style.fontSize = "24px"; btn.style.padding = "5px";
  1187. btn.classList.add("addEmoji");
  1188. btn.style.border = "1px solid #ccc"; btn.style.borderRadius = "5px";
  1189. btn.style.cursor = "pointer"; btn.style.backgroundColor = "#f9f9f9";
  1190. btn.addEventListener("click", () => {
  1191. const element = { type: "emoji", emoji: emojiData.emoji, x: 50, y: 50, fontSize: 70, width: 0, height: 0, rotation: 0 };
  1192. customElements.push(element); saveHistory(); drawCustomCanvas();
  1193. });
  1194. emojiContent.appendChild(btn);
  1195. });
  1196. }
  1197. loadEmojis(currentCategory);
  1198.  
  1199. // =============================
  1200. // Configurar menú emergente del lápiz
  1201. // =============================
  1202. pencilColorContainer.innerHTML = "";
  1203. const pencilColors = colorsText;
  1204. pencilColors.forEach(color => {
  1205. const btn = document.createElement("div");
  1206. btn.className = "colorButton";
  1207. btn.style.backgroundColor = color;
  1208. btn.addEventListener("click", () => {
  1209. drawingColor = color;
  1210. Array.from(pencilColorContainer.children).forEach(b => b.style.borderColor = "#ccc");
  1211. btn.style.borderColor = "#000";
  1212. });
  1213. pencilColorContainer.appendChild(btn);
  1214. });
  1215. pencilSizeContainer.innerHTML = "";
  1216. const pencilSizes = [2, 4, 6, 8];
  1217. pencilSizes.forEach(size => {
  1218. const btn = document.createElement("div");
  1219. btn.className = "sizeButton";
  1220. btn.setAttribute("data-size", size);
  1221. btn.style.backgroundColor = "#777";
  1222. btn.addEventListener("click", () => {
  1223. brushSizeInput.value = size;
  1224. if (selectedElement && selectedElement.type === "drawing") {
  1225. selectedElement.brushSize = size; drawCustomCanvas();
  1226. }
  1227. Array.from(pencilSizeContainer.children).forEach(b => b.style.borderColor = "#ccc");
  1228. btn.style.borderColor = "#000";
  1229. });
  1230. pencilSizeContainer.appendChild(btn);
  1231. });
  1232. // Toggle lápiz: ahora alterna entre activar y desactivar el modo dibujo
  1233. toggleDrawingButton.addEventListener("click", () => {
  1234. if (isDrawingMode) {
  1235. isDrawingMode = false;
  1236. pencilOptionsPanel.style.display = "none";
  1237. toggleDrawingButton.style.backgroundColor = "#005c4b";
  1238. } else {
  1239. isDrawingMode = true;
  1240. pencilOptionsPanel.style.display = "block";
  1241. toggleDrawingButton.style.backgroundColor = "#ddd";
  1242. // Si se activa el lápiz, desactivar modo formas
  1243. shapeOptionsPanel.style.display = "none";
  1244. selectedElement = null;
  1245. }
  1246. });
  1247.  
  1248. // =============================
  1249. // Configurar menú emergente para formas
  1250. // =============================
  1251. toggleShapesButton.addEventListener("click", () => {
  1252. pencilOptionsPanel.style.display = "none";
  1253. shapeOptionsPanel.style.display = (shapeOptionsPanel.style.display === "none" || shapeOptionsPanel.style.display === "") ? "block" : "none";
  1254. toggleShapesButton.style.backgroundColor = shapeOptionsPanel.style.display === "block" ? "#ddd" : "#005c4b";
  1255. });
  1256. shapeSquareButton.addEventListener("click", () => {
  1257. const element = { type: "shape", shape: "square", x: 50, y: 50, width: 100, height: 100, color: "#000000", rotation: 0 };
  1258. customElements.push(element); saveHistory(); drawCustomCanvas();
  1259. });
  1260. shapeCircleButton.addEventListener("click", () => {
  1261. const element = { type: "shape", shape: "circle", x: 50, y: 50, width: 100, height: 100, color: "#000000", rotation: 0 };
  1262. customElements.push(element); saveHistory(); drawCustomCanvas();
  1263. });
  1264.  
  1265. // =============================
  1266. // Configurar botón multi-select
  1267. // =============================
  1268. toggleMultiSelectButton.addEventListener("click", () => {
  1269. isMultiSelectMode = !isMultiSelectMode;
  1270. toggleMultiSelectButton.style.backgroundColor = isMultiSelectMode ? "#ddd" : "#005c4b";
  1271. if (!isMultiSelectMode) { multiSelectedElements = []; drawCustomCanvas(); }
  1272. });
  1273.  
  1274. // =============================
  1275. // Configurar edición de texto (botones de color)
  1276. // =============================
  1277. const textColors = colorsText;
  1278. function populateColorButtons(container, callback) {
  1279. container.innerHTML = "";
  1280. textColors.forEach(color => {
  1281. const btn = document.createElement("div");
  1282. btn.className = "colorButton";
  1283. btn.style.backgroundColor = color;
  1284. btn.addEventListener("click", () => { callback(color); });
  1285. container.appendChild(btn);
  1286. });
  1287. }
  1288. populateColorButtons(textColorButtons, (color) => { if (selectedElement && selectedElement.type === "text") { selectedElement.color = color; drawCustomCanvas(); } });
  1289. populateColorButtons(textBgButtons, (color) => { if (selectedElement && selectedElement.type === "text") { selectedElement.bgColor = color; drawCustomCanvas(); } });
  1290. addTextButton.addEventListener("click", () => {
  1291. const text = prompt("Enter the text:");
  1292. if (text) {
  1293. const element = { type: "text", text: text, x: 50, y: 50, fontSize: 30, width: 0, height: 0, color: "#000000", bgColor: "", rotation: 0 };
  1294. customElements.push(element); saveHistory(); drawCustomCanvas();
  1295. }
  1296. });
  1297.  
  1298. // =============================
  1299. // Resto de controles: bringForward, sendBackward, etc.
  1300. // =============================
  1301. bringForwardButton.addEventListener("click", () => {
  1302. if (selectedElement) {
  1303. const idx = customElements.indexOf(selectedElement);
  1304. if (idx !== -1 && idx < customElements.length - 1) { customElements.splice(idx, 1); customElements.push(selectedElement); saveHistory(); drawCustomCanvas(); }
  1305. }
  1306. });
  1307. sendBackwardButton.addEventListener("click", () => {
  1308. if (selectedElement) {
  1309. const idx = customElements.indexOf(selectedElement);
  1310. if (idx > 0) { customElements.splice(idx, 1); customElements.unshift(selectedElement); saveHistory(); drawCustomCanvas(); }
  1311. }
  1312. });
  1313. deleteElementButton.addEventListener("click", () => {
  1314. if (selectedElement) {
  1315. const idx = customElements.indexOf(selectedElement);
  1316. if (idx !== -1) { customElements.splice(idx, 1); selectedElement = null; saveHistory(); drawCustomCanvas(); }
  1317. }
  1318. });
  1319. clearCanvasButton.addEventListener("click", () => { customElements = []; selectedElement = null; saveHistory(); drawCustomCanvas(); });
  1320. downloadButton.addEventListener("click", () => {
  1321. const dataURL = customCanvas.toDataURL("image/png");
  1322. const a = document.createElement("a");
  1323. a.href = dataURL; a.download = "sticker.png"; a.click();
  1324. });
  1325. undoButton.addEventListener("click", () => {
  1326. if (historyIndex > 0) { historyIndex--; customElements = cloneCustomElements(history[historyIndex]); drawCustomCanvas(); }
  1327. });
  1328. redoButton.addEventListener("click", () => {
  1329. if (historyIndex < history.length - 1) { historyIndex++; customElements = cloneCustomElements(history[historyIndex]); drawCustomCanvas(); }
  1330. });
  1331.  
  1332. // =============================
  1333. // Panel de edición de texto: actualización en tiempo real
  1334. // =============================
  1335. function updateTextEditorPanel() {
  1336. if (selectedElement && selectedElement.type === "text") {
  1337. textEditorPanel.style.display = "block";
  1338. textContentInput.value = selectedElement.text;
  1339. textFontSelect.value = selectedElement.fontFamily || "";
  1340. textFontSizeInput.value = selectedElement.fontSize;
  1341. } else { textEditorPanel.style.display = "none"; }
  1342. }
  1343. textContentInput.addEventListener("input", () => { if (selectedElement && selectedElement.type === "text") { selectedElement.text = textContentInput.value; drawCustomCanvas(); } });
  1344. textFontSelect.addEventListener("change", () => { if (selectedElement && selectedElement.type === "text") { selectedElement.fontFamily = textFontSelect.value; drawCustomCanvas(); } });
  1345. textFontSizeInput.addEventListener("input", () => { if (selectedElement && selectedElement.type === "text") { selectedElement.fontSize = parseInt(textFontSizeInput.value); drawCustomCanvas(); } });
  1346.  
  1347. // =============================
  1348. // Animación: Si hay algún GIF, redibujar continuamente
  1349. // =============================
  1350. function animateCanvas() {
  1351. if (customElements.some(el => el.type === "image" && el.isGif)) { drawCustomCanvas(); }
  1352. requestAnimationFrame(animateCanvas);
  1353. }
  1354. animateCanvas();
  1355.  
  1356. // =============================
  1357. // Crear sticker personalizado
  1358. // =============================
  1359. createCustomStickerButton.addEventListener("click", () => {
  1360.  
  1361. if (customCreateClicked) return;
  1362. customCreateClicked = true;
  1363. createCustomStickerButton.disabled = true;
  1364. customStatus.textContent = "Generating custom sticker...";
  1365. selectedElement = null;
  1366. hoveredElement = null;
  1367. multiSelectedElements = [];
  1368. drawCustomCanvas();
  1369. const gifElements = customElements.filter(el => el.type === "image" && el.isGif);
  1370. if (gifElements.length === 1 && customElements.length === 1) {
  1371. simulateWhatsAppFileUpload(gifElements[0].originalBlob, customStatus);
  1372. customElements = []; selectedElement = null; drawCustomCanvas();
  1373. createCustomStickerButton.disabled = false; customCreateClicked = false;
  1374. } else {
  1375. customCanvas.toBlob(function (blob) {
  1376. if (!blob) { customStatus.textContent = "Error generating sticker."; return; }
  1377. const stickerFile = new File([blob], "sticker_personalizado.webp", { type: "image/webp" });
  1378. simulateWhatsAppFileUpload(stickerFile, customStatus);
  1379. customElements = []; selectedElement = null; drawCustomCanvas();
  1380. createCustomStickerButton.disabled = false; customCreateClicked = false;
  1381. }, "image/webp");
  1382. }
  1383. });
  1384. }
  1385.  
  1386. // =============================
  1387. // Simulación de subida a WhatsApp
  1388. // =============================
  1389. function simulateWhatsAppFileUpload(file, statusElement) {
  1390. openAttachmentMenu();
  1391. setTimeout(() => {
  1392. const waFileInput = document.querySelector("input[type='file'][accept*='image']");
  1393. if (!waFileInput) { statusElement.textContent = "WhatsApp file input not found"; return; }
  1394. const dt = new DataTransfer();
  1395. dt.items.add(file);
  1396. waFileInput.files = dt.files;
  1397. const event = new Event("change", { bubbles: true });
  1398. waFileInput.dispatchEvent(event);
  1399. statusElement.textContent = "Sticker loaded. Sending...";
  1400. setTimeout(() => {
  1401. const sendButton = document.querySelector("span[data-icon='send']");
  1402. if (sendButton) { sendButton.click(); statusElement.textContent = "Sticker send"; }
  1403. else { statusElement.textContent = "Submit button not found"; }
  1404. }, 1000);
  1405. }, 500);
  1406. }
  1407.  
  1408. // =============================
  1409. // Abrir menú de adjuntos de WhatsApp
  1410. // =============================
  1411. function openAttachmentMenu() {
  1412. const attachmentButton = document.querySelector("div[title='Adjuntar']") || document.querySelector("span[data-icon='clip']");
  1413. if (attachmentButton) { attachmentButton.click(); }
  1414. }
  1415.  
  1416. // Función auxiliar: distancia entre dos puntos
  1417. function distance(p1, p2) {
  1418. return Math.sqrt((p1.x - p2.x) ** 2 + (p1.y - p2.y) ** 2);
  1419. }
  1420. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址