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