Greasy Fork is available in English.

Kawaii Helper & Drawing Bot for Gartic.io

Helper for Gartic.io with auto-guess, drawing assistance, and drawing bot

  1. // ==UserScript==
  2. // @name Kawaii Helper & Drawing Bot for Gartic.io
  3. // @name:tr Gartic.io için Kawaii Yardımcı & Çizim Botu
  4. // @namespace https://github.com/Gartic-Developers/Kawaii-Helper
  5. // @version 2025-05-10
  6. // @description Helper for Gartic.io with auto-guess, drawing assistance, and drawing bot
  7. // @description:tr Gartic.io için otomatik tahmin, çizim yardımı ve çizim botu ile yardımcı
  8. // @author anonimbiri & Gartic-Developers
  9. // @license MIT
  10. // @match *://*.gartic.io/*
  11. // @exclude *://gartic.io/_next/*
  12. // @exclude *://gartic.io/static/*
  13. // @icon https://cdn.jsdelivr.net/gh/anonimbiri-IsBack/Kawaii-Helper-copy@refs/heads/main/Assets/kawaii-logo.png
  14. // @supportURL https://github.com/Gartic-Developers/Kawaii-Helper/issues/new?labels=bug&type=bug&template=bug_report.md&title=Bug+Report
  15. // @homepage https://github.com/Gartic-Developers/Kawaii-Helper
  16. // @run-at document-start
  17. // @tag games
  18. // @grant none
  19. // @noframes
  20. // ==/UserScript==
  21.  
  22. (function() {
  23. 'use strict';
  24.  
  25. class KawaiiHelper {
  26. constructor() {
  27. this.translations = {
  28. en: {
  29. "✧ Kawaii Helper ✧": "✧ Kawaii Helper ✧",
  30. "Guessing": "Guessing",
  31. "Drawing": "Drawing",
  32. "Auto Guess": "Auto Guess",
  33. "Speed": "Speed",
  34. "Custom Words": "Custom Words",
  35. "Drop word list here or click to upload": "Drop word list here or click to upload",
  36. "Enter pattern (e.g., ___e___)": "Enter pattern (e.g., ___e___)",
  37. "Type a pattern to see matches ✧": "Type a pattern to see matches ✧",
  38. "Upload a custom word list ✧": "Upload a custom word list ✧",
  39. "No words available ✧": "No words available ✧",
  40. "No matches found ✧": "No matches found ✧",
  41. "Tried Words": "Tried Words",
  42. "Drop image here or click to upload": "Drop image here or click to upload",
  43. "Search on Google Images 🡵": "Search on Google Images 🡵",
  44. "Draw Speed": "Draw Speed",
  45. "Color Tolerance": "Color Tolerance",
  46. "Draw Now ✧": "Draw Now ✧",
  47. "Stop Drawing ✧": "Stop Drawing ✧",
  48. "Made with ♥ by Anonimbiri & Gartic-Developers": "Made with ♥ by Anonimbiri & Gartic-Developers",
  49. "Loaded ${wordList['Custom'].length} words from ${file.name}": "Loaded ${wordList['Custom'].length} words from ${file.name}",
  50. "Not your turn or game not loaded! ✧": "Not your turn or game not loaded! ✧",
  51. "Game not ready or not your turn! ✧": "Game not ready or not your turn! ✧",
  52. "Canvas not accessible! ✧": "Canvas not accessible! ✧",
  53. "Canvas context not available! ✧": "Canvas context not available! ✧",
  54. "Temp canvas context failed! ✧": "Temp canvas context failed! ✧",
  55. "Image data error: ${e.message} ✧": "Image data error: ${e.message} ✧",
  56. "Drawing completed! ✧": "Drawing completed! ✧",
  57. "Failed to load image! ✧": "Failed to load image! ✧",
  58. "Drawing stopped! ✧": "Drawing stopped! ✧",
  59. "Settings": "Settings",
  60. "Auto Kick": "Auto Kick",
  61. "No Kick Cooldown": "No Kick Cooldown",
  62. "Chat Bypass Censorship": "Chat Bypass Censorship",
  63. "New update available!": "New update available!"
  64. },
  65. tr: {
  66. "✧ Kawaii Helper ✧": "✧ Kawaii Yardımcı ✧",
  67. "Guessing": "Tahmin",
  68. "Drawing": "Çizim",
  69. "Auto Guess": "Otomatik Tahmin",
  70. "Speed": "Hız",
  71. "Custom Words": "Özel Kelimeler",
  72. "Drop word list here or click to upload": "Kelime listesini buraya bırak veya yüklemek için tıkla",
  73. "Enter pattern (e.g., ___e___)": "Desen gir (ör., ___e___)",
  74. "Type a pattern to see matches ✧": "Eşleşmeleri görmek için bir desen yaz ✧",
  75. "Upload a custom word list ✧": "Özel bir kelime listesi yükle ✧",
  76. "No words available ✧": "Kelime yok ✧",
  77. "No matches found ✧": "Eşleşme bulunamadı ✧",
  78. "Tried Words": "Denenen Kelimeler",
  79. "Drop image here or click to upload": "Resmi buraya bırak veya yüklemek için tıkla",
  80. "Search on Google Images 🡵": "Google Görsellerde Ara 🡵",
  81. "Draw Speed": "Çizim Hızı",
  82. "Color Tolerance": "Renk Toleransı",
  83. "Draw Now ✧": "Şimdi Çiz ✧",
  84. "Stop Drawing ✧": "Çizimi Durdur ✧",
  85. "Made with ♥ by Anonimbiri & Gartic-Developers": "Anonimbiri & Gartic-Developers tarafından ♥ ile yapıldı",
  86. "Loaded ${wordList['Custom'].length} words from ${file.name}": "${file.name} dosyasından ${wordList['Custom'].length} kelime yüklendi",
  87. "Not your turn or game not loaded! ✧": "Sıra sende değil veya oyun yüklenmedi! ✧",
  88. "Game not ready or not your turn! ✧": "Oyun hazır değil veya sıra sende değil! ✧",
  89. "Canvas not accessible! ✧": "Tuval erişilemez! ✧",
  90. "Canvas context not available! ✧": "Tuval bağlamı kullanılamıyor! ✧",
  91. "Temp canvas context failed! ✧": "Geçici tuval bağlamı başarısız! ✧",
  92. "Image data error: ${e.message} ✧": "Görüntü verisi hatası: ${e.message} ✧",
  93. "Drawing completed! ✧": "Çizim tamamlandı! ✧",
  94. "Failed to load image! ✧": "Görüntü yüklenemedi! ✧",
  95. "Drawing stopped! ✧": "Çizim durduruldu! ✧",
  96. "Settings": "Ayarlar",
  97. "Auto Kick": "Otomatik Atma",
  98. "No Kick Cooldown": "Atma Bekleme Süresi Yok",
  99. "Chat Bypass Censorship": "Sohbet Sansürünü Atlat",
  100. "New update available!": "Yeni güncelleme var!"
  101. }
  102. };
  103. this.currentLang = navigator.language.split('-')[0] in this.translations ? navigator.language.split('-')[0] : 'en';
  104. this.isDrawing = false;
  105. this.wordList = { "Custom": [] };
  106. this.wordListURLs = {
  107. "General (en)": "https://cdn.jsdelivr.net/gh/Qwyua/Gartic-WordList@master/languages/English/general.json",
  108. "General (tr)": "https://cdn.jsdelivr.net/gh/Qwyua/Gartic-WordList@master/languages/Turkish/general.json",
  109. "General (ja)": "https://cdn.jsdelivr.net/gh/Qwyua/Gartic-WordList@master/languages/Japanese/general.json"
  110. };
  111. this.elements = {};
  112. this.state = {
  113. isDragging: false,
  114. initialX: 0,
  115. initialY: 0,
  116. xOffset: 0,
  117. yOffset: 0,
  118. rafId: null,
  119. autoGuessInterval: null,
  120. triedLabelAdded: false
  121. };
  122. this.lastTheme = "Custom";
  123. this.settings = this.loadSettings();
  124. }
  125.  
  126. static init() {
  127. const helper = new KawaiiHelper();
  128. helper.setup();
  129. return helper;
  130. }
  131.  
  132. checkForUpdates() {
  133. const url = 'https://api.github.com/repos/Gartic-Developers/Kawaii-Helper/releases/latest';
  134. const req = new XMLHttpRequest();
  135. req.open("GET", url, false);
  136. req.setRequestHeader('Accept', 'application/vnd.github.v3+json');
  137. try {
  138. req.send();
  139. if (req.status === 200) {
  140. const latest = JSON.parse(req.responseText).tag_name.replace(/^v/, '');
  141. if (latest > GM_info.script.version) {
  142. this.showNotification(
  143. this.localize("New update available!"),
  144. 1e4,
  145. { text: 'Update', action: () => window.open('https://github.com/Gartic-Developers/Kawaii-Helper/releases/latest', '_blank') }
  146. );
  147. }
  148. }
  149. } catch (e) {}
  150. }
  151.  
  152. loadSettings() {
  153. const savedSettings = localStorage.getItem('kawaiiSettings');
  154. return savedSettings ? JSON.parse(savedSettings) : {
  155. autoGuess: false,
  156. guessSpeed: 1000,
  157. customWords: false,
  158. autoKick: false,
  159. noKickCooldown: false,
  160. chatBypassCensorship: false,
  161. drawSpeed: 200,
  162. colorTolerance: 20,
  163. position: null
  164. };
  165. }
  166.  
  167. saveSettings() {
  168. const settings = {
  169. autoGuess: this.elements.autoGuessCheckbox.checked,
  170. guessSpeed: parseInt(this.elements.guessSpeed.value),
  171. customWords: this.elements.customWordsCheckbox.checked,
  172. autoKick: this.elements.autoKickCheckbox.checked,
  173. noKickCooldown: this.elements.noKickCooldownCheckbox.checked,
  174. chatBypassCensorship: this.elements.chatBypassCensorship.checked,
  175. drawSpeed: parseInt(this.elements.drawSpeed.value),
  176. colorTolerance: parseInt(this.elements.colorTolerance.value),
  177. position: {
  178. x: this.state.xOffset,
  179. y: this.state.yOffset
  180. }
  181. };
  182. localStorage.setItem('kawaiiSettings', JSON.stringify(settings));
  183. }
  184.  
  185. localize(key, params = {}) {
  186. let text = this.translations[this.currentLang][key] || key;
  187. for (const [param, value] of Object.entries(params)) {
  188. text = text.replace(`\${${param}}`, value);
  189. }
  190. return text;
  191. }
  192.  
  193. showNotification(message, duration = 3000, button = null) {
  194. const notification = document.createElement('div');
  195. notification.className = 'kawaii-notification';
  196.  
  197. let notificationHTML = `
  198. <span class="kawaii-notification-icon">✧</span>
  199. <span class="kawaii-notification-text">${message}</span>
  200. <button class="kawaii-notification-close">✕</button>
  201. `;
  202.  
  203. if (button) {
  204. notificationHTML = `
  205. <span class="kawaii-notification-icon">✧</span>
  206. <span class="kawaii-notification-text">${message}</span>
  207. <button class="kawaii-notification-button">${button.text}</button>
  208. <button class="kawaii-notification-close">✕</button>
  209. `;
  210. }
  211.  
  212. notification.innerHTML = notificationHTML;
  213. this.elements.notifications.appendChild(notification);
  214. setTimeout(() => notification.classList.add('show'), 10);
  215.  
  216. const timeout = setTimeout(() => {
  217. notification.classList.remove('show');
  218. setTimeout(() => notification.remove(), 300);
  219. }, duration);
  220.  
  221. notification.querySelector('.kawaii-notification-close').addEventListener('click', () => {
  222. clearTimeout(timeout);
  223. notification.classList.remove('show');
  224. setTimeout(() => notification.remove(), 300);
  225. });
  226.  
  227. if (button) {
  228. notification.querySelector('.kawaii-notification-button').addEventListener('click', () => {
  229. button.action();
  230. clearTimeout(timeout);
  231. notification.classList.remove('show');
  232. setTimeout(() => notification.remove(), 300);
  233. });
  234. }
  235. }
  236.  
  237. setup() {
  238. this.interceptScripts();
  239. this.injectFonts();
  240. this.waitForBody(() => {
  241. this.injectHTML();
  242. this.cacheElements();
  243. this.setInitialPosition();
  244. this.applySavedSettings();
  245. this.checkForUpdates();
  246. this.addStyles();
  247. this.bindEvents();
  248. this.initializeGameCheck();
  249. });
  250. }
  251.  
  252. interceptScripts() {
  253. /*const roomScript = `https://cdn.jsdelivr.net/gh/Gartic-Developers/Kawaii-Helper@${GM_info.script.version}/GameSource/room.js`;
  254. const createScript = `https://cdn.jsdelivr.net/gh/Gartic-Developers/Kawaii-Helper@${GM_info.script.version}/GameSource/create.js`;*/
  255. const roomScript = `https://cdn.jsdelivr.net/gh/anonimbiri-IsBack/Kawaii-Helper-copy@master/GameSource/room.js`;
  256. const createScript = `https://cdn.jsdelivr.net/gh/anonimbiri-IsBack/Kawaii-Helper-copy@master/GameSource/create.js`;
  257.  
  258. function downloadFileSync(url) {
  259. const request = new XMLHttpRequest();
  260. request.open("GET", url, false);
  261. request.send();
  262. return request.status === 200 ? request.response : null;
  263. }
  264.  
  265. const observer = new MutationObserver((mutations) => {
  266. mutations.forEach((mutation) => {
  267. if (mutation.addedNodes) {
  268. Array.from(mutation.addedNodes).forEach((node) => {
  269. if (node.nodeName.toLowerCase() === 'script' && node.src && node.src.includes('room') && !node.src.includes('rooms')) {
  270. node.remove();
  271. node.src = '';
  272. node.textContent = '';
  273. const newScript = downloadFileSync(roomScript);
  274. window.kawaiiHelper = this;
  275. Function(newScript)();
  276. } else if (node.nodeName.toLowerCase() === 'script' && node.src && node.src.includes('create')) {
  277. node.remove();
  278. node.src = '';
  279. node.textContent = '';
  280. const newScript = downloadFileSync(createScript);
  281. window.kawaiiHelper = this;
  282. Function(newScript)();
  283. }
  284. });
  285. }
  286. });
  287. });
  288.  
  289. observer.observe(document, { childList: true, subtree: true });
  290. }
  291.  
  292. injectFonts() {
  293. const fontLink = document.createElement('link');
  294. fontLink.rel = 'stylesheet';
  295. fontLink.href = 'https://fonts.googleapis.com/css2?family=M+PLUS+Rounded+1c:wght@400;700&display=swap';
  296. document.head.appendChild(fontLink);
  297. }
  298.  
  299. waitForBody(callback) {
  300. const interval = setInterval(() => {
  301. if (document.body) {
  302. clearInterval(interval);
  303. callback();
  304. }
  305. }, 100);
  306. }
  307.  
  308. injectHTML() {
  309. const kawaiiHTML = `
  310. <div class="kawaii-cheat" id="kawaiiCheat">
  311. <div class="kawaii-header" id="kawaiiHeader">
  312. <img src="https://cdn.jsdelivr.net/gh/anonimbiri-IsBack/Kawaii-Helper-copy@refs/heads/main/Assets/kawaii-logo.png" alt="Anime Girl" class="header-icon">
  313. <h2 data-translate="✧ Kawaii Helper ✧">✧ Kawaii Helper ✧</h2>
  314. <button class="minimize-btn" id="minimizeBtn">▼</button>
  315. </div>
  316. <div class="kawaii-body" id="kawaiiBody">
  317. <div class="kawaii-tabs">
  318. <button class="kawaii-tab active" data-tab="guessing" data-translate="Guessing">Guessing</button>
  319. <button class="kawaii-tab" data-tab="drawing" data-translate="Drawing">Drawing</button>
  320. <button class="kawaii-tab" data-tab="settings" data-translate="Settings">Settings</button>
  321. </div>
  322. <div class="kawaii-content" id="guessing-tab">
  323. <div class="checkbox-container">
  324. <input type="checkbox" id="autoGuess">
  325. <label for="autoGuess" data-translate="Auto Guess">Auto Guess</label>
  326. </div>
  327. <div class="slider-container" id="speedContainer" style="display: none;">
  328. <div class="slider-label" data-translate="Speed">Speed</div>
  329. <div class="custom-slider">
  330. <input type="range" id="guessSpeed" min="100" max="5000" value="1000" step="100">
  331. <div class="slider-track"></div>
  332. <span id="speedValue">1s</span>
  333. </div>
  334. </div>
  335. <div class="checkbox-container">
  336. <input type="checkbox" id="customWords">
  337. <label for="customWords" data-translate="Custom Words">Custom Words</label>
  338. </div>
  339. <div class="dropzone-container" id="wordListContainer" style="display: none;">
  340. <div class="dropzone" id="wordListDropzone">
  341. <input type="file" id="wordList" accept=".txt">
  342. <div class="dropzone-content">
  343. <div class="dropzone-icon">❀</div>
  344. <p data-translate="Drop word list here or click to upload">Drop word list here or click to upload</p>
  345. </div>
  346. </div>
  347. </div>
  348. <div class="input-container">
  349. <input type="text" id="guessPattern" data-translate-placeholder="Enter pattern (e.g., ___e___)" placeholder="Enter pattern (e.g., ___e___)">
  350. </div>
  351. <div class="hit-list" id="hitList">
  352. <div class="message" data-translate="Type a pattern to see matches ✧">Type a pattern to see matches ✧</div>
  353. </div>
  354. </div>
  355. <div class="kawaii-content" id="drawing-tab" style="display: none;">
  356. <div class="dropzone-container">
  357. <div class="dropzone" id="imageDropzone">
  358. <input type="file" id="imageUpload" accept="image/*">
  359. <div class="dropzone-content">
  360. <div class="dropzone-icon">✎</div>
  361. <p data-translate="Drop image here or click to upload">Drop image here or click to upload</p>
  362. </div>
  363. </div>
  364. <div class="image-preview" id="imagePreview" style="display: none;">
  365. <img id="previewImg">
  366. <div class="preview-controls">
  367. <button class="cancel-btn" id="cancelImage">✕</button>
  368. </div>
  369. </div>
  370. </div>
  371. <button class="google-search-btn" id="googleSearchBtn" data-translate="Search on Google Images 🡵">Search on Google Images 🡵</button>
  372. <div class="slider-container">
  373. <div class="slider-label" data-translate="Draw Speed">Draw Speed</div>
  374. <div class="custom-slider">
  375. <input type="range" id="drawSpeed" min="20" max="5000" value="200" step="100">
  376. <div class="slider-track"></div>
  377. <span id="drawSpeedValue">200ms</span>
  378. </div>
  379. </div>
  380. <div class="slider-container">
  381. <div class="slider-label" data-translate="Color Tolerance">Color Tolerance</div>
  382. <div class="custom-slider">
  383. <input type="range" id="colorTolerance" min="5" max="100" value="20" step="1">
  384. <div class="slider-track"></div>
  385. <span id="colorToleranceValue">20</span>
  386. </div>
  387. </div>
  388. <button class="draw-btn" id="sendDraw" disabled data-translate="Draw Now ✧">Draw Now ✧</button>
  389. </div>
  390. <div class="kawaii-content" id="settings-tab" style="display: none;">
  391. <div class="checkbox-container">
  392. <input type="checkbox" id="autoKick">
  393. <label for="autoKick" data-translate="Auto Kick">Auto Kick</label>
  394. </div>
  395. <div class="checkbox-container">
  396. <input type="checkbox" id="noKickCooldown">
  397. <label for="noKickCooldown" data-translate="No Kick Cooldown">No Kick Cooldown</label>
  398. </div>
  399. <div class="checkbox-container">
  400. <input type="checkbox" id="chatBypassCensorship">
  401. <label for="chatBypassCensorship" data-translate="Chat Bypass Censorship">Chat Bypass Censorship</label>
  402. </div>
  403. </div>
  404. <div class="kawaii-footer">
  405. <span class="credit-text" data-translate="Made with ♥ by Anonimbiri & Gartic-Developers">Made with by Anonimbiri & Gartic-Developers</span>
  406. </div>
  407. </div>
  408. </div>
  409. <div class="kawaii-notifications" id="kawaiiNotifications"></div>
  410. `;
  411. document.body.insertAdjacentHTML('beforeend', kawaiiHTML);
  412. }
  413.  
  414. cacheElements() {
  415. this.elements = {
  416. kawaiiCheat: document.getElementById('kawaiiCheat'),
  417. kawaiiHeader: document.getElementById('kawaiiHeader'),
  418. minimizeBtn: document.getElementById('minimizeBtn'),
  419. tabButtons: document.querySelectorAll('.kawaii-tab'),
  420. tabContents: document.querySelectorAll('.kawaii-content'),
  421. autoGuessCheckbox: document.getElementById('autoGuess'),
  422. speedContainer: document.getElementById('speedContainer'),
  423. guessSpeed: document.getElementById('guessSpeed'),
  424. speedValue: document.getElementById('speedValue'),
  425. customWordsCheckbox: document.getElementById('customWords'),
  426. wordListContainer: document.getElementById('wordListContainer'),
  427. wordListDropzone: document.getElementById('wordListDropzone'),
  428. wordListInput: document.getElementById('wordList'),
  429. guessPattern: document.getElementById('guessPattern'),
  430. hitList: document.getElementById('hitList'),
  431. imageDropzone: document.getElementById('imageDropzone'),
  432. imageUpload: document.getElementById('imageUpload'),
  433. imagePreview: document.getElementById('imagePreview'),
  434. previewImg: document.getElementById('previewImg'),
  435. cancelImage: document.getElementById('cancelImage'),
  436. googleSearchBtn: document.getElementById('googleSearchBtn'),
  437. drawSpeed: document.getElementById('drawSpeed'),
  438. drawSpeedValue: document.getElementById('drawSpeedValue'),
  439. colorTolerance: document.getElementById('colorTolerance'),
  440. colorToleranceValue: document.getElementById('colorToleranceValue'),
  441. sendDraw: document.getElementById('sendDraw'),
  442. autoKickCheckbox: document.getElementById('autoKick'),
  443. noKickCooldownCheckbox: document.getElementById('noKickCooldown'),
  444. chatBypassCensorship: document.getElementById('chatBypassCensorship'),
  445. notifications: document.getElementById('kawaiiNotifications')
  446. };
  447. }
  448.  
  449. setInitialPosition() {
  450. const waitForRender = () => {
  451. if (this.elements.kawaiiCheat.offsetWidth > 0 && this.elements.kawaiiCheat.offsetHeight > 0) {
  452. const savedPosition = this.settings.position;
  453. let initialX, initialY;
  454.  
  455. if (savedPosition && savedPosition.x !== null && savedPosition.y !== null) {
  456. initialX = savedPosition.x;
  457. initialY = savedPosition.y;
  458. } else {
  459. const windowWidth = window.innerWidth;
  460. const windowHeight = window.innerHeight;
  461. const cheatWidth = this.elements.kawaiiCheat.offsetWidth;
  462. const cheatHeight = this.elements.kawaiiCheat.offsetHeight;
  463. initialX = (windowWidth - cheatWidth) / 2;
  464. initialY = (windowHeight - cheatHeight) / 2;
  465. }
  466.  
  467. this.elements.kawaiiCheat.style.left = `${initialX}px`;
  468. this.elements.kawaiiCheat.style.top = `${initialY}px`;
  469. this.state.xOffset = initialX;
  470. this.state.yOffset = initialY;
  471. this.elements.kawaiiCheat.classList.add('twirl-minimize');
  472. this.saveSettings();
  473. } else {
  474. requestAnimationFrame(waitForRender);
  475. }
  476. };
  477. requestAnimationFrame(waitForRender);
  478. }
  479.  
  480. applySavedSettings() {
  481. this.elements.autoGuessCheckbox.checked = this.settings.autoGuess;
  482. this.elements.guessSpeed.value = this.settings.guessSpeed;
  483. this.elements.customWordsCheckbox.checked = this.settings.customWords;
  484. this.elements.autoKickCheckbox.checked = this.settings.autoKick;
  485. this.elements.noKickCooldownCheckbox.checked = this.settings.noKickCooldown;
  486. this.elements.chatBypassCensorship.checked = this.settings.chatBypassCensorship;
  487. this.elements.drawSpeed.value = this.settings.drawSpeed;
  488. this.elements.colorTolerance.value = this.settings.colorTolerance;
  489.  
  490. this.elements.speedContainer.style.display = this.settings.autoGuess ? 'flex' : 'none';
  491. this.elements.wordListContainer.style.display = this.settings.customWords ? 'block' : 'none';
  492. this.updateGuessSpeed({ target: this.elements.guessSpeed });
  493. this.updateDrawSpeed({ target: this.elements.drawSpeed });
  494. this.updateColorTolerance({ target: this.elements.colorTolerance });
  495. }
  496.  
  497. addStyles() {
  498. const style = document.createElement('style');
  499. style.textContent = `
  500. :root {
  501. --primary-color: #FF69B4;
  502. --primary-dark: #FF1493;
  503. --primary-light: #FFC0CB;
  504. --bg-color: #FFB6C1;
  505. --text-color: #5d004f;
  506. --panel-bg: rgba(255, 182, 193, 0.95);
  507. --panel-border: #FF69B4;
  508. --element-bg: rgba(255, 240, 245, 0.7);
  509. --element-hover: rgba(255, 240, 245, 0.9);
  510. --element-active: #FF69B4;
  511. --element-active-text: #FFF0F5;
  512. }
  513.  
  514. .kawaii-cheat {
  515. position: fixed;
  516. width: 280px;
  517. background: var(--panel-bg);
  518. border-radius: 15px;
  519. box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2);
  520. padding: 10px;
  521. display: flex;
  522. flex-direction: column;
  523. gap: 10px;
  524. color: var(--text-color);
  525. user-select: none;
  526. z-index: 1000;
  527. font-family: 'M PLUS Rounded 1c', sans-serif;
  528. border: 2px solid var(--panel-border);
  529. transition: height 0.4s ease-in-out, opacity 0.4s ease-in-out;
  530. max-height: calc(100vh - 40px);
  531. overflow: hidden;
  532. opacity: 0;
  533. }
  534.  
  535. .kawaii-cheat.comet-enter {
  536. animation: cometEnter 1.2s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
  537. }
  538.  
  539. @keyframes cometEnter {
  540. 0% { opacity: 0; transform: translateY(-80px) translateX(50px) scale(0.6); filter: brightness(1.5); }
  541. 50% { opacity: 0.8; transform: translateY(15px) translateX(-10px) scale(1.08); filter: brightness(1.2); }
  542. 75% { transform: translateY(-8px) translateX(5px) scale(0.95); }
  543. 100% { opacity: 1; transform: translateY(0) translateX(0) scale(1); filter: brightness(1); }
  544. }
  545.  
  546. .kawaii-cheat.minimized {
  547. height: 50px;
  548. opacity: 0.85;
  549. overflow: hidden;
  550. animation: cometMinimize 0.6s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
  551. }
  552.  
  553. @keyframes cometMinimize {
  554. 0% { transform: scale(1); }
  555. 30% { transform: scale(0.92); }
  556. 60% { transform: scale(0.88) translateY(5px); }
  557. 100% { transform: scale(0.85) translateY(10px); }
  558. }
  559.  
  560. .kawaii-cheat:not(.minimized) {
  561. opacity: 1;
  562. animation: cometMaximize 0.6s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
  563. }
  564.  
  565. @keyframes cometMaximize {
  566. 0% { transform: scale(0.85) translateY(10px); }
  567. 60% { transform: scale(1.05) translateY(-5px); }
  568. 80% { transform: scale(0.98) translateY(2px); }
  569. 100% { transform: scale(1) translateY(0); }
  570. }
  571.  
  572. .kawaii-cheat.minimized .kawaii-body {
  573. opacity: 0;
  574. max-height: 0;
  575. overflow: hidden;
  576. transition: opacity 0.2s ease-in-out, max-height 0.4s ease-in-out;
  577. }
  578.  
  579. .kawaii-cheat:not(.minimized) .kawaii-body {
  580. opacity: 1;
  581. max-height: 500px;
  582. transition: opacity 0.2s ease-in-out 0.2s, max-height 0.4s ease-in-out;
  583. }
  584.  
  585. .kawaii-cheat.dragging {
  586. opacity: 0.8;
  587. transition: none;
  588. }
  589.  
  590. .kawaii-header {
  591. display: flex;
  592. justify-content: space-between;
  593. align-items: center;
  594. padding: 5px 10px;
  595. cursor: move;
  596. background: var(--element-bg);
  597. border-radius: 10px;
  598. border: 2px solid var(--primary-color);
  599. }
  600.  
  601. .header-icon {
  602. width: 30px;
  603. height: 30px;
  604. border-radius: 50%;
  605. margin-right: 10px;
  606. object-fit: cover;
  607. object-position: top;
  608. border: 1px dashed var(--primary-color);
  609. }
  610.  
  611. .kawaii-header h2 {
  612. margin: 0;
  613. font-size: 18px;
  614. font-weight: 700;
  615. color: var(--primary-dark);
  616. text-shadow: 1px 1px 2px var(--primary-light);
  617. }
  618.  
  619. .minimize-btn {
  620. background: transparent;
  621. border: 1px dashed var(--primary-dark);
  622. border-radius: 6px;
  623. width: 24px;
  624. height: 24px;
  625. color: var(--primary-dark);
  626. font-size: 16px;
  627. line-height: 20px;
  628. text-align: center;
  629. cursor: pointer;
  630. transition: all 0.3s ease;
  631. }
  632.  
  633. .minimize-btn:hover {
  634. background: var(--primary-color);
  635. color: var(--element-active-text);
  636. border-color: var(--primary-color);
  637. transform: rotate(180deg);
  638. }
  639.  
  640. .kawaii-tabs {
  641. display: flex;
  642. gap: 8px;
  643. padding: 5px 0;
  644. }
  645.  
  646. .kawaii-tab {
  647. flex: 1;
  648. background: var(--element-bg);
  649. border: 1px dashed var(--primary-color);
  650. padding: 6px;
  651. border-radius: 10px;
  652. font-size: 12px;
  653. font-weight: 700;
  654. color: var(--text-color);
  655. cursor: pointer;
  656. transition: background 0.3s ease, transform 0.3s ease;
  657. text-align: center;
  658. }
  659.  
  660. .kawaii-tab.active {
  661. background: var(--primary-color);
  662. color: var(--element-active-text);
  663. border-color: var(--primary-dark);
  664. }
  665.  
  666. .kawaii-tab:hover:not(.active) {
  667. background: var(--element-hover);
  668. transform: scale(1.05);
  669. }
  670.  
  671. .kawaii-content {
  672. display: flex;
  673. flex-direction: column;
  674. gap: 10px;
  675. min-height: 0;
  676. flex-grow: 1;
  677. overflow: hidden;
  678. padding: 5px;
  679. }
  680.  
  681. .checkbox-container {
  682. display: flex;
  683. align-items: center;
  684. gap: 8px;
  685. background: var(--element-bg);
  686. padding: 8px;
  687. border-radius: 10px;
  688. border: 1px dashed var(--primary-color);
  689. cursor: pointer;
  690. transition: background 0.3s ease;
  691. }
  692.  
  693. .checkbox-container:hover {
  694. background: var(--element-hover);
  695. }
  696.  
  697. .checkbox-container input[type="checkbox"] {
  698. appearance: none;
  699. width: 18px;
  700. height: 18px;
  701. background: var(--element-active-text);
  702. border: 1px dashed var(--primary-color);
  703. border-radius: 50%;
  704. cursor: pointer;
  705. position: relative;
  706. }
  707.  
  708. .checkbox-container input[type="checkbox"]:checked {
  709. background: var(--primary-color);
  710. border-color: var(--primary-dark);
  711. }
  712.  
  713. .checkbox-container input[type="checkbox"]:checked::after {
  714. content: "♥";
  715. position: absolute;
  716. top: 50%;
  717. left: 50%;
  718. transform: translate(-50%, -50%);
  719. color: var(--element-active-text);
  720. font-size: 12px;
  721. }
  722.  
  723. .checkbox-container label {
  724. font-size: 12px;
  725. font-weight: 700;
  726. color: var(--text-color);
  727. cursor: pointer;
  728. }
  729.  
  730. .input-container {
  731. background: var(--element-bg);
  732. padding: 8px;
  733. border-radius: 10px;
  734. border: 1px dashed var(--primary-color);
  735. }
  736.  
  737. .input-container input[type="text"] {
  738. width: 100%;
  739. background: var(--element-active-text);
  740. border: 1px dashed var(--primary-light);
  741. border-radius: 8px;
  742. padding: 6px 10px;
  743. color: var(--text-color);
  744. font-size: 12px;
  745. font-weight: 500;
  746. box-sizing: border-box;
  747. transition: border-color 0.3s ease;
  748. outline: none;
  749. }
  750.  
  751. .input-container input[type="text"]:focus {
  752. border-color: var(--primary-dark);
  753. }
  754.  
  755. .dropzone-container {
  756. display: flex;
  757. flex-direction: column;
  758. gap: 10px;
  759. }
  760.  
  761. .dropzone {
  762. position: relative;
  763. background: var(--element-bg);
  764. border: 1px dashed var(--primary-color);
  765. border-radius: 10px;
  766. padding: 15px;
  767. display: flex;
  768. flex-direction: column;
  769. align-items: center;
  770. justify-content: center;
  771. cursor: pointer;
  772. transition: background 0.3s ease, border-color 0.3s ease;
  773. min-height: 80px;
  774. }
  775.  
  776. .dropzone:hover, .dropzone.drag-over {
  777. background: var(--element-hover);
  778. border-color: var(--primary-dark);
  779. }
  780.  
  781. .dropzone input[type="file"] {
  782. position: absolute;
  783. top: 0;
  784. left: 0;
  785. width: 100%;
  786. height: 100%;
  787. opacity: 0;
  788. cursor: pointer;
  789. }
  790.  
  791. .dropzone-content {
  792. display: flex;
  793. flex-direction: column;
  794. align-items: center;
  795. gap: 8px;
  796. text-align: center;
  797. pointer-events: none;
  798. }
  799.  
  800. .dropzone-icon {
  801. font-size: 24px;
  802. color: var(--primary-color);
  803. animation: pulse 1.5s infinite ease-in-out;
  804. }
  805.  
  806. @keyframes pulse {
  807. 0%, 100% { transform: scale(1); }
  808. 50% { transform: scale(1.1); }
  809. }
  810.  
  811. .dropzone-content p {
  812. margin: 0;
  813. color: var(--text-color);
  814. font-size: 12px;
  815. font-weight: 500;
  816. }
  817.  
  818. .slider-container {
  819. display: flex;
  820. flex-direction: column;
  821. gap: 6px;
  822. background: var(--element-bg);
  823. padding: 8px;
  824. border-radius: 10px;
  825. border: 1px dashed var(--primary-color);
  826. }
  827.  
  828. .slider-label {
  829. font-size: 12px;
  830. color: var(--text-color);
  831. font-weight: 700;
  832. text-align: center;
  833. }
  834.  
  835. .custom-slider {
  836. position: relative;
  837. height: 25px;
  838. padding: 0 8px;
  839. }
  840.  
  841. .custom-slider input[type="range"] {
  842. -webkit-appearance: none;
  843. width: 100%;
  844. height: 6px;
  845. background: transparent;
  846. position: absolute;
  847. top: 50%;
  848. left: 0;
  849. transform: translateY(-50%);
  850. z-index: 2;
  851. }
  852.  
  853. .custom-slider .slider-track {
  854. position: absolute;
  855. top: 50%;
  856. left: 0;
  857. width: 100%;
  858. height: 6px;
  859. background: linear-gradient(to right, var(--primary-dark) 0%, var(--primary-dark) var(--slider-progress), var(--primary-light) var(--slider-progress), var(--primary-light) 100%);
  860. border-radius: 3px;
  861. transform: translateY(-50%);
  862. z-index: 1;
  863. }
  864.  
  865. .custom-slider input[type="range"]::-webkit-slider-thumb {
  866. -webkit-appearance: none;
  867. width: 16px;
  868. height: 16px;
  869. background: var(--primary-color);
  870. border-radius: 50%;
  871. border: 1px dashed var(--element-active-text);
  872. cursor: pointer;
  873. transition: transform 0.3s ease;
  874. }
  875.  
  876. .custom-slider input[type="range"]::-webkit-slider-thumb:hover {
  877. transform: scale(1.2);
  878. }
  879.  
  880. .custom-slider span {
  881. position: absolute;
  882. bottom: -15px;
  883. left: 50%;
  884. transform: translateX(-50%);
  885. font-size: 10px;
  886. color: var(--text-color);
  887. background: var(--element-active-text);
  888. padding: 2px 6px;
  889. border-radius: 8px;
  890. border: 1px dashed var(--primary-color);
  891. white-space: nowrap;
  892. }
  893.  
  894. .hit-list {
  895. max-height: 180px;
  896. min-height: 40px;
  897. overflow-y: auto;
  898. background: var(--element-bg);
  899. border: 1px dashed var(--primary-color);
  900. border-radius: 10px;
  901. padding: 8px;
  902. display: flex;
  903. flex-direction: column;
  904. gap: 6px;
  905. scrollbar-width: thin;
  906. scrollbar-color: var(--primary-color) var(--element-bg);
  907. box-sizing: border-box;
  908. }
  909.  
  910. .hit-list:empty {
  911. min-height: 40px;
  912. overflow-y: hidden;
  913. }
  914.  
  915. .hit-list::-webkit-scrollbar {
  916. width: 6px;
  917. }
  918.  
  919. .hit-list::-webkit-scrollbar-thumb {
  920. background-color: var(--primary-color);
  921. border-radius: 10px;
  922. }
  923.  
  924. .hit-list::-webkit-scrollbar-track {
  925. background: var(--element-bg);
  926. }
  927.  
  928. .hit-list button {
  929. background: rgba(255, 240, 245, 0.8);
  930. border: 1px dashed var(--primary-color);
  931. padding: 8px 10px;
  932. border-radius: 8px;
  933. color: var(--text-color);
  934. font-size: 12px;
  935. font-weight: 700;
  936. line-height: 1.5;
  937. cursor: pointer;
  938. position: relative;
  939. overflow: hidden;
  940. transition: all 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
  941. text-align: left;
  942. box-sizing: border-box;
  943. min-height: 32px;
  944. }
  945.  
  946. .hit-list button:before {
  947. content: '';
  948. position: absolute;
  949. left: -100%;
  950. top: 0;
  951. width: 100%;
  952. height: 100%;
  953. background: linear-gradient(90deg, transparent, var(--primary-light), transparent);
  954. transition: all 0.5s ease;
  955. z-index: 0;
  956. }
  957.  
  958. .hit-list button:hover:not(.tried):before {
  959. left: 100%;
  960. }
  961.  
  962. .hit-list button:hover:not(.tried) {
  963. background: var(--primary-dark);
  964. color: var(--element-active-text);
  965. box-shadow: 0 0 15px rgba(255, 105, 180, 0.5);
  966. }
  967.  
  968. .hit-list button span {
  969. position: relative;
  970. z-index: 1;
  971. }
  972.  
  973. .hit-list button.tried {
  974. background: rgba(255, 182, 193, 0.6);
  975. border-color: var(--primary-light);
  976. color: var(--primary-dark);
  977. opacity: 0.7;
  978. cursor: not-allowed;
  979. }
  980.  
  981. .hit-list .tried-label {
  982. font-size: 10px;
  983. color: var(--primary-dark);
  984. text-align: center;
  985. padding: 4px;
  986. background: var(--element-active-text);
  987. border-radius: 8px;
  988. border: 1px dashed var(--primary-color);
  989. }
  990.  
  991. .hit-list .message {
  992. font-size: 12px;
  993. color: var(--text-color);
  994. text-align: center;
  995. padding: 8px;
  996. }
  997.  
  998. .image-preview {
  999. position: relative;
  1000. margin-top: 10px;
  1001. background: var(--element-bg);
  1002. padding: 8px;
  1003. border-radius: 10px;
  1004. border: 1px dashed var(--primary-color);
  1005. }
  1006.  
  1007. .image-preview img {
  1008. max-width: 100%;
  1009. max-height: 120px;
  1010. border-radius: 8px;
  1011. display: block;
  1012. margin: 0 auto;
  1013. }
  1014.  
  1015. .preview-controls {
  1016. position: absolute;
  1017. top: 12px;
  1018. right: 12px;
  1019. display: flex;
  1020. gap: 6px;
  1021. }
  1022.  
  1023. .cancel-btn {
  1024. background: transparent;
  1025. border: 1px dashed var(--primary-dark);
  1026. border-radius: 6px;
  1027. width: 24px;
  1028. height: 24px;
  1029. color: var(--primary-dark);
  1030. font-size: 16px;
  1031. line-height: 20px;
  1032. text-align: center;
  1033. cursor: pointer;
  1034. transition: all 0.3s ease;
  1035. }
  1036.  
  1037. .cancel-btn:hover {
  1038. background: var(--primary-dark);
  1039. color: var(--element-active-text);
  1040. transform: scale(1.1);
  1041. }
  1042.  
  1043. .draw-btn {
  1044. background: var(--primary-color);
  1045. border: 1px dashed var(--primary-dark);
  1046. padding: 8px;
  1047. border-radius: 10px;
  1048. color: var(--element-active-text);
  1049. font-size: 14px;
  1050. font-weight: 700;
  1051. cursor: pointer;
  1052. position: relative;
  1053. overflow: hidden;
  1054. transition: all 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
  1055. text-align: center;
  1056. width: 100%;
  1057. box-sizing: border-box;
  1058. }
  1059.  
  1060. .draw-btn:before {
  1061. content: '';
  1062. position: absolute;
  1063. left: -100%;
  1064. top: 0;
  1065. width: 100%;
  1066. height: 100%;
  1067. background: linear-gradient(90deg, transparent, var(--primary-light), transparent);
  1068. transition: all 0.5s ease;
  1069. }
  1070.  
  1071. .draw-btn:hover:not(:disabled):before {
  1072. left: 100%;
  1073. }
  1074.  
  1075. .draw-btn:hover:not(:disabled) {
  1076. background: var(--primary-dark);
  1077. box-shadow: 0 0 15px rgba(255, 105, 180, 0.5);
  1078. }
  1079.  
  1080. .draw-btn:disabled {
  1081. background: rgba(255, 105, 180, 0.5);
  1082. cursor: not-allowed;
  1083. }
  1084.  
  1085. .kawaii-footer {
  1086. display: flex;
  1087. justify-content: center;
  1088. align-items: center;
  1089. margin-top: 10px;
  1090. padding: 6px;
  1091. background: var(--element-bg);
  1092. border-radius: 10px;
  1093. border: 2px solid var(--primary-color);
  1094. }
  1095.  
  1096. .credit-text {
  1097. font-size: 10px;
  1098. color: var(--text-color);
  1099. font-weight: 700;
  1100. }
  1101.  
  1102. .kawaii-notifications {
  1103. position: fixed;
  1104. top: 20px;
  1105. right: 20px;
  1106. display: flex;
  1107. flex-direction: column;
  1108. gap: 10px;
  1109. z-index: 2000;
  1110. pointer-events: none;
  1111. }
  1112.  
  1113. .kawaii-notification {
  1114. background: var(--panel-bg);
  1115. border: 2px solid var(--panel-border);
  1116. border-radius: 12px;
  1117. padding: 12px 18px;
  1118. color: var(--text-color);
  1119. font-family: 'M PLUS Rounded 1c', sans-serif;
  1120. font-size: 14px;
  1121. font-weight: 700;
  1122. box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
  1123. display: flex;
  1124. align-items: center;
  1125. gap: 10px;
  1126. max-width: 300px;
  1127. opacity: 0;
  1128. transform: translateX(100%);
  1129. transition: opacity 0.3s ease, transform 0.3s ease;
  1130. pointer-events: auto;
  1131. gap: 8px;
  1132. padding: 12px 12px;
  1133. }
  1134.  
  1135. .kawaii-notification.show {
  1136. opacity: 1;
  1137. transform: translateX(0);
  1138. }
  1139.  
  1140. .kawaii-notification-icon {
  1141. font-size: 20px;
  1142. color: var(--primary-dark);
  1143. animation: bounce 1s infinite ease-in-out;
  1144. }
  1145.  
  1146. @keyframes bounce {
  1147. 0%, 100% { transform: translateY(0); }
  1148. 50% { transform: translateY(-5px); }
  1149. }
  1150.  
  1151. .kawaii-notification-button {
  1152. background: var(--primary-color);
  1153. border: 1px dashed var(--primary-dark);
  1154. border-radius: 6px;
  1155. padding: 4px 8px;
  1156. color: var(--element-active-text);
  1157. font-size: 12px;
  1158. font-weight: 700;
  1159. cursor: pointer;
  1160. transition: all 0.3s ease;
  1161. white-space: nowrap;
  1162. }
  1163.  
  1164. .kawaii-notification-button:hover {
  1165. background: var(--primary-dark);
  1166. transform: scale(1.05);
  1167. }
  1168.  
  1169. .kawaii-notification-close {
  1170. background: transparent;
  1171. border: 1px dashed var(--primary-dark);
  1172. border-radius: 6px;
  1173. width: 20px;
  1174. height: 20px;
  1175. color: var(--primary-dark);
  1176. font-size: 12px;
  1177. line-height: 18px;
  1178. text-align: center;
  1179. cursor: pointer;
  1180. transition: all 0.3s ease;
  1181. margin-left: auto;
  1182. }
  1183.  
  1184. .kawaii-notification-close:hover {
  1185. background: var(--primary-dark);
  1186. color: var(--element-active-text);
  1187. transform: scale(1.1);
  1188. }
  1189.  
  1190. .google-search-btn {
  1191. background: var(--primary-color);
  1192. border: 1px dashed var(--primary-dark);
  1193. border-radius: 8px;
  1194. padding: 6px 10px;
  1195. color: var(--element-active-text);
  1196. font-size: 12px;
  1197. font-weight: 700;
  1198. cursor: pointer;
  1199. position: relative;
  1200. overflow: hidden;
  1201. transition: all 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
  1202. width: 100%;
  1203. box-sizing: border-box;
  1204. height: 30px;
  1205. text-align: center;
  1206. }
  1207.  
  1208. .google-search-btn:before {
  1209. content: '';
  1210. position: absolute;
  1211. left: -100%;
  1212. top: 0;
  1213. width: 100%;
  1214. height: 100%;
  1215. background: linear-gradient(90deg, transparent, var(--primary-light), transparent);
  1216. transition: all 0.5s ease;
  1217. }
  1218.  
  1219. .google-search-btn:hover:not(:disabled):before {
  1220. left: 100%;
  1221. }
  1222.  
  1223. .google-search-btn:hover:not(:disabled) {
  1224. background: var(--primary-dark);
  1225. box-shadow: 0 0 15px rgba(255, 105, 180, 0.5);
  1226. }
  1227.  
  1228. .google-search-btn:disabled {
  1229. background: rgba(255, 105, 180, 0.5);
  1230. cursor: not-allowed;
  1231. }
  1232. `;
  1233. document.head.appendChild(style);
  1234. this.updateLanguage();
  1235. [this.elements.guessSpeed, this.elements.drawSpeed, this.elements.colorTolerance].forEach(this.updateSliderTrack.bind(this));
  1236. }
  1237.  
  1238. updateLanguage() {
  1239. document.querySelectorAll('[data-translate]').forEach(element => {
  1240. element.textContent = this.localize(element.getAttribute('data-translate'));
  1241. });
  1242. document.querySelectorAll('[data-translate-placeholder]').forEach(element => {
  1243. element.setAttribute('placeholder', this.localize(element.getAttribute('data-translate-placeholder')));
  1244. });
  1245. }
  1246.  
  1247. updateSliderTrack(slider) {
  1248. const min = parseInt(slider.min);
  1249. const max = parseInt(slider.max);
  1250. const value = parseInt(slider.value);
  1251. const progress = ((value - min) / (max - min)) * 100;
  1252. slider.parentElement.querySelector('.slider-track').style.setProperty('--slider-progress', `${progress}%`);
  1253. }
  1254.  
  1255. preventDefaults(e) {
  1256. e.preventDefault();
  1257. e.stopPropagation();
  1258. }
  1259.  
  1260. bindEvents() {
  1261. this.elements.kawaiiHeader.addEventListener('mousedown', this.startDragging.bind(this));
  1262. document.addEventListener('mousemove', this.drag.bind(this));
  1263. document.addEventListener('mouseup', this.stopDragging.bind(this));
  1264. this.elements.minimizeBtn.addEventListener('click', this.toggleMinimize.bind(this));
  1265. this.elements.tabButtons.forEach(btn => btn.addEventListener('click', this.switchTab.bind(this, btn)));
  1266.  
  1267. document.querySelectorAll('.checkbox-container').forEach(container => {
  1268. const checkbox = container.querySelector('input[type="checkbox"]');
  1269. const label = container.querySelector('label');
  1270. container.addEventListener('click', e => {
  1271. if (e.target !== checkbox && e.target !== label) {
  1272. checkbox.checked = !checkbox.checked;
  1273. checkbox.dispatchEvent(new Event('change'));
  1274. }
  1275. });
  1276. label.addEventListener('click', e => e.stopPropagation());
  1277. });
  1278.  
  1279. this.elements.autoGuessCheckbox.addEventListener('change', (e) => {
  1280. this.toggleAutoGuess(e);
  1281. this.saveSettings();
  1282. });
  1283. this.elements.guessSpeed.addEventListener('input', (e) => {
  1284. this.updateGuessSpeed(e);
  1285. this.saveSettings();
  1286. });
  1287. this.elements.customWordsCheckbox.addEventListener('change', (e) => {
  1288. this.toggleCustomWords(e);
  1289. this.saveSettings();
  1290. });
  1291. this.elements.guessPattern.addEventListener('input', e => this.updateHitList(e.target.value.trim()));
  1292. this.elements.hitList.addEventListener('click', this.handleHitListClick.bind(this));
  1293.  
  1294. ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
  1295. this.elements.wordListDropzone.addEventListener(eventName, this.preventDefaults, false);
  1296. this.elements.imageDropzone.addEventListener(eventName, this.preventDefaults, false);
  1297. });
  1298. this.elements.wordListDropzone.addEventListener('dragenter', () => this.elements.wordListDropzone.classList.add('drag-over'));
  1299. this.elements.wordListDropzone.addEventListener('dragover', () => this.elements.wordListDropzone.classList.add('drag-over'));
  1300. this.elements.wordListDropzone.addEventListener('dragleave', () => this.elements.wordListDropzone.classList.remove('drag-over'));
  1301. this.elements.wordListDropzone.addEventListener('drop', this.handleWordListDrop.bind(this));
  1302. this.elements.wordListInput.addEventListener('change', this.handleWordListInput.bind(this));
  1303.  
  1304. this.elements.imageDropzone.addEventListener('dragenter', () => this.elements.imageDropzone.classList.add('drag-over'));
  1305. this.elements.imageDropzone.addEventListener('dragover', () => this.elements.imageDropzone.classList.add('drag-over'));
  1306. this.elements.imageDropzone.addEventListener('dragleave', () => this.elements.imageDropzone.classList.remove('drag-over'));
  1307. this.elements.imageDropzone.addEventListener('drop', this.handleImageDrop.bind(this));
  1308. this.elements.imageUpload.addEventListener('change', this.handleImageInput.bind(this));
  1309. this.elements.cancelImage.addEventListener('click', this.cancelImagePreview.bind(this));
  1310. this.elements.googleSearchBtn.addEventListener('click', () => {
  1311. if (!window.game || !window.game._palavra || !window.game.turn) {
  1312. this.showNotification(this.localize("Game not ready or not your turn! ✧"), 3000);
  1313. return;
  1314. }
  1315. const word = window.game._palavra;
  1316. const searchUrl = `https://www.google.com/search?q=${encodeURIComponent(word)}+vectorial&tbm=isch`;
  1317. window.open(searchUrl, '_blank');
  1318. });
  1319. this.elements.drawSpeed.addEventListener('input', (e) => {
  1320. this.updateDrawSpeed(e);
  1321. this.saveSettings();
  1322. });
  1323. this.elements.colorTolerance.addEventListener('input', (e) => {
  1324. this.updateColorTolerance(e);
  1325. this.saveSettings();
  1326. });
  1327. this.elements.sendDraw.addEventListener('click', this.toggleDrawing.bind(this));
  1328.  
  1329. this.elements.autoKickCheckbox.addEventListener('change', () => {
  1330. this.showNotification(`Auto Kick: ${this.elements.autoKickCheckbox.checked ? 'Enabled' : 'Disabled'}`, 2000);
  1331. this.saveSettings();
  1332. });
  1333. this.elements.noKickCooldownCheckbox.addEventListener('change', () => {
  1334. this.showNotification(`No Kick Cooldown: ${this.elements.noKickCooldownCheckbox.checked ? 'Enabled' : 'Disabled'}`, 2000);
  1335. this.saveSettings();
  1336. });
  1337. this.elements.chatBypassCensorship.addEventListener('change', () => {
  1338. this.showNotification(`Chat Bypass Censorship: ${this.elements.chatBypassCensorship.checked ? 'Enabled' : 'Disabled'}`, 2000);
  1339. this.saveSettings();
  1340. });
  1341.  
  1342. window.addEventListener('resize', () => {
  1343. const windowWidth = window.innerWidth;
  1344. const windowHeight = window.innerHeight;
  1345. const cheatWidth = this.elements.kawaiiCheat.offsetWidth;
  1346. const cheatHeight = this.elements.kawaiiCheat.offsetHeight;
  1347.  
  1348. let newX = this.state.xOffset;
  1349. let newY = this.state.yOffset;
  1350.  
  1351. newX = Math.max(0, Math.min(newX, windowWidth - cheatWidth));
  1352. newY = Math.max(0, Math.min(newY, windowHeight - cheatHeight));
  1353.  
  1354. if (newX !== this.state.xOffset || newY !== this.state.yOffset) {
  1355. this.state.xOffset = newX;
  1356. this.state.yOffset = newY;
  1357. this.elements.kawaiiCheat.style.left = `${newX}px`;
  1358. this.elements.kawaiiCheat.style.top = `${newY}px`;
  1359. this.saveSettings();
  1360. }
  1361. });
  1362. }
  1363.  
  1364. startDragging(e) {
  1365. if (e.target !== this.elements.minimizeBtn) {
  1366. this.state.initialX = e.clientX - this.state.xOffset;
  1367. this.state.initialY = e.clientY - this.state.yOffset;
  1368. this.state.isDragging = true;
  1369. this.elements.kawaiiCheat.classList.add('dragging');
  1370. if (this.state.rafId) cancelAnimationFrame(this.state.rafId);
  1371. }
  1372. }
  1373.  
  1374. drag(e) {
  1375. if (!this.state.isDragging) return;
  1376. e.preventDefault();
  1377. const newX = e.clientX - this.state.initialX;
  1378. const newY = e.clientY - this.state.initialY;
  1379.  
  1380. // Get window and menu dimensions
  1381. const windowWidth = window.innerWidth;
  1382. const windowHeight = window.innerHeight;
  1383. const cheatWidth = this.elements.kawaiiCheat.offsetWidth;
  1384. const cheatHeight = this.elements.kawaiiCheat.offsetHeight;
  1385.  
  1386. // Constrain position within window boundaries
  1387. const clampedX = Math.max(0, Math.min(newX, windowWidth - cheatWidth));
  1388. const clampedY = Math.max(0, Math.min(newY, windowHeight - cheatHeight));
  1389.  
  1390. if (this.state.rafId) cancelAnimationFrame(this.state.rafId);
  1391. this.state.rafId = requestAnimationFrame(() => {
  1392. this.elements.kawaiiCheat.style.left = `${clampedX}px`;
  1393. this.elements.kawaiiCheat.style.top = `${clampedY}px`;
  1394. this.state.xOffset = clampedX;
  1395. this.state.yOffset = clampedY;
  1396. this.saveSettings();
  1397. });
  1398. }
  1399.  
  1400. stopDragging() {
  1401. if (this.state.isDragging) {
  1402. this.state.isDragging = false;
  1403. this.elements.kawaiiCheat.classList.remove('dragging');
  1404. if (this.state.rafId) cancelAnimationFrame(this.state.rafId);
  1405. this.saveSettings();
  1406. }
  1407. }
  1408.  
  1409. toggleMinimize() {
  1410. this.elements.kawaiiCheat.classList.toggle('minimized');
  1411. this.elements.minimizeBtn.textContent = this.elements.kawaiiCheat.classList.contains('minimized') ? '▲' : '▼';
  1412. }
  1413.  
  1414. switchTab(btn) {
  1415. this.elements.tabButtons.forEach(b => b.classList.remove('active'));
  1416. this.elements.tabContents.forEach(c => c.style.display = 'none');
  1417. btn.classList.add('active');
  1418. document.getElementById(`${btn.dataset.tab}-tab`).style.display = 'flex';
  1419. }
  1420.  
  1421. toggleAutoGuess(e) {
  1422. this.elements.speedContainer.style.display = e.target.checked ? 'flex' : 'none';
  1423. if (!e.target.checked) this.stopAutoGuess();
  1424. else if (this.elements.guessPattern.value) this.startAutoGuess();
  1425. }
  1426.  
  1427. updateGuessSpeed(e) {
  1428. this.updateSliderTrack(e.target);
  1429. this.elements.speedValue.textContent = e.target.value >= 1000 ? `${e.target.value / 1000}s` : `${e.target.value}ms`;
  1430. if (this.elements.autoGuessCheckbox.checked && this.state.autoGuessInterval) {
  1431. this.stopAutoGuess();
  1432. this.startAutoGuess();
  1433. }
  1434. }
  1435.  
  1436. toggleCustomWords(e) {
  1437. this.elements.wordListContainer.style.display = e.target.checked ? 'block' : 'none';
  1438. this.updateHitList(this.elements.guessPattern.value.trim());
  1439. }
  1440.  
  1441. handleWordListDrop(e) {
  1442. this.elements.wordListDropzone.classList.remove('drag-over');
  1443. const file = e.dataTransfer.files[0];
  1444. if (file && file.type === 'text/plain') this.handleWordListFile(file);
  1445. }
  1446.  
  1447. handleWordListInput(e) {
  1448. const file = e.target.files[0];
  1449. if (file) {
  1450. this.handleWordListFile(file);
  1451. e.target.value = '';
  1452. }
  1453. }
  1454.  
  1455. handleWordListFile(file) {
  1456. const reader = new FileReader();
  1457. reader.onload = (event) => {
  1458. this.wordList["Custom"] = event.target.result.split('\n').map(word => word.trim()).filter(word => word.length > 0);
  1459. this.showNotification(this.localize("Loaded ${wordList['Custom'].length} words from ${file.name}", {
  1460. "wordList['Custom'].length": this.wordList["Custom"].length,
  1461. "file.name": file.name
  1462. }), 4000);
  1463. this.updateHitList(this.elements.guessPattern.value.trim());
  1464. };
  1465. reader.readAsText(file);
  1466. }
  1467.  
  1468. handleHitListClick(e) {
  1469. if (e.target.tagName !== 'BUTTON' || e.target.classList.contains('tried')) return;
  1470. const button = e.target;
  1471. button.classList.add('tried');
  1472. if (!this.state.triedLabelAdded && this.elements.hitList.querySelectorAll('button.tried').length === 1) {
  1473. const triedLabel = document.createElement('div');
  1474. triedLabel.classList.add('tried-label');
  1475. triedLabel.textContent = this.localize("Tried Words");
  1476. this.elements.hitList.appendChild(triedLabel);
  1477. this.state.triedLabelAdded = true;
  1478. }
  1479. if (window.game && window.game._socket) {
  1480. window.game._socket.emit(13, window.game._codigo, button.textContent);
  1481. }
  1482. this.elements.hitList.appendChild(button);
  1483. }
  1484.  
  1485. startAutoGuess() {
  1486. if (!this.elements.autoGuessCheckbox.checked) return;
  1487. this.stopAutoGuess();
  1488. const speed = parseInt(this.elements.guessSpeed.value);
  1489. this.state.autoGuessInterval = setInterval(() => {
  1490. const buttons = this.elements.hitList.querySelectorAll('button:not(.tried)');
  1491. if (buttons.length > 0 && window.game && window.game._socket) {
  1492. const word = buttons[0].textContent;
  1493. buttons[0].classList.add('tried');
  1494. window.game._socket.emit(13, window.game._codigo, word);
  1495. if (!this.state.triedLabelAdded && this.elements.hitList.querySelectorAll('button.tried').length === 1) {
  1496. const triedLabel = document.createElement('div');
  1497. triedLabel.classList.add('tried-label');
  1498. triedLabel.textContent = this.localize("Tried Words");
  1499. this.elements.hitList.appendChild(triedLabel);
  1500. this.state.triedLabelAdded = true;
  1501. }
  1502. this.elements.hitList.appendChild(buttons[0]);
  1503. }
  1504. }, speed);
  1505. }
  1506.  
  1507. stopAutoGuess() {
  1508. if (this.state.autoGuessInterval) {
  1509. clearInterval(this.state.autoGuessInterval);
  1510. this.state.autoGuessInterval = null;
  1511. }
  1512. }
  1513.  
  1514. updateHitList(pattern) {
  1515. if (!this.elements.hitList) return;
  1516. this.elements.hitList.innerHTML = '';
  1517. this.state.triedLabelAdded = false;
  1518.  
  1519. const activeTheme = this.elements.customWordsCheckbox.checked || !window.game || !window.game._dadosSala || !window.game._dadosSala.tema
  1520. ? "Custom"
  1521. : window.game._dadosSala.tema;
  1522. const activeList = this.wordList[activeTheme] || [];
  1523.  
  1524. if (!pattern) {
  1525. if (activeList.length === 0) {
  1526. this.elements.hitList.innerHTML = `<div class="message">${this.localize(this.elements.customWordsCheckbox.checked ? "Upload a custom word list ✧" : "No words available ✧")}</div>`;
  1527. } else {
  1528. activeList.forEach(word => {
  1529. const button = document.createElement('button');
  1530. button.textContent = word;
  1531. this.elements.hitList.appendChild(button);
  1532. });
  1533. }
  1534. return;
  1535. }
  1536.  
  1537. const regex = new RegExp(`^${pattern.split('').map(char => char === '_' ? '.' : char).join('')}$`, 'i');
  1538. const matches = activeList.filter(word => regex.test(word));
  1539.  
  1540. if (matches.length === 0) {
  1541. this.elements.hitList.innerHTML = `<div class="message">${this.localize("No matches found ✧")}</div>`;
  1542. } else {
  1543. matches.forEach(word => {
  1544. const button = document.createElement('button');
  1545. button.textContent = word;
  1546. this.elements.hitList.appendChild(button);
  1547. });
  1548. }
  1549. }
  1550.  
  1551. async fetchWordList(theme) {
  1552. if (!this.wordList[theme] && this.wordListURLs[theme]) {
  1553. try {
  1554. const response = await fetch(this.wordListURLs[theme]);
  1555. if (!response.ok) throw new Error(`Failed to fetch ${theme} word list`);
  1556. const data = await response.json();
  1557. this.wordList[theme] = data.words || data;
  1558. } catch (error) {
  1559. this.wordList[theme] = [];
  1560. }
  1561. }
  1562. }
  1563.  
  1564. handleImageDrop(e) {
  1565. this.elements.imageDropzone.classList.remove('drag-over');
  1566. const file = e.dataTransfer.files[0];
  1567. if (file && file.type.startsWith('image/')) this.handleImageFile(file);
  1568. }
  1569.  
  1570. handleImageInput(e) {
  1571. const file = e.target.files[0];
  1572. if (file) {
  1573. this.handleImageFile(file);
  1574. e.target.value = '';
  1575. }
  1576. }
  1577.  
  1578. handleImageFile(file) {
  1579. const reader = new FileReader();
  1580. reader.onload = (event) => {
  1581. this.elements.previewImg.src = event.target.result;
  1582. this.elements.imageDropzone.style.display = 'none';
  1583. this.elements.imagePreview.style.display = 'block';
  1584. this.elements.sendDraw.disabled = false;
  1585. };
  1586. reader.readAsDataURL(file);
  1587. }
  1588.  
  1589. cancelImagePreview() {
  1590. this.elements.previewImg.src = '';
  1591. this.elements.imageDropzone.style.display = 'flex';
  1592. this.elements.imagePreview.style.display = 'none';
  1593. this.elements.sendDraw.disabled = true;
  1594. this.elements.imageUpload.value = '';
  1595. }
  1596.  
  1597. updateDrawSpeed(e) {
  1598. this.updateSliderTrack(e.target);
  1599. this.elements.drawSpeedValue.textContent = e.target.value >= 1000 ? `${e.target.value / 1000}s` : `${e.target.value}ms`;
  1600. }
  1601.  
  1602. updateColorTolerance(e) {
  1603. this.updateSliderTrack(e.target);
  1604. this.elements.colorToleranceValue.textContent = e.target.value;
  1605. }
  1606.  
  1607. toggleDrawing() {
  1608. if (!this.elements.previewImg.src) return;
  1609.  
  1610. if (!this.isDrawing) {
  1611. if (!window.game || !window.game.turn) {
  1612. this.showNotification(this.localize("Not your turn or game not loaded! ✧"), 3000);
  1613. return;
  1614. }
  1615. this.isDrawing = true;
  1616. this.elements.sendDraw.textContent = this.localize("Stop Drawing ✧");
  1617. this.processAndDrawImage(this.elements.previewImg.src);
  1618. } else {
  1619. this.isDrawing = false;
  1620. this.stopDrawing();
  1621. }
  1622. }
  1623.  
  1624. initializeGameCheck() {
  1625. const checkGame = setInterval(() => {
  1626. if (window.game) {
  1627. clearInterval(checkGame);
  1628. const currentTheme = window.game._dadosSala.tema || "Custom";
  1629. if (currentTheme !== "Custom") {
  1630. this.fetchWordList(currentTheme).then(() => this.updateHitList(this.elements.guessPattern.value.trim()));
  1631. }
  1632. }
  1633. }, 100);
  1634. }
  1635.  
  1636. async processAndDrawImage(imageSrc) {
  1637. if (!window.game || !window.game._socket || !window.game._desenho || !window.game.turn) {
  1638. this.showNotification(this.localize("Game not ready or not your turn! ✧"), 3000);
  1639. this.stopDrawing();
  1640. return;
  1641. }
  1642.  
  1643. const img = new Image();
  1644. img.crossOrigin = "Anonymous";
  1645.  
  1646. img.onload = async () => {
  1647. let gameCanvas, ctx, canvasWidth, canvasHeight;
  1648. try {
  1649. gameCanvas = window.game._desenho._canvas.canvas;
  1650. if (!gameCanvas || !(gameCanvas instanceof HTMLCanvasElement)) throw new Error("Canvas not accessible!");
  1651. ctx = gameCanvas.getContext('2d', { willReadFrequently: true });
  1652. if (!ctx) throw new Error("Canvas context not available!");
  1653. canvasWidth = Math.floor(gameCanvas.width);
  1654. canvasHeight = Math.floor(gameCanvas.height);
  1655. if (canvasWidth <= 0 || canvasHeight <= 0) throw new Error("Invalid canvas dimensions!");
  1656. } catch (e) {
  1657. this.showNotification(this.localize(e.message.includes("Canvas not accessible") ? "Canvas not accessible! ✧" : "Canvas context not available! ✧"), 3000);
  1658. this.stopDrawing();
  1659. return;
  1660. }
  1661.  
  1662. const { tempCtx, imgLeft, imgRight, imgTop, imgBottom } = this.prepareImageCanvas(img, canvasWidth, canvasHeight);
  1663. if (!tempCtx) {
  1664. this.showNotification(this.localize("Temp canvas context failed! ✧"), 3000);
  1665. this.stopDrawing();
  1666. return;
  1667. }
  1668.  
  1669. const { imageData, data } = this.getImageData(tempCtx, canvasWidth, canvasHeight);
  1670. if (!imageData) {
  1671. this.stopDrawing();
  1672. return;
  1673. }
  1674.  
  1675. const drawSpeedValue = parseInt(this.elements.drawSpeed.value) || 200;
  1676. const colorToleranceValue = parseInt(this.elements.colorTolerance.value) || 20;
  1677.  
  1678. const regions = await this.detectRegions(data, canvasWidth, canvasHeight, imgLeft, imgRight, imgTop, imgBottom, colorToleranceValue);
  1679. if (!this.isDrawing || !window.game.turn) {
  1680. this.stopDrawing();
  1681. return;
  1682. }
  1683.  
  1684. this.showNotification(`Processing ${regions.length} color regions...`, 2000);
  1685. for (const region of regions) {
  1686. if (!this.isDrawing || !window.game.turn) {
  1687. this.showNotification(this.localize("Drawing stopped! ✧"), 2000);
  1688. this.stopDrawing();
  1689. return;
  1690. }
  1691.  
  1692. try {
  1693. await this.fillRegion(region.coords, region.hex);
  1694. await this.delay(drawSpeedValue);
  1695. } catch (e) {
  1696. console.error("Kawaii Helper: Error during region fill:", e);
  1697. this.showNotification("Error during region fill.", 2000);
  1698. }
  1699. }
  1700.  
  1701. if (this.isDrawing && window.game.turn) {
  1702. this.showNotification(this.localize("Drawing completed! ✧"), 3000);
  1703. }
  1704. this.stopDrawing();
  1705. };
  1706.  
  1707. img.onerror = () => {
  1708. this.showNotification(this.localize("Failed to load image! ✧"), 3000);
  1709. this.stopDrawing();
  1710. };
  1711.  
  1712. img.src = imageSrc;
  1713. }
  1714.  
  1715. prepareImageCanvas(img, canvasWidth, canvasHeight) {
  1716. const tempCanvas = document.createElement('canvas');
  1717. tempCanvas.width = canvasWidth;
  1718. tempCanvas.height = canvasHeight;
  1719. const tempCtx = tempCanvas.getContext('2d', { willReadFrequently: true });
  1720.  
  1721. const scale = Math.min(canvasWidth / img.width, canvasHeight / img.height);
  1722. const newWidth = Math.floor(img.width * scale);
  1723. const newHeight = Math.floor(img.height * scale);
  1724. const offsetX = Math.floor((canvasWidth - newWidth) / 2);
  1725. const offsetY = Math.floor((canvasHeight - newHeight) / 2);
  1726.  
  1727. tempCtx.imageSmoothingEnabled = false;
  1728. tempCtx.fillStyle = 'white';
  1729. tempCtx.fillRect(0, 0, canvasWidth, canvasHeight);
  1730. tempCtx.drawImage(img, offsetX, offsetY, newWidth, newHeight);
  1731.  
  1732. return {
  1733. tempCtx,
  1734. imgLeft: offsetX,
  1735. imgRight: offsetX + newWidth,
  1736. imgTop: offsetY,
  1737. imgBottom: offsetY + newHeight
  1738. };
  1739. }
  1740.  
  1741. getImageData(tempCtx, canvasWidth, canvasHeight) {
  1742. let imageData, data;
  1743. try {
  1744. imageData = tempCtx.getImageData(0, 0, canvasWidth, canvasHeight);
  1745. data = imageData.data;
  1746. } catch (e) {
  1747. this.showNotification(this.localize("Image data error: ${e.message} ✧", { "e.message": e.message }), 3000);
  1748. return { imageData: null, data: null };
  1749. }
  1750. return { imageData, data };
  1751. }
  1752.  
  1753. async detectRegions(data, canvasWidth, canvasHeight, imgLeft, imgRight, imgTop, imgBottom, colorToleranceValue) {
  1754. const backgroundColor = [255, 255, 255, 255];
  1755. const visited = new Uint8Array(canvasWidth * canvasHeight);
  1756. const regions = [];
  1757.  
  1758. const getColorAt = (x, y) => {
  1759. if (x < 0 || x >= canvasWidth || y < 0 || y >= canvasHeight) {
  1760. return backgroundColor;
  1761. }
  1762. const index = (y * canvasWidth + x) * 4;
  1763. return [data[index], data[index + 1], data[index + 2], data[index + 3]];
  1764. };
  1765.  
  1766. const traceRegion = (startX, startY, startColor, tolerance) => {
  1767. const regionCoords = [];
  1768. const stack = [[startX, startY]];
  1769. const currentRegionVisited = new Set([`${startX},${startY}`]);
  1770. let minX = startX, minY = startY, maxX = startX, maxY = startY;
  1771.  
  1772. visited[startY * canvasWidth + startX] = 1;
  1773.  
  1774. const neighbors = [
  1775. [1, 0], [-1, 0], [0, 1], [0, -1] // 4-direction check
  1776. ];
  1777.  
  1778. while (stack.length > 0) {
  1779. const [x, y] = stack.pop();
  1780. regionCoords.push([x, y]);
  1781. minX = Math.min(minX, x); minY = Math.min(minY, y);
  1782. maxX = Math.max(maxX, x); maxY = Math.max(maxY, y);
  1783.  
  1784. for (const [dx, dy] of neighbors) {
  1785. const nx = x + dx;
  1786. const ny = y + dy;
  1787. const nKey = `${nx},${ny}`;
  1788. if (nx >= imgLeft && nx <= imgRight && ny >= imgTop && ny <= imgBottom &&
  1789. visited[ny * canvasWidth + nx] === 0 &&
  1790. !currentRegionVisited.has(nKey)
  1791. ) {
  1792. const neighborColor = getColorAt(nx, ny);
  1793. const distance = this.colorDistance(neighborColor, startColor);
  1794.  
  1795. if (distance <= tolerance * 1.2) { // Dynamic tolerance
  1796. visited[ny * canvasWidth + nx] = 1;
  1797. currentRegionVisited.add(nKey);
  1798. stack.push([nx, ny]);
  1799. }
  1800. }
  1801. }
  1802. }
  1803.  
  1804. return regionCoords.length > 0 ? { // Allow smaller regions
  1805. coords: regionCoords,
  1806. color: startColor.slice(0, 3),
  1807. bounds: { minX, minY, maxX, maxY }
  1808. } : null;
  1809. };
  1810.  
  1811. for (let y = imgTop; y <= imgBottom && this.isDrawing; y += 1) {
  1812. for (let x = imgLeft; x <= imgRight && this.isDrawing; x += 1) {
  1813. if (!window.game.turn) { this.stopDrawing(); return []; }
  1814. const index = y * canvasWidth + x;
  1815. if (visited[index] === 1) continue;
  1816.  
  1817. const pixelColor = getColorAt(x, y);
  1818. const regionResult = traceRegion(x, y, pixelColor, colorToleranceValue);
  1819.  
  1820. if (regionResult) {
  1821. if (this.colorDistance(regionResult.color, backgroundColor) > colorToleranceValue) {
  1822. regions.push({
  1823. color: regionResult.color,
  1824. hex: this.rgbToHex(regionResult.color),
  1825. coords: regionResult.coords,
  1826. size: regionResult.coords.length,
  1827. bounds: regionResult.bounds
  1828. });
  1829. }
  1830. } else {
  1831. visited[index] = 1;
  1832. }
  1833. }
  1834. }
  1835.  
  1836. regions.sort((a, b) => b.size - a.size);
  1837. return regions;
  1838. }
  1839.  
  1840. areRegionsClose(boundsA, boundsB, threshold) {
  1841. const horizontalClose = Math.abs(boundsA.maxX - boundsB.minX) <= threshold ||
  1842. Math.abs(boundsB.maxX - boundsA.minX) <= threshold;
  1843. const verticalClose = Math.abs(boundsA.maxY - boundsB.minY) <= threshold ||
  1844. Math.abs(boundsB.maxY - boundsA.minY) <= threshold;
  1845. return horizontalClose && verticalClose;
  1846. }
  1847.  
  1848. async fillRegion(region, colorHex) {
  1849. if (!this.isDrawing || !window.game || !window.game._socket || !window.game.turn) {
  1850. this.stopDrawing();
  1851. return;
  1852. }
  1853.  
  1854. const canvas = window.game._desenho._canvas.canvas;
  1855. const ctx = canvas.getContext('2d');
  1856. const canvasWidth = canvas.width;
  1857. const canvasHeight = canvas.height;
  1858.  
  1859. const regionSet = new Set(region.map(([x, y]) => `${x},${y}`));
  1860. const visited = new Set();
  1861. const fills = [];
  1862. const queue = [region[0]];
  1863.  
  1864. const isInRegion = (x, y) => regionSet.has(`${x},${y}`) && !visited.has(`${x},${y}`);
  1865.  
  1866. while (queue.length > 0 && this.isDrawing) {
  1867. const [x, y] = queue.shift();
  1868. if (!isInRegion(x, y)) continue;
  1869.  
  1870. let leftX = x;
  1871. let rightX = x;
  1872.  
  1873. while (leftX - 1 >= 0 && isInRegion(leftX - 1, y)) leftX--;
  1874. while (rightX + 1 < canvasWidth && isInRegion(rightX + 1, y)) rightX++;
  1875.  
  1876. const width = rightX - leftX + 1;
  1877. fills.push([leftX, y, width, 1]);
  1878.  
  1879. for (let i = leftX; i <= rightX; i++) visited.add(`${i},${y}`);
  1880.  
  1881. if (y - 1 >= 0) {
  1882. for (let i = leftX; i <= rightX; i++) {
  1883. if (isInRegion(i, y - 1)) queue.push([i, y - 1]);
  1884. }
  1885. }
  1886. if (y + 1 < canvasHeight) {
  1887. for (let i = leftX; i <= rightX; i++) {
  1888. if (isInRegion(i, y + 1)) queue.push([i, y + 1]);
  1889. }
  1890. }
  1891. }
  1892.  
  1893. if (fills.length > 0 && this.isDrawing) {
  1894. window.game._socket.emit(10, window.game._codigo, [5, colorHex]);
  1895. ctx.fillStyle = `#${colorHex.slice(1)}`;
  1896.  
  1897. const fillCommand = [3, ...fills.flat()];
  1898. window.game._socket.emit(10, window.game._codigo, fillCommand);
  1899. fills.forEach(([x, y, w, h]) => ctx.fillRect(x, y, w, h));
  1900. }
  1901. }
  1902.  
  1903. stopDrawing() {
  1904. this.isDrawing = false;
  1905. this.elements.sendDraw.textContent = this.localize("Draw Now ✧");
  1906. this.elements.sendDraw.disabled = !(this.elements.previewImg.src && this.elements.previewImg.src !== '#');
  1907. }
  1908.  
  1909. colorDistance(color1_rgb, color2_rgb) {
  1910. if (!color1_rgb || !color2_rgb || color1_rgb.length < 3 || color2_rgb.length < 3) return Infinity;
  1911. const rDiff = color1_rgb[0] - color2_rgb[0];
  1912. const gDiff = color1_rgb[1] - color2_rgb[1];
  1913. const bDiff = color1_rgb[2] - color2_rgb[2];
  1914. return Math.sqrt(rDiff * rDiff + gDiff * gDiff + bDiff * bDiff);
  1915. }
  1916.  
  1917. rgbToHex(rgb) {
  1918. if (!rgb || rgb.length < 3) return '#000000';
  1919. const r = Math.min(255, Math.max(0, Math.round(rgb[0])));
  1920. const g = Math.min(255, Math.max(0, Math.round(rgb[1])));
  1921. const b = Math.min(255, Math.max(0, Math.round(rgb[2])));
  1922. return 'x' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1).toUpperCase();
  1923. }
  1924.  
  1925. delay(ms) {
  1926. return new Promise(resolve => setTimeout(resolve, ms));
  1927. }
  1928. }
  1929.  
  1930. KawaiiHelper.init();
  1931. })();