;(async () => { // CONFIGURATION CONSTANTS const CONFIG = { COOLDOWN_DEFAULT: 31000, TRANSPARENCY_THRESHOLD: 100, WHITE_THRESHOLD: 250, LOG_INTERVAL: 10, PAINTING_SPEED: { MIN: 1, // Minimum 1 pixel per second MAX: 1000, // Maximum 1000 pixels per second DEFAULT: 5, // Default 5 pixels per second }, PAINTING_SPEED_ENABLED: false, // --- START: Color data from colour-converter.js --- COLOR_PALETTE: [ [0,0,0],[60,60,60],[120,120,120],[170,170,170],[210,210,210],[255,255,255], [96,0,24],[165,14,30],[237,28,36],[250,128,114],[228,92,26],[255,127,39],[246,170,9], [249,221,59],[255,250,188],[156,132,49],[197,173,49],[232,212,95],[74,107,58],[90,148,74],[132,197,115], [14,185,104],[19,230,123],[135,255,94],[12,129,110],[16,174,166],[19,225,190],[15,121,159],[96,247,242], [187,250,242],[40,80,158],[64,147,228],[125,199,255],[77,49,184],[107,80,246],[153,177,251], [74,66,132],[122,113,196],[181,174,241],[170,56,185],[224,159,249], [203,0,122],[236,31,128],[243,141,169],[155,82,73],[209,128,120],[250,182,164], [104,70,52],[149,104,42],[219,164,99],[123,99,82],[156,132,107],[214,181,148], [209,128,81],[248,178,119],[255,197,165],[109,100,63],[148,140,107],[205,197,158], [51,57,65],[109,117,141],[179,185,209] ], COLOR_NAMES: { "0,0,0": "Black", "60,60,60": "Dark Gray", "120,120,120": "Gray", "210,210,210": "Light Gray", "255,255,255": "White", "96,0,24": "Deep Red", "237,28,36": "Red", "255,127,39": "Orange", "246,170,9": "Gold", "249,221,59": "Yellow", "255,250,188": "Light Yellow", "14,185,104": "Dark Green", "19,230,123": "Green", "135,255,94": "Light Green", "12,129,110": "Dark Teal", "16,174,166": "Teal", "19,225,190": "Light Teal", "96,247,242": "Cyan", "40,80,158": "Dark Blue", "64,147,228": "Blue", "107,80,246": "Indigo", "153,177,251": "Light Indigo", "120,12,153": "Dark Purple", "170,56,185": "Purple", "224,159,249": "Light Purple", "203,0,122": "Dark Pink", "236,31,128": "Pink", "243,141,169": "Light Pink", "104,70,52": "Dark Brown", "149,104,42": "Brown", "248,178,119": "Beige", "170,170,170": "Medium Gray", "165,14,30": "Dark Red", "250,128,114": "Light Red", "228,92,26": "Dark Orange", "156,132,49": "Dark Goldenrod", "197,173,49": "Goldenrod", "232,212,95": "Light Goldenrod", "74,107,58": "Dark Olive", "90,148,74": "Olive", "132,197,115": "Light Olive", "15,121,159": "Dark Cyan", "187,250,242": "Light Cyan", "125,199,255": "Light Blue", "77,49,184": "Dark Indigo", "74,66,132": "Dark Slate Blue", "122,113,196": "Slate Blue", "181,174,241": "Light Slate Blue", "155,82,73": "Dark Peach", "209,128,120": "Peach", "250,182,164": "Light Peach", "219,164,99": "Light Brown", "123,99,82": "Dark Tan", "156,132,107": "Tan", "214,181,148": "Light Tan", "209,128,81": "Dark Beige", "255,197,165": "Light Beige", "109,100,63": "Dark Stone", "148,140,107": "Stone", "205,197,158": "Light Stone", "51,57,65": "Dark Slate", "109,117,141": "Slate", "179,185,209": "Light Slate", }, PAID_COLORS: new Set([ "170,170,170", "165,14,30", "250,128,114", "228,92,26", "156,132,49", "197,173,49", "232,212,95", "74,107,58", "90,148,74", "132,197,115", "15,121,159", "187,250,242", "125,199,255", "77,49,184", "74,66,132", "122,113,196", "181,174,241", "155,82,73", "209,128,120", "250,182,164", "219,164,99", "123,99,82", "156,132,107", "214,181,148", "209,128,81", "255,197,165", "109,100,63", "148,140,107", "205,197,158", "51,57,65", "109,117,141", "179,185,209", ]), // --- END: Color data --- // Optimized CSS Classes for reuse CSS_CLASSES: { BUTTON_PRIMARY: ` background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); color: white; border: none; border-radius: 8px; padding: 10px 16px; cursor: pointer; font-weight: 500; transition: all 0.3s ease; display: flex; align-items: center; gap: 8px; `, BUTTON_SECONDARY: ` background: rgba(255,255,255,0.1); color: white; border: 1px solid rgba(255,255,255,0.2); border-radius: 8px; padding: 8px 12px; cursor: pointer; transition: all 0.3s ease; `, MODERN_CARD: ` background: rgba(255,255,255,0.1); border-radius: 12px; padding: 18px; border: 1px solid rgba(255,255,255,0.1); backdrop-filter: blur(5px); `, GRADIENT_TEXT: ` background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; font-weight: bold; ` }, THEMES: { "Classic Autobot": { primary: "#000000", secondary: "#111111", accent: "#222222", text: "#ffffff", highlight: "#775ce3", success: "#00ff00", error: "#ff0000", warning: "#ffaa00", fontFamily: "'Segoe UI', Roboto, sans-serif", borderRadius: "12px", borderStyle: "solid", borderWidth: "1px", boxShadow: "0 8px 32px rgba(0,0,0,0.6), 0 0 0 1px rgba(255,255,255,0.1)", backdropFilter: "blur(10px)", animations: { glow: false, scanline: false, pixelBlink: false, }, }, "Neon Retro": { primary: "#1a1a2e", secondary: "#16213e", accent: "#0f3460", text: "#00ff41", highlight: "#ff6b35", success: "#39ff14", error: "#ff073a", warning: "#ffff00", neon: "#00ffff", purple: "#bf00ff", pink: "#ff1493", fontFamily: "'Press Start 2P', monospace", borderRadius: "0", borderStyle: "solid", borderWidth: "3px", boxShadow: "0 0 20px rgba(0, 255, 65, 0.3), inset 0 0 20px rgba(0, 255, 65, 0.1)", backdropFilter: "none", animations: { glow: true, scanline: true, pixelBlink: true, }, }, }, currentTheme: "Classic Autobot", } const getCurrentTheme = () => CONFIG.THEMES[CONFIG.currentTheme] const switchTheme = (themeName) => { if (CONFIG.THEMES[themeName]) { CONFIG.currentTheme = themeName saveThemePreference() // Remove existing theme styles const existingStyle = document.querySelector('style[data-wplace-theme="true"]') if (existingStyle) { existingStyle.remove() } // Recreate UI with new theme (cleanup is handled in createUI) createUI() } } const saveThemePreference = () => { try { localStorage.setItem("wplace-theme", CONFIG.currentTheme) } catch (e) { console.warn("Could not save theme preference:", e) } } const loadThemePreference = () => { try { const saved = localStorage.getItem("wplace-theme") if (saved && CONFIG.THEMES[saved]) { CONFIG.currentTheme = saved } } catch (e) { console.warn("Could not load theme preference:", e) } } const loadLanguagePreference = () => { try { const saved = localStorage.getItem("wplace_language") if (saved && TEXT[saved]) { state.language = saved } } catch (e) { console.warn("Could not load language preference:", e) } } // BILINGUAL TEXT STRINGS const TEXT = { en: { title: "WPlace Auto-Image", initBot: "Start Auto-BOT", uploadImage: "Upload Image", resizeImage: "Resize Image", selectPosition: "Select Position", startPainting: "Start Painting", stopPainting: "Stop Painting", checkingColors: "🔍 Checking available colors...", noColorsFound: "❌ Open the color palette on the site and try again!", colorsFound: "✅ {count} available colors found", loadingImage: "🖼️ Loading image...", imageLoaded: "✅ Image loaded with {count} valid pixels", imageError: "❌ Error loading image", selectPositionAlert: "Paint the first pixel at the location where you want the art to start!", waitingPosition: "👆 Waiting for you to paint the reference pixel...", positionSet: "✅ Position set successfully!", positionTimeout: "❌ Timeout for position selection", startPaintingMsg: "🎨 Starting painting...", paintingProgress: "🧱 Progress: {painted}/{total} pixels...", noCharges: "⌛ No charges. Waiting {time}...", paintingStopped: "⏹️ Painting stopped by user", paintingComplete: "✅ Painting complete! {count} pixels painted.", paintingError: "❌ Error during painting", missingRequirements: "❌ Load an image and select a position first", progress: "Progress", pixels: "Pixels", charges: "Charges", estimatedTime: "Estimated time", initMessage: "Click 'Upload Image' to begin", waitingInit: "Waiting for initialization...", resizeSuccess: "✅ Image resized to {width}x{height}", paintingPaused: "⏸️ Painting paused at position X: {x}, Y: {y}", captchaNeeded: "❗ CAPTCHA token needed. Paint one pixel manually to continue.", saveData: "Save Progress", loadData: "Load Progress", saveToFile: "Save to File", loadFromFile: "Load from File", dataManager: "Data Manager", autoSaved: "✅ Progress saved automatically", dataLoaded: "✅ Progress loaded successfully", fileSaved: "✅ Progress saved to file successfully", fileLoaded: "✅ Progress loaded from file successfully", noSavedData: "❌ No saved progress found", savedDataFound: "✅ Saved progress found! Load to continue?", savedDate: "Saved on: {date}", clickLoadToContinue: "Click 'Load Progress' to continue.", fileError: "❌ Error processing file", invalidFileFormat: "❌ Invalid file format", paintingSpeed: "Painting Speed", pixelsPerSecond: "pixels/second", speedSetting: "Speed: {speed} pixels/sec", settings: "Settings", botSettings: "Bot Settings", close: "Close", language: "Language", themeSettings: "Theme Settings", themeSettingsDesc: "Choose your preferred color theme for the interface.", languageSelectDesc: "Select your preferred language. Changes will take effect immediately.", }, ru: { title: "WPlace Авто-Изображение", initBot: "Запустить Авто-БОТ", uploadImage: "Загрузить изображение", resizeImage: "Изменить размер изображения", selectPosition: "Выбрать позицию", startPainting: "Начать рисование", stopPainting: "Остановить рисование", checkingColors: "🔍 Проверка доступных цветов...", noColorsFound: "❌ Откройте палитру цветов на сайте и попробуйте снова!", colorsFound: "✅ Найдено доступных цветов: {count}", loadingImage: "🖼️ Загрузка изображения...", imageLoaded: "✅ Изображение загружено, валидных пикселей: {count}", imageError: "❌ Ошибка при загрузке изображения", selectPositionAlert: "Нарисуйте первый пиксель в месте, откуда начнётся рисунок!", waitingPosition: "👆 Ожидание, пока вы нарисуете опорный пиксель...", positionSet: "✅ Позиция успешно установлена!", positionTimeout: "❌ Время ожидания выбора позиции истекло", startPaintingMsg: "🎨 Начинаем рисование...", paintingProgress: "🧱 Прогресс: {painted}/{total} пикселей...", noCharges: "⌛ Нет зарядов. Ожидание {time}...", paintingStopped: "⏹️ Рисование остановлено пользователем", paintingComplete: "✅ Рисование завершено! Нарисовано пикселей: {count}.", paintingError: "❌ Ошибка во время рисования", missingRequirements: "❌ Сначала загрузите изображение и выберите позицию", progress: "Прогресс", pixels: "Пиксели", charges: "Заряды", estimatedTime: "Примерное время", initMessage: "Нажмите 'Загрузить изображение', чтобы начать", waitingInit: "Ожидание инициализации...", resizeSuccess: "✅ Изображение изменено до {width}x{height}", paintingPaused: "⏸️ Рисование приостановлено на позиции X: {x}, Y: {y}", captchaNeeded: "❗ Требуется токен CAPTCHA. Нарисуйте один пиксель вручную, чтобы продолжить.", saveData: "Сохранить прогресс", loadData: "Загрузить прогресс", saveToFile: "Сохранить в файл", loadFromFile: "Загрузить из файла", dataManager: "Менеджер данных", autoSaved: "✅ Прогресс сохранён автоматически", dataLoaded: "✅ Прогресс успешно загружен", fileSaved: "✅ Прогресс успешно сохранён в файл", fileLoaded: "✅ Прогресс успешно загружен из файла", noSavedData: "❌ Сохранённый прогресс не найден", savedDataFound: "✅ Найден сохранённый прогресс! Загрузить, чтобы продолжить?", savedDate: "Сохранено: {date}", clickLoadToContinue: "Нажмите 'Загрузить прогресс', чтобы продолжить.", fileError: "❌ Ошибка при обработке файла", invalidFileFormat: "❌ Неверный формат файла", paintingSpeed: "Скорость рисования", pixelsPerSecond: "пикселей/сек", speedSetting: "Скорость: {speed} пикс./сек", settings: "Настройки", botSettings: "Настройки бота", close: "Закрыть", language: "Язык", themeSettings: "Настройки темы", themeSettingsDesc: "Выберите предпочтительную цветовую тему интерфейса.", languageSelectDesc: "Выберите предпочтительный язык. Изменения вступят в силу немедленно." }, pt: { title: "WPlace Auto-Image", initBot: "Iniciar Auto-BOT", uploadImage: "Upload da Imagem", resizeImage: "Redimensionar Imagem", selectPosition: "Selecionar Posição", startPainting: "Iniciar Pintura", stopPainting: "Parar Pintura", checkingColors: "🔍 Verificando cores disponíveis...", noColorsFound: "❌ Abra a paleta de cores no site e tente novamente!", colorsFound: "✅ {count} cores disponíveis encontradas", loadingImage: "🖼️ Carregando imagem...", imageLoaded: "✅ Imagem carregada com {count} pixels válidos", imageError: "❌ Erro ao carregar imagem", selectPositionAlert: "Pinte o primeiro pixel на localização onde deseja que a arte comece!", waitingPosition: "👆 Aguardando você pintar o pixel de referência...", positionSet: "✅ Posição definida com sucesso!", positionTimeout: "❌ Tempo esgotado para selecionar posição", startPaintingMsg: "🎨 Iniciando pintura...", paintingProgress: "🧱 Progresso: {painted}/{total} pixels...", noCharges: "⌛ Sem cargas. Aguardando {time}...", paintingStopped: "⏹️ Pintura interromпида pelo usuário", paintingComplete: "✅ Pintura concluída! {count} pixels pintados.", paintingError: "❌ Erro durante a pintura", missingRequirements: "❌ Carregue uma imagem e selecione uma posição primeiro", progress: "Progresso", pixels: "Pixels", charges: "Cargas", estimatedTime: "Tempo estimado", initMessage: "Clique em 'Upload da Imagem' para começar", waitingInit: "Aguardando inicialização...", resizeSuccess: "✅ Imagem redimensionada para {width}x{height}", paintingPaused: "⏸️ Pintura pausada na posição X: {x}, Y: {y}", captchaNeeded: "❗ Token CAPTCHA necessário. Pinte um pixel manualmente para continuar.", saveData: "Salvar Progresso", loadData: "Carregar Progresso", saveToFile: "Salvar em Arquivo", loadFromFile: "Carregar de Arquivo", dataManager: "Dados", autoSaved: "✅ Progresso salvo automaticamente", dataLoaded: "✅ Progresso carregado com sucesso", fileSaved: "✅ Salvo em arquivo com sucesso", fileLoaded: "✅ Carregado de arquivo com sucesso", noSavedData: "❌ Nenhum progresso salvo encontrado", savedDataFound: "✅ Progresso salvo encontrado! Carregar para continuar?", savedDate: "Salvo em: {date}", clickLoadToContinue: "Clique em 'Carregar Progresso' para continuar.", fileError: "❌ Erro ao processar arquivo", invalidFileFormat: "❌ Formato de arquivo inválido", paintingSpeed: "Velocidade de Pintura", pixelsPerSecond: "pixels/segundo", speedSetting: "Velocidade: {speed} pixels/seg", settings: "Configurações", botSettings: "Configurações do Bot", close: "Fechar", language: "Idioma", themeSettings: "Configurações de Tema", themeSettingsDesc: "Escolha seu tema de cores preferido para a interface.", languageSelectDesc: "Selecione seu idioma preferido. As alterações terão efeito imediatamente.", }, vi: { title: "WPlace Auto-Image", initBot: "Khởi động Auto-BOT", uploadImage: "Tải lên hình ảnh", resizeImage: "Thay đổi kích thước", selectPosition: "Chọn vị trí", startPainting: "Bắt đầu vẽ", stopPainting: "Dừng vẽ", checkingColors: "🔍 Đang kiểm tra màu sắc có sẵn...", noColorsFound: "❌ Hãy mở bảng màu trên trang web và thử lại!", colorsFound: "✅ Tìm thấy {count} màu sắc có sẵn", loadingImage: "🖼️ Đang tải hình ảnh...", imageLoaded: "✅ Đã tải hình ảnh với {count} pixel hợp lệ", imageError: "❌ Lỗi khi tải hình ảnh", selectPositionAlert: "Vẽ pixel đầu tiên tại vị trí bạn muốn tác phẩm nghệ thuật bắt đầu!", waitingPosition: "👆 Đang chờ bạn vẽ pixel tham chiếu...", positionSet: "✅ Đã đặt vị trí thành công!", positionTimeout: "❌ Hết thời gian chọn vị trí", startPaintingMsg: "🎨 Bắt đầu vẽ...", paintingProgress: "🧱 Tiến trình: {painted}/{total} pixel...", noCharges: "⌛ Không có điện tích. Đang chờ {time}...", paintingStopped: "⏹️ Người dùng đã dừng vẽ", paintingComplete: "✅ Hoàn thành vẽ! Đã vẽ {count} pixel.", paintingError: "❌ Lỗi trong quá trình vẽ", missingRequirements: "❌ Hãy tải lên hình ảnh và chọn vị trí trước", progress: "Tiến trình", pixels: "Pixel", charges: "Điện tích", estimatedTime: "Thời gian ước tính", initMessage: "Nhấp 'Tải lên hình ảnh' để bắt đầu", waitingInit: "Đang chờ khởi tạo...", resizeSuccess: "✅ Đã thay đổi kích thước hình ảnh thành {width}x{height}", paintingPaused: "⏸️ Tạm dừng vẽ tại vị trí X: {x}, Y: {y}", captchaNeeded: "❗ Cần token CAPTCHA. Vẽ một pixel thủ công để tiếp tục.", saveData: "Lưu tiến trình", loadData: "Tải tiến trình", saveToFile: "Lưu vào tệp", loadFromFile: "Tải từ tệp", dataManager: "Dữ liệu", autoSaved: "✅ Đã tự động lưu tiến trình", dataLoaded: "✅ Đã tải tiến trình thành công", fileSaved: "✅ Đã lưu vào tệp thành công", fileLoaded: "✅ Đã tải từ tệp thành công", noSavedData: "❌ Không tìm thấy tiến trình đã lưu", savedDataFound: "✅ Tìm thấy tiến trình đã lưu! Tải để tiếp tục?", savedDate: "Đã lưu vào: {date}", clickLoadToContinue: "Nhấp 'Tải tiến trình' để tiếp tục.", fileError: "❌ Lỗi khi xử lý tệp", invalidFileFormat: "❌ Định dạng tệp không hợp lệ", paintingSpeed: "Tốc độ vẽ", pixelsPerSecond: "pixel/giây", speedSetting: "Tốc độ: {speed} pixel/giây", settings: "Cài đặt", botSettings: "Cài đặt Bot", close: "Đóng", language: "Ngôn ngữ", themeSettings: "Cài đặt Giao diện", themeSettingsDesc: "Chọn chủ đề màu sắc yêu thích cho giao diện.", languageSelectDesc: "Chọn ngôn ngữ ưa thích. Thay đổi sẽ có hiệu lực ngay lập tức.", }, fr: { title: "WPlace Auto-Image", initBot: "Démarrer Auto-BOT", uploadImage: "Télécharger l'image", resizeImage: "Redimensionner l'image", selectPosition: "Sélectionner la position", startPainting: "Commencer à peindre", stopPainting: "Arrêter de peindre", checkingColors: "🔍 Vérification des couleurs disponibles...", noColorsFound: "❌ Ouvrez la palette de couleurs sur le site et réessayez!", colorsFound: "✅ {count} couleurs disponibles trouvées", loadingImage: "🖼️ Chargement de l'image...", imageLoaded: "✅ Image chargée avec {count} pixels valides", imageError: "❌ Erreur lors du chargement de l'image", selectPositionAlert: "Peignez le premier pixel à l'endroit où vous voulez que l'art commence!", waitingPosition: "👆 En attente que vous peigniez le pixel de référence...", positionSet: "✅ Position définie avec succès!", positionTimeout: "❌ Délai d'attente pour la sélection de position", startPaintingMsg: "🎨 Début de la peinture...", paintingProgress: "🧱 Progrès: {painted}/{total} pixels...", noCharges: "⌛ Aucune charge. En attente {time}...", paintingStopped: "⏹️ Peinture arrêtée par l'utilisateur", paintingComplete: "✅ Peinture terminée! {count} pixels peints.", paintingError: "❌ Erreur pendant la peinture", missingRequirements: "❌ Veuillez charger une image et sélectionner une position d'abord", progress: "Progrès", pixels: "Pixels", charges: "Charges", estimatedTime: "Temps estimé", initMessage: "Cliquez sur 'Télécharger l'image' pour commencer", waitingInit: "En attente d'initialisation...", resizeSuccess: "✅ Image redimensionnée en {width}x{height}", paintingPaused: "⏸️ Peinture en pause à la position X: {x}, Y: {y}", captchaNeeded: "❗ Token CAPTCHA nécessaire. Peignez un pixel manuellement pour continuer.", saveData: "Sauvegarder le progrès", loadData: "Charger le progrès", saveToFile: "Sauvegarder dans un fichier", loadFromFile: "Charger depuis un fichier", dataManager: "Données", autoSaved: "✅ Progrès sauvegardé automatiquement", dataLoaded: "✅ Progrès chargé avec succès", fileSaved: "✅ Sauvegardé dans un fichier avec succès", fileLoaded: "✅ Chargé depuis un fichier avec succès", noSavedData: "❌ Aucun progrès sauvegardé trouvé", savedDataFound: "✅ Progrès sauvegardé trouvé! Charger pour continuer?", savedDate: "Sauvegardé le: {date}", clickLoadToContinue: "Cliquez sur 'Charger le progrès' pour continuer.", fileError: "❌ Erreur lors du traitement du fichier", invalidFileFormat: "❌ Format de fichier invalide", paintingSpeed: "Vitesse de peinture", pixelsPerSecond: "pixels/seconde", speedSetting: "Vitesse: {speed} pixels/sec", settings: "Paramètres", botSettings: "Paramètres du Bot", close: "Fermer", language: "Langue", themeSettings: "Paramètres de Thème", themeSettingsDesc: "Choisissez votre thème de couleurs préféré pour l'interface.", languageSelectDesc: "Sélectionnez votre langue préférée. Les changements prendront effet immédiatement.", }, } // GLOBAL STATE const state = { running: false, imageLoaded: false, processing: false, totalPixels: 0, paintedPixels: 0, availableColors: [], activeColorPalette: [], // User-selected colors for conversion paintWhitePixels: true, // Default to ON currentCharges: 0, cooldown: CONFIG.COOLDOWN_DEFAULT, imageData: null, stopFlag: false, colorsChecked: false, startPosition: null, selectingPosition: false, region: null, minimized: false, lastPosition: { x: 0, y: 0 }, estimatedTime: 0, language: "en", paintingSpeed: CONFIG.PAINTING_SPEED.DEFAULT, // pixels per second } // Placeholder for the resize preview update function let _updateResizePreview = () => {}; // Turnstile token handling (promise-based) inspired by external logic let turnstileToken = null let _resolveToken = null let tokenPromise = new Promise((resolve) => { _resolveToken = resolve }) function setTurnstileToken(t) { if (_resolveToken) { _resolveToken(t) _resolveToken = null } turnstileToken = t } async function ensureToken() { if (!turnstileToken) { updateUI("captchaNeeded", "error") Utils.showAlert(Utils.t("captchaNeeded"), "error") try { await tokenPromise } catch (_) {} } return turnstileToken } // Intercept fetch to capture Turnstile token from pixel placement requests const originalFetch = window.fetch window.fetch = async (url, options) => { if (typeof url === "string" && url.includes("https://backend.wplace.live/s0/pixel/")) { try { const payload = JSON.parse(options.body) if (payload.t) { console.log("✅ Turnstile Token Captured:", payload.t) setTurnstileToken(payload.t) if (document.querySelector("#statusText")?.textContent.includes("CAPTCHA")) { Utils.showAlert("Token captured successfully! You can start the bot now.", "success") updateUI("colorsFound", "success", { count: state.availableColors.length }) } } } catch (_) { /* ignore */ } } return originalFetch(url, options) } // LANGUAGE DETECTION async function detectLanguage() { try { const response = await fetch("https://backend.wplace.live/me", { credentials: "include", }) const data = await response.json() state.language = data.language === "pt" ? "pt" : "en" } catch { state.language = navigator.language.startsWith("pt") ? "pt" : "en" } } // UTILITY FUNCTIONS const Utils = { sleep: (ms) => new Promise((r) => setTimeout(r, ms)), // Optimized DOM creation helpers createElement: (tag, props = {}, children = []) => { const element = document.createElement(tag) // Set properties efficiently Object.entries(props).forEach(([key, value]) => { if (key === 'style' && typeof value === 'object') { Object.assign(element.style, value) } else if (key === 'className') { element.className = value } else if (key === 'innerHTML') { element.innerHTML = value } else { element.setAttribute(key, value) } }) // Append children efficiently if (typeof children === 'string') { element.textContent = children } else if (Array.isArray(children)) { children.forEach(child => { if (typeof child === 'string') { element.appendChild(document.createTextNode(child)) } else { element.appendChild(child) } }) } return element }, // Create button with common styling createButton: (id, text, icon, onClick, style = CONFIG.CSS_CLASSES.BUTTON_PRIMARY) => { const button = Utils.createElement('button', { id: id, style: style, innerHTML: `${icon ? `` : ''}${text}` }) if (onClick) button.addEventListener('click', onClick) return button }, t: (key, params = {}) => { let text = TEXT[state.language]?.[key] || TEXT.en[key] || key Object.keys(params).forEach((param) => { text = text.replace(`{${param}}`, params[param]) }) return text }, showAlert: (message, type = "info") => { const alertDiv = document.createElement("div") alertDiv.style.cssText = ` position: fixed; top: 20px; left: 50%; transform: translateX(-50%); padding: 12px 20px; border-radius: 8px; color: white; font-weight: 600; z-index: 10001; max-width: 400px; text-align: center; box-shadow: 0 4px 12px rgba(0,0,0,0.3); animation: slideDown 0.3s ease-out; font-family: 'Segoe UI', sans-serif; ` const colors = { info: "background: linear-gradient(135deg, #3498db, #2980b9);", success: "background: linear-gradient(135deg, #27ae60, #229954);", warning: "background: linear-gradient(135deg, #f39c12, #e67e22);", error: "background: linear-gradient(135deg, #e74c3c, #c0392b);", } alertDiv.style.cssText += colors[type] || colors.info const style = document.createElement("style") style.textContent = ` @keyframes slideDown { from { transform: translateX(-50%) translateY(-20px); opacity: 0; } to { transform: translateX(-50%) translateY(0); opacity: 1; } } ` document.head.appendChild(style) alertDiv.textContent = message document.body.appendChild(alertDiv) setTimeout(() => { alertDiv.style.animation = "slideDown 0.3s ease-out reverse" setTimeout(() => { document.body.removeChild(alertDiv) document.head.removeChild(style) }, 300) }, 4000) }, colorDistance: (a, b) => Math.sqrt(Math.pow(a[0] - b[0], 2) + Math.pow(a[1] - b[1], 2) + Math.pow(a[2] - b[2], 2)), // The color metric from colour-converter.js for higher accuracy findClosestPaletteColor: (r, g, b, palette) => { let menorDist = Infinity; let cor = [0, 0, 0]; if (!palette || palette.length === 0) return cor; for (let i = 0; i < palette.length; i++) { const [pr, pg, pb] = palette[i]; const rmean = (pr + r) / 2; const rdiff = pr - r; const gdiff = pg - g; const bdiff = pb - b; const dist = Math.sqrt(((512 + rmean) * rdiff * rdiff >> 8) + 4 * gdiff * gdiff + ((767 - rmean) * bdiff * bdiff >> 8)); if (dist < menorDist) { menorDist = dist; cor = [pr, pg, pb]; } } return cor; }, isWhitePixel: (r, g, b) => r >= CONFIG.WHITE_THRESHOLD && g >= CONFIG.WHITE_THRESHOLD && b >= CONFIG.WHITE_THRESHOLD, createImageUploader: () => new Promise((resolve) => { const input = document.createElement("input") input.type = "file" input.accept = "image/png,image/jpeg" input.onchange = () => { const fr = new FileReader() fr.onload = () => resolve(fr.result) fr.readAsDataURL(input.files[0]) } input.click() }), createFileDownloader: (data, filename) => { const blob = new Blob([data], { type: "application/json" }) const url = URL.createObjectURL(blob) const a = document.createElement("a") a.href = url a.download = filename document.body.appendChild(a) a.click() document.body.removeChild(a) URL.revokeObjectURL(url) }, createFileUploader: () => new Promise((resolve, reject) => { const input = document.createElement("input") input.type = "file" input.accept = ".json" input.onchange = (e) => { const file = e.target.files[0] if (file) { const reader = new FileReader() reader.onload = () => { try { const data = JSON.parse(reader.result) resolve(data) } catch (error) { reject(new Error("Invalid JSON file")) } } reader.onerror = () => reject(new Error("File reading error")) reader.readAsText(file) } else { reject(new Error("No file selected")) } } input.click() }), extractAvailableColors: () => { const colorElements = document.querySelectorAll('[id^="color-"]') return Array.from(colorElements) .filter((el) => !el.querySelector("svg")) .filter((el) => { const id = Number.parseInt(el.id.replace("color-", "")) return id !== 0 && id !== 5 }) .map((el) => { const id = Number.parseInt(el.id.replace("color-", "")) const rgbStr = el.style.backgroundColor.match(/\d+/g) const rgb = rgbStr ? rgbStr.map(Number) : [0, 0, 0] return { id, rgb } }) }, formatTime: (ms) => { const seconds = Math.floor((ms / 1000) % 60) const minutes = Math.floor((ms / (1000 * 60)) % 60) const hours = Math.floor((ms / (1000 * 60 * 60)) % 24) const days = Math.floor(ms / (1000 * 60 * 60 * 24)) let result = "" if (days > 0) result += `${days}d ` if (hours > 0 || days > 0) result += `${hours}h ` if (minutes > 0 || hours > 0 || days > 0) result += `${minutes}m ` result += `${seconds}s` return result }, calculateEstimatedTime: (remainingPixels, charges, cooldown) => { if (remainingPixels <= 0) return 0 // Calculate time based on painting speed (pixels per second) const paintingSpeedDelay = state.paintingSpeed > 0 ? (1000 / state.paintingSpeed) : 1000 const timeFromSpeed = remainingPixels * paintingSpeedDelay // ms // Calculate time based on charges and cooldown const cyclesNeeded = Math.ceil(remainingPixels / Math.max(charges, 1)) const timeFromCharges = cyclesNeeded * cooldown // ms // Return the maximum of both calculations (the limiting factor) return Math.max(timeFromSpeed, timeFromCharges) }, // Save/Load Progress Functions saveProgress: () => { try { const progressData = { timestamp: Date.now(), state: { totalPixels: state.totalPixels, paintedPixels: state.paintedPixels, lastPosition: state.lastPosition, startPosition: state.startPosition, region: state.region, imageLoaded: state.imageLoaded, colorsChecked: state.colorsChecked, availableColors: state.availableColors, }, imageData: state.imageData ? { width: state.imageData.width, height: state.imageData.height, pixels: Array.from(state.imageData.pixels), totalPixels: state.imageData.totalPixels, } : null, paintedMap: state.paintedMap ? state.paintedMap.map((row) => Array.from(row)) : null, } localStorage.setItem("wplace-bot-progress", JSON.stringify(progressData)) return true } catch (error) { console.error("Error saving progress:", error) return false } }, loadProgress: () => { try { const saved = localStorage.getItem("wplace-bot-progress") return saved ? JSON.parse(saved) : null } catch (error) { console.error("Error loading progress:", error) return null } }, clearProgress: () => { try { localStorage.removeItem("wplace-bot-progress") return true } catch (error) { console.error("Error clearing progress:", error) return false } }, restoreProgress: (savedData) => { try { // Restore state Object.assign(state, savedData.state) // Restore image data if (savedData.imageData) { state.imageData = { ...savedData.imageData, pixels: new Uint8ClampedArray(savedData.imageData.pixels), } } // Restore painted map if (savedData.paintedMap) { state.paintedMap = savedData.paintedMap.map((row) => Array.from(row)) } return true } catch (error) { console.error("Error restoring progress:", error) return false } }, saveProgressToFile: () => { try { const progressData = { timestamp: Date.now(), version: "1.0", state: { totalPixels: state.totalPixels, paintedPixels: state.paintedPixels, lastPosition: state.lastPosition, startPosition: state.startPosition, region: state.region, imageLoaded: state.imageLoaded, colorsChecked: state.colorsChecked, availableColors: state.availableColors, }, imageData: state.imageData ? { width: state.imageData.width, height: state.imageData.height, pixels: Array.from(state.imageData.pixels), totalPixels: state.imageData.totalPixels, } : null, paintedMap: state.paintedMap ? state.paintedMap.map((row) => Array.from(row)) : null, } const filename = `wplace-bot-progress-${new Date().toISOString().slice(0, 19).replace(/:/g, "-")}.json` Utils.createFileDownloader(JSON.stringify(progressData, null, 2), filename) return true } catch (error) { console.error("Error saving to file:", error) return false } }, loadProgressFromFile: async () => { try { const data = await Utils.createFileUploader() if (!data.version || !data.state) { throw new Error("Invalid file format") } const success = Utils.restoreProgress(data) return success } catch (error) { console.error("Error loading from file:", error) throw error } }, } // IMAGE PROCESSOR CLASS class ImageProcessor { constructor(imageSrc) { this.imageSrc = imageSrc this.img = null this.canvas = null this.ctx = null } async load() { return new Promise((resolve, reject) => { this.img = new Image() this.img.crossOrigin = "anonymous" this.img.onload = () => { this.canvas = document.createElement("canvas") this.ctx = this.canvas.getContext("2d") this.canvas.width = this.img.width this.canvas.height = this.img.height this.ctx.drawImage(this.img, 0, 0) resolve() } this.img.onerror = reject this.img.src = this.imageSrc }) } getDimensions() { return { width: this.canvas.width, height: this.canvas.height, } } getPixelData() { return this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height).data } resize(newWidth, newHeight) { const tempCanvas = document.createElement("canvas") const tempCtx = tempCanvas.getContext("2d") tempCanvas.width = newWidth tempCanvas.height = newHeight tempCtx.imageSmoothingEnabled = false tempCtx.drawImage(this.canvas, 0, 0, newWidth, newHeight) this.canvas.width = newWidth this.canvas.height = newHeight this.ctx.imageSmoothingEnabled = false this.ctx.drawImage(tempCanvas, 0, 0) return this.ctx.getImageData(0, 0, newWidth, newHeight).data } generatePreview(width, height) { const previewCanvas = document.createElement("canvas") const previewCtx = previewCanvas.getContext("2d") previewCanvas.width = width previewCanvas.height = height previewCtx.imageSmoothingEnabled = false previewCtx.drawImage(this.img, 0, 0, width, height) return previewCanvas.toDataURL() } } // WPLACE API SERVICE const WPlaceService = { async paintPixelInRegion(regionX, regionY, pixelX, pixelY, color) { try { await ensureToken() if (!turnstileToken) return "token_error" const payload = { coords: [pixelX, pixelY], colors: [color], t: turnstileToken } const res = await fetch(`https://backend.wplace.live/s0/pixel/${regionX}/${regionY}`, { method: "POST", headers: { "Content-Type": "text/plain;charset=UTF-8" }, credentials: "include", body: JSON.stringify(payload), }) if (res.status === 403) { console.error("❌ 403 Forbidden. Turnstile token might be invalid or expired.") turnstileToken = null tokenPromise = new Promise((resolve) => { _resolveToken = resolve }) return "token_error" } const data = await res.json() return data?.painted === 1 } catch (e) { console.error("Paint request failed:", e) return false } }, async getCharges() { try { const res = await fetch("https://backend.wplace.live/me", { credentials: "include", }) const data = await res.json() return { charges: data.charges?.count || 0, cooldown: data.charges?.next || CONFIG.COOLDOWN_DEFAULT, } } catch (e) { console.error("Failed to get charges:", e) return { charges: 0, cooldown: CONFIG.COOLDOWN_DEFAULT, } } }, } // COLOR MATCHING FUNCTION - Optimized with caching const colorCache = new Map() function findClosestColor(targetRgb, availableColors) { // Create cache key from RGB values const cacheKey = `${targetRgb[0]},${targetRgb[1]},${targetRgb[2]}` // Check cache first if (colorCache.has(cacheKey)) { return colorCache.get(cacheKey) } let minDistance = Number.POSITIVE_INFINITY let closestColorId = availableColors[0]?.id || 1 // Use optimized loop for better performance for (let i = 0; i < availableColors.length; i++) { const color = availableColors[i] const distance = Utils.colorDistance(targetRgb, color.rgb) if (distance < minDistance) { minDistance = distance closestColorId = color.id // If perfect match, break early if (distance === 0) break } } // Cache the result for future use colorCache.set(cacheKey, closestColorId) // Limit cache size to prevent memory leaks if (colorCache.size > 10000) { const firstKey = colorCache.keys().next().value colorCache.delete(firstKey) } return closestColorId } // UI UPDATE FUNCTIONS (declared early to avoid reference errors) let updateUI = () => {} let updateStats = () => {} let updateDataButtons = () => {} // --- START: Color Palette Functions --- function updateActiveColorPalette() { state.activeColorPalette = []; const activeSwatches = document.querySelectorAll('.wplace-color-swatch.active'); if (activeSwatches) { activeSwatches.forEach(swatch => { const rgb = swatch.getAttribute('data-rgb').split(',').map(Number); state.activeColorPalette.push(rgb); }); } // If the resize dialog is open, update its preview if (document.querySelector('.resize-container')?.style.display === 'block') { _updateResizePreview(); } } function toggleAllColors(select, isPaid) { const selector = isPaid ? '.wplace-color-swatch.paid' : '.wplace-color-swatch:not(.paid)'; const swatches = document.querySelectorAll(selector); if (swatches) { swatches.forEach(swatch => { swatch.classList.toggle('active', select); }); } updateActiveColorPalette(); } function initializeColorPalette(container) { const freeContainer = container.querySelector('#colors-free'); const paidContainer = container.querySelector('#colors-paid'); if (!freeContainer || !paidContainer) return; freeContainer.innerHTML = ''; paidContainer.innerHTML = ''; // Create a unique set of colors to avoid duplicates const uniqueColors = [...new Set(CONFIG.COLOR_PALETTE.map(JSON.stringify))].map(JSON.parse); uniqueColors.forEach(rgb => { const key = rgb.join(','); const name = CONFIG.COLOR_NAMES[key] || `rgb(${key})`; const isPaid = CONFIG.PAID_COLORS.has(key); const colorItem = Utils.createElement('div', { className: 'wplace-color-item' }); const swatch = Utils.createElement('button', { className: `wplace-color-swatch ${isPaid ? 'paid' : ''}`, title: name, 'data-rgb': key, }); swatch.style.backgroundColor = `rgb(${key})`; const nameLabel = Utils.createElement('span', { className: 'wplace-color-item-name' }, name); // Default state: free are active, paid are not if (!isPaid) { swatch.classList.add('active'); } swatch.addEventListener('click', () => { swatch.classList.toggle('active'); updateActiveColorPalette(); }); colorItem.appendChild(swatch); colorItem.appendChild(nameLabel); if (isPaid) { paidContainer.appendChild(colorItem); } else { freeContainer.appendChild(colorItem); } }); // Add event listeners for master buttons container.querySelector('#selectAllFreeBtn')?.addEventListener('click', () => toggleAllColors(true, false)); container.querySelector('#unselectAllFreeBtn')?.addEventListener('click', () => toggleAllColors(false, false)); container.querySelector('#selectAllPaidBtn')?.addEventListener('click', () => toggleAllColors(true, true)); container.querySelector('#unselectAllPaidBtn')?.addEventListener('click', () => toggleAllColors(false, true)); // Set the initial state updateActiveColorPalette(); } // --- END: Color Palette Functions --- // Refactored function to handle color checking async function checkAndSetColors() { // If colors are already checked, no need to do it again. if (state.colorsChecked) return true; try { updateUI("checkingColors", "default"); state.availableColors = Utils.extractAvailableColors(); if (state.availableColors.length === 0) { Utils.showAlert(Utils.t("noColorsFound"), "error"); updateUI("noColorsFound", "error"); return false; // Indicate failure } state.colorsChecked = true; // Enable other buttons now that we're initialized const selectPosBtn = document.getElementById('selectPosBtn'); if(selectPosBtn) selectPosBtn.disabled = false; updateUI("colorsFound", "success", { count: state.availableColors.length, }); updateStats(); return true; // Indicate success } catch (e) { console.error("Error checking colors:", e); updateUI("imageError", "error"); return false; // Indicate failure } } async function createUI() { await detectLanguage() // Clean up existing UI elements to prevent duplicates const existingContainer = document.getElementById("wplace-image-bot-container") const existingStats = document.getElementById("wplace-stats-container") const existingSettings = document.getElementById("wplace-settings-container") const existingResizeContainer = document.querySelector(".resize-container") const existingResizeOverlay = document.querySelector(".resize-overlay") if (existingContainer) existingContainer.remove() if (existingStats) existingStats.remove() if (existingSettings) existingSettings.remove() if (existingResizeContainer) existingResizeContainer.remove() if (existingResizeOverlay) existingResizeOverlay.remove() loadThemePreference() loadLanguagePreference() const theme = getCurrentTheme() const fontAwesome = document.createElement("link") fontAwesome.rel = "stylesheet" fontAwesome.href = "https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" document.head.appendChild(fontAwesome) if (theme.fontFamily.includes("Press Start 2P")) { const googleFonts = document.createElement("link") googleFonts.rel = "stylesheet" googleFonts.href = "https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap" document.head.appendChild(googleFonts) } const style = document.createElement("style") style.setAttribute("data-wplace-theme", "true") style.textContent = ` ${ theme.animations.glow ? ` @keyframes neonGlow { 0%, 100% { text-shadow: 0 0 5px currentColor, 0 0 10px currentColor, 0 0 15px currentColor; } 50% { text-shadow: 0 0 2px currentColor, 0 0 5px currentColor, 0 0 8px currentColor; } }` : "" } ${ theme.animations.pixelBlink ? ` @keyframes pixelBlink { 0%, 50% { opacity: 1; } 51%, 100% { opacity: 0.7; } }` : "" } ${ theme.animations.scanline ? ` @keyframes scanline { 0% { transform: translateY(-100%); } 100% { transform: translateY(400px); } }` : "" } @keyframes pulse { 0% { box-shadow: 0 0 0 0 rgba(0, 255, 0, 0.7); } 70% { box-shadow: 0 0 0 10px rgba(0, 255, 0, 0); } 100% { box-shadow: 0 0 0 0 rgba(0, 255, 0, 0); } } @keyframes slideIn { from { transform: translateY(-10px); opacity: 0; } to { transform: translateY(0); opacity: 1; } } @keyframes shimmer { 0% { transform: translateX(-100%); } 100% { transform: translateX(100%); } } #wplace-image-bot-container { position: fixed; top: 20px; right: 20px; width: ${CONFIG.currentTheme === "Neon Retro" ? "280px" : "280px"}; max-height: calc(100vh - 40px); background: ${ CONFIG.currentTheme === "Classic Autobot" ? `linear-gradient(135deg, ${theme.primary} 0%, #1a1a1a 100%)` : theme.primary }; border: ${theme.borderWidth} ${theme.borderStyle} ${CONFIG.currentTheme === "Classic Autobot" ? theme.accent : theme.text}; border-radius: ${theme.borderRadius}; padding: 0; box-shadow: ${theme.boxShadow}; z-index: 9998; font-family: ${theme.fontFamily}; color: ${theme.text}; animation: slideIn 0.4s ease-out; overflow-y: auto; /* Allow scrolling for main panel */ overflow-x: hidden; ${theme.backdropFilter ? `backdrop-filter: ${theme.backdropFilter};` : ""} transition: all 0.3s ease; user-select: none; ${CONFIG.currentTheme === "Neon Retro" ? "image-rendering: pixelated;" : ""} } ${ theme.animations.scanline ? ` #wplace-image-bot-container::before { content: ''; position: absolute; top: 0; left: 0; right: 0; height: 2px; background: linear-gradient(90deg, transparent, ${theme.neon}, transparent); animation: scanline 3s linear infinite; z-index: 1; pointer-events: none; }` : "" } ${ CONFIG.currentTheme === "Neon Retro" ? ` #wplace-image-bot-container::after { content: ''; position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: repeating-linear-gradient( 0deg, transparent, transparent 2px, rgba(0, 255, 65, 0.03) 2px, rgba(0, 255, 65, 0.03) 4px ); pointer-events: none; z-index: 1; }` : "" } #wplace-image-bot-container.wplace-dragging { transition: none; box-shadow: 0 12px 40px rgba(0,0,0,0.8), 0 0 0 2px rgba(255,255,255,0.2); transform: scale(1.02); z-index: 9999; } #wplace-image-bot-container.wplace-minimized { width: 200px; height: auto; overflow: hidden; } #wplace-image-bot-container.wplace-compact { width: 240px; } /* Stats Container */ #wplace-stats-container { position: fixed; top: 20px; left: 20px; width: ${CONFIG.currentTheme === "Neon Retro" ? "280px" : "280px"}; max-height: calc(100vh - 40px); background: ${ CONFIG.currentTheme === "Classic Autobot" ? `linear-gradient(135deg, ${theme.primary} 0%, #1a1a1a 100%)` : theme.primary }; border: ${theme.borderWidth} ${theme.borderStyle} ${CONFIG.currentTheme === "Classic Autobot" ? theme.accent : theme.text}; border-radius: ${theme.borderRadius}; padding: 0; box-shadow: ${theme.boxShadow}; z-index: 9997; font-family: ${theme.fontFamily}; color: ${theme.text}; animation: slideIn 0.4s ease-out; overflow: hidden; ${theme.backdropFilter ? `backdrop-filter: ${theme.backdropFilter};` : ""} transition: all 0.3s ease; user-select: none; ${CONFIG.currentTheme === "Neon Retro" ? "image-rendering: pixelated;" : ""} } /* FIX: Disable transition during drag to prevent lag */ #wplace-stats-container.wplace-dragging { transition: none; } .wplace-header { padding: ${CONFIG.currentTheme === "Neon Retro" ? "8px 12px" : "8px 12px"}; background: ${ CONFIG.currentTheme === "Classic Autobot" ? `linear-gradient(135deg, ${theme.secondary} 0%, #2a2a2a 100%)` : theme.secondary }; color: ${theme.highlight}; font-size: ${CONFIG.currentTheme === "Neon Retro" ? "11px" : "13px"}; font-weight: ${CONFIG.currentTheme === "Neon Retro" ? "normal" : "700"}; display: flex; justify-content: space-between; align-items: center; cursor: move; user-select: none; border-bottom: ${CONFIG.currentTheme === "Neon Retro" ? "2px" : "1px"} solid ${CONFIG.currentTheme === "Classic Autobot" ? "rgba(255,255,255,0.1)" : theme.text}; ${CONFIG.currentTheme === "Classic Autobot" ? "text-shadow: 0 1px 2px rgba(0,0,0,0.5);" : "text-transform: uppercase; letter-spacing: 1px;"} transition: background 0.2s ease; position: relative; z-index: 2; ${theme.animations.glow ? "animation: neonGlow 2s ease-in-out infinite alternate;" : ""} } .wplace-header-title { display: flex; align-items: center; gap: ${CONFIG.currentTheme === "Neon Retro" ? "6px" : "6px"}; } .wplace-header-controls { display: flex; gap: ${CONFIG.currentTheme === "Neon Retro" ? "6px" : "6px"}; } .wplace-header-btn { background: ${CONFIG.currentTheme === "Classic Autobot" ? "rgba(255,255,255,0.1)" : theme.accent}; border: ${CONFIG.currentTheme === "Neon Retro" ? `2px solid ${theme.text}` : "none"}; color: ${theme.text}; cursor: pointer; border-radius: ${CONFIG.currentTheme === "Classic Autobot" ? "4px" : "0"}; width: ${CONFIG.currentTheme === "Classic Autobot" ? "18px" : "auto"}; height: ${CONFIG.currentTheme === "Classic Autobot" ? "18px" : "auto"}; padding: ${CONFIG.currentTheme === "Neon Retro" ? "4px 6px" : "0"}; font-size: ${CONFIG.currentTheme === "Neon Retro" ? "8px" : "10px"}; display: flex; align-items: center; justify-content: center; transition: all 0.2s; font-family: ${theme.fontFamily}; ${CONFIG.currentTheme === "Neon Retro" ? "image-rendering: pixelated;" : ""} } .wplace-header-btn:hover { background: ${CONFIG.currentTheme === "Classic Autobot" ? theme.accent : theme.text}; color: ${CONFIG.currentTheme === "Classic Autobot" ? theme.text : theme.primary}; transform: ${CONFIG.currentTheme === "Classic Autobot" ? "scale(1.1)" : "none"}; ${CONFIG.currentTheme === "Neon Retro" ? `box-shadow: 0 0 10px ${theme.text};` : ""} } .wplace-content { padding: ${CONFIG.currentTheme === "Neon Retro" ? "12px" : "12px"}; display: block; position: relative; z-index: 2; } .wplace-content.wplace-hidden { display: none; } .wplace-status-section { margin-bottom: 12px; padding: 8px; background: rgba(255,255,255,0.03); border-radius: ${theme.borderRadius}; border: 1px solid rgba(255,255,255,0.1); } .wplace-section { margin-bottom: ${CONFIG.currentTheme === "Neon Retro" ? "12px" : "12px"}; padding: 12px; background: rgba(255,255,255,0.03); border-radius: ${theme.borderRadius}; border: 1px solid rgba(255,255,255,0.1); } .wplace-section-title { font-size: 11px; font-weight: 600; margin-bottom: 8px; color: ${theme.highlight}; display: flex; align-items: center; gap: 6px; text-transform: uppercase; letter-spacing: 0.5px; } .wplace-controls { display: flex; flex-direction: column; gap: 8px; } .wplace-row { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; } .wplace-row.single { grid-template-columns: 1fr; } .wplace-btn { padding: ${CONFIG.currentTheme === "Neon Retro" ? "12px 8px" : "8px 12px"}; border: ${CONFIG.currentTheme === "Neon Retro" ? "2px solid" : "none"}; border-radius: ${theme.borderRadius}; font-weight: ${CONFIG.currentTheme === "Neon Retro" ? "normal" : "500"}; cursor: pointer; display: flex; align-items: center; justify-content: center; gap: ${CONFIG.currentTheme === "Neon Retro" ? "8px" : "6px"}; font-size: ${CONFIG.currentTheme === "Neon Retro" ? "8px" : "11px"}; transition: all 0.3s ease; position: relative; overflow: hidden; font-family: ${theme.fontFamily}; ${CONFIG.currentTheme === "Neon Retro" ? "text-transform: uppercase; letter-spacing: 1px; image-rendering: pixelated;" : ""} background: ${ CONFIG.currentTheme === "Classic Autobot" ? `linear-gradient(135deg, ${theme.accent} 0%, #4a4a4a 100%)` : theme.accent }; ${CONFIG.currentTheme === "Classic Autobot" ? "border: 1px solid rgba(255,255,255,0.1);" : ""} } ${ CONFIG.currentTheme === "Classic Autobot" ? ` .wplace-btn::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%; background: linear-gradient(90deg, transparent, rgba(255,255,255,0.1), transparent); transition: left 0.5s ease; } .wplace-btn:hover:not(:disabled)::before { left: 100%; }` : ` .wplace-btn::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%; background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); transition: left 0.5s; } .wplace-btn:hover::before { left: 100%; }` } .wplace-btn:hover:not(:disabled) { transform: ${CONFIG.currentTheme === "Classic Autobot" ? "translateY(-1px)" : "none"}; box-shadow: ${ CONFIG.currentTheme === "Classic Autobot" ? "0 4px 12px rgba(0,0,0,0.4)" : "0 0 15px currentColor" }; ${theme.animations.pixelBlink ? "animation: pixelBlink 0.5s infinite;" : ""} } .wplace-btn:active:not(:disabled) { transform: translateY(0); } .wplace-btn-primary { background: ${ CONFIG.currentTheme === "Classic Autobot" ? `linear-gradient(135deg, ${theme.accent} 0%, #6a5acd 100%)` : theme.accent }; color: ${theme.text}; ${CONFIG.currentTheme === "Neon Retro" ? `border-color: ${theme.text};` : ""} } .wplace-btn-upload { background: ${ CONFIG.currentTheme === "Classic Autobot" ? `linear-gradient(135deg, ${theme.secondary} 0%, #4a4a4a 100%)` : theme.purple }; color: ${theme.text}; ${ CONFIG.currentTheme === "Classic Autobot" ? `border: 1px dashed ${theme.highlight};` : `border-color: ${theme.text}; border-style: dashed;` } } .wplace-btn-start { background: ${ CONFIG.currentTheme === "Classic Autobot" ? `linear-gradient(135deg, ${theme.success} 0%, #228b22 100%)` : theme.success }; color: ${CONFIG.currentTheme === "Classic Autobot" ? "white" : theme.primary}; ${CONFIG.currentTheme === "Neon Retro" ? `border-color: ${theme.success};` : ""} } .wplace-btn-stop { background: ${ CONFIG.currentTheme === "Classic Autobot" ? `linear-gradient(135deg, ${theme.error} 0%, #dc143c 100%)` : theme.error }; color: ${CONFIG.currentTheme === "Classic Autobot" ? "white" : theme.text}; ${CONFIG.currentTheme === "Neon Retro" ? `border-color: ${theme.error};` : ""} } .wplace-btn-select { background: ${ CONFIG.currentTheme === "Classic Autobot" ? `linear-gradient(135deg, ${theme.highlight} 0%, #9370db 100%)` : theme.highlight }; color: ${CONFIG.currentTheme === "Classic Autobot" ? "white" : theme.primary}; ${CONFIG.currentTheme === "Neon Retro" ? `border-color: ${theme.highlight};` : ""} } .wplace-btn-file { background: ${ CONFIG.currentTheme === "Classic Autobot" ? "linear-gradient(135deg, #ff8c00 0%, #ff7f50 100%)" : theme.warning }; color: ${CONFIG.currentTheme === "Classic Autobot" ? "white" : theme.primary}; ${CONFIG.currentTheme === "Neon Retro" ? `border-color: ${theme.warning};` : ""} } .wplace-btn:disabled { opacity: ${CONFIG.currentTheme === "Classic Autobot" ? "0.5" : "0.3"}; cursor: not-allowed; transform: none !important; ${theme.animations.pixelBlink ? "animation: none !important;" : ""} box-shadow: none !important; } .wplace-btn:disabled::before { display: none; } .wplace-stats { background: ${CONFIG.currentTheme === "Classic Autobot" ? "rgba(255,255,255,0.03)" : theme.secondary}; padding: ${CONFIG.currentTheme === "Neon Retro" ? "12px" : "8px"}; border: ${CONFIG.currentTheme === "Neon Retro" ? `2px solid ${theme.text}` : "1px solid rgba(255,255,255,0.1)"}; border-radius: ${theme.borderRadius}; margin-bottom: ${CONFIG.currentTheme === "Neon Retro" ? "15px" : "8px"}; ${CONFIG.currentTheme === "Neon Retro" ? "box-shadow: inset 0 0 10px rgba(0, 255, 65, 0.1);" : ""} } .wplace-stat-item { display: flex; justify-content: space-between; padding: ${CONFIG.currentTheme === "Neon Retro" ? "6px 0" : "4px 0"}; font-size: ${CONFIG.currentTheme === "Neon Retro" ? "8px" : "11px"}; border-bottom: 1px solid rgba(255,255,255,0.05); ${CONFIG.currentTheme === "Neon Retro" ? "text-transform: uppercase; letter-spacing: 1px;" : ""} } .wplace-stat-item:last-child { border-bottom: none; } .wplace-stat-label { display: flex; align-items: center; gap: 6px; opacity: 0.9; font-size: ${CONFIG.currentTheme === "Neon Retro" ? "8px" : "10px"}; } .wplace-stat-value { font-weight: 600; color: ${theme.highlight}; } .wplace-progress { width: 100%; background: ${CONFIG.currentTheme === "Classic Autobot" ? "rgba(0,0,0,0.3)" : theme.secondary}; border: ${CONFIG.currentTheme === "Neon Retro" ? `2px solid ${theme.text}` : "1px solid rgba(255,255,255,0.1)"}; border-radius: ${theme.borderRadius}; margin: ${CONFIG.currentTheme === "Neon Retro" ? "10px 0" : "8px 0"}; overflow: hidden; height: ${CONFIG.currentTheme === "Neon Retro" ? "16px" : "6px"}; position: relative; } ${ CONFIG.currentTheme === "Neon Retro" ? ` .wplace-progress::before { content: ''; position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: repeating-linear-gradient( 45deg, transparent, transparent 2px, rgba(0, 255, 65, 0.1) 2px, rgba(0, 255, 65, 0.1) 4px ); pointer-events: none; }` : "" } .wplace-progress-bar { height: ${CONFIG.currentTheme === "Neon Retro" ? "100%" : "6px"}; background: ${ CONFIG.currentTheme === "Classic Autobot" ? `linear-gradient(135deg, ${theme.highlight} 0%, #9370db 100%)` : `linear-gradient(90deg, ${theme.success}, ${theme.neon})` }; transition: width ${CONFIG.currentTheme === "Neon Retro" ? "0.3s" : "0.5s"} ease; position: relative; ${CONFIG.currentTheme === "Neon Retro" ? `box-shadow: 0 0 10px ${theme.success};` : ""} } ${ CONFIG.currentTheme === "Classic Autobot" ? ` .wplace-progress-bar::after { content: ''; position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: linear-gradient(90deg, transparent, rgba(255,255,255,0.3), transparent); animation: shimmer 2s infinite; }` : ` .wplace-progress-bar::after { content: ''; position: absolute; top: 0; right: 0; width: 4px; height: 100%; background: ${theme.text}; animation: pixelBlink 1s infinite; }` } .wplace-status { padding: ${CONFIG.currentTheme === "Neon Retro" ? "10px" : "6px"}; border: ${CONFIG.currentTheme === "Neon Retro" ? "2px solid" : "1px solid"}; border-radius: ${theme.borderRadius}; text-align: center; font-size: ${CONFIG.currentTheme === "Neon Retro" ? "8px" : "11px"}; ${CONFIG.currentTheme === "Neon Retro" ? "text-transform: uppercase; letter-spacing: 1px;" : ""} position: relative; overflow: hidden; } .status-default { background: ${CONFIG.currentTheme === "Classic Autobot" ? "rgba(255,255,255,0.1)" : theme.accent}; border-color: ${theme.text}; color: ${theme.text}; } .status-success { background: ${CONFIG.currentTheme === "Classic Autobot" ? "rgba(0, 255, 0, 0.1)" : theme.success}; border-color: ${theme.success}; color: ${CONFIG.currentTheme === "Classic Autobot" ? theme.success : theme.primary}; box-shadow: 0 0 15px ${theme.success}; } .status-error { background: ${CONFIG.currentTheme === "Classic Autobot" ? "rgba(255, 0, 0, 0.1)" : theme.error}; border-color: ${theme.error}; color: ${CONFIG.currentTheme === "Classic Autobot" ? theme.error : theme.text}; box-shadow: 0 0 15px ${theme.error}; ${theme.animations.pixelBlink ? "animation: pixelBlink 0.5s infinite;" : ""} } .status-warning { background: ${CONFIG.currentTheme === "Classic Autobot" ? "rgba(255, 165, 0, 0.1)" : theme.warning}; border-color: ${theme.warning}; color: ${CONFIG.currentTheme === "Classic Autobot" ? "orange" : theme.primary}; box-shadow: 0 0 15px ${theme.warning}; } .resize-container { display: none; position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: ${theme.primary}; padding: 20px; border: ${theme.borderWidth} ${theme.borderStyle} ${theme.text}; border-radius: ${theme.borderRadius}; z-index: 10000; box-shadow: ${ CONFIG.currentTheme === "Classic Autobot" ? "0 0 20px rgba(0,0,0,0.5)" : "0 0 30px rgba(0, 255, 65, 0.5)" }; width: 90%; max-width: 700px; /* Increased width */ max-height: 90%; overflow: auto; font-family: ${theme.fontFamily}; } .resize-preview-wrapper { display: flex; justify-content: center; align-items: center; border: 1px solid ${theme.accent}; background: rgba(0,0,0,0.2); margin: 15px 0; height: 300px; overflow: auto; } .resize-preview { max-width: none; /* Allow image to exceed wrapper for zoom */ transition: transform 0.1s ease; image-rendering: pixelated; image-rendering: -moz-crisp-edges; image-rendering: crisp-edges; } .resize-controls { display: grid; grid-template-columns: 1fr 1fr; gap: 15px; align-items: center; } .resize-controls label { font-size: ${CONFIG.currentTheme === "Neon Retro" ? "8px" : "12px"}; ${CONFIG.currentTheme === "Neon Retro" ? "text-transform: uppercase; letter-spacing: 1px;" : ""} color: ${theme.text}; } .resize-slider { width: 100%; height: ${CONFIG.currentTheme === "Neon Retro" ? "8px" : "4px"}; background: ${CONFIG.currentTheme === "Classic Autobot" ? "#ccc" : theme.secondary}; border: ${CONFIG.currentTheme === "Neon Retro" ? `2px solid ${theme.text}` : "none"}; border-radius: ${theme.borderRadius}; outline: none; -webkit-appearance: none; } ${ CONFIG.currentTheme === "Neon Retro" ? ` .resize-slider::-webkit-slider-thumb { -webkit-appearance: none; width: 16px; height: 16px; background: ${theme.highlight}; border: 2px solid ${theme.text}; border-radius: 0; cursor: pointer; box-shadow: 0 0 5px ${theme.highlight}; } .resize-slider::-moz-range-thumb { width: 16px; height: 16px; background: ${theme.highlight}; border: 2px solid ${theme.text}; border-radius: 0; cursor: pointer; box-shadow: 0 0 5px ${theme.highlight}; }` : "" } .resize-zoom-controls { grid-column: 1 / -1; /* Span full width */ display: flex; align-items: center; gap: 10px; margin-top: 15px; } .resize-buttons { display: flex; gap: 10px; justify-content: center; margin-top: 20px; } .resize-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.8); z-index: 9999; display: none; } /* --- START: Color Palette Styles --- */ .wplace-color-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(60px, 1fr)); /* Wider columns for name */ gap: 10px; padding-top: 8px; } .wplace-color-item { display: flex; flex-direction: column; align-items: center; gap: 4px; } .wplace-color-item-name { font-size: 9px; color: #ccc; text-align: center; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; width: 100%; } .wplace-color-swatch { width: 22px; height: 22px; border: 1px solid rgba(255,255,255,0.2); border-radius: 4px; cursor: pointer; transition: transform 0.1s ease, box-shadow 0.2s ease; position: relative; margin: 0 auto; } .wplace-color-swatch.paid { border-color: gold; } .wplace-color-swatch:hover { transform: scale(1.1); z-index: 1; } .wplace-color-swatch:not(.active) { opacity: 0.3; filter: grayscale(80%); } .wplace-color-swatch.active::after { content: '✔'; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); color: white; font-size: 12px; font-weight: bold; text-shadow: 0 0 3px black; } .wplace-color-divider { border: none; height: 1px; background: rgba(255,255,255,0.1); margin: 8px 0; } /* --- END: Color Palette Styles --- */ ${ CONFIG.currentTheme === "Neon Retro" ? ` /* Retro checkbox styling */ input[type="checkbox"] { -webkit-appearance: none; -moz-appearance: none; appearance: none; width: 16px; height: 16px; border: 2px solid ${theme.text}; background: ${theme.secondary}; margin-right: 8px; position: relative; cursor: pointer; } input[type="checkbox"]:checked { background: ${theme.success}; } input[type="checkbox"]:checked::after { content: '✓'; position: absolute; top: -2px; left: 1px; color: ${theme.primary}; font-size: 12px; font-weight: bold; } /* Icon styling for retro feel */ .fas, .fa { filter: drop-shadow(0 0 3px currentColor); } /* Speed Control Styles */ .wplace-speed-control { margin-top: 12px; padding: 12px; background: ${theme.secondary}; border: ${theme.borderWidth} ${theme.borderStyle} ${theme.accent}; border-radius: ${theme.borderRadius}; backdrop-filter: ${theme.backdropFilter}; } .wplace-speed-label { display: flex; align-items: center; margin-bottom: 8px; color: ${theme.text}; font-size: 13px; font-weight: 600; } .wplace-speed-label i { margin-right: 6px; color: ${theme.highlight}; } .wplace-speed-slider-container { display: flex; align-items: center; gap: 12px; } .wplace-speed-slider { flex: 1; height: 6px; border-radius: 3px; background: ${theme.primary}; outline: none; cursor: pointer; -webkit-appearance: none; appearance: none; } .wplace-speed-slider::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; width: 18px; height: 18px; border-radius: 50%; background: ${theme.highlight}; cursor: pointer; border: 2px solid ${theme.text}; box-shadow: ${theme.boxShadow}; } .wplace-speed-slider::-moz-range-thumb { width: 18px; height: 18px; border-radius: 50%; background: ${theme.highlight}; cursor: pointer; border: 2px solid ${theme.text}; box-shadow: ${theme.boxShadow}; } .wplace-speed-display { display: flex; align-items: center; gap: 4px; min-width: 90px; justify-content: flex-end; } #speedValue { color: ${theme.highlight}; font-weight: 600; font-size: 14px; } .wplace-speed-unit { color: ${theme.text}; font-size: 11px; opacity: 0.8; } /* Settings Window Styles */ #wplace-settings-container { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 10001; min-width: 400px; max-width: 500px; background: ${theme.primary}; border: ${theme.borderWidth} ${theme.borderStyle} ${theme.accent}; border-radius: ${theme.borderRadius}; box-shadow: ${theme.boxShadow}; backdrop-filter: ${theme.backdropFilter}; } .wplace-settings { padding: 16px; max-height: 400px; overflow-y: auto; } .wplace-setting-section { margin-bottom: 20px; padding: 12px; background: ${theme.secondary}; border: ${theme.borderWidth} ${theme.borderStyle} ${theme.accent}; border-radius: ${theme.borderRadius}; } .wplace-setting-title { display: flex; align-items: center; margin-bottom: 12px; color: ${theme.text}; font-size: 14px; font-weight: 600; } .wplace-setting-title i { margin-right: 8px; color: ${theme.highlight}; } .wplace-setting-content { color: ${theme.text}; } .wplace-section { margin-bottom: 20px; padding: 15px; background: ${theme.secondary}; border: ${theme.borderWidth} ${theme.borderStyle} ${theme.accent}; border-radius: ${theme.borderRadius}; } .wplace-section-title { display: flex; align-items: center; margin-bottom: 15px; color: ${theme.text}; font-size: 14px; font-weight: 600; } .wplace-section-title i { margin-right: 8px; color: ${theme.highlight}; } .wplace-speed-container { display: flex; align-items: center; gap: 15px; margin-bottom: 10px; } .wplace-slider { flex: 1; height: 6px; background: ${theme.accent}; border-radius: 3px; outline: none; -webkit-appearance: none; } .wplace-slider::-webkit-slider-thumb { -webkit-appearance: none; width: 18px; height: 18px; background: ${theme.highlight}; border-radius: 50%; cursor: pointer; border: 2px solid ${theme.primary}; } .wplace-speed-display { background: ${theme.accent}; padding: 5px 10px; border-radius: 4px; color: ${theme.text}; font-weight: 600; min-width: 80px; text-align: center; border: ${theme.borderWidth} ${theme.borderStyle} ${theme.highlight}; } .wplace-select { width: 100%; padding: 8px 12px; background: ${theme.secondary}; border: ${theme.borderWidth} ${theme.borderStyle} ${theme.accent}; border-radius: ${theme.borderRadius}; color: ${theme.text}; font-size: 14px; margin-bottom: 10px; } .wplace-select:focus { outline: none; border-color: ${theme.highlight}; } .wplace-description { color: ${theme.text}; font-size: 12px; opacity: 0.8; line-height: 1.4; } .wplace-theme-custom { margin-top: 15px; padding: 15px; background: ${theme.accent}; border-radius: ${theme.borderRadius}; border: ${theme.borderWidth} ${theme.borderStyle} ${theme.highlight}; } .wplace-custom-group { margin-bottom: 15px; } .wplace-custom-label { display: flex; align-items: center; margin-bottom: 8px; color: ${theme.text}; font-size: 13px; font-weight: 600; } .wplace-custom-label i { margin-right: 8px; color: ${theme.highlight}; width: 16px; } .wplace-color-input-group { display: flex; gap: 8px; align-items: center; } .wplace-color-input { width: 50px; height: 30px; border: none; border-radius: 4px; cursor: pointer; background: transparent; } .wplace-color-text { flex: 1; padding: 6px 10px; background: ${theme.secondary}; border: ${theme.borderWidth} ${theme.borderStyle} ${theme.accent}; border-radius: 4px; color: ${theme.text}; font-size: 12px; font-family: monospace; } .wplace-animation-controls { display: flex; flex-direction: column; gap: 8px; } .wplace-checkbox-label { display: flex; align-items: center; gap: 8px; color: ${theme.text}; font-size: 12px; cursor: pointer; } .wplace-checkbox-label input[type="checkbox"] { accent-color: ${theme.highlight}; } .wplace-slider-container { display: flex; align-items: center; gap: 10px; } .wplace-slider-container .wplace-slider { flex: 1; } .wplace-slider-container span { color: ${theme.text}; font-size: 12px; font-weight: 600; min-width: 40px; } .wplace-custom-actions { display: flex; gap: 10px; margin-top: 20px; border-top: 1px solid ${theme.accent}; padding-top: 15px; } .wplace-btn-secondary { background: ${theme.accent}; color: ${theme.text}; border: ${theme.borderWidth} ${theme.borderStyle} ${theme.highlight}; } .wplace-btn-secondary:hover { background: ${theme.secondary}; }` : "" } ` document.head.appendChild(style) const container = document.createElement("div") container.id = "wplace-image-bot-container" container.innerHTML = `