- // ==UserScript==
- // @name Kawaii Helper & Drawing Bot for Gartic.io
- // @name:tr Gartic.io için Kawaii Yardımcı & Çizim Botu
- // @namespace https://github.com/Gartic-Developers/Kawaii-Helper
- // @version 2025-04-19
- // @description Helper for Gartic.io with auto-guess, drawing assistance, and drawing bot
- // @description:tr Gartic.io için otomatik tahmin, çizim yardımı ve çizim botu ile yardımcı
- // @author anonimbiri & Gartic-Developers
- // @license MIT
- // @match *://*.gartic.io/*
- // @exclude *://gartic.io/_next/*
- // @exclude *://gartic.io/static/*
- // @icon https://cdn.jsdelivr.net/gh/Gartic-Developers/Kawaii-Helper@refs/heads/main/Assets/kawaii-logo.png
- // @supportURL https://github.com/Gartic-Developers/Kawaii-Helper/issues/new?labels=bug&type=bug&template=bug_report.md&title=Bug+Report
- // @homepage https://github.com/Gartic-Developers/Kawaii-Helper
- // @run-at document-start
- // @tag games
- // @grant none
- // @noframes
- // ==/UserScript==
-
- (function() {
- 'use strict';
-
- class KawaiiHelper {
- constructor() {
- this.translations = {
- en: {
- "✧ Kawaii Helper ✧": "✧ Kawaii Helper ✧",
- "Guessing": "Guessing",
- "Drawing": "Drawing",
- "Auto Guess": "Auto Guess",
- "Speed": "Speed",
- "Custom Words": "Custom Words",
- "Drop word list here or click to upload": "Drop word list here or click to upload",
- "Enter pattern (e.g., ___e___)": "Enter pattern (e.g., ___e___)",
- "Type a pattern to see matches ✧": "Type a pattern to see matches ✧",
- "Upload a custom word list ✧": "Upload a custom word list ✧",
- "No words available ✧": "No words available ✧",
- "No matches found ✧": "No matches found ✧",
- "Tried Words": "Tried Words",
- "Drop image here or click to upload": "Drop image here or click to upload",
- "Search on Google Images 🡵": "Search on Google Images 🡵",
- "Draw Speed": "Draw Speed",
- "Color Tolerance": "Color Tolerance",
- "Draw Now ✧": "Draw Now ✧",
- "Stop Drawing ✧": "Stop Drawing ✧",
- "Made with ♥ by Anonimbiri & Gartic-Developers": "Made with ♥ by Anonimbiri & Gartic-Developers",
- "Loaded ${wordList['Custom'].length} words from ${file.name}": "Loaded ${wordList['Custom'].length} words from ${file.name}",
- "Not your turn or game not loaded! ✧": "Not your turn or game not loaded! ✧",
- "Game not ready or not your turn! ✧": "Game not ready or not your turn! ✧",
- "Canvas not accessible! ✧": "Canvas not accessible! ✧",
- "Canvas context not available! ✧": "Canvas context not available! ✧",
- "Temp canvas context failed! ✧": "Temp canvas context failed! ✧",
- "Image data error: ${e.message} ✧": "Image data error: ${e.message} ✧",
- "Drawing completed! ✧": "Drawing completed! ✧",
- "Failed to load image! ✧": "Failed to load image! ✧",
- "Drawing stopped! ✧": "Drawing stopped! ✧",
- "Settings": "Settings",
- "Auto Kick": "Auto Kick",
- "No Kick Cooldown": "No Kick Cooldown",
- "Chat Bypass Censorship": "Chat Bypass Censorship",
- "New update available!": "New update available!"
- },
- tr: {
- "✧ Kawaii Helper ✧": "✧ Kawaii Yardımcı ✧",
- "Guessing": "Tahmin",
- "Drawing": "Çizim",
- "Auto Guess": "Otomatik Tahmin",
- "Speed": "Hız",
- "Custom Words": "Özel Kelimeler",
- "Drop word list here or click to upload": "Kelime listesini buraya bırak veya yüklemek için tıkla",
- "Enter pattern (e.g., ___e___)": "Desen gir (ör., ___e___)",
- "Type a pattern to see matches ✧": "Eşleşmeleri görmek için bir desen yaz ✧",
- "Upload a custom word list ✧": "Özel bir kelime listesi yükle ✧",
- "No words available ✧": "Kelime yok ✧",
- "No matches found ✧": "Eşleşme bulunamadı ✧",
- "Tried Words": "Denenen Kelimeler",
- "Drop image here or click to upload": "Resmi buraya bırak veya yüklemek için tıkla",
- "Search on Google Images 🡵": "Google Görsellerde Ara 🡵",
- "Draw Speed": "Çizim Hızı",
- "Color Tolerance": "Renk Toleransı",
- "Draw Now ✧": "Şimdi Çiz ✧",
- "Stop Drawing ✧": "Çizimi Durdur ✧",
- "Made with ♥ by Anonimbiri & Gartic-Developers": "Anonimbiri & Gartic-Developers tarafından ♥ ile yapıldı",
- "Loaded ${wordList['Custom'].length} words from ${file.name}": "${file.name} dosyasından ${wordList['Custom'].length} kelime yüklendi",
- "Not your turn or game not loaded! ✧": "Sıra sende değil veya oyun yüklenmedi! ✧",
- "Game not ready or not your turn! ✧": "Oyun hazır değil veya sıra sende değil! ✧",
- "Canvas not accessible! ✧": "Tuval erişilemez! ✧",
- "Canvas context not available! ✧": "Tuval bağlamı kullanılamıyor! ✧",
- "Temp canvas context failed! ✧": "Geçici tuval bağlamı başarısız! ✧",
- "Image data error: ${e.message} ✧": "Görüntü verisi hatası: ${e.message} ✧",
- "Drawing completed! ✧": "Çizim tamamlandı! ✧",
- "Failed to load image! ✧": "Görüntü yüklenemedi! ✧",
- "Drawing stopped! ✧": "Çizim durduruldu! ✧",
- "Settings": "Ayarlar",
- "Auto Kick": "Otomatik Atma",
- "No Kick Cooldown": "Atma Bekleme Süresi Yok",
- "Chat Bypass Censorship": "Sohbet Sansürünü Atlat",
- "New update available!": "Yeni güncelleme var!"
- }
- };
- this.currentLang = navigator.language.split('-')[0] in this.translations ? navigator.language.split('-')[0] : 'en';
- this.isDrawing = false;
- this.wordList = { "Custom": [] };
- this.wordListURLs = {
- "General (en)": "https://cdn.jsdelivr.net/gh/Gartic-Developers/Gartic-WordList@master/languages/English/general.json",
- "General (tr)": "https://cdn.jsdelivr.net/gh/Gartic-Developers/Gartic-WordList@master/languages/Turkish/general.json",
- "General (ja)": "https://cdn.jsdelivr.net/gh/Gartic-Developers/Gartic-WordList@master/languages/Japanese/general.json"
- };
- this.elements = {};
- this.state = {
- isDragging: false,
- initialX: 0,
- initialY: 0,
- xOffset: 0,
- yOffset: 0,
- rafId: null,
- autoGuessInterval: null,
- triedLabelAdded: false
- };
- this.lastTheme = "Custom";
- this.settings = this.loadSettings();
- }
-
- static init() {
- const helper = new KawaiiHelper();
- helper.setup();
- return helper;
- }
-
- checkForUpdates() {
- const url = 'https://api.github.com/repos/Gartic-Developers/Kawaii-Helper/releases/latest';
- const req = new XMLHttpRequest();
- req.open("GET", url, false);
- req.setRequestHeader('Accept', 'application/vnd.github.v3+json');
- try {
- req.send();
- if (req.status === 200) {
- const latest = JSON.parse(req.responseText).tag_name.replace(/^v/, '');
- if (latest > GM_info.script.version) {
- this.showNotification(
- this.localize("New update available!"),
- 1e4,
- { text: 'Update', action: () => window.open('https://github.com/Gartic-Developers/Kawaii-Helper/releases/latest', '_blank') }
- );
- }
- }
- } catch (e) {}
- }
-
- loadSettings() {
- const savedSettings = localStorage.getItem('kawaiiSettings');
- return savedSettings ? JSON.parse(savedSettings) : {
- autoGuess: false,
- guessSpeed: 1000,
- customWords: false,
- autoKick: false,
- noKickCooldown: false,
- chatBypassCensorship: false,
- drawSpeed: 200,
- colorTolerance: 20,
- position: null
- };
- }
-
- saveSettings() {
- const settings = {
- autoGuess: this.elements.autoGuessCheckbox.checked,
- guessSpeed: parseInt(this.elements.guessSpeed.value),
- customWords: this.elements.customWordsCheckbox.checked,
- autoKick: this.elements.autoKickCheckbox.checked,
- noKickCooldown: this.elements.noKickCooldownCheckbox.checked,
- chatBypassCensorship: this.elements.chatBypassCensorship.checked,
- drawSpeed: parseInt(this.elements.drawSpeed.value),
- colorTolerance: parseInt(this.elements.colorTolerance.value),
- position: {
- x: this.state.xOffset,
- y: this.state.yOffset
- }
- };
- localStorage.setItem('kawaiiSettings', JSON.stringify(settings));
- }
-
- localize(key, params = {}) {
- let text = this.translations[this.currentLang][key] || key;
- for (const [param, value] of Object.entries(params)) {
- text = text.replace(`\${${param}}`, value);
- }
- return text;
- }
-
- showNotification(message, duration = 3000, button = null) {
- const notification = document.createElement('div');
- notification.className = 'kawaii-notification';
-
- let notificationHTML = `
- <span class="kawaii-notification-icon">✧</span>
- <span class="kawaii-notification-text">${message}</span>
- <button class="kawaii-notification-close">✕</button>
- `;
-
- if (button) {
- notificationHTML = `
- <span class="kawaii-notification-icon">✧</span>
- <span class="kawaii-notification-text">${message}</span>
- <button class="kawaii-notification-button">${button.text}</button>
- <button class="kawaii-notification-close">✕</button>
- `;
- }
-
- notification.innerHTML = notificationHTML;
- this.elements.notifications.appendChild(notification);
- setTimeout(() => notification.classList.add('show'), 10);
-
- const timeout = setTimeout(() => {
- notification.classList.remove('show');
- setTimeout(() => notification.remove(), 300);
- }, duration);
-
- notification.querySelector('.kawaii-notification-close').addEventListener('click', () => {
- clearTimeout(timeout);
- notification.classList.remove('show');
- setTimeout(() => notification.remove(), 300);
- });
-
- if (button) {
- notification.querySelector('.kawaii-notification-button').addEventListener('click', () => {
- button.action();
- clearTimeout(timeout);
- notification.classList.remove('show');
- setTimeout(() => notification.remove(), 300);
- });
- }
- }
-
- setup() {
- this.interceptScripts();
- this.injectFonts();
- this.waitForBody(() => {
- this.injectHTML();
- this.cacheElements();
- this.setInitialPosition();
- this.applySavedSettings();
- this.checkForUpdates();
- this.addStyles();
- this.bindEvents();
- this.initializeGameCheck();
- });
- }
-
- interceptScripts() {
- const roomScript = `https://cdn.jsdelivr.net/gh/Gartic-Developers/Kawaii-Helper@${GM_info.script.version}/GameSource/room.js`;
- const createScript = `https://cdn.jsdelivr.net/gh/Gartic-Developers/Kawaii-Helper@${GM_info.script.version}/GameSource/create.js`;
-
- function downloadFileSync(url) {
- const request = new XMLHttpRequest();
- request.open("GET", url, false);
- request.send();
- return request.status === 200 ? request.response : null;
- }
-
- const observer = new MutationObserver((mutations) => {
- mutations.forEach((mutation) => {
- if (mutation.addedNodes) {
- Array.from(mutation.addedNodes).forEach((node) => {
- if (node.nodeName.toLowerCase() === 'script' && node.src && node.src.includes('room') && !node.src.includes('rooms')) {
- node.remove();
- node.src = '';
- node.textContent = '';
- const newScript = downloadFileSync(roomScript);
- window.kawaiiHelper = this;
- Function(newScript)();
- } else if (node.nodeName.toLowerCase() === 'script' && node.src && node.src.includes('create')) {
- node.remove();
- node.src = '';
- node.textContent = '';
- const newScript = downloadFileSync(createScript);
- window.kawaiiHelper = this;
- Function(newScript)();
- }
- });
- }
- });
- });
-
- observer.observe(document, { childList: true, subtree: true });
- }
-
- injectFonts() {
- const fontLink = document.createElement('link');
- fontLink.rel = 'stylesheet';
- fontLink.href = 'https://fonts.googleapis.com/css2?family=M+PLUS+Rounded+1c:wght@400;700&display=swap';
- document.head.appendChild(fontLink);
- }
-
- waitForBody(callback) {
- const interval = setInterval(() => {
- if (document.body) {
- clearInterval(interval);
- callback();
- }
- }, 100);
- }
-
- injectHTML() {
- const kawaiiHTML = `
- <div class="kawaii-cheat" id="kawaiiCheat">
- <div class="kawaii-header" id="kawaiiHeader">
- <img src="https://cdn.jsdelivr.net/gh/Gartic-Developers/Kawaii-Helper@refs/heads/main/Assets/kawaii-logo.png" alt="Anime Girl" class="header-icon">
- <h2 data-translate="✧ Kawaii Helper ✧">✧ Kawaii Helper ✧</h2>
- <button class="minimize-btn" id="minimizeBtn">▼</button>
- </div>
- <div class="kawaii-body" id="kawaiiBody">
- <div class="kawaii-tabs">
- <button class="kawaii-tab active" data-tab="guessing" data-translate="Guessing">Guessing</button>
- <button class="kawaii-tab" data-tab="drawing" data-translate="Drawing">Drawing</button>
- <button class="kawaii-tab" data-tab="settings" data-translate="Settings">Settings</button>
- </div>
- <div class="kawaii-content" id="guessing-tab">
- <div class="checkbox-container">
- <input type="checkbox" id="autoGuess">
- <label for="autoGuess" data-translate="Auto Guess">Auto Guess</label>
- </div>
- <div class="slider-container" id="speedContainer" style="display: none;">
- <div class="slider-label" data-translate="Speed">Speed</div>
- <div class="custom-slider">
- <input type="range" id="guessSpeed" min="100" max="5000" value="1000" step="100">
- <div class="slider-track"></div>
- <span id="speedValue">1s</span>
- </div>
- </div>
- <div class="checkbox-container">
- <input type="checkbox" id="customWords">
- <label for="customWords" data-translate="Custom Words">Custom Words</label>
- </div>
- <div class="dropzone-container" id="wordListContainer" style="display: none;">
- <div class="dropzone" id="wordListDropzone">
- <input type="file" id="wordList" accept=".txt">
- <div class="dropzone-content">
- <div class="dropzone-icon">❀</div>
- <p data-translate="Drop word list here or click to upload">Drop word list here or click to upload</p>
- </div>
- </div>
- </div>
- <div class="input-container">
- <input type="text" id="guessPattern" data-translate-placeholder="Enter pattern (e.g., ___e___)" placeholder="Enter pattern (e.g., ___e___)">
- </div>
- <div class="hit-list" id="hitList">
- <div class="message" data-translate="Type a pattern to see matches ✧">Type a pattern to see matches ✧</div>
- </div>
- </div>
- <div class="kawaii-content" id="drawing-tab" style="display: none;">
- <div class="dropzone-container">
- <div class="dropzone" id="imageDropzone">
- <input type="file" id="imageUpload" accept="image/*">
- <div class="dropzone-content">
- <div class="dropzone-icon">✎</div>
- <p data-translate="Drop image here or click to upload">Drop image here or click to upload</p>
- </div>
- </div>
- <div class="image-preview" id="imagePreview" style="display: none;">
- <img id="previewImg">
- <div class="preview-controls">
- <button class="cancel-btn" id="cancelImage">✕</button>
- </div>
- </div>
- </div>
- <button class="google-search-btn" id="googleSearchBtn" data-translate="Search on Google Images 🡵">Search on Google Images 🡵</button>
- <div class="slider-container">
- <div class="slider-label" data-translate="Draw Speed">Draw Speed</div>
- <div class="custom-slider">
- <input type="range" id="drawSpeed" min="20" max="5000" value="200" step="100">
- <div class="slider-track"></div>
- <span id="drawSpeedValue">200ms</span>
- </div>
- </div>
- <div class="slider-container">
- <div class="slider-label" data-translate="Color Tolerance">Color Tolerance</div>
- <div class="custom-slider">
- <input type="range" id="colorTolerance" min="5" max="100" value="20" step="1">
- <div class="slider-track"></div>
- <span id="colorToleranceValue">20</span>
- </div>
- </div>
- <button class="draw-btn" id="sendDraw" disabled data-translate="Draw Now ✧">Draw Now ✧</button>
- </div>
- <div class="kawaii-content" id="settings-tab" style="display: none;">
- <div class="checkbox-container">
- <input type="checkbox" id="autoKick">
- <label for="autoKick" data-translate="Auto Kick">Auto Kick</label>
- </div>
- <div class="checkbox-container">
- <input type="checkbox" id="noKickCooldown">
- <label for="noKickCooldown" data-translate="No Kick Cooldown">No Kick Cooldown</label>
- </div>
- <div class="checkbox-container">
- <input type="checkbox" id="chatBypassCensorship">
- <label for="chatBypassCensorship" data-translate="Chat Bypass Censorship">Chat Bypass Censorship</label>
- </div>
- </div>
- <div class="kawaii-footer">
- <span class="credit-text" data-translate="Made with ♥ by Anonimbiri & Gartic-Developers">Made with ♥ by Anonimbiri & Gartic-Developers</span>
- </div>
- </div>
- </div>
- <div class="kawaii-notifications" id="kawaiiNotifications"></div>
- `;
- document.body.insertAdjacentHTML('beforeend', kawaiiHTML);
- }
-
- cacheElements() {
- this.elements = {
- kawaiiCheat: document.getElementById('kawaiiCheat'),
- kawaiiHeader: document.getElementById('kawaiiHeader'),
- minimizeBtn: document.getElementById('minimizeBtn'),
- tabButtons: document.querySelectorAll('.kawaii-tab'),
- tabContents: document.querySelectorAll('.kawaii-content'),
- autoGuessCheckbox: document.getElementById('autoGuess'),
- speedContainer: document.getElementById('speedContainer'),
- guessSpeed: document.getElementById('guessSpeed'),
- speedValue: document.getElementById('speedValue'),
- customWordsCheckbox: document.getElementById('customWords'),
- wordListContainer: document.getElementById('wordListContainer'),
- wordListDropzone: document.getElementById('wordListDropzone'),
- wordListInput: document.getElementById('wordList'),
- guessPattern: document.getElementById('guessPattern'),
- hitList: document.getElementById('hitList'),
- imageDropzone: document.getElementById('imageDropzone'),
- imageUpload: document.getElementById('imageUpload'),
- imagePreview: document.getElementById('imagePreview'),
- previewImg: document.getElementById('previewImg'),
- cancelImage: document.getElementById('cancelImage'),
- googleSearchBtn: document.getElementById('googleSearchBtn'),
- drawSpeed: document.getElementById('drawSpeed'),
- drawSpeedValue: document.getElementById('drawSpeedValue'),
- colorTolerance: document.getElementById('colorTolerance'),
- colorToleranceValue: document.getElementById('colorToleranceValue'),
- sendDraw: document.getElementById('sendDraw'),
- autoKickCheckbox: document.getElementById('autoKick'),
- noKickCooldownCheckbox: document.getElementById('noKickCooldown'),
- chatBypassCensorship: document.getElementById('chatBypassCensorship'),
- notifications: document.getElementById('kawaiiNotifications')
- };
- }
-
- setInitialPosition() {
- const waitForRender = () => {
- if (this.elements.kawaiiCheat.offsetWidth > 0 && this.elements.kawaiiCheat.offsetHeight > 0) {
- const savedPosition = this.settings.position;
- let initialX, initialY;
-
- if (savedPosition && savedPosition.x !== null && savedPosition.y !== null) {
- initialX = savedPosition.x;
- initialY = savedPosition.y;
- } else {
- const windowWidth = window.innerWidth;
- const windowHeight = window.innerHeight;
- const cheatWidth = this.elements.kawaiiCheat.offsetWidth;
- const cheatHeight = this.elements.kawaiiCheat.offsetHeight;
- initialX = (windowWidth - cheatWidth) / 2;
- initialY = (windowHeight - cheatHeight) / 2;
- }
-
- this.elements.kawaiiCheat.style.left = `${initialX}px`;
- this.elements.kawaiiCheat.style.top = `${initialY}px`;
- this.state.xOffset = initialX;
- this.state.yOffset = initialY;
- this.elements.kawaiiCheat.classList.add('twirl-minimize');
- this.saveSettings();
- } else {
- requestAnimationFrame(waitForRender);
- }
- };
- requestAnimationFrame(waitForRender);
- }
-
- applySavedSettings() {
- this.elements.autoGuessCheckbox.checked = this.settings.autoGuess;
- this.elements.guessSpeed.value = this.settings.guessSpeed;
- this.elements.customWordsCheckbox.checked = this.settings.customWords;
- this.elements.autoKickCheckbox.checked = this.settings.autoKick;
- this.elements.noKickCooldownCheckbox.checked = this.settings.noKickCooldown;
- this.elements.chatBypassCensorship.checked = this.settings.chatBypassCensorship;
- this.elements.drawSpeed.value = this.settings.drawSpeed;
- this.elements.colorTolerance.value = this.settings.colorTolerance;
-
- this.elements.speedContainer.style.display = this.settings.autoGuess ? 'flex' : 'none';
- this.elements.wordListContainer.style.display = this.settings.customWords ? 'block' : 'none';
- this.updateGuessSpeed({ target: this.elements.guessSpeed });
- this.updateDrawSpeed({ target: this.elements.drawSpeed });
- this.updateColorTolerance({ target: this.elements.colorTolerance });
- }
-
- addStyles() {
- const style = document.createElement('style');
- style.textContent = `
- :root {
- --primary-color: #FF69B4;
- --primary-dark: #FF1493;
- --primary-light: #FFC0CB;
- --bg-color: #FFB6C1;
- --text-color: #5d004f;
- --panel-bg: rgba(255, 182, 193, 0.95);
- --panel-border: #FF69B4;
- --element-bg: rgba(255, 240, 245, 0.7);
- --element-hover: rgba(255, 240, 245, 0.9);
- --element-active: #FF69B4;
- --element-active-text: #FFF0F5;
- }
-
- .kawaii-cheat {
- position: fixed;
- width: 280px;
- background: var(--panel-bg);
- border-radius: 15px;
- box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2);
- padding: 10px;
- display: flex;
- flex-direction: column;
- gap: 10px;
- color: var(--text-color);
- user-select: none;
- z-index: 1000;
- font-family: 'M PLUS Rounded 1c', sans-serif;
- border: 2px solid var(--panel-border);
- transition: height 0.4s ease-in-out, opacity 0.4s ease-in-out;
- max-height: calc(100vh - 40px);
- overflow: hidden;
- opacity: 0;
- }
-
- .kawaii-cheat.comet-enter {
- animation: cometEnter 1.2s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
- }
-
- @keyframes cometEnter {
- 0% { opacity: 0; transform: translateY(-80px) translateX(50px) scale(0.6); filter: brightness(1.5); }
- 50% { opacity: 0.8; transform: translateY(15px) translateX(-10px) scale(1.08); filter: brightness(1.2); }
- 75% { transform: translateY(-8px) translateX(5px) scale(0.95); }
- 100% { opacity: 1; transform: translateY(0) translateX(0) scale(1); filter: brightness(1); }
- }
-
- .kawaii-cheat.minimized {
- height: 50px;
- opacity: 0.85;
- overflow: hidden;
- animation: cometMinimize 0.6s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
- }
-
- @keyframes cometMinimize {
- 0% { transform: scale(1); }
- 30% { transform: scale(0.92); }
- 60% { transform: scale(0.88) translateY(5px); }
- 100% { transform: scale(0.85) translateY(10px); }
- }
-
- .kawaii-cheat:not(.minimized) {
- opacity: 1;
- animation: cometMaximize 0.6s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
- }
-
- @keyframes cometMaximize {
- 0% { transform: scale(0.85) translateY(10px); }
- 60% { transform: scale(1.05) translateY(-5px); }
- 80% { transform: scale(0.98) translateY(2px); }
- 100% { transform: scale(1) translateY(0); }
- }
-
- .kawaii-cheat.minimized .kawaii-body {
- opacity: 0;
- max-height: 0;
- overflow: hidden;
- transition: opacity 0.2s ease-in-out, max-height 0.4s ease-in-out;
- }
-
- .kawaii-cheat:not(.minimized) .kawaii-body {
- opacity: 1;
- max-height: 500px;
- transition: opacity 0.2s ease-in-out 0.2s, max-height 0.4s ease-in-out;
- }
-
- .kawaii-cheat.dragging {
- opacity: 0.8;
- transition: none;
- }
-
- .kawaii-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 5px 10px;
- cursor: move;
- background: var(--element-bg);
- border-radius: 10px;
- border: 2px solid var(--primary-color);
- }
-
- .header-icon {
- width: 30px;
- height: 30px;
- border-radius: 50%;
- margin-right: 10px;
- object-fit: cover;
- object-position: top;
- border: 1px dashed var(--primary-color);
- }
-
- .kawaii-header h2 {
- margin: 0;
- font-size: 18px;
- font-weight: 700;
- color: var(--primary-dark);
- text-shadow: 1px 1px 2px var(--primary-light);
- }
-
- .minimize-btn {
- background: transparent;
- border: 1px dashed var(--primary-dark);
- border-radius: 6px;
- width: 24px;
- height: 24px;
- color: var(--primary-dark);
- font-size: 16px;
- line-height: 20px;
- text-align: center;
- cursor: pointer;
- transition: all 0.3s ease;
- }
-
- .minimize-btn:hover {
- background: var(--primary-color);
- color: var(--element-active-text);
- border-color: var(--primary-color);
- transform: rotate(180deg);
- }
-
- .kawaii-tabs {
- display: flex;
- gap: 8px;
- padding: 5px 0;
- }
-
- .kawaii-tab {
- flex: 1;
- background: var(--element-bg);
- border: 1px dashed var(--primary-color);
- padding: 6px;
- border-radius: 10px;
- font-size: 12px;
- font-weight: 700;
- color: var(--text-color);
- cursor: pointer;
- transition: background 0.3s ease, transform 0.3s ease;
- text-align: center;
- }
-
- .kawaii-tab.active {
- background: var(--primary-color);
- color: var(--element-active-text);
- border-color: var(--primary-dark);
- }
-
- .kawaii-tab:hover:not(.active) {
- background: var(--element-hover);
- transform: scale(1.05);
- }
-
- .kawaii-content {
- display: flex;
- flex-direction: column;
- gap: 10px;
- min-height: 0;
- flex-grow: 1;
- overflow: hidden;
- padding: 5px;
- }
-
- .checkbox-container {
- display: flex;
- align-items: center;
- gap: 8px;
- background: var(--element-bg);
- padding: 8px;
- border-radius: 10px;
- border: 1px dashed var(--primary-color);
- cursor: pointer;
- transition: background 0.3s ease;
- }
-
- .checkbox-container:hover {
- background: var(--element-hover);
- }
-
- .checkbox-container input[type="checkbox"] {
- appearance: none;
- width: 18px;
- height: 18px;
- background: var(--element-active-text);
- border: 1px dashed var(--primary-color);
- border-radius: 50%;
- cursor: pointer;
- position: relative;
- }
-
- .checkbox-container input[type="checkbox"]:checked {
- background: var(--primary-color);
- border-color: var(--primary-dark);
- }
-
- .checkbox-container input[type="checkbox"]:checked::after {
- content: "♥";
- position: absolute;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- color: var(--element-active-text);
- font-size: 12px;
- }
-
- .checkbox-container label {
- font-size: 12px;
- font-weight: 700;
- color: var(--text-color);
- cursor: pointer;
- }
-
- .input-container {
- background: var(--element-bg);
- padding: 8px;
- border-radius: 10px;
- border: 1px dashed var(--primary-color);
- }
-
- .input-container input[type="text"] {
- width: 100%;
- background: var(--element-active-text);
- border: 1px dashed var(--primary-light);
- border-radius: 8px;
- padding: 6px 10px;
- color: var(--text-color);
- font-size: 12px;
- font-weight: 500;
- box-sizing: border-box;
- transition: border-color 0.3s ease;
- outline: none;
- }
-
- .input-container input[type="text"]:focus {
- border-color: var(--primary-dark);
- }
-
- .dropzone-container {
- display: flex;
- flex-direction: column;
- gap: 10px;
- }
-
- .dropzone {
- position: relative;
- background: var(--element-bg);
- border: 1px dashed var(--primary-color);
- border-radius: 10px;
- padding: 15px;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- cursor: pointer;
- transition: background 0.3s ease, border-color 0.3s ease;
- min-height: 80px;
- }
-
- .dropzone:hover, .dropzone.drag-over {
- background: var(--element-hover);
- border-color: var(--primary-dark);
- }
-
- .dropzone input[type="file"] {
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- opacity: 0;
- cursor: pointer;
- }
-
- .dropzone-content {
- display: flex;
- flex-direction: column;
- align-items: center;
- gap: 8px;
- text-align: center;
- pointer-events: none;
- }
-
- .dropzone-icon {
- font-size: 24px;
- color: var(--primary-color);
- animation: pulse 1.5s infinite ease-in-out;
- }
-
- @keyframes pulse {
- 0%, 100% { transform: scale(1); }
- 50% { transform: scale(1.1); }
- }
-
- .dropzone-content p {
- margin: 0;
- color: var(--text-color);
- font-size: 12px;
- font-weight: 500;
- }
-
- .slider-container {
- display: flex;
- flex-direction: column;
- gap: 6px;
- background: var(--element-bg);
- padding: 8px;
- border-radius: 10px;
- border: 1px dashed var(--primary-color);
- }
-
- .slider-label {
- font-size: 12px;
- color: var(--text-color);
- font-weight: 700;
- text-align: center;
- }
-
- .custom-slider {
- position: relative;
- height: 25px;
- padding: 0 8px;
- }
-
- .custom-slider input[type="range"] {
- -webkit-appearance: none;
- width: 100%;
- height: 6px;
- background: transparent;
- position: absolute;
- top: 50%;
- left: 0;
- transform: translateY(-50%);
- z-index: 2;
- }
-
- .custom-slider .slider-track {
- position: absolute;
- top: 50%;
- left: 0;
- width: 100%;
- height: 6px;
- 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%);
- border-radius: 3px;
- transform: translateY(-50%);
- z-index: 1;
- }
-
- .custom-slider input[type="range"]::-webkit-slider-thumb {
- -webkit-appearance: none;
- width: 16px;
- height: 16px;
- background: var(--primary-color);
- border-radius: 50%;
- border: 1px dashed var(--element-active-text);
- cursor: pointer;
- transition: transform 0.3s ease;
- }
-
- .custom-slider input[type="range"]::-webkit-slider-thumb:hover {
- transform: scale(1.2);
- }
-
- .custom-slider span {
- position: absolute;
- bottom: -15px;
- left: 50%;
- transform: translateX(-50%);
- font-size: 10px;
- color: var(--text-color);
- background: var(--element-active-text);
- padding: 2px 6px;
- border-radius: 8px;
- border: 1px dashed var(--primary-color);
- white-space: nowrap;
- }
-
- .hit-list {
- max-height: 180px;
- min-height: 40px;
- overflow-y: auto;
- background: var(--element-bg);
- border: 1px dashed var(--primary-color);
- border-radius: 10px;
- padding: 8px;
- display: flex;
- flex-direction: column;
- gap: 6px;
- scrollbar-width: thin;
- scrollbar-color: var(--primary-color) var(--element-bg);
- box-sizing: border-box;
- }
-
- .hit-list:empty {
- min-height: 40px;
- overflow-y: hidden;
- }
-
- .hit-list::-webkit-scrollbar {
- width: 6px;
- }
-
- .hit-list::-webkit-scrollbar-thumb {
- background-color: var(--primary-color);
- border-radius: 10px;
- }
-
- .hit-list::-webkit-scrollbar-track {
- background: var(--element-bg);
- }
-
- .hit-list button {
- background: rgba(255, 240, 245, 0.8);
- border: 1px dashed var(--primary-color);
- padding: 8px 10px;
- border-radius: 8px;
- color: var(--text-color);
- font-size: 12px;
- font-weight: 700;
- line-height: 1.5;
- cursor: pointer;
- position: relative;
- overflow: hidden;
- transition: all 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
- text-align: left;
- box-sizing: border-box;
- min-height: 32px;
- }
-
- .hit-list button:before {
- content: '';
- position: absolute;
- left: -100%;
- top: 0;
- width: 100%;
- height: 100%;
- background: linear-gradient(90deg, transparent, var(--primary-light), transparent);
- transition: all 0.5s ease;
- z-index: 0;
- }
-
- .hit-list button:hover:not(.tried):before {
- left: 100%;
- }
-
- .hit-list button:hover:not(.tried) {
- background: var(--primary-dark);
- color: var(--element-active-text);
- box-shadow: 0 0 15px rgba(255, 105, 180, 0.5);
- }
-
- .hit-list button span {
- position: relative;
- z-index: 1;
- }
-
- .hit-list button.tried {
- background: rgba(255, 182, 193, 0.6);
- border-color: var(--primary-light);
- color: var(--primary-dark);
- opacity: 0.7;
- cursor: not-allowed;
- }
-
- .hit-list .tried-label {
- font-size: 10px;
- color: var(--primary-dark);
- text-align: center;
- padding: 4px;
- background: var(--element-active-text);
- border-radius: 8px;
- border: 1px dashed var(--primary-color);
- }
-
- .hit-list .message {
- font-size: 12px;
- color: var(--text-color);
- text-align: center;
- padding: 8px;
- }
-
- .image-preview {
- position: relative;
- margin-top: 10px;
- background: var(--element-bg);
- padding: 8px;
- border-radius: 10px;
- border: 1px dashed var(--primary-color);
- }
-
- .image-preview img {
- max-width: 100%;
- max-height: 120px;
- border-radius: 8px;
- display: block;
- margin: 0 auto;
- }
-
- .preview-controls {
- position: absolute;
- top: 12px;
- right: 12px;
- display: flex;
- gap: 6px;
- }
-
- .cancel-btn {
- background: transparent;
- border: 1px dashed var(--primary-dark);
- border-radius: 6px;
- width: 24px;
- height: 24px;
- color: var(--primary-dark);
- font-size: 16px;
- line-height: 20px;
- text-align: center;
- cursor: pointer;
- transition: all 0.3s ease;
- }
-
- .cancel-btn:hover {
- background: var(--primary-dark);
- color: var(--element-active-text);
- transform: scale(1.1);
- }
-
- .draw-btn {
- background: var(--primary-color);
- border: 1px dashed var(--primary-dark);
- padding: 8px;
- border-radius: 10px;
- color: var(--element-active-text);
- font-size: 14px;
- font-weight: 700;
- cursor: pointer;
- position: relative;
- overflow: hidden;
- transition: all 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
- text-align: center;
- width: 100%;
- box-sizing: border-box;
- }
-
- .draw-btn:before {
- content: '';
- position: absolute;
- left: -100%;
- top: 0;
- width: 100%;
- height: 100%;
- background: linear-gradient(90deg, transparent, var(--primary-light), transparent);
- transition: all 0.5s ease;
- }
-
- .draw-btn:hover:not(:disabled):before {
- left: 100%;
- }
-
- .draw-btn:hover:not(:disabled) {
- background: var(--primary-dark);
- box-shadow: 0 0 15px rgba(255, 105, 180, 0.5);
- }
-
- .draw-btn:disabled {
- background: rgba(255, 105, 180, 0.5);
- cursor: not-allowed;
- }
-
- .kawaii-footer {
- display: flex;
- justify-content: center;
- align-items: center;
- margin-top: 10px;
- padding: 6px;
- background: var(--element-bg);
- border-radius: 10px;
- border: 2px solid var(--primary-color);
- }
-
- .credit-text {
- font-size: 10px;
- color: var(--text-color);
- font-weight: 700;
- }
-
- .kawaii-notifications {
- position: fixed;
- top: 20px;
- right: 20px;
- display: flex;
- flex-direction: column;
- gap: 10px;
- z-index: 2000;
- pointer-events: none;
- }
-
- .kawaii-notification {
- background: var(--panel-bg);
- border: 2px solid var(--panel-border);
- border-radius: 12px;
- padding: 12px 18px;
- color: var(--text-color);
- font-family: 'M PLUS Rounded 1c', sans-serif;
- font-size: 14px;
- font-weight: 700;
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
- display: flex;
- align-items: center;
- gap: 10px;
- max-width: 300px;
- opacity: 0;
- transform: translateX(100%);
- transition: opacity 0.3s ease, transform 0.3s ease;
- pointer-events: auto;
- gap: 8px;
- padding: 12px 12px;
- }
-
- .kawaii-notification.show {
- opacity: 1;
- transform: translateX(0);
- }
-
- .kawaii-notification-icon {
- font-size: 20px;
- color: var(--primary-dark);
- animation: bounce 1s infinite ease-in-out;
- }
-
- @keyframes bounce {
- 0%, 100% { transform: translateY(0); }
- 50% { transform: translateY(-5px); }
- }
-
- .kawaii-notification-button {
- background: var(--primary-color);
- border: 1px dashed var(--primary-dark);
- border-radius: 6px;
- padding: 4px 8px;
- color: var(--element-active-text);
- font-size: 12px;
- font-weight: 700;
- cursor: pointer;
- transition: all 0.3s ease;
- white-space: nowrap;
- }
-
- .kawaii-notification-button:hover {
- background: var(--primary-dark);
- transform: scale(1.05);
- }
-
- .kawaii-notification-close {
- background: transparent;
- border: 1px dashed var(--primary-dark);
- border-radius: 6px;
- width: 20px;
- height: 20px;
- color: var(--primary-dark);
- font-size: 12px;
- line-height: 18px;
- text-align: center;
- cursor: pointer;
- transition: all 0.3s ease;
- margin-left: auto;
- }
-
- .kawaii-notification-close:hover {
- background: var(--primary-dark);
- color: var(--element-active-text);
- transform: scale(1.1);
- }
-
- .google-search-btn {
- background: var(--primary-color);
- border: 1px dashed var(--primary-dark);
- border-radius: 8px;
- padding: 6px 10px;
- color: var(--element-active-text);
- font-size: 12px;
- font-weight: 700;
- cursor: pointer;
- position: relative;
- overflow: hidden;
- transition: all 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
- width: 100%;
- box-sizing: border-box;
- height: 30px;
- text-align: center;
- }
-
- .google-search-btn:before {
- content: '';
- position: absolute;
- left: -100%;
- top: 0;
- width: 100%;
- height: 100%;
- background: linear-gradient(90deg, transparent, var(--primary-light), transparent);
- transition: all 0.5s ease;
- }
-
- .google-search-btn:hover:not(:disabled):before {
- left: 100%;
- }
-
- .google-search-btn:hover:not(:disabled) {
- background: var(--primary-dark);
- box-shadow: 0 0 15px rgba(255, 105, 180, 0.5);
- }
-
- .google-search-btn:disabled {
- background: rgba(255, 105, 180, 0.5);
- cursor: not-allowed;
- }
- `;
- document.head.appendChild(style);
- this.updateLanguage();
- [this.elements.guessSpeed, this.elements.drawSpeed, this.elements.colorTolerance].forEach(this.updateSliderTrack.bind(this));
- }
-
- updateLanguage() {
- document.querySelectorAll('[data-translate]').forEach(element => {
- element.textContent = this.localize(element.getAttribute('data-translate'));
- });
- document.querySelectorAll('[data-translate-placeholder]').forEach(element => {
- element.setAttribute('placeholder', this.localize(element.getAttribute('data-translate-placeholder')));
- });
- }
-
- updateSliderTrack(slider) {
- const min = parseInt(slider.min);
- const max = parseInt(slider.max);
- const value = parseInt(slider.value);
- const progress = ((value - min) / (max - min)) * 100;
- slider.parentElement.querySelector('.slider-track').style.setProperty('--slider-progress', `${progress}%`);
- }
-
- preventDefaults(e) {
- e.preventDefault();
- e.stopPropagation();
- }
-
- bindEvents() {
- this.elements.kawaiiHeader.addEventListener('mousedown', this.startDragging.bind(this));
- document.addEventListener('mousemove', this.drag.bind(this));
- document.addEventListener('mouseup', this.stopDragging.bind(this));
- this.elements.minimizeBtn.addEventListener('click', this.toggleMinimize.bind(this));
- this.elements.tabButtons.forEach(btn => btn.addEventListener('click', this.switchTab.bind(this, btn)));
-
- document.querySelectorAll('.checkbox-container').forEach(container => {
- const checkbox = container.querySelector('input[type="checkbox"]');
- const label = container.querySelector('label');
- container.addEventListener('click', e => {
- if (e.target !== checkbox && e.target !== label) {
- checkbox.checked = !checkbox.checked;
- checkbox.dispatchEvent(new Event('change'));
- }
- });
- label.addEventListener('click', e => e.stopPropagation());
- });
-
- this.elements.autoGuessCheckbox.addEventListener('change', (e) => {
- this.toggleAutoGuess(e);
- this.saveSettings();
- });
- this.elements.guessSpeed.addEventListener('input', (e) => {
- this.updateGuessSpeed(e);
- this.saveSettings();
- });
- this.elements.customWordsCheckbox.addEventListener('change', (e) => {
- this.toggleCustomWords(e);
- this.saveSettings();
- });
- this.elements.guessPattern.addEventListener('input', e => this.updateHitList(e.target.value.trim()));
- this.elements.hitList.addEventListener('click', this.handleHitListClick.bind(this));
-
- ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
- this.elements.wordListDropzone.addEventListener(eventName, this.preventDefaults, false);
- this.elements.imageDropzone.addEventListener(eventName, this.preventDefaults, false);
- });
- this.elements.wordListDropzone.addEventListener('dragenter', () => this.elements.wordListDropzone.classList.add('drag-over'));
- this.elements.wordListDropzone.addEventListener('dragover', () => this.elements.wordListDropzone.classList.add('drag-over'));
- this.elements.wordListDropzone.addEventListener('dragleave', () => this.elements.wordListDropzone.classList.remove('drag-over'));
- this.elements.wordListDropzone.addEventListener('drop', this.handleWordListDrop.bind(this));
- this.elements.wordListInput.addEventListener('change', this.handleWordListInput.bind(this));
-
- this.elements.imageDropzone.addEventListener('dragenter', () => this.elements.imageDropzone.classList.add('drag-over'));
- this.elements.imageDropzone.addEventListener('dragover', () => this.elements.imageDropzone.classList.add('drag-over'));
- this.elements.imageDropzone.addEventListener('dragleave', () => this.elements.imageDropzone.classList.remove('drag-over'));
- this.elements.imageDropzone.addEventListener('drop', this.handleImageDrop.bind(this));
- this.elements.imageUpload.addEventListener('change', this.handleImageInput.bind(this));
- this.elements.cancelImage.addEventListener('click', this.cancelImagePreview.bind(this));
- this.elements.googleSearchBtn.addEventListener('click', () => {
- if (!window.game || !window.game._palavra || !window.game.turn) {
- this.showNotification(this.localize("Game not ready or not your turn! ✧"), 3000);
- return;
- }
- const word = window.game._palavra;
- const searchUrl = `https://www.google.com/search?q=${encodeURIComponent(word)}+vectorial&tbm=isch`;
- window.open(searchUrl, '_blank');
- });
- this.elements.drawSpeed.addEventListener('input', (e) => {
- this.updateDrawSpeed(e);
- this.saveSettings();
- });
- this.elements.colorTolerance.addEventListener('input', (e) => {
- this.updateColorTolerance(e);
- this.saveSettings();
- });
- this.elements.sendDraw.addEventListener('click', this.toggleDrawing.bind(this));
-
- this.elements.autoKickCheckbox.addEventListener('change', () => {
- this.showNotification(`Auto Kick: ${this.elements.autoKickCheckbox.checked ? 'Enabled' : 'Disabled'}`, 2000);
- this.saveSettings();
- });
- this.elements.noKickCooldownCheckbox.addEventListener('change', () => {
- this.showNotification(`No Kick Cooldown: ${this.elements.noKickCooldownCheckbox.checked ? 'Enabled' : 'Disabled'}`, 2000);
- this.saveSettings();
- });
- this.elements.chatBypassCensorship.addEventListener('change', () => {
- this.showNotification(`Chat Bypass Censorship: ${this.elements.chatBypassCensorship.checked ? 'Enabled' : 'Disabled'}`, 2000);
- this.saveSettings();
- });
-
- window.addEventListener('resize', () => {
- const windowWidth = window.innerWidth;
- const windowHeight = window.innerHeight;
- const cheatWidth = this.elements.kawaiiCheat.offsetWidth;
- const cheatHeight = this.elements.kawaiiCheat.offsetHeight;
-
- let newX = this.state.xOffset;
- let newY = this.state.yOffset;
-
- newX = Math.max(0, Math.min(newX, windowWidth - cheatWidth));
- newY = Math.max(0, Math.min(newY, windowHeight - cheatHeight));
-
- if (newX !== this.state.xOffset || newY !== this.state.yOffset) {
- this.state.xOffset = newX;
- this.state.yOffset = newY;
- this.elements.kawaiiCheat.style.left = `${newX}px`;
- this.elements.kawaiiCheat.style.top = `${newY}px`;
- this.saveSettings();
- }
- });
- }
-
- startDragging(e) {
- if (e.target !== this.elements.minimizeBtn) {
- this.state.initialX = e.clientX - this.state.xOffset;
- this.state.initialY = e.clientY - this.state.yOffset;
- this.state.isDragging = true;
- this.elements.kawaiiCheat.classList.add('dragging');
- if (this.state.rafId) cancelAnimationFrame(this.state.rafId);
- }
- }
-
- drag(e) {
- if (!this.state.isDragging) return;
- e.preventDefault();
- const newX = e.clientX - this.state.initialX;
- const newY = e.clientY - this.state.initialY;
-
- // Get window and menu dimensions
- const windowWidth = window.innerWidth;
- const windowHeight = window.innerHeight;
- const cheatWidth = this.elements.kawaiiCheat.offsetWidth;
- const cheatHeight = this.elements.kawaiiCheat.offsetHeight;
-
- // Constrain position within window boundaries
- const clampedX = Math.max(0, Math.min(newX, windowWidth - cheatWidth));
- const clampedY = Math.max(0, Math.min(newY, windowHeight - cheatHeight));
-
- if (this.state.rafId) cancelAnimationFrame(this.state.rafId);
- this.state.rafId = requestAnimationFrame(() => {
- this.elements.kawaiiCheat.style.left = `${clampedX}px`;
- this.elements.kawaiiCheat.style.top = `${clampedY}px`;
- this.state.xOffset = clampedX;
- this.state.yOffset = clampedY;
- this.saveSettings();
- });
- }
-
- stopDragging() {
- if (this.state.isDragging) {
- this.state.isDragging = false;
- this.elements.kawaiiCheat.classList.remove('dragging');
- if (this.state.rafId) cancelAnimationFrame(this.state.rafId);
- this.saveSettings();
- }
- }
-
- toggleMinimize() {
- this.elements.kawaiiCheat.classList.toggle('minimized');
- this.elements.minimizeBtn.textContent = this.elements.kawaiiCheat.classList.contains('minimized') ? '▲' : '▼';
- }
-
- switchTab(btn) {
- this.elements.tabButtons.forEach(b => b.classList.remove('active'));
- this.elements.tabContents.forEach(c => c.style.display = 'none');
- btn.classList.add('active');
- document.getElementById(`${btn.dataset.tab}-tab`).style.display = 'flex';
- }
-
- toggleAutoGuess(e) {
- this.elements.speedContainer.style.display = e.target.checked ? 'flex' : 'none';
- if (!e.target.checked) this.stopAutoGuess();
- else if (this.elements.guessPattern.value) this.startAutoGuess();
- }
-
- updateGuessSpeed(e) {
- this.updateSliderTrack(e.target);
- this.elements.speedValue.textContent = e.target.value >= 1000 ? `${e.target.value / 1000}s` : `${e.target.value}ms`;
- if (this.elements.autoGuessCheckbox.checked && this.state.autoGuessInterval) {
- this.stopAutoGuess();
- this.startAutoGuess();
- }
- }
-
- toggleCustomWords(e) {
- this.elements.wordListContainer.style.display = e.target.checked ? 'block' : 'none';
- this.updateHitList(this.elements.guessPattern.value.trim());
- }
-
- handleWordListDrop(e) {
- this.elements.wordListDropzone.classList.remove('drag-over');
- const file = e.dataTransfer.files[0];
- if (file && file.type === 'text/plain') this.handleWordListFile(file);
- }
-
- handleWordListInput(e) {
- const file = e.target.files[0];
- if (file) {
- this.handleWordListFile(file);
- e.target.value = '';
- }
- }
-
- handleWordListFile(file) {
- const reader = new FileReader();
- reader.onload = (event) => {
- this.wordList["Custom"] = event.target.result.split('\n').map(word => word.trim()).filter(word => word.length > 0);
- this.showNotification(this.localize("Loaded ${wordList['Custom'].length} words from ${file.name}", {
- "wordList['Custom'].length": this.wordList["Custom"].length,
- "file.name": file.name
- }), 4000);
- this.updateHitList(this.elements.guessPattern.value.trim());
- };
- reader.readAsText(file);
- }
-
- handleHitListClick(e) {
- if (e.target.tagName !== 'BUTTON' || e.target.classList.contains('tried')) return;
- const button = e.target;
- button.classList.add('tried');
- if (!this.state.triedLabelAdded && this.elements.hitList.querySelectorAll('button.tried').length === 1) {
- const triedLabel = document.createElement('div');
- triedLabel.classList.add('tried-label');
- triedLabel.textContent = this.localize("Tried Words");
- this.elements.hitList.appendChild(triedLabel);
- this.state.triedLabelAdded = true;
- }
- if (window.game && window.game._socket) {
- window.game._socket.emit(13, window.game._codigo, button.textContent);
- }
- this.elements.hitList.appendChild(button);
- }
-
- startAutoGuess() {
- if (!this.elements.autoGuessCheckbox.checked) return;
- this.stopAutoGuess();
- const speed = parseInt(this.elements.guessSpeed.value);
- this.state.autoGuessInterval = setInterval(() => {
- const buttons = this.elements.hitList.querySelectorAll('button:not(.tried)');
- if (buttons.length > 0 && window.game && window.game._socket) {
- const word = buttons[0].textContent;
- buttons[0].classList.add('tried');
- window.game._socket.emit(13, window.game._codigo, word);
- if (!this.state.triedLabelAdded && this.elements.hitList.querySelectorAll('button.tried').length === 1) {
- const triedLabel = document.createElement('div');
- triedLabel.classList.add('tried-label');
- triedLabel.textContent = this.localize("Tried Words");
- this.elements.hitList.appendChild(triedLabel);
- this.state.triedLabelAdded = true;
- }
- this.elements.hitList.appendChild(buttons[0]);
- }
- }, speed);
- }
-
- stopAutoGuess() {
- if (this.state.autoGuessInterval) {
- clearInterval(this.state.autoGuessInterval);
- this.state.autoGuessInterval = null;
- }
- }
-
- updateHitList(pattern) {
- if (!this.elements.hitList) return;
- this.elements.hitList.innerHTML = '';
- this.state.triedLabelAdded = false;
-
- const activeTheme = this.elements.customWordsCheckbox.checked || !window.game || !window.game._dadosSala || !window.game._dadosSala.tema
- ? "Custom"
- : window.game._dadosSala.tema;
- const activeList = this.wordList[activeTheme] || [];
-
- if (!pattern) {
- if (activeList.length === 0) {
- this.elements.hitList.innerHTML = `<div class="message">${this.localize(this.elements.customWordsCheckbox.checked ? "Upload a custom word list ✧" : "No words available ✧")}</div>`;
- } else {
- activeList.forEach(word => {
- const button = document.createElement('button');
- button.textContent = word;
- this.elements.hitList.appendChild(button);
- });
- }
- return;
- }
-
- const regex = new RegExp(`^${pattern.split('').map(char => char === '_' ? '.' : char).join('')}$`, 'i');
- const matches = activeList.filter(word => regex.test(word));
-
- if (matches.length === 0) {
- this.elements.hitList.innerHTML = `<div class="message">${this.localize("No matches found ✧")}</div>`;
- } else {
- matches.forEach(word => {
- const button = document.createElement('button');
- button.textContent = word;
- this.elements.hitList.appendChild(button);
- });
- }
- }
-
- async fetchWordList(theme) {
- if (!this.wordList[theme] && this.wordListURLs[theme]) {
- try {
- const response = await fetch(this.wordListURLs[theme]);
- if (!response.ok) throw new Error(`Failed to fetch ${theme} word list`);
- const data = await response.json();
- this.wordList[theme] = data.words || data;
- } catch (error) {
- this.wordList[theme] = [];
- }
- }
- }
-
- handleImageDrop(e) {
- this.elements.imageDropzone.classList.remove('drag-over');
- const file = e.dataTransfer.files[0];
- if (file && file.type.startsWith('image/')) this.handleImageFile(file);
- }
-
- handleImageInput(e) {
- const file = e.target.files[0];
- if (file) {
- this.handleImageFile(file);
- e.target.value = '';
- }
- }
-
- handleImageFile(file) {
- const reader = new FileReader();
- reader.onload = (event) => {
- this.elements.previewImg.src = event.target.result;
- this.elements.imageDropzone.style.display = 'none';
- this.elements.imagePreview.style.display = 'block';
- this.elements.sendDraw.disabled = false;
- };
- reader.readAsDataURL(file);
- }
-
- cancelImagePreview() {
- this.elements.previewImg.src = '';
- this.elements.imageDropzone.style.display = 'flex';
- this.elements.imagePreview.style.display = 'none';
- this.elements.sendDraw.disabled = true;
- this.elements.imageUpload.value = '';
- }
-
- updateDrawSpeed(e) {
- this.updateSliderTrack(e.target);
- this.elements.drawSpeedValue.textContent = e.target.value >= 1000 ? `${e.target.value / 1000}s` : `${e.target.value}ms`;
- }
-
- updateColorTolerance(e) {
- this.updateSliderTrack(e.target);
- this.elements.colorToleranceValue.textContent = e.target.value;
- }
-
- toggleDrawing() {
- if (!this.elements.previewImg.src) return;
-
- if (!this.isDrawing) {
- if (!window.game || !window.game.turn) {
- this.showNotification(this.localize("Not your turn or game not loaded! ✧"), 3000);
- return;
- }
- this.isDrawing = true;
- this.elements.sendDraw.textContent = this.localize("Stop Drawing ✧");
- this.processAndDrawImage(this.elements.previewImg.src);
- } else {
- this.isDrawing = false;
- this.stopDrawing();
- }
- }
-
- initializeGameCheck() {
- const checkGame = setInterval(() => {
- if (window.game) {
- clearInterval(checkGame);
- const currentTheme = window.game._dadosSala.tema || "Custom";
- if (currentTheme !== "Custom") {
- this.fetchWordList(currentTheme).then(() => this.updateHitList(this.elements.guessPattern.value.trim()));
- }
- }
- }, 100);
- }
-
- async processAndDrawImage(imageSrc) {
- if (!window.game || !window.game._socket || !window.game._desenho || !window.game.turn) {
- this.showNotification(this.localize("Game not ready or not your turn! ✧"), 3000);
- this.stopDrawing();
- return;
- }
-
- const img = new Image();
- img.crossOrigin = "Anonymous";
-
- img.onload = async () => {
- let gameCanvas, ctx, canvasWidth, canvasHeight;
- try {
- gameCanvas = window.game._desenho._canvas.canvas;
- if (!gameCanvas || !(gameCanvas instanceof HTMLCanvasElement)) throw new Error("Canvas not accessible!");
- ctx = gameCanvas.getContext('2d', { willReadFrequently: true });
- if (!ctx) throw new Error("Canvas context not available!");
- canvasWidth = Math.floor(gameCanvas.width);
- canvasHeight = Math.floor(gameCanvas.height);
- if (canvasWidth <= 0 || canvasHeight <= 0) throw new Error("Invalid canvas dimensions!");
- } catch (e) {
- this.showNotification(this.localize(e.message.includes("Canvas not accessible") ? "Canvas not accessible! ✧" : "Canvas context not available! ✧"), 3000);
- this.stopDrawing();
- return;
- }
-
- const { tempCtx, imgLeft, imgRight, imgTop, imgBottom } = this.prepareImageCanvas(img, canvasWidth, canvasHeight);
- if (!tempCtx) {
- this.showNotification(this.localize("Temp canvas context failed! ✧"), 3000);
- this.stopDrawing();
- return;
- }
-
- const { imageData, data } = this.getImageData(tempCtx, canvasWidth, canvasHeight);
- if (!imageData) {
- this.stopDrawing();
- return;
- }
-
- const drawSpeedValue = parseInt(this.elements.drawSpeed.value) || 200;
- const colorToleranceValue = parseInt(this.elements.colorTolerance.value) || 20;
-
- const regions = await this.detectRegions(data, canvasWidth, canvasHeight, imgLeft, imgRight, imgTop, imgBottom, colorToleranceValue);
- if (!this.isDrawing || !window.game.turn) {
- this.stopDrawing();
- return;
- }
-
- this.showNotification(`Processing ${regions.length} color regions...`, 2000);
- for (const region of regions) {
- if (!this.isDrawing || !window.game.turn) {
- this.showNotification(this.localize("Drawing stopped! ✧"), 2000);
- this.stopDrawing();
- return;
- }
-
- try {
- await this.fillRegion(region.coords, region.hex);
- await this.delay(drawSpeedValue);
- } catch (e) {
- console.error("Kawaii Helper: Error during region fill:", e);
- this.showNotification("Error during region fill.", 2000);
- }
- }
-
- if (this.isDrawing && window.game.turn) {
- this.showNotification(this.localize("Drawing completed! ✧"), 3000);
- }
- this.stopDrawing();
- };
-
- img.onerror = () => {
- this.showNotification(this.localize("Failed to load image! ✧"), 3000);
- this.stopDrawing();
- };
-
- img.src = imageSrc;
- }
-
- prepareImageCanvas(img, canvasWidth, canvasHeight) {
- const tempCanvas = document.createElement('canvas');
- tempCanvas.width = canvasWidth;
- tempCanvas.height = canvasHeight;
- const tempCtx = tempCanvas.getContext('2d', { willReadFrequently: true });
-
- const scale = Math.min(canvasWidth / img.width, canvasHeight / img.height);
- const newWidth = Math.floor(img.width * scale);
- const newHeight = Math.floor(img.height * scale);
- const offsetX = Math.floor((canvasWidth - newWidth) / 2);
- const offsetY = Math.floor((canvasHeight - newHeight) / 2);
-
- tempCtx.imageSmoothingEnabled = false;
- tempCtx.fillStyle = 'white';
- tempCtx.fillRect(0, 0, canvasWidth, canvasHeight);
- tempCtx.drawImage(img, offsetX, offsetY, newWidth, newHeight);
-
- return {
- tempCtx,
- imgLeft: offsetX,
- imgRight: offsetX + newWidth,
- imgTop: offsetY,
- imgBottom: offsetY + newHeight
- };
- }
-
- getImageData(tempCtx, canvasWidth, canvasHeight) {
- let imageData, data;
- try {
- imageData = tempCtx.getImageData(0, 0, canvasWidth, canvasHeight);
- data = imageData.data;
- } catch (e) {
- this.showNotification(this.localize("Image data error: ${e.message} ✧", { "e.message": e.message }), 3000);
- return { imageData: null, data: null };
- }
- return { imageData, data };
- }
-
- async detectRegions(data, canvasWidth, canvasHeight, imgLeft, imgRight, imgTop, imgBottom, colorToleranceValue) {
- const backgroundColor = [255, 255, 255, 255];
- const visited = new Uint8Array(canvasWidth * canvasHeight);
- const regions = [];
-
- const getColorAt = (x, y) => {
- if (x < 0 || x >= canvasWidth || y < 0 || y >= canvasHeight) {
- return backgroundColor;
- }
- const index = (y * canvasWidth + x) * 4;
- return [data[index], data[index + 1], data[index + 2], data[index + 3]];
- };
-
- const traceRegion = (startX, startY, startColor, tolerance) => {
- const regionCoords = [];
- const stack = [[startX, startY]];
- const currentRegionVisited = new Set([`${startX},${startY}`]);
- let minX = startX, minY = startY, maxX = startX, maxY = startY;
-
- visited[startY * canvasWidth + startX] = 1;
-
- const neighbors = [
- [1, 0], [-1, 0], [0, 1], [0, -1] // 4-direction check
- ];
-
- while (stack.length > 0) {
- const [x, y] = stack.pop();
- regionCoords.push([x, y]);
- minX = Math.min(minX, x); minY = Math.min(minY, y);
- maxX = Math.max(maxX, x); maxY = Math.max(maxY, y);
-
- for (const [dx, dy] of neighbors) {
- const nx = x + dx;
- const ny = y + dy;
- const nKey = `${nx},${ny}`;
- if (nx >= imgLeft && nx <= imgRight && ny >= imgTop && ny <= imgBottom &&
- visited[ny * canvasWidth + nx] === 0 &&
- !currentRegionVisited.has(nKey)
- ) {
- const neighborColor = getColorAt(nx, ny);
- const distance = this.colorDistance(neighborColor, startColor);
-
- if (distance <= tolerance * 1.2) { // Dynamic tolerance
- visited[ny * canvasWidth + nx] = 1;
- currentRegionVisited.add(nKey);
- stack.push([nx, ny]);
- }
- }
- }
- }
-
- return regionCoords.length > 0 ? { // Allow smaller regions
- coords: regionCoords,
- color: startColor.slice(0, 3),
- bounds: { minX, minY, maxX, maxY }
- } : null;
- };
-
- for (let y = imgTop; y <= imgBottom && this.isDrawing; y += 1) {
- for (let x = imgLeft; x <= imgRight && this.isDrawing; x += 1) {
- if (!window.game.turn) { this.stopDrawing(); return []; }
- const index = y * canvasWidth + x;
- if (visited[index] === 1) continue;
-
- const pixelColor = getColorAt(x, y);
- const regionResult = traceRegion(x, y, pixelColor, colorToleranceValue);
-
- if (regionResult) {
- if (this.colorDistance(regionResult.color, backgroundColor) > colorToleranceValue) {
- regions.push({
- color: regionResult.color,
- hex: this.rgbToHex(regionResult.color),
- coords: regionResult.coords,
- size: regionResult.coords.length,
- bounds: regionResult.bounds
- });
- }
- } else {
- visited[index] = 1;
- }
- }
- }
-
- regions.sort((a, b) => b.size - a.size);
- return regions;
- }
-
- areRegionsClose(boundsA, boundsB, threshold) {
- const horizontalClose = Math.abs(boundsA.maxX - boundsB.minX) <= threshold ||
- Math.abs(boundsB.maxX - boundsA.minX) <= threshold;
- const verticalClose = Math.abs(boundsA.maxY - boundsB.minY) <= threshold ||
- Math.abs(boundsB.maxY - boundsA.minY) <= threshold;
- return horizontalClose && verticalClose;
- }
-
- async fillRegion(region, colorHex) {
- if (!this.isDrawing || !window.game || !window.game._socket || !window.game.turn) {
- this.stopDrawing();
- return;
- }
-
- const canvas = window.game._desenho._canvas.canvas;
- const ctx = canvas.getContext('2d');
- const canvasWidth = canvas.width;
- const canvasHeight = canvas.height;
-
- const regionSet = new Set(region.map(([x, y]) => `${x},${y}`));
- const visited = new Set();
- const fills = [];
- const queue = [region[0]];
-
- const isInRegion = (x, y) => regionSet.has(`${x},${y}`) && !visited.has(`${x},${y}`);
-
- while (queue.length > 0 && this.isDrawing) {
- const [x, y] = queue.shift();
- if (!isInRegion(x, y)) continue;
-
- let leftX = x;
- let rightX = x;
-
- while (leftX - 1 >= 0 && isInRegion(leftX - 1, y)) leftX--;
- while (rightX + 1 < canvasWidth && isInRegion(rightX + 1, y)) rightX++;
-
- const width = rightX - leftX + 1;
- fills.push([leftX, y, width, 1]);
-
- for (let i = leftX; i <= rightX; i++) visited.add(`${i},${y}`);
-
- if (y - 1 >= 0) {
- for (let i = leftX; i <= rightX; i++) {
- if (isInRegion(i, y - 1)) queue.push([i, y - 1]);
- }
- }
- if (y + 1 < canvasHeight) {
- for (let i = leftX; i <= rightX; i++) {
- if (isInRegion(i, y + 1)) queue.push([i, y + 1]);
- }
- }
- }
-
- if (fills.length > 0 && this.isDrawing) {
- window.game._socket.emit(10, window.game._codigo, [5, colorHex]);
- ctx.fillStyle = `#${colorHex.slice(1)}`;
-
- const fillCommand = [3, ...fills.flat()];
- window.game._socket.emit(10, window.game._codigo, fillCommand);
- fills.forEach(([x, y, w, h]) => ctx.fillRect(x, y, w, h));
- }
- }
-
- stopDrawing() {
- this.isDrawing = false;
- this.elements.sendDraw.textContent = this.localize("Draw Now ✧");
- this.elements.sendDraw.disabled = !(this.elements.previewImg.src && this.elements.previewImg.src !== '#');
- }
-
- colorDistance(color1_rgb, color2_rgb) {
- if (!color1_rgb || !color2_rgb || color1_rgb.length < 3 || color2_rgb.length < 3) return Infinity;
- const rDiff = color1_rgb[0] - color2_rgb[0];
- const gDiff = color1_rgb[1] - color2_rgb[1];
- const bDiff = color1_rgb[2] - color2_rgb[2];
- return Math.sqrt(rDiff * rDiff + gDiff * gDiff + bDiff * bDiff);
- }
-
- rgbToHex(rgb) {
- if (!rgb || rgb.length < 3) return '#000000';
- const r = Math.min(255, Math.max(0, Math.round(rgb[0])));
- const g = Math.min(255, Math.max(0, Math.round(rgb[1])));
- const b = Math.min(255, Math.max(0, Math.round(rgb[2])));
- return 'x' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1).toUpperCase();
- }
-
- delay(ms) {
- return new Promise(resolve => setTimeout(resolve, ms));
- }
- }
-
- KawaiiHelper.init();
- })();