WhatsApp Sticker Creator with Custom Maker Enhanced

Create custom stickers in WhatsApp Web.

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