Pixel Battles Bot

Бот для автоматического рисования на Pixel Battles

// ==UserScript==
// @license MIT
// @name         Pixel Battles Bot
// @namespace    http://tampermonkey.net/
// @version      2.2
// @description  Бот для автоматического рисования на Pixel Battles
// @author       .hilkach.
// @match        https://pixelbattles.ru/*
// @grant        GM_addStyle
// @grant        GM_xmlhttpRequest
// @connect      pixelbattles.ru
// ==/UserScript==

(function() {
    'use strict';

    // ===== ГЛОБАЛЬНЫЕ НАСТРОЙКИ ===== //
    const SETTINGS = {
        modes: {
            "top-down": "⬇️ Сверху вниз",
            "bottom-up": "⬆️ Снизу вверх",
            "left-right": "➡️ Слева направо",
            "right-left": "⬅️ Справа налево",
            "random": "🎲 Случайные точки",
            "diagonal-lt-rb": "↘️ Лев.верх → прав.низ",
            "diagonal-rt-lb": "↙️ Прав.верх → лев.низ",
            "diagonal-lb-rt": "↗️ Лев.низ → прав.верх",
            "diagonal-rb-lt": "↖️ Прав.низ → лев.верх"
        },
        colors: {
            "deepcarmine": "Красный",
            "flame": "Оранжево-красный",
            "yelloworange": "Жёлто-оранжевый",
            "naplesyellow": "Жёлтый",
            "mediumseagreen": "Зелёный",
            "emerald": "Изумрудный",
            "inchworm": "Салатовый",
            "myrtlegreen": "Тёмно-зелёный",
            "verdigris": "Бирюзовый",
            "cyancobaltblue": "Синий кобальт",
            "unitednationsblue": "Синий",
            "mediumskyblue": "Голубой",
            "oceanblue": "Морской волны",
            "VeryLightBlue": "Светло-голубой",
            "grape": "Фиолетовый",
            "purpleplum": "Сливовый",
            "darkpink": "Розовый",
            "mauvelous": "Розоватый",
            "coffee": "Коричневый",
            "coconut": "Бежевый",
            "black": "Чёрный",
            "philippinegray": "Серый",
            "lightsilver": "Серебристый",
            "white": "Белый"
        },
        colorHexes: [
            "#ae233d", "#ec5427", "#f4ab3c", "#f9d759", "#48a06d",
            "#5cc87f", "#9ae96c", "#317270", "#469ca8", "#2d519e",
            "#4d90e3", "#7ee6f2", "#4440ba", "#6662f6", "#772b99",
            "#a754ba", "#eb4e81", "#f19eab", "#684a34", "#956a34",
            "#000000", "#898d90", "#d5d7d9", "#ffffff"
        ],
        color: "black",
        delay: 1000,
        isRunning: false,
        abortController: null,
        configs: [],
        MAX_CONFIGS: 5 // Лимит конфигов
    };

    // ===== ФУНКЦИИ РИСОВАНИЯ ===== //
    async function placePixel(x, y, color, signal) {
        try {
            if (signal?.aborted) throw new Error("Операция прервана");

            const response = await fetch("https://api.pixelbattles.ru/pix", {
                method: "PUT",
                headers: { "Content-Type": "application/json" },
                body: JSON.stringify({ x, y, color }),
                credentials: "include",
                signal
            });

            console.log(`✅ (${x}, ${y})`);
            return true;
        } catch (error) {
            if (error.name !== "AbortError") console.error(`❌ (${x}, ${y}):`, error.message);
            return false;
        }
    }

    // ===== ФУНКЦИИ РИСОВАНИЯ (РЕЖИМЫ) ===== //
    const drawingModes = {
        "top-down": async function*(x, y, w, h) {
            for (let row = y; row < y + h; row++) {
                for (let col = x; col < x + w; col++) {
                    yield {x: col, y: row};
                }
            }
        },
        "bottom-up": async function*(x, y, w, h) {
            for (let row = y + h - 1; row >= y; row--) {
                for (let col = x; col < x + w; col++) {
                    yield {x: col, y: row};
                }
            }
        },
        "left-right": async function*(x, y, w, h) {
            for (let col = x; col < x + w; col++) {
                for (let row = y; row < y + h; row++) {
                    yield {x: col, y: row};
                }
            }
        },
        "right-left": async function*(x, y, w, h) {
            for (let col = x + w - 1; col >= x; col--) {
                for (let row = y; row < y + h; row++) {
                    yield {x: col, y: row};
                }
            }
        },
        "random": async function*(x, y, w, h) {
            const pixels = [];
            for (let row = y; row < y + h; row++) {
                for (let col = x; col < x + w; col++) {
                    pixels.push({x: col, y: row});
                }
            }
            for (let i = pixels.length - 1; i > 0; i--) {
                const j = Math.floor(Math.random() * (i + 1));
                [pixels[i], pixels[j]] = [pixels[j], pixels[i]];
            }
            for (const pixel of pixels) yield pixel;
        },
        "diagonal-lt-rb": async function*(x, y, w, h) {
            for (let d = 0; d < w + h - 1; d++) {
                const startCol = Math.max(0, d - h + 1);
                const endCol = Math.min(d, w - 1);
                for (let col = startCol; col <= endCol; col++) {
                    yield {x: x + col, y: y + (d - col)};
                }
            }
        },
        "diagonal-rt-lb": async function*(x, y, w, h) {
            for (let d = 0; d < w + h - 1; d++) {
                const startCol = Math.max(0, (w - 1) - d);
                const endCol = Math.min(w - 1, (w + h - 2) - d);
                for (let col = startCol; col <= endCol; col++) {
                    yield {x: x + col, y: y + (d - ((w - 1) - col))};
                }
            }
        },
        "diagonal-lb-rt": async function*(x, y, w, h) {
            for (let d = 0; d < w + h - 1; d++) {
                const startCol = Math.max(0, d - h + 1);
                const endCol = Math.min(d, w - 1);
                for (let col = startCol; col <= endCol; col++) {
                    yield {x: x + col, y: y + (h - 1) - (d - col)};
                }
            }
        },
        "diagonal-rb-lt": async function*(x, y, w, h) {
            for (let d = 0; d < w + h - 1; d++) {
                const startCol = Math.max(0, (w - 1) - d);
                const endCol = Math.min(w - 1, (w + h - 2) - d);
                for (let col = startCol; col <= endCol; col++) {
                    yield {x: x + col, y: y + (h - 1) - (d - ((w - 1) - col))};
                }
            }
        }
    };

    // ===== СИСТЕМА КОНФИГУРАЦИЙ ===== //
    function saveConfig(name) {
        if (!name.trim()) {
            alert("Введите название конфига!");
            return;
        }

        if (SETTINGS.configs.length >= SETTINGS.MAX_CONFIGS && !SETTINGS.configs.some(c => c.name === name.trim())) {
            alert(`Достигнут лимит в ${SETTINGS.MAX_CONFIGS} конфигов. Удалите старые, чтобы сохранить новые.`);
            return;
        }

        const config = {
            name: name.trim(),
            date: new Date().toISOString(),
            settings: {
                mode: document.querySelector('#mode-select').value,
                color: SETTINGS.color,
                x: parseInt(document.querySelector('#start-x').value),
                y: parseInt(document.querySelector('#start-y').value),
                w: parseInt(document.querySelector('#width').value),
                h: parseInt(document.querySelector('#height').value),
                delay: parseInt(document.querySelector('#delay').value)
            }
        };

        const existingIndex = SETTINGS.configs.findIndex(c => c.name === config.name);
        if (existingIndex >= 0) {
            if (!confirm(`Конфиг "${config.name}" уже существует. Перезаписать?`)) return;
            SETTINGS.configs[existingIndex] = config;
        } else {
            SETTINGS.configs.push(config);
        }

        localStorage.setItem('pixelBotConfigs', JSON.stringify(SETTINGS.configs));
        updateConfigsList();
        alert(`Конфиг "${config.name}" сохранён!`);
    }

    function applyConfig(config) {
        const { settings } = config;
        document.querySelector('#mode-select').value = settings.mode;
        document.querySelector('#color-select').value = settings.color;
        document.querySelector('#start-x').value = settings.x;
        document.querySelector('#start-y').value = settings.y;
        document.querySelector('#width').value = settings.w;
        document.querySelector('#height').value = settings.h;
        document.querySelector('#delay').value = settings.delay;
        document.querySelector('#config-name').value = config.name;

        SETTINGS.color = settings.color;
        const colorIndex = Object.keys(SETTINGS.colors).indexOf(SETTINGS.color);
        document.querySelector('#current-color').style.background = SETTINGS.colorHexes[colorIndex];
    }

    function renameConfig(oldName, newName) {
        if (!newName || !newName.trim()) {
            alert("Введите новое название!");
            return;
        }

        newName = newName.trim();
        if (oldName === newName) return;

        if (SETTINGS.configs.some(c => c.name === newName)) {
            alert("Конфиг с таким именем уже существует!");
            return;
        }

        const configIndex = SETTINGS.configs.findIndex(c => c.name === oldName);
        if (configIndex >= 0) {
            SETTINGS.configs[configIndex].name = newName;
            SETTINGS.configs[configIndex].date = new Date().toISOString();
            localStorage.setItem('pixelBotConfigs', JSON.stringify(SETTINGS.configs));
            updateConfigsList();
        }
    }

    function deleteConfig(name) {
        if (!confirm(`Удалить конфиг "${name}"?`)) return;

        SETTINGS.configs = SETTINGS.configs.filter(c => c.name !== name);
        localStorage.setItem('pixelBotConfigs', JSON.stringify(SETTINGS.configs));
        updateConfigsList();
    }

    function exportConfig() {
        const configName = document.querySelector('#config-name').value.trim() || "pixelbot_config";
        const config = {
            name: configName,
            date: new Date().toISOString(),
            settings: {
                mode: document.querySelector('#mode-select').value,
                color: SETTINGS.color,
                x: parseInt(document.querySelector('#start-x').value),
                y: parseInt(document.querySelector('#start-y').value),
                w: parseInt(document.querySelector('#width').value),
                h: parseInt(document.querySelector('#height').value),
                delay: parseInt(document.querySelector('#delay').value)
            }
        };

        const blob = new Blob([JSON.stringify(config, null, 2)], {type: 'application/json'});
        const url = URL.createObjectURL(blob);

        const a = document.createElement('a');
        a.href = url;
        a.download = `pixelbot_${configName.replace(/\s+/g, '_')}.json`;
        document.body.appendChild(a);
        a.click();
        document.body.removeChild(a);
        URL.revokeObjectURL(url);
    }

    function importConfig(event) {
        const file = event.target.files[0];
        if (!file) return;

        const reader = new FileReader();
        reader.onload = (e) => {
            try {
                const config = JSON.parse(e.target.result);
                if (!config.name || !config.settings) {
                    throw new Error("Некорректный формат конфига");
                }

                if (SETTINGS.configs.length >= SETTINGS.MAX_CONFIGS && !SETTINGS.configs.some(c => c.name === config.name)) {
                    alert(`Достигнут лимит в ${SETTINGS.MAX_CONFIGS} конфигов. Удалите старые, чтобы импортировать новые.`);
                    return;
                }

                if (confirm(`Импортировать конфиг "${config.name}"?`)) {
                    const existingIndex = SETTINGS.configs.findIndex(c => c.name === config.name);
                    if (existingIndex >= 0) {
                        if (!confirm(`Конфиг "${config.name}" уже существует. Перезаписать?`)) return;
                        SETTINGS.configs[existingIndex] = config;
                    } else {
                        SETTINGS.configs.push(config);
                    }

                    localStorage.setItem('pixelBotConfigs', JSON.stringify(SETTINGS.configs));
                    updateConfigsList();
                    document.querySelector('#config-name').value = config.name;
                    alert(`Конфиг "${config.name}" успешно импортирован!`);
                }
            } catch (error) {
                console.error("Ошибка импорта:", error);
                alert("Ошибка при импорте конфига: " + error.message);
            }
        };
        reader.readAsText(file);
        event.target.value = '';
    }

    function updateConfigsList() {
    const configsList = document.querySelector('#configs-list');
    if (!configsList) return;

    const sortedConfigs = [...SETTINGS.configs].sort((a, b) =>
        new Date(b.date) - new Date(a.date)
    );

    const visibleConfigs = sortedConfigs.slice(0, 2);
    const hiddenConfigs = sortedConfigs.slice(2);

    configsList.innerHTML = `
        ${visibleConfigs.map(config => `
            <div class="config-item" data-name="${config.name.replace(/"/g, '&quot;')}"
                 style="display: flex; justify-content: space-between; align-items: center; padding: 5px; margin: 2px 0; background: #f9f9f9; border-radius: 3px;">
                <span style="flex: 1; cursor: pointer;">
                    ${config.name}
                    <span style="font-size: 0.8em; color: #666; margin-left: 5px;">
                        ${new Date(config.date).toLocaleString()}
                    </span>
                </span>
                <div>
                    <button class="rename-config-btn" style="background: none; border: none; cursor: pointer; font-size: 0.9em; margin-left: 5px;" title="Переименовать">
                        ✏️
                    </button>
                    <button class="delete-config-btn" style="background: none; border: none; cursor: pointer; color: red; font-size: 0.9em; margin-left: 5px;" title="Удалить">
                        ✖
                    </button>
                </div>
            </div>
        `).join('')}

        ${hiddenConfigs.length > 0 ? `
            <div id="hidden-configs" style="display: none;">
                ${hiddenConfigs.map(config => `
                    <div class="config-item" data-name="${config.name.replace(/"/g, '&quot;')}"
                         style="display: flex; justify-content: space-between; align-items: center; padding: 5px; margin: 2px 0; background: #f9f9f9; border-radius: 3px;">
                        <span style="flex: 1; cursor: pointer;">
                            ${config.name}
                            <span style="font-size: 0.8em; color: #666; margin-left: 5px;">
                                ${new Date(config.date).toLocaleString()}
                            </span>
                        </span>
                        <div>
                            <button class="rename-config-btn" style="background: none; border: none; cursor: pointer; font-size: 0.9em; margin-left: 5px;" title="Переименовать">
                                ✏️
                            </button>
                            <button class="delete-config-btn" style="background: none; border: none; cursor: pointer; color: red; font-size: 0.9em; margin-left: 5px;" title="Удалить">
                                ✖
                            </button>
                        </div>
                    </div>
                `).join('')}
            </div>
            <button id="toggle-configs-btn" style="width: 100%; padding: 5px; margin-top: 5px; background: #f0f0f0; border: 1px solid #ddd; border-radius: 3px; cursor: pointer; font-size: 12px;">
                ▼ Показать ещё (${hiddenConfigs.length}) ▼
            </button>
        ` : ''}

        ${sortedConfigs.length === 0 ? `
            <div style="text-align: center; padding: 10px; color: #666; font-size: 0.9em;">
                Нет сохранённых конфигов
            </div>
        ` : ''}

        <div style="font-size: 0.8em; color: #666; text-align: center; margin-top: 5px;">
            Лимит: ${SETTINGS.configs.length}/${SETTINGS.MAX_CONFIGS} конфигов
        </div>
    `;

    // Добавляем обработчик для кнопки "Показать ещё"
    const toggleBtn = configsList.querySelector('#toggle-configs-btn');
    if (toggleBtn) {
        toggleBtn.addEventListener('click', function() {
            const hiddenConfigs = configsList.querySelector('#hidden-configs');
            if (hiddenConfigs.style.display === 'none') {
                hiddenConfigs.style.display = 'block';
                toggleBtn.textContent = `▲ Скрыть (${hiddenConfigs.children.length}) ▲`;
            } else {
                hiddenConfigs.style.display = 'none';
                toggleBtn.textContent = `▼ Показать ещё (${hiddenConfigs.children.length}) ▼`;
            }
        });
    }
}

    function promptRenameConfig(name) {
        const newName = prompt("Введите новое название конфига:", name);
        if (newName && newName !== name) {
            renameConfig(name, newName);
        }
    }

    function loadConfigs() {
        try {
            const savedConfigs = localStorage.getItem('pixelBotConfigs');
            if (savedConfigs) {
                SETTINGS.configs = JSON.parse(savedConfigs);
                if (SETTINGS.configs.length > SETTINGS.MAX_CONFIGS) {
                    SETTINGS.configs = SETTINGS.configs.slice(0, SETTINGS.MAX_CONFIGS);
                    localStorage.setItem('pixelBotConfigs', JSON.stringify(SETTINGS.configs));
                }
                updateConfigsList();
            }
        } catch (e) {
            console.error("Ошибка загрузки конфигов:", e);
        }
    }

    function setupConfigHandlers() {
        document.addEventListener('click', function(e) {
            if (e.target && e.target.classList.contains('delete-config-btn')) {
                const configItem = e.target.closest('.config-item');
                const configName = configItem.getAttribute('data-name');
                deleteConfig(configName);
            }

            if (e.target && e.target.classList.contains('rename-config-btn')) {
                const configItem = e.target.closest('.config-item');
                const configName = configItem.getAttribute('data-name');
                promptRenameConfig(configName);
            }

            if (e.target && e.target.closest('.config-item span')) {
                const configItem = e.target.closest('.config-item');
                const configName = configItem.getAttribute('data-name');
                const config = SETTINGS.configs.find(c => c.name === configName);
                if (config) applyConfig(config);
            }
        });
    }

    // ===== ПАНЕЛЬ УПРАВЛЕНИЯ ===== //
    function createControlPanel() {
        const panel = document.createElement('div');
        panel.style.cssText = `
            position: fixed;
            top: 20px;
            right: 20px;
            background: white;
            padding: 15px;
            border: 1px solid #ddd;
            border-radius: 10px;
            z-index: 9999;
            font-family: Arial, sans-serif;
            font-size: 13px;
            width: 280px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
            user-select: none;
        `;

        panel.innerHTML = `
        <div id="panel-header" style="cursor: move; padding: 8px 10px; margin: -15px -15px 15px -15px; background: #f5f5f5; border-radius: 8px 8px 0 0; font-size: 14px; display: flex; justify-content: space-between; align-items: center;">
            <strong>🎨 PixelBot v2.2</strong>
            <span id="current-color" style="display: inline-block; width: 18px; height: 18px; background: ${SETTINGS.colorHexes[Object.keys(SETTINGS.colors).indexOf(SETTINGS.color)]}; border: 1px solid #ccc; border-radius: 3px;"></span>
        </div>

        <div style="margin-bottom: 12px;">
            <label style="display: block; margin-bottom: 3px; font-weight: bold; color: #555;">Режим рисования:</label>
            <select id="mode-select" style="width: 100%; padding: 6px; border: 1px solid #ddd; border-radius: 4px; font-size: 13px; background: white;">
                ${Object.entries(SETTINGS.modes).map(([key, desc]) =>
                    `<option value="${key}">${desc}</option>`).join('')}
            </select>
        </div>

        <div style="margin-bottom: 12px;">
            <label style="display: block; margin-bottom: 3px; font-weight: bold; color: #555;">Цвет:</label>
            <select id="color-select" style="width: 100%; padding: 6px; border: 1px solid #ddd; border-radius: 4px; font-size: 13px; background: white;">
                ${Object.keys(SETTINGS.colors).map((key, index) =>
                    `<option value="${key}" ${key === SETTINGS.color ? 'selected' : ''}>
                        ${SETTINGS.colors[key]}
                        <span style="float: right; display: inline-block; width: 12px; height: 12px; background: ${SETTINGS.colorHexes[index]}; border: 1px solid #ccc; border-radius: 2px;"></span>
                    </option>`).join('')}
            </select>
        </div>

        <div style="margin-bottom: 12px;">
            <label style="display: block; margin-bottom: 3px; font-weight: bold; color: #555;">Область рисования:</label>
            <div style="display: grid; grid-template-columns: repeat(4, 1fr); gap: 5px; font-size: 13px;">
                <div>
                    <label style="font-size: 0.8em; color: #666; display: block; margin-bottom: 2px;">X:</label>
                    <input type="number" id="start-x" value="0" style="width: 100%; padding: 5px; border: 1px solid #ddd; border-radius: 3px; box-sizing: border-box;">
                </div>
                <div>
                    <label style="font-size: 0.8em; color: #666; display: block; margin-bottom: 2px;">Y:</label>
                    <input type="number" id="start-y" value="0" style="width: 100%; padding: 5px; border: 1px solid #ddd; border-radius: 3px; box-sizing: border-box;">
                </div>
                <div>
                    <label style="font-size: 0.8em; color: #666; display: block; margin-bottom: 2px;">Шир.:</label>
                    <input type="number" id="width" value="10" style="width: 100%; padding: 5px; border: 1px solid #ddd; border-radius: 3px; box-sizing: border-box;">
                </div>
                <div>
                    <label style="font-size: 0.8em; color: #666; display: block; margin-bottom: 2px;">Выс.:</label>
                    <input type="number" id="height" value="10" style="width: 100%; padding: 5px; border: 1px solid #ddd; border-radius: 3px; box-sizing: border-box;">
                </div>
            </div>
        </div>

        <div style="margin-bottom: 15px;">
            <label style="display: block; margin-bottom: 3px; font-weight: bold; color: #555;">Задержка (мс):</label>
            <input type="number" id="delay" value="1000" style="width: 100%; padding: 5px; border: 1px solid #ddd; border-radius: 3px; box-sizing: border-box;">
        </div>

        <div style="display: flex; gap: 8px; margin-bottom: 15px;">
            <button id="start-btn" style="flex: 1; padding: 8px; background: #2196F3; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 13px; font-weight: bold;">
                ▶ Начать рисование
            </button>
            <button id="stop-btn" style="flex: 1; padding: 8px; background: #f44336; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 13px; display: none;">
                ⏹ Остановить
            </button>
        </div>

        <div style="margin-bottom: 15px;">
            <div id="progress-text" style="font-size: 12px; text-align: center; margin-bottom: 5px; color: #555;">Готов к работе</div>
            <div id="progress-bar" style="height: 6px; background: #eee; border-radius: 3px;">
                <div id="progress-fill" style="height: 100%; width: 0%; background: #4CAF50; border-radius: 3px; transition: width 0.3s;"></div>
            </div>
        </div>

        <div style="margin: 15px 0; border-top: 1px solid #eee; padding-top: 15px;">
            <button id="toggle-configs-section" style="width: 100%; padding: 8px; background: #f5f5f5; border: 1px solid #ddd; border-radius: 4px; cursor: pointer; font-size: 13px; text-align: center; margin-bottom: 10px;">
                ▼ Управление конфигами ▼
            </button>

            <div id="configs-section" style="display: none;">
                <div style="margin-bottom: 10px;">
                    <div style="display: flex; gap: 8px; margin-bottom: 8px;">
                        <input type="text" id="config-name" placeholder="Название конфига" style="flex: 1; padding: 6px; border: 1px solid #ddd; border-radius: 4px;">
                        <button id="save-config" style="padding: 6px 10px; background: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 13px;">Сохранить</button>
                    </div>

                    <div id="configs-list" style="max-height: 150px; overflow-y: auto; border: 1px solid #ddd; border-radius: 4px; padding: 5px; background: #fafafa; margin-bottom: 5px;"></div>

                    <div style="display: flex; gap: 8px; margin-top: 10px;">
                        <button id="export-config" style="flex: 1; padding: 6px; background: #2196F3; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 13px;">Экспорт</button>
                        <label for="import-file" style="flex: 1; padding: 6px; background: #FF9800; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 13px; text-align: center;">
                            Импорт
                            <input type="file" id="import-file" accept=".json" style="display: none;">
                        </label>
                    </div>
                </div>
            </div>
        </div>
        `;

        // Элементы управления
        const startBtn = panel.querySelector('#start-btn');
        const stopBtn = panel.querySelector('#stop-btn');
        const colorSelect = panel.querySelector('#color-select');
        const currentColorIndicator = panel.querySelector('#current-color');
        const saveConfigBtn = panel.querySelector('#save-config');
        const configNameInput = panel.querySelector('#config-name');
        const exportBtn = panel.querySelector('#export-config');
        const importInput = panel.querySelector('#import-file');
        const toggleConfigsBtn = panel.querySelector('#toggle-configs-section');
        const configsSection = panel.querySelector('#configs-section');

        // Обработчики событий
        colorSelect.addEventListener('change', (e) => {
            SETTINGS.color = e.target.value;
            const colorIndex = Object.keys(SETTINGS.colors).indexOf(SETTINGS.color);
            currentColorIndicator.style.background = SETTINGS.colorHexes[colorIndex];
        });

        saveConfigBtn.addEventListener('click', () => {
            saveConfig(configNameInput.value);
        });

        exportBtn.addEventListener('click', exportConfig);

        importInput.addEventListener('change', importConfig);

        // Перетаскивание панели
        const header = panel.querySelector('#panel-header');
        let isDragging = false;
        let offsetX, offsetY;

        header.addEventListener('mousedown', (e) => {
            isDragging = true;
            offsetX = e.clientX - panel.getBoundingClientRect().left;
            offsetY = e.clientY - panel.getBoundingClientRect().top;
            panel.style.cursor = 'grabbing';
            e.preventDefault();
        });

        document.addEventListener('mousemove', (e) => {
            if (!isDragging) return;
            panel.style.left = `${e.clientX - offsetX}px`;
            panel.style.top = `${e.clientY - offsetY}px`;
        });

        document.addEventListener('mouseup', () => {
            isDragging = false;
            panel.style.cursor = 'default';
        });

        // Управление рисованием
        startBtn.addEventListener('click', async () => {
            if (SETTINGS.isRunning) return;

            const mode = panel.querySelector('#mode-select').value;
            const config = {
                x: parseInt(panel.querySelector('#start-x').value),
                y: parseInt(panel.querySelector('#start-y').value),
                w: parseInt(panel.querySelector('#width').value),
                h: parseInt(panel.querySelector('#height').value),
                delay: parseInt(panel.querySelector('#delay').value)
            };

            if (isNaN(config.x) || isNaN(config.y) || isNaN(config.w) || isNaN(config.h) || isNaN(config.delay)) {
                alert('Пожалуйста, введите корректные значения!');
                return;
            }

            const totalPixels = config.w * config.h;
            let processedPixels = 0;

            SETTINGS.delay = config.delay;
            SETTINGS.isRunning = true;
            startBtn.style.display = 'none';
            stopBtn.style.display = 'block';
            SETTINGS.abortController = new AbortController();

            try {
                const generator = drawingModes[mode](config.x, config.y, config.w, config.h);

                for await (const pixel of generator) {
                    if (SETTINGS.abortController.signal.aborted) break;

                    await placePixel(pixel.x, pixel.y, SETTINGS.color, SETTINGS.abortController.signal);

                    processedPixels++;
                    const progress = Math.round((processedPixels / totalPixels) * 100);
                    panel.querySelector('#progress-fill').style.width = `${progress}%`;
                    panel.querySelector('#progress-text').textContent = `Прогресс: ${progress}% (${processedPixels}/${totalPixels})`;

                    await new Promise(r => setTimeout(r, SETTINGS.delay));
                }

                panel.querySelector('#progress-text').textContent = "Рисование завершено!";
            } catch (err) {
                if (err.name !== 'AbortError') {
                    console.error(err);
                    panel.querySelector('#progress-text').textContent = "Ошибка: " + err.message;
                } else {
                    panel.querySelector('#progress-text').textContent = "Рисование прервано";
                }
            } finally {
                SETTINGS.isRunning = false;
                startBtn.style.display = 'block';
                stopBtn.style.display = 'none';
            }
        });

        stopBtn.addEventListener('click', () => {
            if (SETTINGS.abortController) {
                SETTINGS.abortController.abort();
            }
        });

        // Обработчик для кнопки показа/скрытия раздела конфигов
        toggleConfigsBtn.addEventListener('click', function() {
            if (configsSection.style.display === 'none') {
                configsSection.style.display = 'block';
                toggleConfigsBtn.textContent = '▲ Скрыть конфиги ▲';
            } else {
                configsSection.style.display = 'none';
                toggleConfigsBtn.textContent = '▼ Управление конфигами ▼';
            }
        });

        document.body.appendChild(panel);

        // Настраиваем обработчики и загружаем конфиги
        setupConfigHandlers();
        loadConfigs();
    }

    // ===== ЗАПУСК ПРИЛОЖЕНИЯ ===== //
    if (document.readyState === 'complete') {
        createControlPanel();
    } else {
        document.addEventListener('DOMContentLoaded', createControlPanel);
    }
})();