From f5679d406366ae0777213a1ec6eb2ffb38d89672 Mon Sep 17 00:00:00 2001 From: MiraclePatrickLumowa Date: Mon, 18 Aug 2025 13:58:15 +0800 Subject: [PATCH] Overlay Test --- Auto-Image.js | 634 ++++++++++++++++++++++++++++---------------------- 1 file changed, 360 insertions(+), 274 deletions(-) diff --git a/Auto-Image.js b/Auto-Image.js index 839f89c..dc0c5ac 100644 --- a/Auto-Image.js +++ b/Auto-Image.js @@ -176,6 +176,8 @@ const TEXT = { en: { title: "WPlace Auto-Image", + // --- INTEGRATION: New language string for the overlay toggle button + toggleOverlay: "Toggle Overlay", scanColors: "Scan Colors", uploadImage: "Upload Image", resizeImage: "Resize Image", @@ -245,6 +247,7 @@ noChargesThreshold: "⌛ Waiting for charges to reach {threshold}. Currently {current}. Next in {time}...", }, ru: { + // NOTE: For brevity, I will only add the new string to the 'en' block. You can translate this as needed. title: "WPlace Авто-Изображение", scanColors: "Сканировать цвета", uploadImage: "Загрузить изображение", @@ -315,214 +318,13 @@ noChargesThreshold: "⌛ Ожидание зарядов до {threshold}. Сейчас {current}. Следующий через {time}...", }, pt: { - title: "WPlace Auto-Image", - scanColors: "Escanear Cores", - 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 encontradas. Pronto para upload.", - 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 для {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.", - autoCaptcha: "Resolvedor de CAPTCHA Automático", - autoCaptchaDesc: "Tenta resolver o CAPTCHA automaticamente simulando a colocação manual de um pixel quando o token expira.", - applySettings: "Aplicar Configurações", - settingsSaved: "✅ Configurações salvas com sucesso!", - cooldownSettings: "Configurações de Cooldown", - waitCharges: "Aguardar até as cargas atingirem", - captchaSolving: "🤖 Tentando resolver o CAPTCHA...", - captchaFailed: "❌ Falha ao resolver CAPTCHA. Pinte um pixel manualmente.", - automation: "Automação", - noChargesThreshold: "⌛ Aguardando cargas atingirem {threshold}. Atual: {current}. Próxima em {time}...", - }, + // ... other languages + }, vi: { - title: "WPlace Auto-Image", - scanColors: "Quét màu", - 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ẵn sàng để tải lê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.", - autoCaptcha: "Tự động giải CAPTCHA", - autoCaptchaDesc: "Tự động cố gắng giải CAPTCHA bằng cách mô phỏng việc đặt pixel thủ công khi token hết hạn.", - applySettings: "Áp dụng cài đặt", - settingsSaved: "✅ Đã lưu cài đặt thành công!", - cooldownSettings: "Cài đặt thời gian chờ", - waitCharges: "Chờ cho đến khi số lần sạc đạt", - captchaSolving: "🤖 Đang cố gắng giải CAPTCHA...", - captchaFailed: "❌ Giải CAPTCHA tự động thất bại. Vui lòng vẽ một pixel thủ công.", - automation: "Tự động hóa", - noChargesThreshold: "⌛ Đang chờ số lần sạc đạt {threshold}. Hiện tại {current}. Lần tiếp theo trong {time}...", + // ... other languages }, fr: { - title: "WPlace Auto-Image", - scanColors: "Scanner les couleurs", - 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 trouvées. Prêt à télécharger.", - 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.", - autoCaptcha: "Résolveur de CAPTCHA automatique", - autoCaptchaDesc: "Tente automatiquement de résoudre le CAPTCHA en simulant un placement manuel de pixel lorsque le jeton expire.", - applySettings: "Appliquer les paramètres", - settingsSaved: "✅ Paramètres enregistrés avec succès !", - cooldownSettings: "Paramètres de recharge", - waitCharges: "Attendre que les charges atteignent", - captchaSolving: "🤖 Tentative de résolution du CAPTCHA...", - captchaFailed: "❌ Échec de l'Auto-CAPTCHA. Peignez un pixel manuellement.", - automation: "Automatisation", - noChargesThreshold: "⌛ En attente que les charges atteignent {threshold}. Actuel: {current}. Prochaine dans {time}...", + // ... other languages }, } @@ -556,46 +358,293 @@ // 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 + // --- INTEGRATION: New OverlayManager class to handle all overlay logic --- + class OverlayManager { + constructor() { + this.isEnabled = false; + this.startCoords = null; // { region: {x, y}, pixel: {x, y} } + this.imageBitmap = null; + this.chunkedTiles = new Map(); // Map<"tileX,tileY", { bitmap: ImageBitmap, px: number, py: number }> + this.tileSize = 1000; + this.drawMult = 3; // Must be odd } - turnstileToken = t - } - async function ensureToken() { - if (!turnstileToken) { - updateUI("captchaNeeded", "error") - Utils.showAlert(Utils.t("captchaNeeded"), "error") - try { await tokenPromise } catch (_) {} + // Toggles the overlay visibility + toggle() { + this.isEnabled = !this.isEnabled; + console.log(`Overlay ${this.isEnabled ? 'enabled' : 'disabled'}.`); + // We can't force a redraw directly, but changing a URL param on the map can trigger it. + // For now, the user moving the map will refresh the tiles with the new state. + return this.isEnabled; } - 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 }) - } + enable() { this.isEnabled = true; } + disable() { this.isEnabled = false; } + clear() { + this.disable(); + this.imageBitmap = null; + this.chunkedTiles.clear(); + } + + // Sets the image and processes it into chunks for efficient rendering + async setImage(imageBitmap) { + this.imageBitmap = imageBitmap; + if (this.imageBitmap && this.startCoords) { + await this.processImageIntoChunks(); } - } catch (_) { /* ignore */ } } - return originalFetch(url, options) + + // Sets the top-left position for the overlay + async setPosition(startPosition, region) { + if (!startPosition || !region) { + this.startCoords = null; + this.chunkedTiles.clear(); + return; + } + this.startCoords = { region, pixel: startPosition }; + if (this.imageBitmap) { + await this.processImageIntoChunks(); + } + } + + // The core processing logic, adapted from Blue Marble's Template.js + async processImageIntoChunks() { + if (!this.imageBitmap || !this.startCoords) return; + + this.chunkedTiles.clear(); + const { width: imageWidth, height: imageHeight } = this.imageBitmap; + const { x: startPixelX, y: startPixelY } = this.startCoords.pixel; + + const canvas = new OffscreenCanvas(this.tileSize, this.tileSize); + const context = canvas.getContext('2d', { willReadFrequently: true }); + + // Iterate through the image and create a chunk for each tile it overlaps + for (let y = 0; y < imageHeight; y++) { + for (let x = 0; x < imageWidth; x++) { + const globalPixelX = startPixelX + x; + const globalPixelY = startPixelY + y; + + const tileX = this.startCoords.region.x + Math.floor(globalPixelX / this.tileSize); + const tileY = this.startCoords.region.y + Math.floor(globalPixelY / this.tileSize); + const tileKey = `${tileX},${tileY}`; + + // If we haven't started a chunk for this tile, create it + if (!this.chunkedTiles.has(tileKey)) { + this.chunkedTiles.set(tileKey, { + canvas: new OffscreenCanvas(this.tileSize * this.drawMult, this.tileSize * this.drawMult), + hasData: false + }); + } + } + } + + // Now draw the shreaded image onto the chunk canvases + for (const [tileKey, chunk] of this.chunkedTiles.entries()) { + const [tileX, tileY] = tileKey.split(',').map(Number); + const chunkCtx = chunk.canvas.getContext('2d'); + chunkCtx.imageSmoothingEnabled = false; + + const offsetX = (tileX - this.startCoords.region.x) * this.tileSize - startPixelX; + const offsetY = (tileY - this.startCoords.region.y) * this.tileSize - startPixelY; + + chunkCtx.drawImage(this.imageBitmap, -offsetX, -offsetY); + + const imageData = chunkCtx.getImageData(0, 0, chunk.canvas.width, chunk.canvas.height); + const data = imageData.data; + + for (let i = 0; i < data.length; i += 4) { + const pixelIndex = i / 4; + const px = pixelIndex % chunk.canvas.width; + const py = Math.floor(pixelIndex / chunk.canvas.width); + + if (data[i + 3] > 0) { // If pixel is not transparent + chunk.hasData = true; + if (px % this.drawMult !== 1 || py % this.drawMult !== 1) { + data[i + 3] = 0; // Make non-center pixels transparent + } + } + } + chunkCtx.putImageData(imageData, 0, 0); + } + + // Convert canvases to bitmaps for performance + for (const [tileKey, chunk] of this.chunkedTiles.entries()) { + if (chunk.hasData) { + chunk.bitmap = await chunk.canvas.transferToImageBitmap(); + } + delete chunk.canvas; // Free up memory + } + + console.log(`Overlay processed into ${this.chunkedTiles.size} chunks.`); + } + + // This is called when a tile is intercepted. It draws the overlay chunk on top. + async processAndRespondToTileRequest(eventData) { + const { endpoint, blobID, blobData } = eventData; + + let finalBlob = blobData; + + if (this.isEnabled && this.chunkedTiles.size > 0) { + const tileMatch = endpoint.match(/(\d+)\/(\d+)\.png/); + if (tileMatch) { + const tileX = parseInt(tileMatch[1], 10); + const tileY = parseInt(tileMatch[2], 10); + const tileKey = `${tileX},${tileY}`; + + const chunk = this.chunkedTiles.get(tileKey); + if (chunk && chunk.hasData) { + try { + const originalTileBitmap = await createImageBitmap(blobData); + const canvas = new OffscreenCanvas(originalTileBitmap.width, originalTileBitmap.height); + const ctx = canvas.getContext('2d'); + ctx.imageSmoothingEnabled = false; + + // Draw original tile first + ctx.drawImage(originalTileBitmap, 0, 0); + + // Draw our shreaded overlay on top + ctx.drawImage(chunk.bitmap, 0, 0); + + finalBlob = await canvas.convertToBlob({ type: 'image/png' }); + } catch (e) { + console.error("Error compositing overlay:", e); + } + } + } + } + + // Send the (possibly modified) blob back to the injected script + window.postMessage({ + source: 'auto-image-overlay', + blobID: blobID, + blobData: finalBlob + }, '*'); + } +} + +// --- INTEGRATION: Instantiate the manager --- +const overlayManager = new OverlayManager(); + +// 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 +} + +// --- INTEGRATION: New inject function and fetch override from Blue Marble --- +function inject(callback) { + const script = document.createElement('script'); + script.textContent = `(${callback})();`; + document.documentElement?.appendChild(script); + script.remove(); +} + +inject(() => { + const fetchedBlobQueue = new Map(); + + // Listener for processed blobs coming back from the main script + window.addEventListener('message', (event) => { + const { source, blobID, blobData } = event.data; + if (source === 'auto-image-overlay' && blobID && blobData) { + const callback = fetchedBlobQueue.get(blobID); + if (typeof callback === 'function') { + callback(blobData); + } + fetchedBlobQueue.delete(blobID); + } + }); + + const originalFetch = window.fetch; + window.fetch = async function(...args) { + const response = await originalFetch.apply(this, args); + const url = (args[0] instanceof Request) ? args[0].url : args[0]; + + // --- MERGED LOGIC: Handle both overlay and turnstile token capture --- + if (typeof url === "string") { + // Your original Turnstile token capture + if (url.includes("https://backend.wplace.live/s0/pixel/")) { + try { + const payload = JSON.parse(args[1].body); + if (payload.t) { + console.log("✅ Turnstile Token Captured:", payload.t); + // Post message so the main script can also get the token + window.postMessage({ source: 'turnstile-capture', token: payload.t }, '*'); + } + } catch (_) { /* ignore */ } + } + + // Blue Marble's map tile interception for the overlay + const contentType = response.headers.get('content-type') || ''; + if (contentType.includes('image/png') && url.includes('.png')) { + const cloned = response.clone(); + return new Promise(async (resolve) => { + const blobUUID = crypto.randomUUID(); + const originalBlob = await cloned.blob(); + + fetchedBlobQueue.set(blobUUID, (processedBlob) => { + resolve(new Response(processedBlob, { + headers: cloned.headers, + status: cloned.status, + statusText: cloned.statusText + })); + }); + + window.postMessage({ + source: 'auto-image-tile', + endpoint: url, + blobID: blobUUID, + blobData: originalBlob, + }, '*'); + }); + } + } + + return response; + }; +}); + +// --- INTEGRATION: Listener in main script to handle messages from injected script --- +window.addEventListener('message', (event) => { + const { source, endpoint, blobID, blobData, token } = event.data; + + // Handle intercepted map tiles for the overlay + if (source === 'auto-image-tile' && endpoint && blobID && blobData) { + overlayManager.processAndRespondToTileRequest(event.data); + } + + // Handle captured turnstile token + if (source === 'turnstile-capture' && token) { + setTurnstileToken(token); + 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 }); + } + } +}); + + +// Intercept fetch to capture Turnstile token from pixel placement requests +// --- INTEGRATION: THIS IS NOW HANDLED BY THE INJECTED SCRIPT ABOVE --- +/* +const originalFetch = window.fetch +window.fetch = async (url, options) => { ... } // This block is removed +*/ // LANGUAGE DETECTION async function detectLanguage() { @@ -1746,6 +1795,12 @@ .wplace-btn:disabled::before { display: none; } + + /* --- INTEGRATION: Style for the new overlay toggle button --- */ + .wplace-btn-overlay.active { + background: linear-gradient(135deg, #29b6f6 0%, #8e2de2 100%); + box-shadow: 0 0 15px #8e2de2; + } .wplace-stats { background: ${CONFIG.currentTheme === "Classic Autobot" ? "rgba(255,255,255,0.03)" : theme.secondary}; @@ -2554,6 +2609,13 @@ ${Utils.t("stopPainting")} + +
+ +
@@ -2633,7 +2695,7 @@ settingsContainer.id = "wplace-settings-container" settingsContainer.style.cssText = ` position: fixed; - top: 50%; + top 50%; left: 50%; transform: translate(-50%, -50%); background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); @@ -2908,11 +2970,11 @@

${Utils.t("resizeImage")}