Kawaii Helper & Drawing Bot for Gartic.io

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

当前为 2025-03-24 提交的版本,查看 最新版本

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