Kawaii Helper & Drawing Bot for Gartic.io

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

目前为 2025-03-12 提交的版本。查看 最新版本

// ==UserScript==
// @name         Kawaii Helper & Drawing Bot for Gartic.io
// @name:tr      Gartic.io için Kawaii Yardımcı & Çizim Botu
// @namespace    http://tampermonkey.net/
// @version      2025-03-13
// @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        https://gartic.io/*
// @icon         https://i.imgur.com/efEvdp5.png
// @run-at       document-start
// @grant        none
// ==/UserScript==

// I used the word list from 'https://github.com/Gartic-Developers/Gartic-WordList/'.
// Thanks to Gartic Developers for providing this resource. Also, thanks to Qwyua!

(function() {
    'use strict';

    const 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",
            "Draw Speed": "Draw Speed",
            "Max Colors": "Max Colors",
            "Draw Now ✧": "Draw Now ✧",
            "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! ✧"
        },
        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",
            "Draw Speed": "Çizim Hızı",
            "Max Colors": "Maksimum Renk",
            "Draw Now ✧": "Şimdi Çiz ✧",
            "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! ✧"
        }
    };

    let currentLang = navigator.language.split('-')[0];
    if (!translations[currentLang]) {
        currentLang = 'en';
    }

    function localize(key, params = {}) {
        let text = translations[currentLang][key] || key;
        for (const [param, value] of Object.entries(params)) {
            text = text.replace(`\${${param}}`, value);
        }
        return text;
    }

    function showNotification(message, duration = 3000) {
        const notificationsContainer = document.getElementById('kawaiiNotifications');
        const notification = document.createElement('div');
        notification.className = 'kawaii-notification';

        notification.innerHTML = `
            <span class="kawaii-notification-icon">✧</span>
            <span class="kawaii-notification-text">${message}</span>
            <button class="kawaii-notification-close">✕</button>
        `;

        notificationsContainer.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);
        });
    }

    let isDrawingActive = false;

    // Script interception
    Node.prototype.appendChild = new Proxy(Node.prototype.appendChild, {
        apply: function(target, thisArg, argumentsList) {
            const node = argumentsList[0];
            if (node.nodeName.toLowerCase() === 'script' && node.src && node.src.includes('room')) {
                console.log('Target script detected:', node.src);
                fetch(node.src)
                    .then(response => response.text())
                    .then(scriptContent => {
                    let modifiedContent = scriptContent
                    .replace(
                        'r.created||c?Rt("input",{type:"text",name:"chat",className:"mousetrap",autoComplete:"off",autoCorrect:"off",autoCapitalize:"off",value:i,placeholder:this._lang.chatHere,maxLength:100,enterKeyHint:"send",onChange:this.handleText,ref:this._ref}):Rt("input",{type:"text",name:"chat",className:"mousetrap",autoComplete:"off",autoCorrect:"off",autoCapitalize:"off",value:this._lang.loginChat,maxLength:100,ref:this._ref,disabled:!0})',
                        'Rt("input",{type:"text",name:"chat",className:"mousetrap",autoComplete:"off",autoCorrect:"off",autoCapitalize:"off",value:i,placeholder:this._lang.chatHere,maxLength:100,enterKeyHint:"send",onChange:this.handleText,ref:this._ref})'
                    )
                    .replace(
                        'this._timerAtivo=setInterval((function(){Date.now()-e._ativo>15e4&&(O(Object(f.a)(n.prototype),"emit",e).call(e,"avisoInativo"),e._ativo=Date.now())}),1e3)',
                        'this._timerAtivo=setInterval((function(){Date.now()-e._ativo>15e4&&e.active()}),1e3)'
                    )
                    .replace(
                        'e.unlock()}',
                        'e.unlock();window.game=e;setInterval(()=>{window.game=e},1000);e.on("votekick",(t,i,o)=>{if(i.id===e.me.id){e.votekick(t.id,true);}});}'
                    );
                    let blob = new Blob([modifiedContent], { type: 'application/javascript' });
                    let blobUrl = URL.createObjectURL(blob);
                    node.src = blobUrl;
                    node.textContent = '';
                    return target.apply(thisArg, [node]);
                })
                    .catch(error => console.error('Failed to fetch/modify script:', error));
                return node;
            }
            return target.apply(thisArg, argumentsList);
        }
    });

    // Load fonts
    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);

    // Inject HTML
    const kawaiiHTML = `
        <div class="kawaii-cheat" id="kawaiiCheat">
            <div class="kawaii-header" id="kawaiiHeader">
                <img src="https://i.imgur.com/efEvdp5.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>
                </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>
                    <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="Max Colors">Max Colors</div>
                        <div class="custom-slider">
                            <input type="range" id="maxColors" min="3" max="1000" value="100" step="1">
                            <div class="slider-track"></div>
                            <span id="maxColorsValue">100</span>
                        </div>
                    </div>
                    <button class="draw-btn" id="sendDraw" disabled data-translate="Draw Now ✧">Draw Now ✧</button>
                </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>
    `;

    const waitForBody = setInterval(() => {
        if (document.body) {
            clearInterval(waitForBody);
            document.body.insertAdjacentHTML('beforeend', kawaiiHTML);
            addStylesAndBehavior();
        }
    }, 100);

    function addStylesAndBehavior() {
        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;
                top: 20px;
                right: 20px;
                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: all 0.4s ease-in-out;
                max-height: calc(100vh - 40px);
                overflow: hidden;
            }

            .kawaii-cheat.minimized {
                height: 50px;
                opacity: 0.9;
                transform: scale(0.95);
                overflow: hidden;
            }

            .kawaii-cheat:not(.minimized) {
                opacity: 1;
                transform: scale(1);
            }

            .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;
                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;
                overflow-y: scroll;
                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);
            }

            .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: 6px 10px;
                border-radius: 8px;
                color: var(--text-color);
                font-size: 12px;
                font-weight: 700;
                cursor: pointer;
                transition: background 0.3s ease, transform 0.3s ease;
                text-align: left;
            }

            .hit-list button:hover:not(.tried) {
                background: var(--primary-color);
                color: var(--element-active-text);
                transform: scale(1.03);
            }

            .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;
                transition: background 0.3s ease, transform 0.3s ease;
                text-align: center;
                width: 100%;
                box-sizing: border-box;
            }

            .draw-btn:hover:not(:disabled) {
                background: var(--primary-dark);
                transform: scale(1.05);
            }

            .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;
            }

            .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-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;
            }

            .kawaii-notification-close:hover {
                background: var(--primary-dark);
                color: var(--element-active-text);
                transform: scale(1.1);
            }
        `;
        document.head.appendChild(style);

        // DOM Elements
        const kawaiiCheat = document.getElementById('kawaiiCheat');
        const kawaiiHeader = document.getElementById('kawaiiHeader');
        const minimizeBtn = document.getElementById('minimizeBtn');
        const tabButtons = document.querySelectorAll('.kawaii-tab');
        const tabContents = document.querySelectorAll('.kawaii-content');
        const autoGuessCheckbox = document.getElementById('autoGuess');
        const speedContainer = document.getElementById('speedContainer');
        const guessSpeed = document.getElementById('guessSpeed');
        const speedValue = document.getElementById('speedValue');
        const customWordsCheckbox = document.getElementById('customWords');
        const wordListContainer = document.getElementById('wordListContainer');
        const wordListDropzone = document.getElementById('wordListDropzone');
        const wordListInput = document.getElementById('wordList');
        const guessPattern = document.getElementById('guessPattern');
        const hitList = document.getElementById('hitList');
        const imageDropzone = document.getElementById('imageDropzone');
        const imageUpload = document.getElementById('imageUpload');
        const imagePreview = document.getElementById('imagePreview');
        const previewImg = document.getElementById('previewImg');
        const cancelImage = document.getElementById('cancelImage');
        const drawSpeed = document.getElementById('drawSpeed');
        const drawSpeedValue = document.getElementById('drawSpeedValue');
        const maxColors = document.getElementById('maxColors');
        const maxColorsValue = document.getElementById('maxColorsValue');
        const sendDraw = document.getElementById('sendDraw');

        // Variables
        let isDragging = false;
        let initialX, initialY;
        let xOffset = 0, yOffset = 0;
        let rafId = null;
        let autoGuessInterval = null;
        let wordList = { "Custom": [] };
        let triedLabelAdded = false;

        const 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"
        };

        // Dil güncelleme fonksiyonu
        function updateLanguage() {
            document.querySelectorAll('[data-translate]').forEach(element => {
                const key = element.getAttribute('data-translate');
                element.textContent = localize(key);
            });
            document.querySelectorAll('[data-translate-placeholder]').forEach(element => {
                const key = element.getAttribute('data-translate-placeholder');
                element.setAttribute('placeholder', localize(key));
            });
        }

        // İlk yüklemede dil uygulansın
        updateLanguage();

        // Utility Functions
        function 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}%`);
        }

        function preventDefaults(e) {
            e.preventDefault();
            e.stopPropagation();
        }

        // Initial Setup
        updateSliderTrack(guessSpeed);
        updateSliderTrack(drawSpeed);
        updateSliderTrack(maxColors);

        // Dragging Functionality with Optimization
        kawaiiHeader.addEventListener('mousedown', (e) => {
            if (e.target !== minimizeBtn) {
                initialX = e.clientX - xOffset;
                initialY = e.clientY - yOffset;
                isDragging = true;
                kawaiiCheat.classList.add('dragging');
                if (rafId) cancelAnimationFrame(rafId);
            }
        });

        document.addEventListener('mousemove', (e) => {
            if (isDragging) {
                e.preventDefault();
                const newX = e.clientX - initialX;
                const newY = e.clientY - initialY;

                if (rafId) cancelAnimationFrame(rafId);
                rafId = requestAnimationFrame(() => {
                    kawaiiCheat.style.transform = `translate(${newX}px, ${newY}px)`;
                    xOffset = newX;
                    yOffset = newY;
                });
            }
        });

        document.addEventListener('mouseup', () => {
            if (isDragging) {
                isDragging = false;
                kawaiiCheat.classList.remove('dragging');
                if (rafId) cancelAnimationFrame(rafId);
            }
        });

        // Minimize Button
        minimizeBtn.addEventListener('click', () => {
            kawaiiCheat.classList.toggle('minimized');
            minimizeBtn.textContent = kawaiiCheat.classList.contains('minimized') ? '▲' : '▼';
        });

        // Tab Switching
        tabButtons.forEach(btn => {
            btn.addEventListener('click', () => {
                tabButtons.forEach(b => b.classList.remove('active'));
                tabContents.forEach(c => c.style.display = 'none');
                btn.classList.add('active');
                document.getElementById(`${btn.dataset.tab}-tab`).style.display = 'flex';
            });
        });

        // Checkbox Container Click
        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();
                checkbox.checked = !checkbox.checked;
                checkbox.dispatchEvent(new Event('change'));
            });
        });

        // Auto Guess Checkbox
        autoGuessCheckbox.addEventListener('change', (e) => {
            speedContainer.style.display = e.target.checked ? 'flex' : 'none';
            if (!e.target.checked) stopAutoGuess();
            else if (guessPattern.value) startAutoGuess();
        });

        // Guess Speed Slider
        guessSpeed.addEventListener('input', (e) => {
            updateSliderTrack(e.target);
            speedValue.textContent = e.target.value >= 1000 ? `${e.target.value / 1000}s` : `${e.target.value}ms`;
            if (autoGuessCheckbox.checked && autoGuessInterval) {
                stopAutoGuess();
                startAutoGuess();
            }
        });

        // Custom Words Checkbox
        customWordsCheckbox.addEventListener('change', (e) => {
            wordListContainer.style.display = e.target.checked ? 'block' : 'none';
            updateHitList(guessPattern.value.trim());
        });

        // Word List Dropzone
        ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
            wordListDropzone.addEventListener(eventName, preventDefaults, false);
        });

        wordListDropzone.addEventListener('dragenter', () => wordListDropzone.classList.add('drag-over'));
        wordListDropzone.addEventListener('dragover', () => wordListDropzone.classList.add('drag-over'));
        wordListDropzone.addEventListener('dragleave', () => wordListDropzone.classList.remove('drag-over'));
        wordListDropzone.addEventListener('drop', (e) => {
            wordListDropzone.classList.remove('drag-over');
            const file = e.dataTransfer.files[0];
            if (file && file.type === 'text/plain') handleWordListFile(file);
        });

        wordListInput.addEventListener('change', (e) => {
            const file = e.target.files[0];
            if (file) {
                handleWordListFile(file);
                e.target.value = '';
            }
        });

        function handleWordListFile(file) {
            const reader = new FileReader();
            reader.onload = function(event) {
                wordList["Custom"] = event.target.result.split('\n').map(word => word.trim()).filter(word => word.length > 0);
                showNotification(localize("Loaded ${wordList['Custom'].length} words from ${file.name}", {
                    "wordList['Custom'].length": wordList["Custom"].length,
                    "file.name": file.name
                }), 4000);
                updateHitList(guessPattern.value.trim());
            };
            reader.readAsText(file);
        }

        // Guess Pattern Input
        guessPattern.addEventListener('input', (e) => updateHitList(e.target.value.trim()));

        // Hit List Functionality
        hitList.addEventListener('click', (e) => {
            if (e.target.tagName === 'BUTTON' && !e.target.classList.contains('tried')) {
                const button = e.target;
                button.classList.add('tried');
                if (!triedLabelAdded && hitList.querySelectorAll('button.tried').length === 1) {
                    const triedLabel = document.createElement('div');
                    triedLabel.classList.add('tried-label');
                    triedLabel.textContent = localize("Tried Words");
                    hitList.appendChild(triedLabel);
                    triedLabelAdded = true;
                }
                if (window.game && window.game._socket) {
                    window.game._socket.emit(13, window.game._codigo, button.textContent);
                }
                hitList.appendChild(button);
            }
        });

        function startAutoGuess() {
            if (!autoGuessCheckbox.checked) return;
            stopAutoGuess();
            const speed = parseInt(guessSpeed.value);
            autoGuessInterval = setInterval(() => {
                const buttons = 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 (!triedLabelAdded && hitList.querySelectorAll('button.tried').length === 1) {
                        const triedLabel = document.createElement('div');
                        triedLabel.classList.add('tried-label');
                        triedLabel.textContent = localize("Tried Words");
                        hitList.appendChild(triedLabel);
                        triedLabelAdded = true;
                    }
                    hitList.appendChild(buttons[0]);
                }
            }, speed);
        }

        function stopAutoGuess() {
            if (autoGuessInterval) {
                clearInterval(autoGuessInterval);
                autoGuessInterval = null;
            }
        }

        function updateHitList(pattern) {
            hitList.innerHTML = '';
            triedLabelAdded = false;
            const activeTheme = customWordsCheckbox.checked || !window.game || !window.game._dadosSala || !window.game._dadosSala.tema
            ? "Custom" : window.game._dadosSala.tema;
            const activeList = wordList[activeTheme] || [];

            if (!pattern) {
                if (activeList.length === 0) {
                    hitList.innerHTML = `<div class="message">${localize(customWordsCheckbox.checked ? "Upload a custom word list ✧" : "No words available ✧")}</div>`;
                } else {
                    activeList.forEach(word => {
                        const button = document.createElement('button');
                        button.textContent = word;
                        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) {
                hitList.innerHTML = `<div class="message">${localize("No matches found ✧")}</div>`;
            } else {
                matches.forEach(word => {
                    const button = document.createElement('button');
                    button.textContent = word;
                    hitList.appendChild(button);
                });
            }
        }

        async function fetchWordList(theme) {
            if (!wordList[theme] && wordListURLs[theme]) {
                try {
                    const response = await fetch(wordListURLs[theme]);
                    if (!response.ok) throw new Error(`Failed to fetch ${theme} word list`);
                    const data = await response.json();
                    wordList[theme] = data.words || data;
                    console.log(`Loaded ${wordList[theme].length} words for ${theme}`);
                } catch (error) {
                    console.error(`Error fetching word list for ${theme}:`, error);
                    wordList[theme] = [];
                }
            }
        }

        // Image Upload
        ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
            imageDropzone.addEventListener(eventName, preventDefaults, false);
        });

        imageDropzone.addEventListener('dragenter', () => imageDropzone.classList.add('drag-over'));
        imageDropzone.addEventListener('dragover', () => imageDropzone.classList.add('drag-over'));
        imageDropzone.addEventListener('dragleave', () => imageDropzone.classList.remove('drag-over'));
        imageDropzone.addEventListener('drop', (e) => {
            imageDropzone.classList.remove('drag-over');
            const file = e.dataTransfer.files[0];
            if (file && file.type.startsWith('image/')) handleImageFile(file);
        });

        imageUpload.addEventListener('change', (e) => {
            const file = e.target.files[0];
            if (file) {
                handleImageFile(file);
                e.target.value = '';
            }
        });

        function handleImageFile(file) {
            const reader = new FileReader();
            reader.onload = function(event) {
                previewImg.src = event.target.result;
                imageDropzone.style.display = 'none';
                imagePreview.style.display = 'block';
                sendDraw.disabled = false;
            };
            reader.readAsDataURL(file);
        }

        cancelImage.addEventListener('click', () => {
            previewImg.src = '';
            imageDropzone.style.display = 'flex';
            imagePreview.style.display = 'none';
            sendDraw.disabled = true;
            imageUpload.value = '';
        });

        drawSpeed.addEventListener('input', (e) => {
            updateSliderTrack(e.target);
            drawSpeedValue.textContent = e.target.value >= 1000 ? `${e.target.value / 1000}s` : `${e.target.value}ms`;
        });

        maxColors.addEventListener('input', (e) => {
            updateSliderTrack(e.target);
            maxColorsValue.textContent = e.target.value;
        });

        sendDraw.addEventListener('click', () => {
            if (previewImg.src) {
                if (!window.game || !window.game.turn) {
                    showNotification(localize("Not your turn or game not loaded! ✧"), 3000);
                    return;
                }
                sendDraw.disabled = true;
                isDrawingActive = true;
                processAndDrawImage(previewImg.src);
            }
        });

        // Socket Integration
        const checkGame = setInterval(() => {
            if (window.game && window.game._socket) {
                clearInterval(checkGame);
                const currentTheme = window.game._dadosSala.tema || "Custom";
                if (currentTheme !== "Custom") {
                    fetchWordList(currentTheme).then(() => updateHitList(guessPattern.value.trim()));
                }

                window.game._socket.on(30, (hint) => {
                    hint = String(hint).replace(/,/g, '');
                    guessPattern.value = hint;
                    updateHitList(hint);
                    if (autoGuessCheckbox.checked) startAutoGuess();
                });

                window.game._socket.on(19, () => {
                    guessPattern.value = '';
                    stopAutoGuess();
                    updateHitList('');
                    if (isDrawingActive) {
                        isDrawingActive = false;
                        sendDraw.disabled = false;
                    }
                });

                window.game._socket.on(15, (playerId) => {
                    if (playerId === window.game.me.id) {
                        guessPattern.value = '';
                        stopAutoGuess();
                        updateHitList('');
                    }
                });

                let lastTheme = currentTheme;
                setInterval(() => {
                    const newTheme = window.game._dadosSala.tema || "Custom";
                    if (newTheme !== lastTheme && newTheme !== "Custom") {
                        lastTheme = newTheme;
                        fetchWordList(newTheme).then(() => updateHitList(guessPattern.value.trim()));
                    }
                }, 1000);
            }
        }, 100);
    }

    function processAndDrawImage(imageSrc) {
        if (!window.game || !window.game._socket || !window.game._desenho || !window.game.turn || !isDrawingActive) {
            showNotification(localize("Game not ready or not your turn! ✧"), 3000);
            sendDraw.disabled = false;
            return;
        }

        const img = new Image();
        img.crossOrigin = "Anonymous";
        img.onload = async function() {
            if (!isDrawingActive) {
                showNotification(localize("Drawing stopped! ✧"), 3000);
                sendDraw.disabled = false;
                return;
            }

            const gameCanvas = window.game._desenho._canvas.canvas;
            if (!gameCanvas || !gameCanvas.width || !gameCanvas.height) {
                showNotification(localize("Canvas not accessible! ✧"), 3000);
                sendDraw.disabled = false;
                isDrawingActive = false;
                return;
            }

            const ctx = gameCanvas.getContext('2d');
            if (!ctx) {
                showNotification(localize("Canvas context not available! ✧"), 3000);
                sendDraw.disabled = false;
                isDrawingActive = false;
                return;
            }

            const canvasWidth = Math.floor(gameCanvas.width);
            const canvasHeight = Math.floor(gameCanvas.height);

            const tempCanvas = document.createElement('canvas');
            const tempCtx = tempCanvas.getContext('2d');
            if (!tempCtx) {
                showNotification(localize("Temp canvas context failed! ✧"), 3000);
                sendDraw.disabled = false;
                isDrawingActive = false;
                return;
            }

            const scale = Math.min(canvasWidth / img.width, canvasHeight / img.height);
            const newWidth = Math.floor(img.width * scale);
            const newHeight = Math.floor(img.height * scale);

            tempCanvas.width = canvasWidth;
            tempCanvas.height = canvasHeight;

            const offsetX = Math.floor((canvasWidth - newWidth) / 2);
            const offsetY = Math.floor((canvasHeight - newHeight) / 2);

            tempCtx.drawImage(img, offsetX, offsetY, newWidth, newHeight);

            let imageData;
            try {
                imageData = tempCtx.getImageData(0, 0, canvasWidth, canvasHeight);
            } catch (e) {
                showNotification(localize("Image data error: ${e.message} ✧", { "e.message": e.message }), 4000);
                sendDraw.disabled = false;
                isDrawingActive = false;
                return;
            }

            if (!isDrawingActive) {
                showNotification(localize("Drawing stopped! ✧"), 3000);
                sendDraw.disabled = false;
                return;
            }

            const data = imageData.data;
            const drawSpeedValue = parseInt(drawSpeed.value) || 300;

            const maxColorsValue = parseInt(maxColors.value) || 20;

            const imgLeft = offsetX;
            const imgRight = offsetX + newWidth - 1;
            const imgTop = offsetY;
            const imgBottom = offsetY + newHeight - 1;

            const colorCounts = new Map();
            const sampleStep = Math.max(1, Math.floor(newWidth / 50));

            for (let x = imgLeft; x <= imgRight; x += sampleStep) {
                for (let y of [imgTop, imgTop+1, imgTop+2, imgBottom-2, imgBottom-1, imgBottom]) {
                    if (y >= imgTop && y <= imgBottom) {
                        const index = (y * canvasWidth + x) * 4;
                        const r = data[index];
                        const g = data[index+1];
                        const b = data[index+2];
                        const key = `${r},${g},${b}`;
                        colorCounts.set(key, (colorCounts.get(key) || 0) + 3);
                    }
                }
            }

            for (let y = imgTop; y <= imgBottom; y += sampleStep) {
                for (let x of [imgLeft, imgLeft+1, imgLeft+2, imgRight-2, imgRight-1, imgRight]) {
                    if (x >= imgLeft && x <= imgRight) {
                        const index = (y * canvasWidth + x) * 4;
                        const r = data[index];
                        const g = data[index+1];
                        const b = data[index+2];
                        const key = `${r},${g},${b}`;
                        colorCounts.set(key, (colorCounts.get(key) || 0) + 3);
                    }
                }
            }

            for (let x = imgLeft + 3; x <= imgRight - 3; x += sampleStep * 2) {
                for (let y = imgTop + 3; y <= imgBottom - 3; y += sampleStep * 2) {
                    const index = (y * canvasWidth + x) * 4;
                    const r = data[index];
                    const g = data[index+1];
                    const b = data[index+2];
                    const key = `${r},${g},${b}`;
                    colorCounts.set(key, (colorCounts.get(key) || 0) + 1);
                }
            }

            let maxCount = 0;
            let backgroundColor = [255, 255, 255];

            for (const [key, count] of colorCounts) {
                if (count > maxCount) {
                    maxCount = count;
                    backgroundColor = key.split(',').map(Number);
                }
            }

            if (backgroundColor[0] > 240 && backgroundColor[1] > 240 && backgroundColor[2] > 240) {
                backgroundColor = [255, 255, 255];
            }

            const bgHex = 'x' + backgroundColor.map(c =>
                                                    c.toString(16).padStart(2, '0').toUpperCase()
                                                   ).join('');

            window.game._socket.emit(10, window.game._codigo, [4]);
            ctx.clearRect(0, 0, canvasWidth, canvasHeight);
            await new Promise(resolve => setTimeout(resolve, drawSpeedValue));

            if (!isDrawingActive) {
                showNotification(localize("Drawing stopped! ✧"), 3000);
                sendDraw.disabled = false;
                return;
            }

            window.game._socket.emit(10, window.game._codigo, [5, bgHex]);
            ctx.fillStyle = `#${bgHex.slice(1)}`;
            await new Promise(resolve => setTimeout(resolve, drawSpeedValue));

            if (!isDrawingActive) {
                showNotification(localize("Drawing stopped! ✧"), 3000);
                sendDraw.disabled = false;
                return;
            }

            window.game._socket.emit(10, window.game._codigo, [3, 0, 0, canvasWidth, canvasHeight]);
            ctx.fillRect(0, 0, canvasWidth, canvasHeight);
            await new Promise(resolve => setTimeout(resolve, drawSpeedValue));

            if (!isDrawingActive) {
                showNotification(localize("Drawing stopped! ✧"), 3000);
                sendDraw.disabled = false;
                return;
            }

            const colorClusters = new Map();
            for (let y = imgTop; y <= imgBottom; y += sampleStep) {
                for (let x = imgLeft; x <= imgRight; x += sampleStep) {
                    const index = (y * canvasWidth + x) * 4;
                    const r = data[index];
                    const g = data[index+1];
                    const b = data[index+2];
                    const key = `${r},${g},${b}`;

                    if (colorDistance([r, g, b], backgroundColor) > 30) {
                        colorClusters.set(key, (colorClusters.get(key) || 0) + 1);
                    }
                }
            }

            const topColors = [...colorClusters.entries()]
            .sort((a, b) => b[1] - a[1])
            .slice(0, maxColorsValue)
            .map(([key]) => ({
                rgb: key.split(',').map(Number),
                hex: 'x' + key.split(',').map(c =>
                                              Number(c).toString(16).padStart(2, '0').toUpperCase()
                                             ).join('')
            }));

            const fillsByColor = {};
            const visited = new Set();
            const stripHeight = 1;
            const stripWidth = 1;
            const minStripSize = 1;

            for (let y = imgTop; y <= imgBottom; y += stripHeight) {
                let startX = null;
                let currentColor = null;
                let stripLength = 0;

                for (let x = imgLeft; x <= imgRight; x += 1) {
                    const index = (y * canvasWidth + x) * 4;
                    const pixelColor = [data[index], data[index+1], data[index+2]];
                    const bgDist = colorDistance(pixelColor, backgroundColor);

                    if (bgDist > 30 && !visited.has(`${x},${y}`)) {
                        const nearestColor = topColors.reduce((prev, curr) =>
                                                              colorDistance(pixelColor, prev.rgb) < colorDistance(pixelColor, curr.rgb) ? prev : curr
                                                             );

                        if (startX === null || currentColor?.hex !== nearestColor.hex) {
                            if (startX !== null && stripLength >= minStripSize) {
                                if (!fillsByColor[currentColor.hex]) fillsByColor[currentColor.hex] = [];
                                fillsByColor[currentColor.hex].push([startX, y, stripLength, stripHeight]);
                                for (let dx = 0; dx < stripLength; dx++) {
                                    visited.add(`${startX + dx},${y}`);
                                }
                            }
                            startX = x;
                            currentColor = nearestColor;
                            stripLength = 1;
                        } else {
                            stripLength++;
                        }
                    } else if (startX !== null && bgDist <= 30) {
                        if (stripLength >= minStripSize) {
                            if (!fillsByColor[currentColor.hex]) fillsByColor[currentColor.hex] = [];
                            fillsByColor[currentColor.hex].push([startX, y, stripLength, stripHeight]);
                            for (let dx = 0; dx < stripLength; dx++) {
                                visited.add(`${startX + dx},${y}`);
                            }
                        }
                        startX = null;
                        currentColor = null;
                        stripLength = 0;
                    }
                }

                if (startX !== null && stripLength >= minStripSize) {
                    if (!fillsByColor[currentColor.hex]) fillsByColor[currentColor.hex] = [];
                    fillsByColor[currentColor.hex].push([startX, y, stripLength, stripHeight]);
                    for (let dx = 0; dx < stripLength; dx++) {
                        visited.add(`${startX + dx},${y}`);
                    }
                }
            }

            for (let x = imgLeft; x <= imgRight; x += stripWidth) {
                let startY = null;
                let currentColor = null;
                let stripLength = 0;

                for (let y = imgTop; y <= imgBottom; y += 1) {
                    const index = (y * canvasWidth + x) * 4;
                    const pixelColor = [data[index], data[index+1], data[index+2]];
                    const bgDist = colorDistance(pixelColor, backgroundColor);

                    if (bgDist > 30 && !visited.has(`${x},${y}`)) {
                        const nearestColor = topColors.reduce((prev, curr) =>
                                                              colorDistance(pixelColor, prev.rgb) < colorDistance(pixelColor, curr.rgb) ? prev : curr
                                                             );

                        if (startY === null || currentColor?.hex !== nearestColor.hex) {
                            if (startY !== null && stripLength >= minStripSize) {
                                if (!fillsByColor[currentColor.hex]) fillsByColor[currentColor.hex] = [];
                                fillsByColor[currentColor.hex].push([x, startY, stripWidth, stripLength]);
                                for (let dy = 0; dy < stripLength; dy++) {
                                    visited.add(`${x},${startY + dy}`);
                                }
                            }
                            startY = y;
                            currentColor = nearestColor;
                            stripLength = 1;
                        } else {
                            stripLength++;
                        }
                    } else if (startY !== null && bgDist <= 30) {
                        if (stripLength >= minStripSize) {
                            if (!fillsByColor[currentColor.hex]) fillsByColor[currentColor.hex] = [];
                            fillsByColor[currentColor.hex].push([x, startY, stripWidth, stripLength]);
                            for (let dy = 0; dy < stripLength; dy++) {
                                visited.add(`${x},${startY + dy}`);
                            }
                        }
                        startY = null;
                        currentColor = null;
                        stripLength = 0;
                    }
                }

                if (startY !== null && stripLength >= minStripSize) {
                    if (!fillsByColor[currentColor.hex]) fillsByColor[currentColor.hex] = [];
                    fillsByColor[currentColor.hex].push([x, startY, stripWidth, stripLength]);
                    for (let dy = 0; dy < stripLength; dy++) {
                        visited.add(`${x},${startY + dy}`);
                    }
                }
            }

            for (let y = imgTop; y <= imgBottom; y += 1) {
                for (let x = imgLeft; x <= imgRight; x += 1) {
                    if (visited.has(`${x},${y}`)) continue;

                    const index = (y * canvasWidth + x) * 4;
                    const pixelColor = [data[index], data[index+1], data[index+2]];
                    if (colorDistance(pixelColor, backgroundColor) <= 30) continue;

                    const nearestColor = topColors.reduce((prev, curr) =>
                                                          colorDistance(pixelColor, prev.rgb) < colorDistance(pixelColor, curr.rgb) ? prev : curr
                                                         );

                    if (!fillsByColor[nearestColor.hex]) fillsByColor[nearestColor.hex] = [];
                    fillsByColor[nearestColor.hex].push([x, y, 1, 1]);
                    visited.add(`${x},${y}`);
                }
            }

            if (!isDrawingActive) {
                showNotification(localize("Drawing stopped! ✧"), 3000);
                sendDraw.disabled = false;
                return;
            }

            for (const color in fillsByColor) {
                const fillCommand = [3];
                fillsByColor[color].forEach(([x, y, width, height]) => {
                    fillCommand.push(x, y, width, height);
                });

                if (!isDrawingActive) {
                    showNotification(localize("Drawing stopped! ✧"), 3000);
                    sendDraw.disabled = false;
                    return;
                }

                window.game._socket.emit(10, window.game._codigo, [5, color]);
                ctx.fillStyle = `#${color.slice(1)}`;
                await new Promise(resolve => setTimeout(resolve, drawSpeedValue));

                if (!isDrawingActive) {
                    showNotification(localize("Drawing stopped! ✧"), 3000);
                    sendDraw.disabled = false;
                    return;
                }

                window.game._socket.emit(10, window.game._codigo, fillCommand);
                fillsByColor[color].forEach(([x, y, width, height]) => {
                    ctx.fillRect(x, y, width, height);
                });
                await new Promise(resolve => setTimeout(resolve, drawSpeedValue));
            }

            if (isDrawingActive) {
                showNotification(localize("Drawing completed! ✧"), 3000);
            }
            sendDraw.disabled = false;
            isDrawingActive = false;
        };
        img.onerror = function() {
            showNotification(localize("Failed to load image! ✧"), 3000);
            sendDraw.disabled = false;
            isDrawingActive = false;
        };
        img.src = imageSrc;
    }

    function colorDistance(color1, color2) {
        const rMean = (color1[0] + color2[0]) / 2;
        const r = color1[0] - color2[0];
        const g = color1[1] - color2[1];
        const b = color1[2] - color2[2];

        return Math.sqrt(
            (2 + rMean/256) * r*r +
            4 * g*g +
            (2 + (255-rMean)/256) * b*b
        );
    }
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址