您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Бот для автоматического рисования на 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, '"')}" 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, '"')}" 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); } })();