From 41bb4318a51a3a4151acb6a6c733fa5cf94c494f Mon Sep 17 00:00:00 2001 From: MiraclePatrickLumowa Date: Tue, 23 Sep 2025 03:29:21 +0800 Subject: [PATCH] MASSIVE UPDATE + 7 new Color processing algorithm >:) + Account manager reworked + Cooldown Skipping + Turnstile widget pooling --- Extension/background.js | 2 +- Extension/manifest.json | 2 +- Extension/popup/popup.css | 5 +- Extension/popup/popup.html | 30 +- Extension/popup/popup.js | 193 + Extension/scripts/Art-Extractor.js | 72 +- Extension/scripts/Auto-Image.js | 1343 ++++-- Extension/scripts/Auto-Repair.js | 6662 ++++++++++++++-------------- Extension/scripts/token-manager.js | 393 +- Extension/themes/neon-cyan.css | 1630 +++---- Extension/themes/neon-light.css | 1762 ++++---- 11 files changed, 6570 insertions(+), 5524 deletions(-) diff --git a/Extension/background.js b/Extension/background.js index fa41233..bb0789e 100644 --- a/Extension/background.js +++ b/Extension/background.js @@ -110,7 +110,7 @@ async function executeLocalScript(scriptName, tabId) { const overlayManagerCode = await overlayManagerResponse.text(); console.log('šŸŽØ Overlay manager loaded, injecting third...'); - // Execute overlay manager third + // Execute overlay manager thirds await chrome.scripting.executeScript({ target: { tabId: tabId }, world: "MAIN", diff --git a/Extension/manifest.json b/Extension/manifest.json index cf2c615..2422540 100644 --- a/Extension/manifest.json +++ b/Extension/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "WPlace AutoBOT Script Launcher", - "version": "2.0.2", + "version": "2.0.4", "description": "Launch and manage automation scripts for WPlace", "icons": { "16": "icons/icon16.png", diff --git a/Extension/popup/popup.css b/Extension/popup/popup.css index b29b080..1c5d909 100644 --- a/Extension/popup/popup.css +++ b/Extension/popup/popup.css @@ -335,7 +335,7 @@ main { display: flex; justify-content: space-between; align-items: center; - margin-bottom: 12px; + margin-bottom: 8px; } .section-header h4 { @@ -351,6 +351,9 @@ main { .header-actions { display: flex; gap: 6px; + margin-bottom: 12px; + justify-content: flex-start; + flex-wrap: wrap; } .btn-small { diff --git a/Extension/popup/popup.html b/Extension/popup/popup.html index fd2edb5..2239adb 100644 --- a/Extension/popup/popup.html +++ b/Extension/popup/popup.html @@ -30,16 +30,24 @@

Account Manager

-
- - -
+
+
+ + + +
@@ -105,7 +113,7 @@ diff --git a/Extension/popup/popup.js b/Extension/popup/popup.js index 7006768..13e32b8 100644 --- a/Extension/popup/popup.js +++ b/Extension/popup/popup.js @@ -39,6 +39,7 @@ const AVAILABLE_SCRIPTS = [ let statusDot, statusText, scriptsList, accountsContainer, accountPlaceholder; let addAccountModal, accountNameInput, accountTokenInput; let refreshAccountBtn, addAccountBtn, saveAccountBtn, cancelAccountBtn, closeModalBtn; +let exportAccountsBtn, importAccountsBtn; // Global state let accounts = []; @@ -75,6 +76,8 @@ function initializeElements() { saveAccountBtn = document.getElementById('saveAccountBtn'); cancelAccountBtn = document.getElementById('cancelAccountBtn'); closeModalBtn = document.getElementById('closeModalBtn'); + exportAccountsBtn = document.getElementById('exportAccountsBtn'); + importAccountsBtn = document.getElementById('importAccountsBtn'); } async function loadScripts() { @@ -126,6 +129,8 @@ function setupEventListeners() { saveAccountBtn.addEventListener('click', handleSaveAccount); cancelAccountBtn.addEventListener('click', closeAddAccountModal); closeModalBtn.addEventListener('click', closeAddAccountModal); + exportAccountsBtn.addEventListener('click', handleExportAccounts); + importAccountsBtn.addEventListener('click', handleImportAccounts); // Modal close on overlay click addAccountModal.addEventListener('click', (e) => { @@ -663,6 +668,194 @@ async function loadStartupScript() { } } +async function handleExportAccounts() { + try { + if (isLoading) return; + + setLoading(true); + showNotification('Exporting accounts...', 'info'); + + console.log('šŸ“¤ Starting account export process'); + + // Get accounts from storage + const result = await chrome.storage.local.get('infoAccounts'); + const accounts = result.infoAccounts || []; + + if (accounts.length === 0) { + showNotification('No accounts to export', 'warning'); + return; + } + + // Create export data structure + const exportData = { + version: '1.0', + exportDate: new Date().toISOString(), + accountCount: accounts.length, + accounts: accounts.map(account => ({ + name: account.name, + token: account.token, + ID: account.ID, + // Include static metadata only (exclude dynamic status like Charges, Max, Droplets) + ...(account.lastActive && { lastActive: account.lastActive }), + ...(account.level && { level: account.level }), + ...(account.totalPixelsPainted && { totalPixelsPainted: account.totalPixelsPainted }), + ...(account.allianceName && { allianceName: account.allianceName }), + ...(account.allianceRole && { allianceRole: account.allianceRole }) + })) + }; + + // Create filename with timestamp + const timestamp = new Date().toISOString().slice(0, 19).replace(/:/g, '-'); + const filename = `wplace-accounts-${timestamp}.json`; + + // Create and download file + const blob = new Blob([JSON.stringify(exportData, null, 2)], { 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); + + console.log(`āœ… Exported ${accounts.length} accounts to ${filename}`); + showNotification(`Exported ${accounts.length} accounts successfully`, 'success'); + + } catch (error) { + console.error('āŒ Error exporting accounts:', error); + showNotification('Failed to export accounts', 'error'); + } finally { + setLoading(false); + } +} + +async function handleImportAccounts() { + try { + if (isLoading) return; + + setLoading(true); + showNotification('Importing accounts...', 'info'); + + console.log('šŸ“„ Starting account import process'); + + // Create file input + const input = document.createElement('input'); + input.type = 'file'; + input.accept = '.json'; + + input.onchange = async (e) => { + const file = e.target.files[0]; + if (!file) { + setLoading(false); + return; + } + + try { + const text = await file.text(); + const importData = JSON.parse(text); + + // Validate import data structure + if (!importData.accounts || !Array.isArray(importData.accounts)) { + throw new Error('Invalid file format: missing accounts array'); + } + + // Validate each account has required fields + for (const account of importData.accounts) { + if (!account.name || !account.token) { + throw new Error('Invalid account data: missing name or token'); + } + } + + // Get existing accounts + const result = await chrome.storage.local.get('infoAccounts'); + let existingAccounts = result.infoAccounts || []; + + let addedCount = 0; + let updatedCount = 0; + let skippedCount = 0; + + // Process each imported account + for (const importAccount of importData.accounts) { + // Check if account already exists (by token or ID) + const existingIndex = existingAccounts.findIndex(existing => + existing.token === importAccount.token || + (existing.ID && importAccount.ID && existing.ID === importAccount.ID) + ); + + if (existingIndex > -1) { + // Account exists - ask user what to do + const shouldUpdate = confirm( + `Account "${importAccount.name}" already exists. Update it?\n\n` + + `Click OK to update, Cancel to skip.` + ); + + if (shouldUpdate) { + // Update existing account + existingAccounts[existingIndex] = { + ...existingAccounts[existingIndex], + ...importAccount, + lastImported: new Date().toISOString() + }; + updatedCount++; + console.log(`šŸ”„ Updated account: ${importAccount.name}`); + } else { + skippedCount++; + console.log(`ā­ļø Skipped account: ${importAccount.name}`); + } + } else { + // New account - add it + existingAccounts.push({ + ...importAccount, + lastImported: new Date().toISOString(), + // Set default values for missing fields + Charges: importAccount.Charges || 0, + Max: importAccount.Max || 100, + Droplets: importAccount.Droplets || 0 + }); + addedCount++; + console.log(`āž• Added new account: ${importAccount.name}`); + } + } + + // Save updated accounts + await chrome.storage.local.set({ infoAccounts: existingAccounts }); + + // Update the accounts array for compatibility + const accountTokens = existingAccounts.map(account => account.token); + await chrome.storage.local.set({ accounts: accountTokens }); + + // Reload the accounts display + await loadAccounts(); + + // Show summary + const summary = [ + `Import completed:`, + addedCount > 0 ? `${addedCount} added` : '', + updatedCount > 0 ? `${updatedCount} updated` : '', + skippedCount > 0 ? `${skippedCount} skipped` : '' + ].filter(Boolean).join(', '); + + console.log(`āœ… Import summary: ${summary}`); + showNotification(summary, 'success'); + + } catch (error) { + console.error('āŒ Error importing accounts:', error); + showNotification(`Import failed: ${error.message}`, 'error'); + } finally { + setLoading(false); + } + }; + + input.click(); + + } catch (error) { + console.error('āŒ Error starting import:', error); + showNotification('Failed to start import', 'error'); + setLoading(false); + } +} + // Enhanced account validation async function validateAccountToken(token) { try { diff --git a/Extension/scripts/Art-Extractor.js b/Extension/scripts/Art-Extractor.js index d8b9d48..66f182e 100644 --- a/Extension/scripts/Art-Extractor.js +++ b/Extension/scripts/Art-Extractor.js @@ -40,7 +40,7 @@ function getText(key, params) { window.WPLACE_ART_EXTRACTOR_LOADED = true; console.log('%cšŸŽØ WPlace Art Extractor Starting...', 'color: #ff6b35; font-weight: bold; font-size: 16px;'); - console.log('%c✨ Now featuring Auto-Guard style interactive pixel capture!', 'color: #26de81; font-weight: bold;'); + console.log('%c✨ Interactive pixel capture for area extraction!', 'color: #26de81; font-weight: bold;'); // Immediate visual confirmation that script is running try { @@ -66,9 +66,9 @@ function getText(key, params) { // CONFIGURATION CONSTANTS const CONFIG = { OVERLAY_OPACITY: 0.7, - SELECTION_COLOR: { r: 50, g: 200, b: 50, a: 180 }, // Green extraction overlay (different from Auto-Guard's red) - CORNER_MARKER_COLOR: { r: 255, g: 255, b: 0, a: 255 }, // Yellow corners like Auto-Guard - CORNER_MARKER_SIZE: 12, // Larger markers like Auto-Guard + SELECTION_COLOR: { r: 50, g: 200, b: 50, a: 180 }, // Green extraction overlay + CORNER_MARKER_COLOR: { r: 255, g: 255, b: 0, a: 255 }, // Yellow corner markers + CORNER_MARKER_SIZE: 12, // Corner marker size MIN_AREA_SIZE: 1, MAX_AREA_SIZE: 2500, COLOR_MAP: { @@ -179,12 +179,12 @@ function getText(key, params) { // Use Auto-Image's overlay system - no custom overlay manager needed // We'll integrate with the global overlay manager that Auto-Image uses - // Monitor WPlace paint events to capture pixel coordinates using Auto-Guard's fetch interception method + // Monitor WPlace paint events to capture pixel coordinates using fetch interception let originalFetch = null; let fetchInterceptionActive = false; function setupPixelCapture() { - console.log('šŸŽÆ Setting up Auto-Guard style pixel paint monitoring...'); + console.log('šŸŽÆ Setting up pixel paint monitoring...'); try { // Store original fetch if not already stored @@ -192,7 +192,7 @@ function getText(key, params) { originalFetch = window.fetch; } - // Install fetch interceptor (Auto-Guard method) + // Install fetch interceptor for pixel capture if (!fetchInterceptionActive) { window.fetch = async (url, options) => { // Check if this is a pixel painting request @@ -256,13 +256,13 @@ function getText(key, params) { }; fetchInterceptionActive = true; - console.log('āœ… Auto-Guard style fetch interception enabled'); + console.log('āœ… Fetch interception enabled'); } return true; } catch (error) { - console.warn('āš ļø Could not set up Auto-Guard style paint monitoring:', error); + console.warn('āš ļø Could not set up paint monitoring:', error); return false; } } @@ -360,7 +360,7 @@ function getText(key, params) { // If user clicked cancel (coordStr === null), do nothing } - // Add a captured pixel (Auto-Guard style with enhanced validation) + // Add a captured pixel with enhanced validation function addCapturedPixel(worldX, worldY) { if (!state.isCapturing) { console.log('Not in capture mode, ignoring pixel'); @@ -400,7 +400,7 @@ function getText(key, params) { updateUI(); - // Auto-Guard style progress messages + // Progress messages if (state.capturedPixels.length === 1) { showAlert(`āœ… Upper-left corner captured: (${worldX}, ${worldY})`, 'success'); setTimeout(() => { @@ -419,7 +419,7 @@ function getText(key, params) { } } - // Complete area capture when we have 2 pixels (Auto-Guard style with validation) + // Complete area capture when we have 2 pixels with validation function completeAreaCapture() { state.isCapturing = false; restoreFetch(); // Restore original fetch function @@ -436,7 +436,7 @@ function getText(key, params) { const maxX = Math.max(...pixels.map(p => p.worldX)); const maxY = Math.max(...pixels.map(p => p.worldY)); - // Auto-Guard style validation: ensure upper-left is actually upper-left + // Validation: ensure upper-left is actually upper-left if (minX >= maxX || minY >= maxY) { showAlert('āŒ Invalid area: upper-left corner must be less than lower-right corner', 'error'); // Reset capture to try again @@ -460,7 +460,7 @@ function getText(key, params) { } state.selectionArea = { - x1: minX, // Auto-Guard style naming + x1: minX, // Coordinate naming y1: minY, x2: maxX, y2: maxY, @@ -502,7 +502,7 @@ function getText(key, params) { const canvas = new OffscreenCanvas(width, height); const ctx = canvas.getContext('2d'); - // Fill with extraction area overlay (different color from Auto-Guard's protection overlay) + // Fill with extraction area overlay ctx.fillStyle = `rgba(${CONFIG.SELECTION_COLOR.r}, ${CONFIG.SELECTION_COLOR.g}, ${CONFIG.SELECTION_COLOR.b}, 0.3)`; ctx.fillRect(0, 0, width, height); @@ -511,7 +511,7 @@ function getText(key, params) { ctx.lineWidth = 2; ctx.strokeRect(1, 1, width - 2, height - 2); - // Add corner markers (like Auto-Guard) + // Add corner markers const cornerSize = CONFIG.CORNER_MARKER_SIZE; ctx.fillStyle = `rgba(${CONFIG.CORNER_MARKER_COLOR.r}, ${CONFIG.CORNER_MARKER_COLOR.g}, ${CONFIG.CORNER_MARKER_COLOR.b}, 1)`; @@ -558,14 +558,14 @@ function getText(key, params) { // Continue without overlay - not critical for extraction functionality } } - // Auto-Guard style tile-based pixel scanning for area extraction + // Tile-based pixel scanning for area extraction async function scanSelectedArea() { if (!state.selectionArea) { showAlert('No area selected', 'error'); return; } - console.log('šŸ” Starting Auto-Guard style area scan...'); + console.log('šŸ” Starting area scan...'); state.isScanning = true; state.scannedPixels = []; updateUI(); @@ -577,7 +577,7 @@ function getText(key, params) { console.log(`šŸ” Analyzing area ${areaWidth}x${areaHeight} from (${x1},${y1}) to (${x2},${y2})`); - // Auto-Guard style tile calculation + // Tile calculation const startTileX = Math.floor(x1 / 1000); const startTileY = Math.floor(y1 / 1000); const endTileX = Math.floor(x2 / 1000); @@ -586,20 +586,20 @@ function getText(key, params) { state.totalPixels = areaWidth * areaHeight; let processedPixels = 0; - // Process each tile that intersects with our area (Auto-Guard method) + // Process each tile that intersects with our area for (let tileY = startTileY; tileY <= endTileY; tileY++) { for (let tileX = startTileX; tileX <= endTileX; tileX++) { try { console.log(`šŸ“„ Processing tile (${tileX}, ${tileY})...`); - // Download tile data using Auto-Guard's getTileImage method + // Download tile data const tileBlob = await getTileImage(tileX, tileY); if (!tileBlob) { console.warn(`āš ļø Could not download tile ${tileX},${tileY}, skipping...`); continue; } - // Process tile data like Auto-Guard + // Process tile data const tileImageData = await processTileBlob(tileBlob); if (!tileImageData) { console.warn(`āš ļø Could not process tile ${tileX},${tileY}, skipping...`); @@ -617,14 +617,14 @@ function getText(key, params) { const intersectEndX = Math.min(x2 + 1, tileEndX); const intersectEndY = Math.min(y2 + 1, tileEndY); - // Extract pixels from intersection area (Auto-Guard style) + // Extract pixels from intersection area for (let globalY = intersectStartY; globalY < intersectEndY; globalY++) { for (let globalX = intersectStartX; globalX < intersectEndX; globalX++) { // Convert to tile-local coordinates const localX = globalX - tileStartX; const localY = globalY - tileStartY; - // Normalize coordinates (Auto-Guard normalization) + // Normalize coordinates const normalizedX = (localX % 1000 + 1000) % 1000; const normalizedY = (localY % 1000 + 1000) % 1000; @@ -633,7 +633,7 @@ function getText(key, params) { normalizedX < tileImageData.width && normalizedY < tileImageData.height) { - // Extract RGBA values (Auto-Guard method) + // Extract RGBA values const pixelIndex = (normalizedY * tileImageData.width + normalizedX) * 4; const r = tileImageData.data[pixelIndex]; const g = tileImageData.data[pixelIndex + 1]; @@ -641,7 +641,7 @@ function getText(key, params) { const a = tileImageData.data[pixelIndex + 3]; if (a > 0) { // Skip transparent pixels - // Find closest color in WPlace palette (Auto-Guard method) + // Find closest color in WPlace palette const closestColor = findClosestColor(r, g, b); if (closestColor) { @@ -675,7 +675,7 @@ function getText(key, params) { } } - console.log(`āœ… Auto-Guard style scan complete: ${state.scannedPixels.length} pixels extracted`); + console.log(`āœ… Scan complete: ${state.scannedPixels.length} pixels extracted`); showAlert(`āœ… Extracted ${state.scannedPixels.length} pixels from area`, 'success'); } catch (error) { @@ -687,7 +687,7 @@ function getText(key, params) { } } - // Auto-Guard's getTileImage function for downloading tiles + // Download tile images async function getTileImage(tileX, tileY) { try { const tileUrl = `https://backend.wplace.live/files/s0/tiles/${tileX}/${tileY}.png`; @@ -702,7 +702,7 @@ function getText(key, params) { } } - // Process tile blob into ImageData like Auto-Guard + // Process tile blob into ImageData async function processTileBlob(blob) { try { const img = new Image(); @@ -726,14 +726,14 @@ function getText(key, params) { } } - // Auto-Guard style color matching using LAB color space for better accuracy + // Color matching using LAB color space for better accuracy function findClosestColor(r, g, b) { if (!CONFIG.COLOR_MAP) return null; let closestColor = null; let minDistance = Infinity; - // Convert input RGB to LAB color space (Auto-Guard method) + // Convert input RGB to LAB color space const inputLab = rgbToLab(r, g, b); for (const [index, colorInfo] of Object.entries(CONFIG.COLOR_MAP)) { @@ -744,7 +744,7 @@ function getText(key, params) { // Convert palette color to LAB const paletteLab = rgbToLab(cr, cg, cb); - // Calculate Delta-E distance (Auto-Guard method) + // Calculate Delta-E distance const deltaE = calculateDeltaE(inputLab, paletteLab); if (deltaE < minDistance) { @@ -756,7 +756,7 @@ function getText(key, params) { return closestColor; } - // Auto-Guard's RGB to LAB color space conversion + // RGB to LAB color space conversion function rgbToLab(r, g, b) { // Normalize RGB values r = r / 255; @@ -789,7 +789,7 @@ function getText(key, params) { return { l, a, b: bLab }; } - // Auto-Guard's Delta-E color difference calculation + // Delta-E color difference calculation function calculateDeltaE(lab1, lab2) { const deltaL = lab1.l - lab2.l; const deltaA = lab1.a - lab2.a; @@ -1483,7 +1483,7 @@ function getText(key, params) { // Control functions function startPixelCapture() { - console.log('šŸŽÆ Starting Auto-Guard style pixel capture mode...'); + console.log('šŸŽÆ Starting pixel capture mode...'); state.isCapturing = true; state.capturedPixels = []; @@ -1495,7 +1495,7 @@ function getText(key, params) { state.overlayManager.disable(); } - // Set up Auto-Guard style pixel paint monitoring with fetch interception + // Set up pixel paint monitoring with fetch interception const setupSuccess = setupPixelCapture(); if (setupSuccess) { diff --git a/Extension/scripts/Auto-Image.js b/Extension/scripts/Auto-Image.js index b76b88b..114b5c9 100644 --- a/Extension/scripts/Auto-Image.js +++ b/Extension/scripts/Auto-Image.js @@ -67,7 +67,7 @@ function getText(key, params) { MIN: 3, // Random range minimum MAX: 20, // Random range maximum }, - PAINTING_SPEED_ENABLED: true, // On by default + PAINTING_SPEED_ENABLED: false, // Off by default AUTO_CAPTCHA_ENABLED: true, // Turnstile generator enabled by default TOKEN_SOURCE: 'generator', // "generator", "manual", or "hybrid" - default to generator COOLDOWN_CHARGE_THRESHOLD: 1, // Default wait threshold @@ -754,6 +754,180 @@ function getText(key, params) { }, text); }; + // UNIFIED ACCOUNT MANAGER + class AccountManager { + constructor() { + this.accounts = []; + this.currentIndex = 0; + } + + // Load accounts from storage + async loadAccounts() { + try { + // Get tokens from localStorage + const tokens = JSON.parse(localStorage.getItem("accounts")) || []; + + // Get account info from storage + const infoAccountsResult = await new Promise((resolve) => { + if (typeof chrome !== 'undefined' && chrome.storage) { + chrome.storage.local.get('infoAccounts', (result) => { + resolve(result.infoAccounts || []); + }); + } else { + // Fallback for when chrome storage isn't available + resolve([]); + } + }); + + console.log(`šŸ“„ [ACCOUNT MANAGER] Loading ${tokens.length} accounts`); + + this.accounts = tokens.map((token, index) => { + const info = infoAccountsResult.find(acc => acc.token === token); + return { + token, + displayName: info?.displayName || info?.name || `Account ${index + 1}`, + ID: info?.ID || null, + Charges: info?.Charges || 0, + Max: info?.Max || 0, + Droplets: info?.Droplets || 0, + isCurrent: info?.isCurrent || false, + lastUsed: info?.lastUsed || null, + lastImported: info?.lastImported || null + }; + }); + + // Find current account index + this.currentIndex = this.accounts.findIndex(acc => acc.isCurrent); + if (this.currentIndex === -1) this.currentIndex = 0; + + console.log(`āœ… [ACCOUNT MANAGER] Loaded ${this.accounts.length} accounts, current index: ${this.currentIndex}`); + console.log(`šŸŽÆ [ACCOUNT MANAGER] Current account: ${this.getCurrentAccount()?.displayName || 'None'}`); + + // Auto-refresh account statuses after loading + if (this.accounts.length > 0) { + console.log('šŸ”„ [ACCOUNT MANAGER] Auto-refreshing account statuses...'); + // Use setTimeout to avoid blocking the load operation + setTimeout(async () => { + try { + await fetchAllAccountDetails(); + console.log('āœ… [ACCOUNT MANAGER] Auto-refresh completed'); + } catch (error) { + console.warn('āš ļø [ACCOUNT MANAGER] Auto-refresh failed:', error); + } + }, 100); + } + + return this.accounts; + } catch (error) { + console.error('āŒ [ACCOUNT MANAGER] Error loading accounts:', error); + this.accounts = []; + this.currentIndex = 0; + return []; + } + } + + // Simple linear switching + switchToNext() { + if (this.accounts.length === 0) return null; + + const previousIndex = this.currentIndex; + this.currentIndex = (this.currentIndex + 1) % this.accounts.length; + + console.log(`šŸ”„ [ACCOUNT MANAGER] Switching: ${previousIndex} → ${this.currentIndex}`); + console.log(`šŸŽÆ [ACCOUNT MANAGER] Target: ${this.accounts[this.currentIndex].displayName}`); + + // Update current flags + this.accounts.forEach((acc, idx) => { + acc.isCurrent = idx === this.currentIndex; + }); + + return this.accounts[this.currentIndex]; + } + + getCurrentAccount() { + return this.accounts[this.currentIndex] || null; + } + + getAccountByIndex(index) { + return this.accounts[index] || null; + } + + updateAccountData(tokenOrData, dataObject = null) { + let accountData; + let targetAccount; + + if (dataObject !== null) { + // Called with (token, dataObject) format + const token = tokenOrData; + accountData = dataObject; + targetAccount = this.accounts.find(acc => acc.token === token); + if (!targetAccount) { + console.warn(`āš ļø [ACCOUNT MANAGER] Account with token not found for update`); + return; + } + } else { + // Called with (dataObject) format - update current account + accountData = tokenOrData; + targetAccount = this.getCurrentAccount(); + } + + if (targetAccount && accountData) { + // Update all provided properties + if (accountData.charges !== undefined || accountData.Charges !== undefined) { + targetAccount.Charges = Math.floor(accountData.charges || accountData.Charges || 0); + } + if (accountData.max !== undefined || accountData.Max !== undefined) { + targetAccount.Max = Math.floor(accountData.max || accountData.Max || 0); + } + if (accountData.droplets !== undefined || accountData.Droplets !== undefined) { + targetAccount.Droplets = Math.floor(accountData.droplets || accountData.Droplets || 0); + } + if (accountData.id !== undefined || accountData.ID !== undefined) { + targetAccount.ID = accountData.id || accountData.ID || targetAccount.ID; + } + if (accountData.displayName !== undefined) { + targetAccount.displayName = accountData.displayName; + } + if (accountData.isCurrent !== undefined) { + // Clear isCurrent from all accounts first, then set for target + this.accounts.forEach(acc => acc.isCurrent = false); + targetAccount.isCurrent = accountData.isCurrent; + } + if (accountData.cooldown !== undefined) { + targetAccount.cooldown = accountData.cooldown; + } + + console.log(`šŸ“Š [ACCOUNT MANAGER] Updated ${targetAccount.displayName}: ⚔${targetAccount.Charges}/${targetAccount.Max} šŸ’§${targetAccount.Droplets}${targetAccount.isCurrent ? ' (CURRENT)' : ''}`); + } + } + + getAccountCount() { + return this.accounts.length; + } + + getAllAccounts() { + return [...this.accounts]; // Return copy to prevent direct mutation + } + + setCurrentIndex(index) { + if (index >= 0 && index < this.accounts.length) { + this.currentIndex = index; + console.log(`šŸ”„ [ACCOUNT MANAGER] Current index set to: ${index}`); + } else { + console.warn(`āš ļø [ACCOUNT MANAGER] Invalid index ${index}, keeping current: ${this.currentIndex}`); + } + } + + getNextAccount() { + if (this.accounts.length === 0) return null; + const nextIndex = (this.currentIndex + 1) % this.accounts.length; + return this.accounts[nextIndex]; + } + } + + // Create global account manager instance + const accountManager = new AccountManager(); + // GLOBAL STATE const state = { running: false, @@ -762,6 +936,7 @@ function getText(key, params) { totalPixels: 0, paintedPixels: 0, preFilteringDone: false, // Track if pre-filtering detection has been done this session + progressResetDone: false, // Track if progress reset has been done this save file session availableColors: [], activeColorPalette: [], // User-selected colors for conversion paintWhitePixels: true, // Default to ON @@ -820,11 +995,8 @@ function getText(key, params) { _lastSaveTime: 0, _saveInProgress: false, paintedMap: null, - accountIndex: 0, - originalAccountOrder: [], // Store the original order of accounts - currentActiveIndex: 0, // Track current position in the original order - // Account switching state - allAccountsInfo: [], + accountIndex: 0, // Keep for backward compatibility with existing logic + // Legacy state removed - now using accountManager instead isFetchingAllAccounts: false, }; @@ -913,7 +1085,7 @@ function getText(key, params) { }); const originalFetch = window.fetch; - // Setup fetch interception + // Setup fetch interceptions window.fetch = async function (...args) { const response = await originalFetch.apply(this, args); @@ -1215,6 +1387,12 @@ function getText(key, params) { console.log('šŸ” [DEBUG] Performing Object.assign...'); Object.assign(window.state, savedData.state); + + // Reset session-specific flags when loading a new save file + window.state.preFilteringDone = false; + window.state.progressResetDone = false; + console.log('šŸ”„ Reset session flags for new save file load'); + console.log('āœ… [DEBUG] Object.assign completed successfully'); // Restore available colors from old save files (backward compatibility) @@ -1919,9 +2097,6 @@ function getText(key, params) { async function createUI() { await detectLanguage(); - - // Load bot settings before creating UI so values are available - loadBotSettings(); const existingContainer = document.getElementById('wplace-image-bot-container'); const existingStats = document.getElementById('wplace-stats-container'); @@ -2164,6 +2339,9 @@ function getText(key, params) { ${Utils.t('charges')} +
@@ -2474,12 +2652,12 @@ function getText(key, params) {
- +
- + pixels
@@ -2519,6 +2697,7 @@ function getText(key, params) { ${Utils.t('enablePaintingSpeedLimit')} +
When disabled, bot uses all available charges immediately for maximum speed
@@ -3503,6 +3682,20 @@ function getText(key, params) { }); } + // Speed control toggle + const enableSpeedToggle = settingsContainer.querySelector('#enableSpeedToggle'); + if (enableSpeedToggle) { + enableSpeedToggle.addEventListener('change', (e) => { + CONFIG.PAINTING_SPEED_ENABLED = e.target.checked; + saveBotSettings(); + console.log(`⚔ Batch speed control: ${CONFIG.PAINTING_SPEED_ENABLED ? 'ON' : 'OFF'}`); + const statusText = CONFIG.PAINTING_SPEED_ENABLED + ? 'Batch speed control enabled - will use configured batch size' + : 'Batch speed control disabled - will use all available charges immediately'; + Utils.showAlert(statusText, 'success'); + }); + } + // (Advanced color listeners moved outside to work with resize dialog) // (Advanced color listeners moved outside to work with resize dialog) // Notifications listeners @@ -3591,14 +3784,6 @@ function getText(key, params) { console.log(`šŸ”„ Coordinate mode changed to: ${state.coordinateMode}`); Utils.showAlert(`Coordinate mode set to: ${state.coordinateMode}`, 'success'); }); - - // Update coordinate UI after setting up the event listener - Utils.updateCoordinateUI({ - mode: state.coordinateMode, - directionControls, - snakeControls, - blockControls, - }); } if (coordinateDirectionSelect) { @@ -3637,7 +3822,7 @@ function getText(key, params) { if (blockHeightInput) { blockHeightInput.value = state.blockHeight; - blockHeightInput.addEventListener('input', (e) => { + blockHeightInput.addEventListener('change', (e) => { const height = parseInt(e.target.value); if (height >= 1 && height <= 50) { state.blockHeight = height; @@ -3646,14 +3831,6 @@ function getText(key, params) { }); } - // Patch block size inputs with saved values after UI creation - if (blockWidthInput) { - blockWidthInput.value = state.blockWidth; - } - if (blockHeightInput) { - blockHeightInput.value = state.blockHeight; - } - if (compactBtn) { compactBtn.addEventListener('click', () => { container.classList.toggle('wplace-compact'); @@ -4150,14 +4327,15 @@ function getText(key, params) { // Calculate multi-account statistics let totalAllCharges = 0; let totalMaxCharges = 0; - if (state.allAccountsInfo.length > 0) { - totalAllCharges = state.allAccountsInfo.reduce((sum, acc) => sum + Math.floor(acc.Charges || 0), 0); - totalMaxCharges = state.allAccountsInfo.reduce((sum, acc) => sum + Math.floor(acc.Max || 0), 0); + const accounts = accountManager.getAllAccounts(); + if (accounts.length > 0) { + totalAllCharges = accounts.reduce((sum, acc) => sum + Math.floor(acc.Charges || 0), 0); + totalMaxCharges = accounts.reduce((sum, acc) => sum + Math.floor(acc.Max || 0), 0); } statsArea.innerHTML = ` ${imageStatsHTML} - ${state.allAccountsInfo.length > 0 ? ` + ${accounts.length > 0 ? `
Total All Accounts Charges @@ -5481,6 +5659,12 @@ function getText(key, params) { state.totalPixels = totalValidPixels; state.paintedPixels = 0; state.imageLoaded = true; + + // Reset session-specific flags when a new image is loaded + state.preFilteringDone = false; + state.progressResetDone = false; + console.log('šŸ”„ Reset session flags for new image load'); + // Keep existing lastPosition to continue from where we left off // state.lastPosition = { x: 0, y: 0 }; // REMOVED: Don't reset position @@ -5887,14 +6071,23 @@ function getText(key, params) { await ensureToken(); if (!getTurnstileToken()) return; - // Only reset painted pixels on first start of session (when pre-filtering hasn't been done) - if (!state.preFilteringDone) { + // Only reset progress once per save file load session + if (!state.progressResetDone) { const savedPaintedPixels = state.paintedPixels; // Store original value state.paintedPixels = 0; - console.log(`šŸ”„ First start this session - reset progress counter for accurate tracking (was: ${savedPaintedPixels})`); + state.progressResetDone = true; + console.log(`šŸ”„ Reset pixel progress for new save file session (was: ${savedPaintedPixels})`); await updateStats(); // Update UI to show 0 progress + } + + // Only reset painted pixels on first start of session (when pre-filtering hasn't been done) + if (!state.preFilteringDone) { + + // Perform progressive pixel detection from top-left to bottom-right + console.log('šŸ” Starting progressive pixel detection from top-left to bottom-right...'); + await performProgressivePixelDetection(); } else { - console.log('šŸ”„ Continuing session - pre-filtering already done, keeping current progress'); + console.log('šŸ”„ Continuing session - pre-filtering already done, but progress reset'); } state.running = true; @@ -6008,8 +6201,66 @@ function getText(key, params) { // Add scroll-to-adjust for cooldown slider Utils.createScrollToAdjust(cooldownSlider, updateCooldown, 1, state.maxCharges, 1); + + // Skip cooldown button event handler + const skipCooldownBtn = document.getElementById('skipCooldownBtn'); + if (skipCooldownBtn) { + skipCooldownBtn.addEventListener('click', () => { + if (state.preciseCurrentCharges < state.cooldownChargeThreshold) { + console.log(`[Auto-Image] Skip cooldown requested - resetting to account index 0`); + + // Reset to account index 0 (start new cycle) + state.currentActiveIndex = 0; + console.log(`šŸ”„ Reset currentActiveIndex to 0 for new cycle`); + + // Reset charges to threshold to bypass cooldown + state.preciseCurrentCharges = state.cooldownChargeThreshold; + console.log(`[Auto-Image] Cooldown skipped! Charges set to threshold: ${state.cooldownChargeThreshold}`); + + // Switch to first account if we have multiple accounts + if (accountManager.getAccountCount() > 1) { + const firstAccountInfo = accountManager.getAccountByIndex(0); + if (firstAccountInfo && firstAccountInfo.token) { + console.log(`šŸ”„ Switching to first account: index 0 (${firstAccountInfo.displayName})`); + // Get accounts array for the switch + const accounts = JSON.parse(localStorage.getItem("accounts")) || []; + if (accounts.length > 0) { + switchToSpecificAccount(firstAccountInfo.token, firstAccountInfo.displayName).then(() => { + console.log(`āœ… Successfully switched to account index 0 after cooldown skip`); + // Update UI immediately + updateChargesThresholdUI(0); + // Trigger immediate check for painting + if (state.isEnabled && !state.stopFlag) { + setTimeout(() => { + checkAndPaint(); + }, 100); + } + }).catch(err => { + console.error(`āŒ Failed to switch to account index 0:`, err); + }); + } else { + console.warn(`āš ļø No accounts available for switching`); + } + } else { + console.warn(`āš ļø First account info not found or missing token`); + } + } else { + console.log(`šŸ“ Single account mode - no switching needed`); + // Update UI immediately + updateChargesThresholdUI(0); + // Trigger immediate check for painting + if (state.isEnabled && !state.stopFlag) { + setTimeout(() => { + checkAndPaint(); + }, 100); + } + } + } + }); + } } + loadBotSettings(); // Ensure notification poller reflects current settings NotificationManager.syncFromState(); } @@ -6041,6 +6292,221 @@ function getText(key, params) { }, true ); + + // Update skip cooldown button state + const skipCooldownBtn = document.getElementById('skipCooldownBtn'); + if (skipCooldownBtn) { + const isInCooldown = state.preciseCurrentCharges < threshold && remainingMs > 0; + skipCooldownBtn.disabled = !isInCooldown; + skipCooldownBtn.title = isInCooldown + ? `Skip cooldown (${timeText} remaining)` + : 'Skip cooldown (only available during cooldown)'; + } + } + + // Fast tile-based pixel detection from top-left to bottom-right + async function performProgressivePixelDetection() { + if (!state.imageLoaded || !state.imageData) { + console.log('āš ļø No image loaded, skipping pixel detection'); + return; + } + + const startTime = performance.now(); + const { width, height } = state.imageData; + const startX = state.startPosition.x; + const startY = state.startPosition.y; + const regionX = state.region.x; + const regionY = state.region.y; + + let detectedPixels = 0; + let totalChecked = 0; + + console.log(`šŸš€ Fast scanning ${width}x${height} image from top-left (0,0) to bottom-right (${width-1},${height-1})...`); + + updateUI('pixelDetection', 'info', { message: 'Fast detecting already painted pixels...' }); + + // Calculate affected tiles + const worldX1 = startX; + const worldY1 = startY; + const worldX2 = startX + width - 1; + const worldY2 = startY + height - 1; + + const startTileX = Math.floor(worldX1 / 1000); + const startTileY = Math.floor(worldY1 / 1000); + const endTileX = Math.floor(worldX2 / 1000); + const endTileY = Math.floor(worldY2 / 1000); + + console.log(`šŸ“„ Processing tiles from (${startTileX},${startTileY}) to (${endTileX},${endTileY})`); + + // Cache for downloaded tile data + const tileDataCache = new Map(); + + // Download and cache all required tiles in parallel + const tilePromises = []; + for (let tileY = startTileY; tileY <= endTileY; tileY++) { + for (let tileX = startTileX; tileX <= endTileX; tileX++) { + const absoluteTileX = regionX + tileX; + const absoluteTileY = regionY + tileY; + const tileKey = `${absoluteTileX},${absoluteTileY}`; + + tilePromises.push( + downloadTileImageData(absoluteTileX, absoluteTileY).then(imageData => { + if (imageData) { + tileDataCache.set(tileKey, imageData); + } + }).catch(e => { + console.warn(`āš ļø Failed to download tile ${absoluteTileX},${absoluteTileY}:`, e.message); + }) + ); + } + } + + // Wait for all tiles to download + await Promise.all(tilePromises); + console.log(`šŸ“¦ Downloaded ${tileDataCache.size} tiles for fast pixel checking`); + + // Fast pixel detection using cached tile data + for (let y = 0; y < height; y++) { + for (let x = 0; x < width; x++) { + totalChecked++; + + // Check if pixel is eligible for painting + const targetPixelInfo = checkPixelEligibility(x, y); + if (!targetPixelInfo.eligible) { + continue; // Skip non-eligible pixels + } + + // Calculate absolute world coordinates + const absX = startX + x; + const absY = startY + y; + const adderX = Math.floor(absX / 1000); + const adderY = Math.floor(absY / 1000); + const pixelX = absX % 1000; + const pixelY = absY % 1000; + const absoluteTileX = regionX + adderX; + const absoluteTileY = regionY + adderY; + + // Check if already marked as painted in local map + if (Utils.isPixelPainted(x, y, absoluteTileX, absoluteTileY)) { + detectedPixels++; + continue; + } + + // Fast pixel color check using cached tile data + const tileKey = `${absoluteTileX},${absoluteTileY}`; + const tileImageData = tileDataCache.get(tileKey); + + if (tileImageData) { + try { + // Direct array access - much faster than canvas operations + const tileWidth = tileImageData.width; + const tileHeight = tileImageData.height; + const data = tileImageData.data; + + // Ensure pixel coordinates are within tile bounds + if (pixelX >= 0 && pixelX < tileWidth && pixelY >= 0 && pixelY < tileHeight) { + const pixelIndex = (pixelY * tileWidth + pixelX) * 4; + const r = data[pixelIndex]; + const g = data[pixelIndex + 1]; + const b = data[pixelIndex + 2]; + const a = data[pixelIndex + 3]; + + // Check alpha threshold + const alphaThresh = state.customTransparencyThreshold || CONFIG.TRANSPARENCY_THRESHOLD; + if (a >= alphaThresh) { + const existingMappedColor = Utils.resolveColor( + [r, g, b], + state.availableColors, + !state.paintUnavailablePixels + ); + const isAlreadyPainted = existingMappedColor.id === targetPixelInfo.mappedColorId; + + if (isAlreadyPainted) { + // Check if pixel is already marked as painted to avoid double counting + if (!Utils.isPixelPainted(x, y, absoluteTileX, absoluteTileY)) { + // Mark as painted in the map but DO NOT increment progress counter + // Progress counter should only reflect actual painting sequence position + Utils.markPixelPainted(x, y, absoluteTileX, absoluteTileY); + detectedPixels++; + } else { + // Pixel already tracked, just count it for detection stats + detectedPixels++; + } + } + } + } + } catch (e) { + // Skip pixels we can't check + console.warn(`āš ļø Could not check pixel (${x}, ${y}):`, e.message); + } + } + + // Update progress periodically + if (totalChecked % 2500 === 0) { + const progress = Math.round((totalChecked / (width * height)) * 100); + updateUI('pixelDetection', 'info', { + message: `Fast detecting... ${progress}% (Found: ${detectedPixels})` + }); + // Yield control briefly to prevent UI blocking + await new Promise(resolve => setTimeout(resolve, 0)); + } + } + } + + const processingTime = Math.round(performance.now() - startTime); + console.log(`šŸ Fast pixel detection complete in ${processingTime}ms:`); + console.log(` - Total pixels checked: ${totalChecked}`); + console.log(` - Already painted pixels found: ${detectedPixels}`); + console.log(` - Updated progress: ${state.paintedPixels}/${state.totalPixels}`); + console.log(` - Performance: ${Math.round(totalChecked / (processingTime / 1000))} pixels/second`); + + // Update progress display + await updateStats(); + updateUI('pixelDetectionComplete', 'success', { + message: `Found ${detectedPixels} already painted pixels in ${processingTime}ms` + }); + } + + // Fast tile download and ImageData extraction (similar to Art-Extractor approach) + async function downloadTileImageData(tileX, tileY) { + try { + const tileUrl = `https://backend.wplace.live/files/s0/tiles/${tileX}/${tileY}.png`; + const response = await fetch(tileUrl); + if (!response.ok) { + throw new Error(`HTTP ${response.status}`); + } + + const blob = await response.blob(); + return await processTileBlob(blob); + } catch (error) { + console.warn(`Failed to download tile ${tileX},${tileY}:`, error.message); + return null; + } + } + + // Process tile blob into ImageData (adapted from Art-Extractor) + async function processTileBlob(blob) { + try { + const img = new Image(); + const canvas = document.createElement('canvas'); + const ctx = canvas.getContext('2d'); + + return new Promise((resolve, reject) => { + img.onload = () => { + canvas.width = img.width; + canvas.height = img.height; + ctx.imageSmoothingEnabled = false; + ctx.drawImage(img, 0, 0); + const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); + resolve(imageData); + }; + img.onerror = () => reject(new Error('Failed to load image')); + img.src = URL.createObjectURL(blob); + }); + } catch (error) { + console.error('Error processing tile blob:', error); + return null; + } } function generateCoordinates(width, height, mode, direction, snake, blockWidth, blockHeight, startFromX = 0, startFromY = 0) { @@ -6226,8 +6692,12 @@ function getText(key, params) { pixelBatch.regionY ); if (success) { + // Only increment progress for actually painted pixels to prevent multiplication + const actuallyPaintedCount = pixelBatch.pixels.length; + state.paintedPixels += actuallyPaintedCount; + console.log(`šŸ“Š Added ${actuallyPaintedCount} painted pixels to progress (total: ${state.paintedPixels})`); + pixelBatch.pixels.forEach((p) => { - state.paintedPixels++; Utils.markPixelPainted(p.x, p.y, pixelBatch.regionX, pixelBatch.regionY); }); @@ -6319,16 +6789,17 @@ function getText(key, params) { } } else { // Debug current state - console.log(`šŸ“Š Account Status - Current ID: ${state.originalAccountOrder[state.currentActiveIndex]?.orderId}, Total IDs: ${state.originalAccountOrder.length}`); + const totalAccounts = accountManager.getAccountCount(); + console.log(`šŸ“Š Account Status - Current index: ${accountManager.currentIndex}, Total accounts: ${totalAccounts}`); - // Check if we're at the last ID in the sequence - const isLastAccount = state.currentActiveIndex >= state.originalAccountOrder.length - 1; - console.log(`šŸ” Is last ID? ${isLastAccount} (ID ${state.originalAccountOrder[state.currentActiveIndex]?.orderId} of ${state.originalAccountOrder.length})`); + // Check if we're at the last account in the sequence + const isLastAccount = accountManager.currentIndex >= totalAccounts - 1; + console.log(`šŸ” Is last account? ${isLastAccount} (index ${accountManager.currentIndex} of ${totalAccounts})`); - if (!isLastAccount) { - // Switch to next ID immediately (no cooldown) - const nextId = state.originalAccountOrder[state.currentActiveIndex + 1]?.orderId; - console.log(`šŸ”„ Switching to next ID ${nextId} (${state.currentActiveIndex + 2}/${state.originalAccountOrder.length})`); + if (!isLastAccount && totalAccounts > 1) { + // Switch to next account immediately (no cooldown) - only if we have multiple accounts + const nextAccount = accountManager.getNextAccount(); + console.log(`šŸ”„ Switching to next account: ${nextAccount?.displayName} (${accountManager.currentIndex + 2}/${totalAccounts})`); const switchResult = await switchToNextAccount(accounts); if (!switchResult) { console.log('āŒ Account switch failed, stopping'); @@ -6337,37 +6808,38 @@ function getText(key, params) { } // Continue painting with new account immediately continue; + } else if (totalAccounts === 1) { + // Only one account available - use cooldown and continue with same account + console.log('ā„¹ļø Only one account available, entering cooldown period'); + const cooldownResult = await executeCooldownPeriod(); + if (cooldownResult === 'stopped') break; + console.log('āœ… Cooldown complete, continuing with same account'); + continue; } else { - // Last account reached - use cooldown then switch to ID 1 (first account) + // Last account reached - use cooldown then switch to first account console.log('ā±ļø Last account reached, entering cooldown period'); - console.log(`šŸ“Š Current ID: ${state.originalAccountOrder[state.currentActiveIndex]?.orderId}, Last ID: ${state.originalAccountOrder[state.originalAccountOrder.length - 1]?.orderId}`); + console.log(`šŸ“Š Current account: index ${accountManager.currentIndex}, Last account: index ${totalAccounts - 1}`); const cooldownResult = await executeCooldownPeriod(); if (cooldownResult === 'stopped') break; - // After cooldown, switch to ID 1 (first account in order) - console.log('šŸ” Cooldown complete, switching to ID 1 (first account)'); - console.log(`šŸ”„ Before switch - Current ID: ${state.originalAccountOrder[state.currentActiveIndex]?.orderId}, Target: ID 1`); + // After cooldown, switch to first account + console.log('šŸ” Cooldown complete, switching to first account'); + console.log(`šŸ”„ Before switch - Current index: ${accountManager.currentIndex}, Target: index 0`); - const firstAccountInfo = state.originalAccountOrder[0]; // ID 1 is always at index 0 + const firstAccountInfo = accountManager.getAccountByIndex(0); const firstAccountToken = firstAccountInfo?.token; if (!firstAccountToken) { - console.log('āŒ First account (ID 1) token not found, stopping'); + console.log('āŒ First account token not found, stopping'); state.stopFlag = true; break; } - // Reset to first position (ID 1) - state.currentActiveIndex = 0; + // Reset to first position + accountManager.setCurrentIndex(0); - // Update accountIndex to match the position in accounts array - const accountsIndex = accounts.findIndex(token => token === firstAccountToken); - if (accountsIndex !== -1) { - state.accountIndex = accountsIndex; - } - - const switchResult = await switchToSpecificAccount(firstAccountToken, 0); + const switchResult = await switchToSpecificAccount(firstAccountToken, firstAccountInfo.displayName); if (!switchResult) { console.log('āŒ Switch to ID 1 failed, stopping'); state.stopFlag = true; @@ -6442,6 +6914,36 @@ function getText(key, params) { // Paint pixels until we run out of charges or complete the image try { + // First generate coordinates without filtering to count skipped ones + const allCoords = generateCoordinates( + width, + height, + state.coordinateMode, + state.coordinateDirection, + state.coordinateSnake, + state.blockWidth, + state.blockHeight, + 0, // Don't filter here + 0 // Don't filter here + ); + + // Find how many coordinates are skipped and set progress accordingly + let skippedCoordinates = 0; + if (state.lastPosition.x > 0 || state.lastPosition.y > 0) { + const startingCoordIndex = allCoords.findIndex(([x, y]) => x === state.lastPosition.x && y === state.lastPosition.y); + skippedCoordinates = startingCoordIndex >= 0 ? startingCoordIndex : 0; + + if (skippedCoordinates > 0) { + // Set progress to exactly match skipped coordinates + state.paintedPixels = skippedCoordinates; + console.log(`šŸ“ˆ Progress set to match skipped coordinates: ${skippedCoordinates} pixels`); + console.log(`šŸ”„ Resuming painting from position (${state.lastPosition.x}, ${state.lastPosition.y})`); + console.log(`šŸ“Š Current progress: ${state.paintedPixels} pixels painted`); + await updateStats(); // Update UI to show correct progress + } + } + + // Now generate the actual filtered coordinates for painting const coords = generateCoordinates( width, height, @@ -6458,12 +6960,6 @@ function getText(key, params) { let eligibleCoords = []; let alreadyPaintedCount = 0; - // Log resume information if applicable - if (state.lastPosition.x > 0 || state.lastPosition.y > 0) { - console.log(`šŸ”„ Resuming painting from position (${state.lastPosition.x}, ${state.lastPosition.y})`); - console.log(`šŸ“Š Current progress: ${state.paintedPixels} pixels painted`); - } - if (!state.preFilteringDone) { console.log('šŸ” Pre-filtering already painted pixels (one-time detection for this session)...'); @@ -6503,8 +6999,8 @@ function getText(key, params) { const isMatch = mappedCanvasColor.id === targetPixelInfo.mappedColorId; if (isMatch) { alreadyPaintedCount++; - // Add detected already-painted pixels to progress (after reset to 0) - state.paintedPixels++; + // Mark as painted in map but DO NOT increment progress counter + // Progress should only reflect actual painting sequence position Utils.markPixelPainted(x, y, regionX + adderX, regionY + adderY); continue; // Skip already painted pixels } @@ -6522,10 +7018,10 @@ function getText(key, params) { // Log pre-filtering results if (alreadyPaintedCount > 0) { - console.log(`āœ“ Pre-filter complete: ${alreadyPaintedCount} already painted pixels detected and added to progress`); + console.log(`āœ“ Pre-filter complete: ${alreadyPaintedCount} already painted pixels detected (not added to progress counter)`); console.log('ā„¹ļø This detection will not happen again until a new image/save is loaded'); - // Update UI to reflect the new progress immediately - await updateStats(); + console.log('šŸ“Š Progress counter only reflects actual painting sequence position'); + // No need to update stats since progress wasn't changed } skippedPixels.alreadyPainted = alreadyPaintedCount; } else { @@ -6561,7 +7057,9 @@ function getText(key, params) { await flushPixelBatch(pixelBatch); } state.lastPosition = { x, y }; - // Removed 'Paused at' message from main panel to reduce UI clutter + // Show paused coordinates in UI with proper translation template + updateUI('paintingPaused', 'warning', { x: x, y: y }); + console.log(`ļæ½ Painting paused at coordinates (${x}, ${y})`); return 'stopped'; } @@ -6592,13 +7090,13 @@ function getText(key, params) { let pixelX = absX % 1000; let pixelY = absY % 1000; - // CRITICAL FIX: Check if pixel is already painted (both locally and on canvas) + // CRITICAL FIX: Always check if pixel is already painted (both locally and on canvas) if (Utils.isPixelPainted(x, y, regionX + adderX, regionY + adderY)) { console.log(`ā­ļø Skipping already painted pixel at (${x}, ${y}) - marked in local map`); continue; // Skip already painted pixels } - // REAL-TIME CANVAS CHECK: Like Acc-Switch, verify against actual canvas state + // REAL-TIME CANVAS CHECK: Verify against actual canvas state to prevent overpainting try { const existingColorRGBA = await overlayManager.getTilePixelColor( regionX + adderX, @@ -6614,14 +7112,14 @@ function getText(key, params) { state.availableColors, !state.paintUnavailablePixels ); - const isAlreadyPainted = existingMappedColor.id === targetPixelInfo.mappedColorId; + const isAlreadyCorrect = existingMappedColor.id === targetPixelInfo.mappedColorId; - if (isAlreadyPainted) { - console.log(`ā­ļø Skipping already painted pixel at (${x}, ${y}) - verified on canvas (existing: ${existingMappedColor.id}, target: ${targetPixelInfo.mappedColorId})`); - // Mark it as painted in local map to avoid future checks + if (isAlreadyCorrect) { + console.log(`āœ… Pixel at (${x}, ${y}) already has correct color (${existingMappedColor.id}) - marking as painted`); + // Mark it as painted in local map but DO NOT increment progress counter + // Progress should only reflect actual painting sequence position Utils.markPixelPainted(x, y, regionX + adderX, regionY + adderY); - state.paintedPixels++; - continue; // Skip already painted pixels + continue; // Skip painting this pixel } } } catch (e) { @@ -6879,6 +7377,13 @@ function getText(key, params) { function calculateBatchSize() { let targetBatchSize; + // If speed control is disabled, use all available charges + if (!CONFIG.PAINTING_SPEED_ENABLED) { + targetBatchSize = state.displayCharges; + console.log(`šŸš€ Speed control disabled: using all ${targetBatchSize} available charges`); + return Math.max(1, targetBatchSize); + } + if (state.batchMode === 'random') { // Generate random batch size within the specified range const min = Math.max(1, state.randomBatchMin); @@ -7145,6 +7650,17 @@ function getText(key, params) { const coordinateSnakeToggle = document.getElementById('coordinateSnakeToggle'); if (coordinateSnakeToggle) coordinateSnakeToggle.checked = state.coordinateSnake; + const settingsContainer = document.getElementById('wplace-settings-container'); + const directionControls = settingsContainer.querySelector('#directionControls'); + const snakeControls = settingsContainer.querySelector('#snakeControls'); + const blockControls = settingsContainer.querySelector('#blockControls'); + Utils.updateCoordinateUI({ + mode: state.coordinateMode, + directionControls, + snakeControls, + blockControls, + }); + const paintUnavailablePixelsToggle = document.getElementById('paintUnavailablePixelsToggle'); if (paintUnavailablePixelsToggle) { paintUnavailablePixelsToggle.checked = state.paintUnavailablePixels; @@ -7583,15 +8099,24 @@ function getText(key, params) { async function getAccounts() { return new Promise((resolve, reject) => { console.log("Requesting accounts from extension..."); + + // Set timeout for extension response + const timeout = setTimeout(() => { + window.removeEventListener("message", handler); + reject(new Error("Extension response timeout")); + }, 5000); // 5 second timeout + // Ask extension for accounts window.postMessage({ source: "my-userscript", type: "getAccounts" }, "*"); + function handler(event) { if (event.source !== window) return; if (event.data.source !== "extension") return; if (event.data.type === "accountsData") { + clearTimeout(timeout); window.removeEventListener("message", handler); // Save to localStorage try { @@ -7606,6 +8131,7 @@ function getText(key, params) { window.addEventListener("message", handler); }); } + async function fetchAccount() { const { ID, Charges, Max, Droplets } = await WPlaceService.fetchCheck(); console.log("User's ID :", ID); @@ -7629,218 +8155,130 @@ function getText(key, params) { const accountsListArea = document.getElementById('accountsListArea'); if (accountsListArea) { - accountsListArea.innerHTML = `
Fetching account details...
`; + accountsListArea.innerHTML = `
Loading accounts...
`; } - let originalToken = null; - let originalAccountData = null; - try { - await getAccounts(); - const accountsTokens = JSON.parse(localStorage.getItem("accounts")) || []; - if (accountsTokens.length === 0) { - if (accountsListArea) accountsListArea.innerHTML = `
No accounts found.
`; + // First, get accounts from the extension + console.log(`šŸ”„ [FETCH] Requesting accounts from extension...`); + try { + await getAccounts(); + console.log(`āœ… [FETCH] Successfully retrieved accounts from extension`); + } catch (error) { + console.warn(`āš ļø [FETCH] Failed to get accounts from extension:`, error); + // Continue anyway in case we have cached accounts + } + + // Load accounts using the new AccountManager + await accountManager.loadAccounts(); + + console.log(`āœ… [FETCH] Loaded ${accountManager.getAccountCount()} accounts from storage`); + + // Debug: Check if we actually have accounts + if (accountManager.getAccountCount() === 0) { + console.warn(`āš ļø [FETCH] No accounts found in storage. Check localStorage 'accounts' and chrome.storage 'infoAccounts'`); + + // Check localStorage for accounts + const localStorageAccounts = JSON.parse(localStorage.getItem("accounts")) || []; + console.log(`šŸ“‹ [DEBUG] localStorage accounts:`, localStorageAccounts); + + // Render empty state and return early + renderAccountsList(); return; } - - // Store original account info to switch back later - originalAccountData = await WPlaceService.getCharges(); - originalToken = accountsTokens.find(token => { - try { - const payload = JSON.parse(atob(token.split('.')[1])); - return payload.userId === originalAccountData.id; - } catch (e) { - return false; - } - }); - - state.allAccountsInfo = []; - renderAccountsList(); - - // Temporarily disable the strict guard for controlled refresh operation - const originalRunningState = state.running; - state.running = true; // Temporarily enable to allow controlled switching - - console.log('šŸ”“ Temporarily enabling controlled account switching for refresh'); - - for (let i = 0; i < accountsTokens.length; i++) { - const token = accountsTokens[i]; - - console.log(`šŸ“‹ Fetching details for account ${i + 1}/${accountsTokens.length}`); - - // Controlled account switch for data fetching - const swapResult = await swapAccountTrigger(token); - if (!swapResult) { - console.warn(`āš ļø Failed to trigger swap for account ${i + 1}`); - continue; - } - - // Wait for account switch to complete - let retries = 0; - let accountData = null; - while (retries < 8 && !accountData) { - // await Utils.sleep(1000); - try { - const fetchedInfo = await WPlaceService.fetchCheck(); - const chargesInfo = await WPlaceService.getCharges(); - if (fetchedInfo.ID) { - accountData = { - ...fetchedInfo, - Charges: Math.floor(chargesInfo.charges), - Max: Math.floor(chargesInfo.max), - Droplets: Math.floor(chargesInfo.droplets) - }; - } - } catch (e) { - retries++; - console.warn(`āš ļø Retry ${retries}/8 for account ${i + 1}`); - } - } - - if (accountData) { - const actualName = accountData.Username || accountData.name || `User${accountData.ID}`; - const isCurrentAccount = accountData.ID === originalAccountData.id; - - state.allAccountsInfo.push({ - ...accountData, - token, - displayName: actualName, - isCurrent: isCurrentAccount - }); - - console.log(`āœ… Fetched details for: ${actualName} (${accountData.Charges}/${accountData.Max} charges)`); - } else { - // Fallback for failed accounts - const fallbackName = `Account ${i + 1}`; - state.allAccountsInfo.push({ - token, - ID: `...${token.slice(-4)}`, - displayName: fallbackName, - isCurrent: false, - error: 'Failed to fetch details' - }); - console.warn(`āŒ Failed to fetch details for account ${i + 1}`); - } - - renderAccountsList(); - } - - // Restore original state and switch back to original account - state.running = originalRunningState; - console.log('šŸ”’ Restored original running state and switching back to original account'); - - } catch (error) { - console.error("Error fetching all account details:", error); - if (accountsListArea) accountsListArea.innerHTML = `
Error loading accounts.
`; - } finally { - // Switch back to original account - if (originalToken) { - console.log('šŸ”„ Switching back to original account...'); - const originalRunningState = state.running; - state.running = true; // Temporarily enable for switch back - - await swapAccountTrigger(originalToken); - await Utils.sleep(2000); - - // Verify we're back on the original account - try { - const verifyData = await WPlaceService.getCharges(); - if (verifyData.id === originalAccountData.id) { - console.log('āœ… Successfully switched back to original account'); - } - } catch (e) { - console.warn('āš ļø Could not verify switch back to original account'); - } - - state.running = originalRunningState; // Restore state - } - - // Update current account info in the list - if (originalAccountData) { - const currentAccountInList = state.allAccountsInfo.find(acc => acc.ID === originalAccountData.id); - if (currentAccountInList) { - currentAccountInList.isCurrent = true; - currentAccountInList.Charges = Math.floor(originalAccountData.charges); - currentAccountInList.Max = Math.floor(originalAccountData.max); - currentAccountInList.Droplets = Math.floor(originalAccountData.droplets); - } - } - - // Keep original account order - do NOT rearrange the array - // Instead, assign IDs based on current account being ID 1 - const currentAccountIndex = state.allAccountsInfo.findIndex(acc => acc.isCurrent); - if (currentAccountIndex !== -1) { - console.log(`šŸŽÆ Current account found at array position ${currentAccountIndex}`); + // Now fetch fresh data for each account + const accounts = accountManager.getAllAccounts(); + if (accounts.length > 0) { + console.log(`šŸ”„ [FETCH] Fetching fresh data for ${accounts.length} accounts...`); - // Assign IDs: current account gets ID 1, others get sequential IDs - state.allAccountsInfo.forEach((acc, index) => { - if (acc.isCurrent) { - acc.orderId = 1; // Current account always gets ID 1 - } else { - // Other accounts get IDs based on their position relative to current - const relativePosition = index > currentAccountIndex ? - (index - currentAccountIndex + 1) : - (state.allAccountsInfo.length - currentAccountIndex + index + 1); - acc.orderId = relativePosition; + // Remember the current account so we can switch back + const originalCurrentAccount = accounts.find(acc => acc.isCurrent); + + for (let i = 0; i < accounts.length; i++) { + const account = accounts[i]; + console.log(`šŸ“Š [FETCH] Fetching data for account ${i + 1}: ${account.displayName}`); + + try { + // Switch to this account temporarily to fetch its data + console.log(`šŸ”„ [FETCH] Switching to ${account.displayName} to fetch fresh data...`); + await switchToSpecificAccount(account.token, account.displayName); + await Utils.sleep(500); // Small delay to ensure switch takes effect + + // Fetch fresh account details + const accountData = await WPlaceService.getCharges(); + const accountInfo = await WPlaceService.fetchCheck(); + + // Update account with fresh data + accountManager.updateAccountData(account.token, { + ID: accountData.id || accountInfo.ID, + Charges: Math.floor(accountData.charges || 0), + Max: Math.floor(accountData.max || 0), + Droplets: Math.floor(accountData.droplets || 0), + displayName: accountInfo.Username || accountInfo.name || account.displayName + }); + + console.log(`āœ… [FETCH] Updated ${account.displayName}: ⚔${Math.floor(accountData.charges)}/${Math.floor(accountData.max)} šŸ’§${Math.floor(accountData.droplets)}`); + + } catch (error) { + console.warn(`āš ļø [FETCH] Failed to fetch data for ${account.displayName}:`, error); } - }); + } - // Sort the display order by orderId for UI, but keep original array intact - const sortedForDisplay = [...state.allAccountsInfo].sort((a, b) => a.orderId - b.orderId); + // Switch back to the original current account if there was one + if (originalCurrentAccount) { + console.log(`šŸ”™ [FETCH] Switching back to original current account: ${originalCurrentAccount.displayName}`); + try { + await switchToSpecificAccount(originalCurrentAccount.token, originalCurrentAccount.displayName); + await Utils.sleep(300); + + // Mark it as current again + accountManager.updateAccountData(originalCurrentAccount.token, { + isCurrent: true + }); + } catch (error) { + console.warn(`āš ļø [FETCH] Failed to switch back to original account:`, error); + } + } - // Create the switching order array (ID 1 first, then sequential) - state.originalAccountOrder = sortedForDisplay; - - // Find the current account's position in the switching order (should be 0 since it's ID 1) - state.currentActiveIndex = 0; // Current account (ID 1) is always first in switching order - state.accountIndex = currentAccountIndex; // Keep track of original array position - - console.log(`šŸ“‹ Account IDs assigned:`); - state.allAccountsInfo.forEach((acc) => { - console.log(` Array pos ${state.allAccountsInfo.indexOf(acc)}: ID ${acc.orderId} - ${acc.displayName}${acc.isCurrent ? ' (CURRENT)' : ''}`); - }); - - console.log(`šŸ“‹ Switching order (by ID):`); - state.originalAccountOrder.forEach((acc, idx) => { - console.log(` Switch pos ${idx}: ID ${acc.orderId} - ${acc.displayName}${acc.isCurrent ? ' (CURRENT)' : ''}`); - }); - } else { - console.warn('āš ļø No current account found, using original order'); - state.allAccountsInfo.forEach((acc, index) => { - acc.orderId = index + 1; - }); - state.originalAccountOrder = [...state.allAccountsInfo]; - state.currentActiveIndex = 0; - state.accountIndex = 0; + console.log(`šŸŽÆ [FETCH] Completed fetching fresh data for all accounts`); } - - await updateStats(); + + // Render the accounts list with fresh data renderAccountsList(); - + + } catch (error) { + console.error('āŒ [FETCH] Error fetching account details:', error); + if (accountsListArea) { + accountsListArea.innerHTML = `
Error loading accounts.
`; + } + } finally { state.isFetchingAllAccounts = false; if (refreshBtn) { refreshBtn.innerHTML = ''; refreshBtn.disabled = false; } - - console.log('āœ… Account list refresh completed with full details'); } } // Function to update current account charges in the account list async function updateCurrentAccountInList() { - if (state.allAccountsInfo.length === 0) return; + if (accountManager.getAccountCount() === 0) return; // Find current account in the list and update its charges - const currentAccountInList = state.allAccountsInfo.find(acc => acc.isCurrent); - if (currentAccountInList) { + const currentAccount = accountManager.getCurrentAccount(); + if (currentAccount) { const { charges, cooldown } = await WPlaceService.getCharges(); state.displayCharges = Math.floor(charges); state.preciseCurrentCharges = charges; await updateStats(); - currentAccountInList.Charges = Math.floor(state.displayCharges || state.preciseCurrentCharges || 0); - currentAccountInList.Max = state.maxCharges; + + // Update the current account data in AccountManager + accountManager.updateAccountData(currentAccount.token, { + Charges: Math.floor(state.displayCharges || state.preciseCurrentCharges || 0), + Max: state.maxCharges + }); + // Re-render the account list to show updated charges renderAccountsList(); } @@ -7848,36 +8286,52 @@ function getText(key, params) { // Function to update current account spotlight when switching during painting async function updateCurrentAccountSpotlight() { - if (state.allAccountsInfo.length === 0) return; + if (accountManager.getAccountCount() === 0) return; await Utils.sleep(500); // Wait a bit for the switch to take effect try { const currentAccountData = await WPlaceService.getCharges(); console.log("Current account after switch:", currentAccountData); console.log(`šŸ” Switched to account with ID: ${currentAccountData.id}`); - // Update all accounts to not be current - state.allAccountsInfo.forEach(acc => acc.isCurrent = false); - - // Find and mark the new current account in the original array - const newCurrentAccount = state.allAccountsInfo.find(acc => acc.ID === currentAccountData.id); - if (newCurrentAccount) { + // Find the current account in AccountManager and update it + const accounts = accountManager.getAllAccounts(); + const currentAccount = accounts.find(acc => acc.ID === currentAccountData.id); + + if (currentAccount) { const currentAccountInfo = await WPlaceService.fetchCheck(); - newCurrentAccount.isCurrent = true; - newCurrentAccount.Charges = Math.floor(currentAccountData.charges); - newCurrentAccount.Max = Math.floor(currentAccountData.max); - newCurrentAccount.Droplets = Math.floor(currentAccountData.droplets); - newCurrentAccount.displayName = currentAccountInfo.Username || currentAccountInfo.name || newCurrentAccount.displayName; - - console.log(`šŸŽÆ Updated current account spotlight: ${newCurrentAccount.displayName} (ID ${newCurrentAccount.orderId})`); - // Update the switching order array to reflect the current account change - const switchingOrderIndex = state.originalAccountOrder.findIndex(acc => acc.orderId === newCurrentAccount.orderId); - if (switchingOrderIndex !== -1) { - state.originalAccountOrder[switchingOrderIndex] = newCurrentAccount; - // DON'T update currentActiveIndex here - it's managed by switchToNextAccount() - console.log(`šŸŽÆ Updated account data at switching order index ${switchingOrderIndex} (ID ${newCurrentAccount.orderId})`); - console.log(`šŸ“Š Current state: activeIndex=${state.currentActiveIndex}, expectedIndex=${switchingOrderIndex}`); - } + // Update account data in AccountManager + accountManager.updateAccountData(currentAccount.token, { + isCurrent: true, + Charges: Math.floor(currentAccountData.charges), + Max: Math.floor(currentAccountData.max), + Droplets: Math.floor(currentAccountData.droplets), + displayName: currentAccountInfo.Username || currentAccountInfo.name || currentAccount.displayName + }); + + console.log(`šŸŽÆ Updated current account spotlight: ${currentAccount.displayName}`); + + // Re-render the account list to show new current account + renderAccountsList(); + + + + + + console.log(`šŸ”’ PRESERVING currentActiveIndex: ${state.currentActiveIndex} (do not recalculate from isCurrent flag)`); + console.log(`ļæ½ Account at currentActiveIndex ${state.currentActiveIndex}: ${state.originalAccountOrder[state.currentActiveIndex]?.displayName} (ID ${state.originalAccountOrder[state.currentActiveIndex]?.orderId})`); + + // Update accountIndex to match original array position + const originalArrayIndex = state.allAccountsInfo.findIndex(acc => acc.isCurrent); + if (originalArrayIndex !== -1) { + state.accountIndex = originalArrayIndex; + } + + console.log(`šŸ“Š Final state: activeIndex=${state.currentActiveIndex}, accountIndex=${state.accountIndex}, orderId=${newCurrentAccount.orderId}`); + + console.warn(`āš ļø Could not find account ID ${newCurrentAccount.orderId} in switching order`); + + console.warn(`āš ļø Could not find switched account with ID ${currentAccountData.id} in account list`); } // Re-render the account list to show new current account @@ -7893,191 +8347,175 @@ function getText(key, params) { if (!accountsListArea) return; accountsListArea.innerHTML = ''; - if (state.allAccountsInfo.length === 0) { - accountsListArea.innerHTML = `
No account data. Click to refresh.
`; + + const accounts = accountManager.getAllAccounts(); + console.log(`šŸ” [RENDER] Rendering ${accounts.length} accounts`); + + // Debug: Log account data to see what we're working with + accounts.forEach((account, index) => { + console.log(`šŸ“Š [RENDER] Account ${index + 1}: ${account.displayName} - ⚔${account.Charges}/${account.Max} šŸ’§${account.Droplets} ${account.isCurrent ? '(CURRENT)' : ''}`); + }); + + if (accounts.length === 0) { + // Don't show any placeholder - just leave empty + console.log(`šŸ“ [RENDER] No accounts to display`); return; } - // Render accounts in switching order (sorted by orderId) - state.originalAccountOrder.forEach((info, switchIndex) => { - const item = document.createElement('div'); - - // Determine if this is the current account or next in sequence - const isCurrentAccount = info.isCurrent; - const isNextInSequence = state.currentActiveIndex !== -1 && - ((state.currentActiveIndex + 1) % state.originalAccountOrder.length) === switchIndex; - - let itemClasses = 'wplace-account-item'; - if (isCurrentAccount) { - itemClasses += ' current'; - } else if (isNextInSequence) { - itemClasses += ' next-in-sequence'; - } - - item.className = itemClasses; - - // Create ordering number element with order ID - const orderNumber = document.createElement('div'); - orderNumber.className = 'wplace-account-number'; - orderNumber.textContent = info.orderId; // Always show the assigned order ID - - // Add visual indicator for current position in sequence - if (isCurrentAccount) { - orderNumber.style.background = '#2ecc71'; // Green color - orderNumber.style.color = 'white'; - orderNumber.style.boxShadow = '0 0 10px rgba(46, 204, 113, 0.8)'; - orderNumber.title = `Order ID ${info.orderId} - Currently active account`; - - // Add a subtle pulsing effect to the account number for extra visibility - orderNumber.style.animation = 'currentAccountNumberPulse 1.5s ease-in-out infinite alternate'; - } else if (isNextInSequence) { - orderNumber.style.background = '#f39c12'; // Orange color - orderNumber.style.color = 'white'; - orderNumber.style.boxShadow = '0 0 8px rgba(243, 156, 18, 0.6)'; - orderNumber.title = `Order ID ${info.orderId} - Next account in switching sequence`; - } else { - orderNumber.style.background = ''; // Reset to default - orderNumber.style.boxShadow = ''; // Remove any glow - orderNumber.title = `Order ID ${info.orderId}`; - orderNumber.style.animation = ''; // Remove any animation - } - - const displayName = info.displayName || `Account ${index + 1}`; - - const details = document.createElement('div'); - details.className = 'wplace-account-details'; - const nameDiv = document.createElement('div'); - nameDiv.className = 'wplace-account-name'; - nameDiv.textContent = displayName; - nameDiv.title = displayName; - details.appendChild(nameDiv); - - let stats; - if (info.error) { - stats = document.createElement('div'); - stats.className = 'wplace-account-stats'; - stats.style.color = 'red'; - stats.textContent = 'Error'; - } else { - stats = document.createElement('div'); - stats.className = 'wplace-account-stats'; - stats.innerHTML = ` - ${Math.floor(info.Charges || 0)}/${Math.floor(info.Max || 0)} - ${Math.floor(info.Droplets || 0)} - `; - } - - item.appendChild(orderNumber); - item.appendChild(details); - item.appendChild(stats); + accounts.forEach((account, index) => { + const item = createAccountItem(account, index); accountsListArea.appendChild(item); }); + + console.log(`āœ… [RENDER] Successfully rendered ${accounts.length} accounts`); } - // Account switching helper functions + function createAccountItem(account, index) { + const item = document.createElement('div'); + + // Determine if this is the current account + const isCurrentAccount = account.isCurrent; + const isNextInSequence = accountManager.currentIndex !== -1 && + ((accountManager.currentIndex + 1) % accountManager.getAccountCount()) === index; + + let itemClasses = 'wplace-account-item'; + if (isCurrentAccount) { + itemClasses += ' current'; + } else if (isNextInSequence) { + itemClasses += ' next-in-sequence'; + } + + item.className = itemClasses; + + // Create ordering number element with 1-based index for user display + const orderNumber = document.createElement('div'); + orderNumber.className = 'wplace-account-number'; + orderNumber.textContent = index + 1; // Show 1-based index for user-friendly display + + // Add visual indicator for current position in sequence + if (isCurrentAccount) { + orderNumber.style.background = '#2ecc71'; // Green color + orderNumber.style.color = 'white'; + orderNumber.style.boxShadow = '0 0 10px rgba(46, 204, 113, 0.8)'; + } else if (isNextInSequence) { + orderNumber.style.background = '#f39c12'; // Orange color for next + orderNumber.style.color = 'white'; + orderNumber.style.boxShadow = '0 0 8px rgba(243, 156, 18, 0.6)'; + } + + // Create account details + const details = document.createElement('div'); + details.className = 'wplace-account-details'; + + const accountName = document.createElement('div'); + accountName.className = 'wplace-account-name'; + const displayName = account.displayName || account.name || `Account ${index + 1}`; + accountName.textContent = displayName; + accountName.title = displayName; + + const accountStats = document.createElement('div'); + accountStats.className = 'wplace-account-stats'; + + // Safely handle undefined values + const charges = account.Charges !== undefined ? Math.floor(account.Charges) : 0; + const max = account.Max !== undefined ? Math.floor(account.Max) : 0; + const droplets = account.Droplets !== undefined ? Math.floor(account.Droplets) : 0; + + accountStats.innerHTML = ` + ${charges}/${max} + ${droplets} + `; + + details.appendChild(accountName); + details.appendChild(accountStats); + + // Add status indicators + const status = document.createElement('div'); + status.className = 'wplace-account-status'; + if (isCurrentAccount) { + status.innerHTML = ''; + } else if (isNextInSequence) { + status.innerHTML = ''; + } + + item.appendChild(orderNumber); + item.appendChild(details); + item.appendChild(status); + + return item; + } + + // SIMPLIFIED ACCOUNT SWITCHING async function switchToNextAccount(accounts) { - const previousActiveIndex = state.currentActiveIndex; + console.log(`šŸ”„ [SWITCH] Starting account switch`); - // Move to next account in strict ID order (next position in switching order) - const nextIndex = (state.currentActiveIndex + 1) % state.originalAccountOrder.length; - - // Get the account info from switching order - const nextAccountInfo = state.originalAccountOrder[nextIndex]; - const prevAccountInfo = state.originalAccountOrder[previousActiveIndex]; - - // Strict validation: ensure ID 1 always goes to ID 2 - if (prevAccountInfo?.orderId === 1 && nextAccountInfo?.orderId !== 2) { - console.error(`āŒ STRICT ORDER VIOLATION: ID 1 must switch to ID 2, but found ID ${nextAccountInfo?.orderId}`); - // Find ID 2 account - const id2Account = state.originalAccountOrder.find(acc => acc.orderId === 2); - if (id2Account) { - const id2Index = state.originalAccountOrder.indexOf(id2Account); - console.log(`šŸ”„ Correcting: Forcing switch from ID 1 to ID 2 (index ${id2Index})`); - state.currentActiveIndex = id2Index; - } else { - console.error(`āŒ FATAL: No ID 2 account found in switching order!`); - return false; - } - } else { - state.currentActiveIndex = nextIndex; - } - - // Re-get account info after potential correction - const finalNextAccountInfo = state.originalAccountOrder[state.currentActiveIndex]; - - console.log(`šŸ”„ Switching from ID ${prevAccountInfo?.orderId} (${prevAccountInfo?.displayName || 'Unknown'}) to ID ${finalNextAccountInfo?.orderId} (${finalNextAccountInfo?.displayName || 'Unknown'})`); - console.log(`šŸ“ Index progression: ${previousActiveIndex} → ${state.currentActiveIndex} (order: ${prevAccountInfo?.orderId} → ${finalNextAccountInfo?.orderId})`); - - // Find the token for this account by matching the account in the original allAccountsInfo array - const originalAccountIndex = state.allAccountsInfo.findIndex(acc => acc.orderId === finalNextAccountInfo?.orderId); - if (originalAccountIndex === -1) { - console.warn(`āš ļø Could not find account with ID ${finalNextAccountInfo?.orderId} in original array`); + // Validate we have accounts + if (accountManager.getAccountCount() === 0) { + console.error('āŒ No accounts available for switching'); return false; } - const nextToken = finalNextAccountInfo?.token; - console.log(`šŸ”‘ Next token for ID ${finalNextAccountInfo?.orderId}: ${nextToken ? nextToken.substring(0, 20) + '...' : 'INVALID'}`); - console.log(`šŸ” Expected account: ${finalNextAccountInfo?.displayName} (ID ${finalNextAccountInfo?.orderId})`); - - if (!nextToken) { - console.warn(`āš ļø Invalid token for ID ${finalNextAccountInfo?.orderId}, skipping...`); + // Get next account using simplified manager + const nextAccount = accountManager.switchToNext(); + if (!nextAccount) { + console.error('āŒ Failed to get next account'); return false; } - // Update accountIndex to match the position in the accounts token array - const accountsIndex = accounts.findIndex(token => token === nextToken); - if (accountsIndex !== -1) { - state.accountIndex = accountsIndex; - console.log(`šŸ”— Found token at accounts array index: ${accountsIndex}`); - console.log(`šŸ” Token from accounts[${accountsIndex}]: ${accounts[accountsIndex].substring(0, 20)}...`); - } else { - console.warn(`āš ļø Token not found in accounts array for ID ${finalNextAccountInfo?.orderId}`); - console.warn(`šŸ” Available accounts tokens: ${accounts.map((t, i) => `[${i}] ${t.substring(0, 20)}...`).join(', ')}`); + console.log(`šŸŽÆ [SWITCH] Switching to: ${nextAccount.displayName} (index ${accountManager.currentIndex})`); + + // Ensure token exists + if (!nextAccount.token) { + console.error(`āŒ Missing token for account: ${nextAccount.displayName}`); return false; } - return await switchToSpecificAccount(nextToken, state.currentActiveIndex); + console.log(`ļæ½ [SWITCH] Using token: ${nextAccount.token.substring(0, 20)}...`); + + // Perform the account switch + try { + await swapAccountTrigger(nextAccount.token); + + // Update account index for backward compatibility + state.accountIndex = accountManager.currentIndex; + + console.log(`āœ… [SWITCH] Successfully switched to ${nextAccount.displayName}`); + + // Re-render the accounts list + renderAccountsList(); + + return true; + } catch (error) { + console.error('āŒ [SWITCH] Account switch failed:', error); + return false; + } } - async function switchToSpecificAccount(token, originalOrderIndex) { - // Get account name and ID for better logging - const accountInfo = state.originalAccountOrder && state.originalAccountOrder[originalOrderIndex] - ? state.originalAccountOrder[originalOrderIndex] - : null; - const accountName = accountInfo?.displayName || `Account ${originalOrderIndex + 1}`; - const accountId = accountInfo?.orderId || (originalOrderIndex + 1); - - console.log(`šŸ”„ Attempting to switch to ID ${accountId} (${accountName})`); - console.log(`šŸ”‘ Using token: ${token.substring(0, 20)}...`); + // SIMPLIFIED helper function for specific account switching + async function switchToSpecificAccount(token, accountName) { + console.log(`šŸ”„ [SPECIFIC SWITCH] Attempting to switch to account: ${accountName}`); + console.log(`šŸ”‘ [SPECIFIC SWITCH] Using token: ${token.substring(0, 20)}...`); await swapAccountTrigger(token); - // Give more time for the initial swap to process (like Acc-Switch.js) - // console.log('ā³ Waiting 5 seconds for initial swap processing...'); - // await new Promise(resolve => setTimeout(resolve, 5000)); - - let maxRetries = 20; // Match Acc-Switch.js retry count + let maxRetries = 20; let retryCount = 0; let swapSuccess = false; while (!swapSuccess && retryCount < maxRetries) { - console.log(`ā³ Waiting for account swap... (Attempt ${retryCount + 1}/${maxRetries})`); - - // Wait for a short period before checking (like Acc-Switch.js) + console.log(`ā³ [SPECIFIC SWITCH] Waiting for account swap... (Attempt ${retryCount + 1}/${maxRetries})`); await new Promise(resolve => setTimeout(resolve, 1000)); try { - // Use the same verification method as Acc-Switch.js await fetchAccount(); - console.log('āœ… Account swap confirmed.'); + console.log('āœ… [SPECIFIC SWITCH] Account swap confirmed.'); swapSuccess = true; } catch (error) { - console.warn('āŒ Account swap not yet successful. Retrying...', error); + console.warn('āŒ [SPECIFIC SWITCH] Account swap not yet successful. Retrying...', error); retryCount++; - // Re-trigger swap every 5 attempts if (retryCount % 5 === 0) { - console.log('šŸ”„ Re-triggering account swap...'); + console.log('šŸ”„ [SPECIFIC SWITCH] Re-triggering account swap...'); await swapAccountTrigger(token); await new Promise(resolve => setTimeout(resolve, 2000)); } @@ -8089,17 +8527,16 @@ function getText(key, params) { state.displayCharges = Math.floor(charges); state.preciseCurrentCharges = charges; state.cooldown = cooldown; - // Don't update state.accountIndex here as it should be managed by the calling function Utils.performSmartSave(); await updateStats(); - // Update account list spotlight to show new current account - await updateCurrentAccountSpotlight(); + // Update account data in manager + accountManager.updateAccountData({ charges, cooldown }); - console.log(`āœ… Successfully switched to ID ${accountId} (${accountName}) with ${Math.floor(charges)} charges`); + console.log(`āœ… [SPECIFIC SWITCH] Successfully switched to ${accountName} with ${Math.floor(charges)} charges`); return true; } else { - console.error(`āŒ Failed to swap to ID ${accountId} (${accountName}) after multiple retries.`); + console.error(`āŒ [SPECIFIC SWITCH] Failed to swap to ${accountName} after multiple retries.`); return false; } } @@ -8149,7 +8586,23 @@ function getText(key, params) { // Generate token automatically after UI is ready setTimeout(initializeTokenGenerator, 1000); - // Auto-refresh account list on startup + // Quick initial account load from cache + setTimeout(async () => { + console.log('šŸ”„ Initial account load from cache...'); + try { + await accountManager.loadAccounts(); + if (accountManager.getAccountCount() > 0) { + console.log(`āœ… Loaded ${accountManager.getAccountCount()} cached accounts`); + renderAccountsList(); + } else { + console.log('šŸ“­ No cached accounts found'); + } + } catch (error) { + console.warn('āš ļø Initial account load failed:', error); + } + }, 500); + + // Auto-refresh account list on startup (with extension communication) setTimeout(() => { console.log('šŸ”„ Auto-refreshing account list on startup...'); fetchAllAccountDetails(); @@ -8238,6 +8691,4 @@ function getText(key, params) { Utils.cleanupTurnstile(); }); }); -})(); - - +})(); \ No newline at end of file diff --git a/Extension/scripts/Auto-Repair.js b/Extension/scripts/Auto-Repair.js index 1b4e0f2..4f6abed 100644 --- a/Extension/scripts/Auto-Repair.js +++ b/Extension/scripts/Auto-Repair.js @@ -1,3331 +1,3331 @@ -// eslint-disable-next-line prettier/prettier -; (async () => { - // CONFIGURATION CONSTANTS - const CONFIG = { - COOLDOWN_DEFAULT: 31000, - TRANSPARENCY_THRESHOLD: 100, - WHITE_THRESHOLD: 250, - LOG_INTERVAL: 10, - PAINTING_SPEED: { - MIN: 1, - MAX: 1000, - DEFAULT: 5, - }, - TOKEN_SOURCE: 'generator', // "generator", "manual", or "hybrid" - AUTONOMOUS_MODE: true, // Enable autonomous operation - AUTO_TOKEN_REFRESH: true, // Automatically refresh tokens - TOKEN_PRELOAD_BUFFER: 60000, // Preload tokens 1 minute before expiry - MAX_RETRIES: 10, - RETRY_DELAY_BASE: 1000, - // Auto-batch configuration - AUTO_BATCH_ENABLED: true, // Enable auto pixel batch sizing - MAX_BATCH_SIZE: 500, // Maximum pixels per batch - MIN_BATCH_SIZE: 1, // Minimum pixels per batch - BATCH_OPTIMIZATION: true, // Optimize batch size based on detected damage - COLOR_MAP: { - 0: { id: 1, name: 'Black', rgb: { r: 0, g: 0, b: 0 } }, - 1: { id: 2, name: 'Dark Gray', rgb: { r: 60, g: 60, b: 60 } }, - 2: { id: 3, name: 'Gray', rgb: { r: 120, g: 120, b: 120 } }, - 3: { id: 4, name: 'Light Gray', rgb: { r: 210, g: 210, b: 210 } }, - 4: { id: 5, name: 'White', rgb: { r: 255, g: 255, b: 255 } }, - 5: { id: 6, name: 'Deep Red', rgb: { r: 96, g: 0, b: 24 } }, - 6: { id: 7, name: 'Red', rgb: { r: 237, g: 28, b: 36 } }, - 7: { id: 8, name: 'Orange', rgb: { r: 255, g: 127, b: 39 } }, - 8: { id: 9, name: 'Gold', rgb: { r: 246, g: 170, b: 9 } }, - 9: { id: 10, name: 'Yellow', rgb: { r: 249, g: 221, b: 59 } }, - 10: { id: 11, name: 'Light Yellow', rgb: { r: 255, g: 250, b: 188 } }, - 11: { id: 12, name: 'Dark Green', rgb: { r: 14, g: 185, b: 104 } }, - 12: { id: 13, name: 'Green', rgb: { r: 19, g: 230, b: 123 } }, - 13: { id: 14, name: 'Light Green', rgb: { r: 135, g: 255, b: 94 } }, - 14: { id: 15, name: 'Dark Teal', rgb: { r: 12, g: 129, b: 110 } }, - 15: { id: 16, name: 'Teal', rgb: { r: 16, g: 174, b: 166 } }, - 16: { id: 17, name: 'Light Teal', rgb: { r: 19, g: 225, b: 190 } }, - 17: { id: 20, name: 'Cyan', rgb: { r: 96, g: 247, b: 242 } }, - 18: { id: 44, name: 'Light Cyan', rgb: { r: 187, g: 250, b: 242 } }, - 19: { id: 18, name: 'Dark Blue', rgb: { r: 40, g: 80, b: 158 } }, - 20: { id: 19, name: 'Blue', rgb: { r: 64, g: 147, b: 228 } }, - 21: { id: 21, name: 'Indigo', rgb: { r: 107, g: 80, b: 246 } }, - 22: { id: 22, name: 'Light Indigo', rgb: { r: 153, g: 177, b: 251 } }, - 23: { id: 23, name: 'Dark Purple', rgb: { r: 120, g: 12, b: 153 } }, - 24: { id: 24, name: 'Purple', rgb: { r: 170, g: 56, b: 185 } }, - 25: { id: 25, name: 'Light Purple', rgb: { r: 224, g: 159, b: 249 } }, - 26: { id: 26, name: 'Dark Pink', rgb: { r: 203, g: 0, b: 122 } }, - 27: { id: 27, name: 'Pink', rgb: { r: 236, g: 31, b: 128 } }, - 28: { id: 28, name: 'Light Pink', rgb: { r: 243, g: 141, b: 169 } }, - 29: { id: 29, name: 'Dark Brown', rgb: { r: 104, g: 70, b: 52 } }, - 30: { id: 30, name: 'Brown', rgb: { r: 149, g: 104, b: 42 } }, - 31: { id: 31, name: 'Beige', rgb: { r: 248, g: 178, b: 119 } }, - 32: { id: 52, name: 'Light Beige', rgb: { r: 255, g: 197, b: 165 } }, - 33: { id: 32, name: 'Medium Gray', rgb: { r: 170, g: 170, b: 170 } }, - 34: { id: 33, name: 'Dark Red', rgb: { r: 165, g: 14, b: 30 } }, - 35: { id: 34, name: 'Light Red', rgb: { r: 250, g: 128, b: 114 } }, - 36: { id: 35, name: 'Dark Orange', rgb: { r: 228, g: 92, b: 26 } }, - 37: { id: 37, name: 'Dark Goldenrod', rgb: { r: 156, g: 132, b: 49 } }, - 38: { id: 38, name: 'Goldenrod', rgb: { r: 197, g: 173, b: 49 } }, - 39: { id: 39, name: 'Light Goldenrod', rgb: { r: 232, g: 212, b: 95 } }, - 40: { id: 40, name: 'Dark Olive', rgb: { r: 74, g: 107, b: 58 } }, - 41: { id: 41, name: 'Olive', rgb: { r: 90, g: 148, b: 74 } }, - 42: { id: 42, name: 'Light Olive', rgb: { r: 132, g: 197, b: 115 } }, - 43: { id: 43, name: 'Dark Cyan', rgb: { r: 15, g: 121, b: 159 } }, - 44: { id: 45, name: 'Light Blue', rgb: { r: 125, g: 199, b: 255 } }, - 45: { id: 46, name: 'Dark Indigo', rgb: { r: 77, g: 49, b: 184 } }, - 46: { id: 47, name: 'Dark Slate Blue', rgb: { r: 74, g: 66, b: 132 } }, - 47: { id: 48, name: 'Slate Blue', rgb: { r: 122, g: 113, b: 196 } }, - 48: { id: 49, name: 'Light Slate Blue', rgb: { r: 181, g: 174, b: 241 } }, - 49: { id: 53, name: 'Dark Peach', rgb: { r: 155, g: 82, b: 73 } }, - 50: { id: 54, name: 'Peach', rgb: { r: 209, g: 128, b: 120 } }, - 51: { id: 55, name: 'Light Peach', rgb: { r: 250, g: 182, b: 164 } }, - 52: { id: 50, name: 'Light Brown', rgb: { r: 219, g: 164, b: 99 } }, - 53: { id: 56, name: 'Dark Tan', rgb: { r: 123, g: 99, b: 82 } }, - 54: { id: 57, name: 'Tan', rgb: { r: 156, g: 132, b: 107 } }, - 55: { id: 36, name: 'Light Tan', rgb: { r: 214, g: 181, b: 148 } }, - 56: { id: 51, name: 'Dark Beige', rgb: { r: 209, g: 128, b: 81 } }, - 57: { id: 61, name: 'Dark Stone', rgb: { r: 109, g: 100, b: 63 } }, - 58: { id: 62, name: 'Stone', rgb: { r: 148, g: 140, b: 107 } }, - 59: { id: 63, name: 'Light Stone', rgb: { r: 205, g: 197, b: 158 } }, - 60: { id: 58, name: 'Dark Slate', rgb: { r: 51, g: 57, b: 65 } }, - 61: { id: 59, name: 'Slate', rgb: { r: 109, g: 117, b: 141 } }, - 62: { id: 60, name: 'Light Slate', rgb: { r: 179, g: 185, b: 209 } }, - 63: { id: 0, name: 'Transparent', rgb: null }, - }, - }; - - // GLOBAL STATE - const state = { - running: false, - imageLoaded: false, - totalPixels: 0, - paintedPixels: 0, - availableColors: [], - displayCharges: 0, - maxCharges: 1, - cooldown: CONFIG.COOLDOWN_DEFAULT, - imageData: null, - stopFlag: false, - startPosition: null, - region: null, - paintWhitePixels: true, - paintTransparentPixels: true, // Changed to true to fix transparent pixel detection - autoRepairEnabled: false, - autoRepairInterval: 30, - autoRepairTimer: null, - debugLogs: [], - customTransparencyThreshold: CONFIG.TRANSPARENCY_THRESHOLD, - customWhiteThreshold: CONFIG.WHITE_THRESHOLD, - tokenSource: CONFIG.TOKEN_SOURCE, - autonomousMode: CONFIG.AUTONOMOUS_MODE, - autoTokenRefresh: CONFIG.AUTO_TOKEN_REFRESH, - tokenPreloadBuffer: CONFIG.TOKEN_PRELOAD_BUFFER, - retryCount: 0, - tokenRetryTimer: null, - tokenPreloadTimer: null, - windowMinimized: false, - lastAttackState: null, - initialSetupComplete: false, - // Auto-batch state - autoBatchEnabled: CONFIG.AUTO_BATCH_ENABLED, - currentBatchSize: CONFIG.MIN_BATCH_SIZE, - batchOptimization: CONFIG.BATCH_OPTIMIZATION, - }; - - // Random string generator - const randStr = (len, chars = 'abcdefghijklmnopqrstuvwxyz0123456789') => - [...Array(len)].map(() => chars[(crypto?.getRandomValues?.(new Uint32Array(1))[0] % chars.length) || Math.floor(Math.random() * chars.length)]).join(''); - - const fpStr32 = randStr(32); - - // Enhanced Turnstile token handling - Actualizado con la lógica de new.txt lol - let turnstileToken = null; - let tokenExpiryTime = 0; - let tokenGenerationInProgress = false; - let _resolveToken = null; - let tokenPromise = new Promise((resolve) => { - _resolveToken = resolve; - }); - let retryCount = 0; - const MAX_RETRIES = 10; - const MAX_BATCH_RETRIES = 10; - const TOKEN_LIFETIME = 240000; // 4 minutes (tokens typically last 5 min, use 4 for safety) - - function setTurnstileToken(token) { - if (_resolveToken) { - _resolveToken(token); - _resolveToken = null; - } - turnstileToken = token; - tokenExpiryTime = Date.now() + TOKEN_LIFETIME; - console.log('āœ… Turnstile token set successfully'); - Utils.addDebugLog('Token cached successfully', 'success'); - } - - function isTokenValid() { - return turnstileToken && Date.now() < tokenExpiryTime; - } - - function invalidateToken() { - turnstileToken = null; - tokenExpiryTime = 0; - console.log('šŸ—‘ļø Token invalidated, will force fresh generation'); - Utils.addDebugLog('Token invalidated, will force fresh generation', 'warning'); - } - - async function ensureToken(forceRefresh = false) { - // Return cached token if still valid and not forcing refresh - if (isTokenValid() && !forceRefresh) { - return turnstileToken; - } - - // Invalidate token if forcing refresh - if (forceRefresh) invalidateToken(); - - // Avoid multiple simultaneous token generations - if (tokenGenerationInProgress) { - console.log('šŸ”„ Token generation already in progress, waiting...'); - await Utils.sleep(2000); - return isTokenValid() ? turnstileToken : null; - } - - tokenGenerationInProgress = true; - - try { - console.log('šŸ”„ Token expired or missing, generating new one...'); - const token = await handleCaptchaWithRetry(); - if (token && token.length > 20) { - setTurnstileToken(token); - console.log('āœ… Token captured and cached successfully'); - return token; - } - - console.log('āš ļø Invisible Turnstile failed, forcing browser automation...'); - const fallbackToken = await handleCaptchaFallback(); - if (fallbackToken && fallbackToken.length > 20) { - setTurnstileToken(fallbackToken); - console.log('āœ… Fallback token captured successfully'); - return fallbackToken; - } - - console.log('āŒ All token generation methods failed'); - return null; - } finally { - tokenGenerationInProgress = false; - } - } - - async function handleCaptchaWithRetry() { - const startTime = performance.now(); - - try { - const { sitekey, token: preGeneratedToken } = await Utils.obtainSitekeyAndToken(); - - if (!sitekey) { - throw new Error('No valid sitekey found'); - } - - console.log('šŸ”‘ Using sitekey:', sitekey); - - if (typeof window !== 'undefined' && window.navigator) { - console.log( - '🧭 UA:', - window.navigator.userAgent.substring(0, 50) + '...', - 'Platform:', - window.navigator.platform - ); - } - - let token = null; - - if ( - preGeneratedToken && - typeof preGeneratedToken === 'string' && - preGeneratedToken.length > 20 - ) { - console.log('ā™»ļø Reusing pre-generated Turnstile token'); - token = preGeneratedToken; - } else { - if (isTokenValid()) { - console.log('ā™»ļø Using existing cached token (from previous session)'); - token = turnstileToken; - } else { - console.log('šŸ” Generating new token with executeTurnstile...'); - token = await Utils.executeTurnstile(sitekey, 'paint'); - if (token) setTurnstileToken(token); - } - } - - if (token && typeof token === 'string' && token.length > 20) { - const elapsed = Math.round(performance.now() - startTime); - console.log(`āœ… Turnstile token generated successfully in ${elapsed}ms`); - return token; - } else { - throw new Error(`Invalid or empty token received - Length: ${token?.length || 0}`); - } - } catch (error) { - const elapsed = Math.round(performance.now() - startTime); - console.error(`āŒ Turnstile token generation failed after ${elapsed}ms:`, error); - throw error; - } - } - - async function handleCaptchaFallback() { - return new Promise(async (resolve, reject) => { - try { - // Ensure we have a fresh promise to await for a new token capture - if (!_resolveToken) { - tokenPromise = new Promise((res) => { - _resolveToken = res; - }); - } - const timeoutPromise = Utils.sleep(20000).then(() => - reject(new Error('Auto-CAPTCHA timed out.')) - ); - - const solvePromise = (async () => { - const mainPaintBtn = await Utils.waitForSelector( - 'button.btn.btn-primary.btn-lg, button.btn-primary.sm\\:btn-xl', - 200, - 10000 - ); - if (!mainPaintBtn) throw new Error('Could not find the main paint button.'); - mainPaintBtn.click(); - await Utils.sleep(500); - - const transBtn = await Utils.waitForSelector('button#color-0', 200, 5000); - if (!transBtn) throw new Error('Could not find the transparent color button.'); - transBtn.click(); - await Utils.sleep(500); - - const canvas = await Utils.waitForSelector('canvas', 200, 5000); - if (!canvas) throw new Error('Could not find the canvas element.'); - - canvas.setAttribute('tabindex', '0'); - canvas.focus(); - const rect = canvas.getBoundingClientRect(); - const centerX = Math.round(rect.left + rect.width / 2); - const centerY = Math.round(rect.top + rect.height / 2); - - canvas.dispatchEvent( - new MouseEvent('mousemove', { - clientX: centerX, - clientY: centerY, - bubbles: true, - }) - ); - canvas.dispatchEvent( - new KeyboardEvent('keydown', { - key: ' ', - code: 'Space', - bubbles: true, - }) - ); - await Utils.sleep(50); - canvas.dispatchEvent( - new KeyboardEvent('keyup', { - key: ' ', - code: 'Space', - bubbles: true, - }) - ); - await Utils.sleep(500); - - // 800ms delay before sending confirmation - await Utils.sleep(800); - - // Keep confirming until token is captured - const confirmLoop = async () => { - while (!turnstileToken) { - let confirmBtn = await Utils.waitForSelector( - 'button.btn.btn-primary.btn-lg, button.btn.btn-primary.sm\\:btn-xl' - ); - if (!confirmBtn) { - const allPrimary = Array.from(document.querySelectorAll('button.btn-primary')); - confirmBtn = allPrimary.length ? allPrimary[allPrimary.length - 1] : null; - } - if (confirmBtn) { - confirmBtn.click(); - } - await Utils.sleep(500); // 500ms delay between confirmation attempts - } - }; - - // Start confirmation loop and wait for token - confirmLoop(); - const token = await tokenPromise; - await Utils.sleep(300); // small delay after token is captured - resolve(token); - })(); - - await Promise.race([solvePromise, timeoutPromise]); - } catch (error) { - console.error('Auto-CAPTCHA process failed:', error); - reject(error); - } - }); - } - - // NUEVA LƓGICA DE INYECCIƓN - ACTUALIZADA DESDE new.txt - function inject(callback) { - try { - const script = document.createElement('script'); - script.textContent = `(${callback})();`; - document.documentElement?.appendChild(script); - script.remove(); - } catch (error) { - console.error('āŒ Injection error:', error); - } - } - - inject(() => { - const fetchedBlobQueue = new Map(); - - 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]; - - if (typeof url === 'string') { - if (url.includes('https://backend.wplace.live/s0/pixel/')) { - try { - const payload = JSON.parse(args[1].body); - if (payload.t) { - // šŸ“Š Debug log - console.log( - `šŸ”āœ… Turnstile Token Captured - Type: ${typeof payload.t}, Value: ${payload.t - ? typeof payload.t === 'string' - ? payload.t.length > 50 - ? payload.t.substring(0, 50) + '...' - : payload.t - : JSON.stringify(payload.t) - : 'null/undefined' - }, Length: ${payload.t?.length || 0}` - ); - window.postMessage({ source: 'turnstile-capture', token: payload.t }, '*'); - } - } catch (_) { - /* ignore */ - } - } - - 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; - }; - }); - - window.addEventListener('message', (event) => { - const { source, endpoint, blobID, blobData, token } = event.data; - - if (source === 'auto-image-tile' && endpoint && blobID && blobData) { - overlayManager.processAndRespondToTileRequest(event.data); - } - - if (source === 'turnstile-capture' && token) { - setTurnstileToken(token); - Utils.addDebugLog('Token captured from injection system', 'success'); - updateTokenStatus(); - } - }); - - // NUEVO SISTEMA WASM - ACTUALIZADO DESDE new.txt - var pawtect_chunk = null; - - // Find module if pawtect_chunk is null - pawtect_chunk ??= await findTokenModule("pawtect_wasm_bg.wasm"); - - async function createWasmToken(regionX, regionY, payload) { - try { - // Load the Pawtect module and WASM - const mod = await import(new URL('/_app/immutable/chunks/'+pawtect_chunk, location.origin).href); - let wasm; - try { - wasm = await mod._(); - console.log('āœ… WASM initialized successfully'); - } catch (wasmError) { - console.error('āŒ WASM initialization failed:', wasmError); - return null; - } - try { - try { - const me = await fetch(`https://backend.wplace.live/me`, { credentials: 'include' }).then(r => r.ok ? r.json() : null); - if (me?.id) { - mod.i(me.id); - console.log('āœ… user ID set:', me.id); - } - } catch { } - } catch (userIdError) { - console.log('āš ļø Error setting user ID:', userIdError.message); - } - try { - const testUrl = `https://backend.wplace.live/s0/pixel/${regionX}/${regionY}`; - if (mod.r) { - mod.r(testUrl); - console.log('āœ… Request URL set:', testUrl); - } else { - console.log('āš ļø request_url function (mod.r) not available'); - } - } catch (urlError) { - console.log('āš ļø Error setting request URL:', urlError.message); - } - - console.log('šŸ” payload:', payload); - - // Encode payload - const enc = new TextEncoder(); - const dec = new TextDecoder(); - const bodyStr = JSON.stringify(payload); - const bytes = enc.encode(bodyStr); - console.log('šŸ” Payload size:', bytes.length, 'bytes'); - console.log('šŸ”„ Payload string:', bodyStr); - - // Allocate WASM memory with validation - let inPtr; - try { - if (!wasm.__wbindgen_malloc) { - console.error('āŒ __wbindgen_malloc function not found'); - return null; - } - - inPtr = wasm.__wbindgen_malloc(bytes.length, 1); - console.log('āœ… WASM memory allocated, pointer:', inPtr); - - // Copy data to WASM memory - const wasmBuffer = new Uint8Array(wasm.memory.buffer, inPtr, bytes.length); - wasmBuffer.set(bytes); - console.log('āœ… Data copied to WASM memory'); - } catch (memError) { - console.error('āŒ Memory allocation error:', memError); - return null; - } - - // Call the WASM function - console.log('šŸš€ Calling get_pawtected_endpoint_payload...'); - let outPtr, outLen, token; - try { - const result = wasm.get_pawtected_endpoint_payload(inPtr, bytes.length); - console.log('āœ… Function called, result type:', typeof result, result); - - if (Array.isArray(result) && result.length === 2) { - [outPtr, outLen] = result; - console.log('āœ… Got output pointer:', outPtr, 'length:', outLen); - - // Decode the result - const outputBuffer = new Uint8Array(wasm.memory.buffer, outPtr, outLen); - token = dec.decode(outputBuffer); - console.log('āœ… Token decoded successfully'); - } else { - console.error('āŒ Unexpected function result format:', result); - return null; - } - } catch (funcError) { - console.error('āŒ Function call error:', funcError); - console.error('Stack trace:', funcError.stack); - return null; - } - - // Cleanup memory - try { - if (wasm.__wbindgen_free && outPtr && outLen) { - wasm.__wbindgen_free(outPtr, outLen, 1); - console.log('āœ… Output memory freed'); - } - if (wasm.__wbindgen_free && inPtr) { - wasm.__wbindgen_free(inPtr, bytes.length, 1); - console.log('āœ… Input memory freed'); - } - } catch (cleanupError) { - console.log('āš ļø Cleanup warning:', cleanupError.message); - } - - console.log('šŸŽ‰ SUCCESS!'); - console.log('šŸ”‘ Full token:', token); - return token; - } catch (error) { - console.error('āŒ Failed to generate fp parameter:', error); - return null; - } - } - - async function findTokenModule(str) { - console.log('šŸ”Ž Searching for wasm Module...'); - const links = Array.from(document.querySelectorAll('link[rel="modulepreload"][href$=".js"]')); - - for (const link of links) { - try { - const url = new URL(link.getAttribute("href"), location.origin).href; - const code = await fetch(url).then(r => r.text()); - if (code.includes(str)) { - console.log('āœ… Found wasm Module...'); - return url.split('/').pop(); - } - } catch { } - } - console.error(`āŒ Could not find Pawtect chunk with string: ${str}`); - } - - // Audio notification system - function playNotificationSound() { - try { - const audio = new Audio('https://cdn.pixabay.com/download/audio/2025/03/21/audio_9bec51b17f.mp3?filename=glass-break-316720.mp3'); - audio.volume = 0.5; - audio.play().catch(() => { - console.warn('Could not play notification sound'); - }); - } catch (error) { - console.warn('Audio notification failed:', error); - } - } - - // Notification system for attacks and repairs - function showAttackNotification(type, count = 0) { - const existing = document.getElementById('attack-notification'); - if (existing) existing.remove(); - - const notification = document.createElement('div'); - notification.id = 'attack-notification'; - notification.style.cssText = ` - position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); - z-index: 15000; background: rgba(0,0,0,0.9); color: white; - border-radius: 15px; padding: 20px; text-align: center; - box-shadow: 0 10px 30px rgba(0,0,0,0.8); - border: 2px solid ${type === 'attack' ? '#ff4444' : '#44ff44'}; - min-width: 350px; max-width: 450px; - `; - - if (type === 'attack') { - notification.innerHTML = ` -
- Attack -
-

- PIXEL ART ATTACKED! -

-

- Detected ${count} damaged pixels! -

- `; - } else if (type === 'repaired') { - notification.innerHTML = ` -
- Repaired -
-

- COMPLETELY REPAIRED! -

-

- All pixels have been restored! -

- `; - } - - document.body.appendChild(notification); - - setTimeout(() => { - if (notification.parentNode) { - notification.style.transition = 'opacity 0.5s ease'; - notification.style.opacity = '0'; - setTimeout(() => { - if (notification.parentNode) notification.remove(); - }, 500); - } - }, 3000); - } - - // Peaceful state display in main window - function showPeacefulState() { - const statusDiv = document.getElementById('status'); - if (statusDiv && !state.running) { - statusDiv.innerHTML = ` -
- Peaceful - No attacks detected - All secure -
- `; - } - } - - // Typewriter effect for title - function createTypewriterTitle() { - const titleText = "WPlace Autonomous Repair Tool"; - let currentIndex = 0; - const titleElement = document.getElementById('main-title'); - - if (!titleElement) return; - - function typeNext() { - if (currentIndex < titleText.length) { - titleElement.textContent = titleText.substring(0, currentIndex + 1); - currentIndex++; - setTimeout(typeNext, 100); - } else { - setTimeout(() => { - currentIndex = 0; - titleElement.textContent = ''; - setTimeout(typeNext, 1000); - }, 3000); - } - } - - typeNext(); - } - - // Fallback translations - const TEXTS = { - title: 'WPlace Autonomous Repair Tool', - loadFromFile: 'Load Progress File', - repairPixels: 'Repair Pixels', - enableAutoRepair: 'Enable Auto Repair', - repairInterval: 'Check Interval (seconds)', - debug: 'Debug Console', - clearDebug: 'Clear Debug', - scanningForDamage: 'Scanning for damaged pixels...', - damageDetected: 'Damage detected: {count} pixels', - noDamageDetected: 'No damage found', - repairingPixels: 'Repairing {count} damaged pixels...', - repairComplete: 'Repair completed: {repaired} pixels fixed', - autoRepairStarted: 'Auto repair started (every {interval}s)', - autoRepairStopped: 'Auto repair stopped', - fileLoaded: 'Progress file loaded successfully', - invalidFile: 'Invalid file format', - noImageData: 'No image data found in file', - turnstileInstructions: 'Complete the verification', - hideTurnstileBtn: 'Hide', - tokenCapturedSuccess: 'Token captured successfully', - autonomousModeActive: 'Autonomous mode active', - tokenSystemReady: 'Advanced token system ready', - fileOperationsAvailable: 'File operations available', - initializingToken: 'Initializing token system...', - tokenReady: 'Token system ready', - }; - - // UTILIDADES ACTUALIZADAS CON NUEVA IMPLEMENTACIƓN TURNSTILE - const Utils = { - sleep: (ms) => new Promise((r) => setTimeout(r, ms)), - - t: (key, params = {}) => { - let text = TEXTS[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; right: 20px; z-index: 10000; - padding: 12px 16px; border-radius: 8px; color: white; font-weight: 500; - background: ${type === 'success' ? '#28a745' : type === 'error' ? '#dc3545' : type === 'warning' ? '#ffc107' : '#17a2b8'}; - box-shadow: 0 4px 12px rgba(0,0,0,0.3); - max-width: 300px; word-wrap: break-word; - `; - alertDiv.textContent = message; - document.body.appendChild(alertDiv); - - setTimeout(() => { - alertDiv.style.transition = 'opacity 0.3s ease'; - alertDiv.style.opacity = '0'; - setTimeout(() => { - if (alertDiv.parentNode) alertDiv.remove(); - }, 300); - }, 4000); - }, - - addDebugLog: (message, type = 'info') => { - const timestamp = new Date().toLocaleTimeString(); - state.debugLogs.push({ timestamp, message, type }); - - if (state.debugLogs.length > 150) { - state.debugLogs = state.debugLogs.slice(-150); - } - - updateDebugConsole(); - console.log(`[${timestamp}] ${message}`); - }, - - waitForSelector: async (selector, interval = 200, timeout = 5000) => { - const start = Date.now(); - while (Date.now() - start < timeout) { - const el = document.querySelector(selector); - if (el) return el; - await Utils.sleep(interval); - } - return null; - }, - - 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(); - }), - - isWhitePixel: (r, g, b) => { - const wt = state.customWhiteThreshold || CONFIG.WHITE_THRESHOLD; - return r >= wt && g >= wt && b >= wt; - }, - - resolveColor(targetRgb, availableColors) { - if (!availableColors || availableColors.length === 0) { - return { id: null, rgb: targetRgb }; - } - - let bestId = availableColors[0].id; - let bestRgb = [...availableColors[0].rgb]; - let bestScore = Infinity; - - for (let i = 0; i < availableColors.length; i++) { - const c = availableColors[i]; - const [r, g, b] = c.rgb; - const rmean = (r + targetRgb[0]) / 2; - const rdiff = r - targetRgb[0]; - const gdiff = g - targetRgb[1]; - const bdiff = b - targetRgb[2]; - const dist = Math.sqrt( - (((512 + rmean) * rdiff * rdiff) >> 8) + - 4 * gdiff * gdiff + - (((767 - rmean) * bdiff * bdiff) >> 8) - ); - if (dist < bestScore) { - bestScore = dist; - bestId = c.id; - bestRgb = [...c.rgb]; - if (dist === 0) break; - } - } - - return { id: bestId, rgb: bestRgb }; - }, - - calculateTileRange( - startRegionX, - startRegionY, - startPixelX, - startPixelY, - width, - height, - tileSize = 1000 - ) { - const endPixelX = startPixelX + width; - const endPixelY = startPixelY + height; - - return { - startTileX: startRegionX + Math.floor(startPixelX / tileSize), - startTileY: startRegionY + Math.floor(startPixelY / tileSize), - endTileX: startRegionX + Math.floor((endPixelX - 1) / tileSize), - endTileY: startRegionY + Math.floor((endPixelY - 1) / tileSize), - }; - }, - - dynamicSleep: async function (tickAndGetRemainingMs) { - let remaining = Math.max(0, await tickAndGetRemainingMs()); - while (remaining > 0) { - const interval = remaining > 5000 ? 2000 : remaining > 1000 ? 500 : 100; - await this.sleep(Math.min(interval, remaining)); - remaining = Math.max(0, await tickAndGetRemainingMs()); - } - }, - - // NUEVA IMPLEMENTACIƓN TURNSTILE COMPLETA - ACTUALIZADA DESDE new.txt - turnstileLoaded: false, - _turnstileContainer: null, - _turnstileOverlay: null, - _turnstileWidgetId: null, - _lastSitekey: null, - _cachedSitekey: null, - - async loadTurnstile() { - if (window.turnstile) { - this.turnstileLoaded = true; - return Promise.resolve(); - } - - return new Promise((resolve, reject) => { - if (document.querySelector('script[src^="https://challenges.cloudflare.com/turnstile/v0/api.js"]')) { - const checkReady = () => { - if (window.turnstile) { - this.turnstileLoaded = true; - resolve(); - } else { - setTimeout(checkReady, 100); - } - }; - return checkReady(); - } - - const script = document.createElement('script'); - script.src = 'https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit'; - script.async = true; - script.defer = true; - script.onload = () => { - this.turnstileLoaded = true; - Utils.addDebugLog('Turnstile script loaded successfully', 'success'); - resolve(); - }; - script.onerror = () => { - Utils.addDebugLog('Failed to load Turnstile script', 'error'); - reject(new Error('Failed to load Turnstile')); - }; - document.head.appendChild(script); - }); - }, - - ensureTurnstileContainer() { - if (!this._turnstileContainer || !document.body.contains(this._turnstileContainer)) { - if (this._turnstileContainer) { - this._turnstileContainer.remove(); - } - - this._turnstileContainer = document.createElement('div'); - this._turnstileContainer.className = 'wplace-turnstile-hidden'; - this._turnstileContainer.setAttribute('aria-hidden', 'true'); - this._turnstileContainer.id = 'turnstile-widget-container'; - document.body.appendChild(this._turnstileContainer); - } - return this._turnstileContainer; - }, - - ensureTurnstileOverlayContainer() { - if (this._turnstileOverlay && document.body.contains(this._turnstileOverlay)) { - return this._turnstileOverlay; - } - - const overlay = document.createElement('div'); - overlay.id = 'turnstile-overlay-container'; - overlay.className = 'wplace-turnstile-overlay wplace-overlay-hidden'; - - const title = document.createElement('div'); - title.textContent = Utils.t('turnstileInstructions'); - title.className = 'wplace-turnstile-title'; - - const host = document.createElement('div'); - host.id = 'turnstile-overlay-host'; - host.className = 'wplace-turnstile-host'; - - const hideBtn = document.createElement('button'); - hideBtn.textContent = Utils.t('hideTurnstileBtn'); - hideBtn.className = 'wplace-turnstile-hide-btn'; - hideBtn.addEventListener('click', () => overlay.remove()); - - overlay.appendChild(title); - overlay.appendChild(host); - overlay.appendChild(hideBtn); - document.body.appendChild(overlay); - - this._turnstileOverlay = overlay; - return overlay; - }, - - async executeTurnstile(sitekey, action = 'paint') { - await this.loadTurnstile(); - - // Try reusing existing widget first if sitekey matches - if (this._turnstileWidgetId && this._lastSitekey === sitekey && window.turnstile?.execute) { - try { - console.log('šŸ”„ Reusing existing Turnstile widget...'); - const token = await Promise.race([ - window.turnstile.execute(this._turnstileWidgetId, { action }), - new Promise((_, reject) => - setTimeout(() => reject(new Error('Execute timeout')), 15000) - ), - ]); - if (token && token.length > 20) { - console.log('āœ… Token generated via widget reuse'); - return token; - } - } catch (error) { - console.log(' Widget reuse failed, will create a fresh widget:', error.message); - } - } - - // Try invisible widget first - const invisibleToken = await this.createTurnstileWidget(sitekey, action); - if (invisibleToken && invisibleToken.length > 20) { - return invisibleToken; - } - - console.log(' Falling back to interactive Turnstile (visible).'); - return await this.createTurnstileWidgetInteractive(sitekey, action); - }, - - async createTurnstileWidget(sitekey, action) { - return new Promise((resolve) => { - try { - // Force cleanup of any existing widget - if (this._turnstileWidgetId && window.turnstile?.remove) { - try { - window.turnstile.remove(this._turnstileWidgetId); - console.log('🧹 Cleaned up existing Turnstile widget'); - } catch (e) { - console.warn('āš ļø Widget cleanup warning:', e.message); - } - } - - const container = this.ensureTurnstileContainer(); - container.innerHTML = ''; - - // Verify Turnstile is available - if (!window.turnstile?.render) { - console.error('āŒ Turnstile not available for rendering'); - resolve(null); - return; - } - - console.log('šŸ”§ Creating invisible Turnstile widget...'); - const widgetId = window.turnstile.render(container, { - sitekey, - action, - size: 'invisible', - retry: 'auto', - 'retry-interval': 8000, - callback: (token) => { - console.log('āœ… Invisible Turnstile callback'); - resolve(token); - }, - 'error-callback': () => resolve(null), - 'timeout-callback': () => resolve(null), - }); - - this._turnstileWidgetId = widgetId; - this._lastSitekey = sitekey; - - if (!widgetId) { - return resolve(null); - } - - // Execute the widget and race with timeout - Promise.race([ - window.turnstile.execute(widgetId, { action }), - new Promise((_, reject) => - setTimeout(() => reject(new Error('Invisible execute timeout')), 12000) - ), - ]) - .then(resolve) - .catch(() => resolve(null)); - } catch (e) { - console.error('āŒ Invisible Turnstile creation failed:', e); - resolve(null); - } - }); - }, - - async createTurnstileWidgetInteractive(sitekey, action) { - // Create a visible widget that users can interact with if needed - console.log('šŸ”„ Creating interactive Turnstile widget (visible)'); - - return new Promise((resolve) => { - try { - // Force cleanup of any existing widget - if (this._turnstileWidgetId && window.turnstile?.remove) { - try { - window.turnstile.remove(this._turnstileWidgetId); - } catch (e) { - console.warn('āš ļø Widget cleanup warning:', e.message); - } - } - - const overlay = this.ensureTurnstileOverlayContainer(); - overlay.classList.remove('wplace-overlay-hidden'); - overlay.style.display = 'block'; - - const host = overlay.querySelector('#turnstile-overlay-host'); - host.innerHTML = ''; - - // Set a timeout for interactive mode - const timeout = setTimeout(() => { - console.warn('ā° Interactive Turnstile widget timeout'); - overlay.classList.add('wplace-overlay-hidden'); - overlay.style.display = 'none'; - resolve(null); - }, 60000); // 60 seconds for user interaction - - const widgetId = window.turnstile.render(host, { - sitekey, - action, - size: 'normal', - theme: 'light', - callback: (token) => { - clearTimeout(timeout); - overlay.classList.add('wplace-overlay-hidden'); - overlay.style.display = 'none'; - console.log('āœ… Interactive Turnstile completed successfully'); - - if (typeof token === 'string' && token.length > 20) { - resolve(token); - } else { - console.warn('āŒ Invalid token from interactive widget'); - resolve(null); - } - }, - 'error-callback': (error) => { - clearTimeout(timeout); - overlay.classList.add('wplace-overlay-hidden'); - overlay.style.display = 'none'; - console.warn('āŒ Interactive Turnstile error:', error); - resolve(null); - }, - }); - - this._turnstileWidgetId = widgetId; - this._lastSitekey = sitekey; - - if (!widgetId) { - clearTimeout(timeout); - overlay.classList.add('wplace-overlay-hidden'); - overlay.style.display = 'none'; - console.warn('āŒ Failed to create interactive Turnstile widget'); - resolve(null); - } else { - console.log('āœ… Interactive Turnstile widget created, waiting for user interaction...'); - } - } catch (e) { - console.error('āŒ Interactive Turnstile creation failed:', e); - resolve(null); - } - }); - }, - - cleanupTurnstile() { - if (this._turnstileWidgetId && window.turnstile?.remove) { - try { - window.turnstile.remove(this._turnstileWidgetId); - } catch (e) { - console.warn('Failed to cleanup Turnstile widget:', e); - } - } - - if (this._turnstileContainer && document.body.contains(this._turnstileContainer)) { - this._turnstileContainer.remove(); - } - - if (this._turnstileOverlay && document.body.contains(this._turnstileOverlay)) { - this._turnstileOverlay.remove(); - } - - this._turnstileWidgetId = null; - this._turnstileContainer = null; - this._turnstileOverlay = null; - this._lastSitekey = null; - }, - - // DETECCIƓN DE SITEKEY MEJORADA - ACTUALIZADA DESDE new.txt - async obtainSitekeyAndToken(fallback = '0x4AAAAAABpqJe8FO0N84q0F') { - // Cache sitekey to avoid repeated DOM queries - if (this._cachedSitekey) { - console.log('šŸ” Using cached sitekey:', this._cachedSitekey); - - return isTokenValid() - ? { - sitekey: this._cachedSitekey, - token: turnstileToken, - } - : { sitekey: this._cachedSitekey, token: null }; - } - - // List of potential sitekeys to try - const potentialSitekeys = [ - '0x4AAAAAABpqJe8FO0N84q0F', // WPlace common sitekey - '0x4AAAAAAAJ7xjKAp6Mt_7zw', // Alternative WPlace sitekey - '0x4AAAAAADm5QWx6Ov2LNF2g', // Another common sitekey - ]; - const trySitekey = async (sitekey, source) => { - if (!sitekey || sitekey.length < 10) return null; - - console.log(`šŸ” Testing sitekey from ${source}:`, sitekey); - const token = await this.executeTurnstile(sitekey); - - if (token && token.length >= 20) { - console.log(`āœ… Valid token generated from ${source} sitekey`); - setTurnstileToken(token); - this._cachedSitekey = sitekey; - return { sitekey, token }; - } else { - console.log(`āŒ Failed to get token from ${source} sitekey`); - return null; - } - }; - - try { - // 1ļøāƒ£ data-sitekey attribute - const sitekeySel = document.querySelector('[data-sitekey]'); - if (sitekeySel) { - const sitekey = sitekeySel.getAttribute('data-sitekey'); - const result = await trySitekey(sitekey, 'data attribute'); - if (result) { - return result; - } - } - - // 2ļøāƒ£ Turnstile element - const turnstileEl = document.querySelector('.cf-turnstile'); - if (turnstileEl?.dataset?.sitekey) { - const sitekey = turnstileEl.dataset.sitekey; - const result = await trySitekey(sitekey, 'turnstile element'); - if (result) { - return result; - } - } - - // 3ļøāƒ£ Meta tags - const metaTags = document.querySelectorAll( - 'meta[name*="turnstile"], meta[property*="turnstile"]' - ); - for (const meta of metaTags) { - const content = meta.getAttribute('content'); - const result = await trySitekey(content, 'meta tag'); - if (result) { - return result; - } - } - - // 4ļøāƒ£ Global variable - if (window.__TURNSTILE_SITEKEY) { - const result = await trySitekey(window.__TURNSTILE_SITEKEY, 'global variable'); - if (result) { - return result; - } - } - - // 5ļøāƒ£ Script tags - const scripts = document.querySelectorAll('script'); - for (const script of scripts) { - const content = script.textContent || script.innerHTML; - const match = content.match( - /(?:sitekey|data-sitekey)['"\s\[\]:\=\(]*['"]?([0-9a-zA-Z_-]{20,})['"]?/i - ); - if (match && match[1]) { - const extracted = match[1].replace(/['"]/g, ''); - const result = await trySitekey(extracted, 'script content'); - if (result) { - return result; - } - } - } - - // 6ļøāƒ£ Known potential sitekeys - console.log('šŸ” Testing known potential sitekeys...'); - for (const testSitekey of potentialSitekeys) { - const result = await trySitekey(testSitekey, 'known list'); - if (result) { - return result; - } - } - } catch (error) { - console.warn('āš ļø Error during sitekey detection:', error); - } - - // 7ļøāƒ£ Fallback - console.log('šŸ”§ Trying fallback sitekey:', fallback); - const fallbackResult = await trySitekey(fallback, 'fallback'); - if (fallbackResult) { - return fallbackResult; - } - - console.error('āŒ No working sitekey or token found.'); - return { sitekey: null, token: null }; - }, - }; - - // Enhanced Overlay Manager for pixel detection with autonomous capabilities - class OverlayManager { - constructor() { - this.originalTiles = new Map(); - this.originalTilesData = new Map(); - this.tileSize = 1000; - this.loadingPromises = new Map(); - this.autonomousMode = state.autonomousMode; - } - - async processAndRespondToTileRequest(eventData) { - const { endpoint, blobID, blobData } = eventData; - - 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}`; - - try { - const originalBitmap = await createImageBitmap(blobData); - this.originalTiles.set(tileKey, originalBitmap); - - try { - let canvas, ctx; - if (typeof OffscreenCanvas !== 'undefined') { - canvas = new OffscreenCanvas(originalBitmap.width, originalBitmap.height); - ctx = canvas.getContext('2d'); - } else { - canvas = document.createElement('canvas'); - canvas.width = originalBitmap.width; - canvas.height = originalBitmap.height; - ctx = canvas.getContext('2d'); - } - ctx.imageSmoothingEnabled = false; - ctx.drawImage(originalBitmap, 0, 0); - const imgData = ctx.getImageData(0, 0, originalBitmap.width, originalBitmap.height); - - this.originalTilesData.set(tileKey, { - w: originalBitmap.width, - h: originalBitmap.height, - data: new Uint8ClampedArray(imgData.data), - }); - - if (this.autonomousMode) { - Utils.addDebugLog(`Auto-cached tile: ${tileKey} (${originalBitmap.width}x${originalBitmap.height})`, 'info'); - } - } catch (e) { - Utils.addDebugLog(`Failed to cache tile ImageData: ${tileKey} - ${e.message}`, 'warning'); - } - } catch (e) { - Utils.addDebugLog(`Failed to create tile bitmap: ${tileKey} - ${e.message}`, 'error'); - } - } - - window.postMessage( - { - source: 'auto-image-overlay', - blobID: blobID, - blobData: blobData, - }, - '*' - ); - } - - async getTilePixelColor(tileX, tileY, pixelX, pixelY) { - const tileKey = `${tileX},${tileY}`; - const alphaThresh = state.customTransparencyThreshold || CONFIG.TRANSPARENCY_THRESHOLD; - - const cached = this.originalTilesData.get(tileKey); - if (cached && cached.data && cached.w > 0 && cached.h > 0) { - const x = Math.max(0, Math.min(cached.w - 1, pixelX)); - const y = Math.max(0, Math.min(cached.h - 1, pixelY)); - const idx = (y * cached.w + x) * 4; - const d = cached.data; - const a = d[idx + 3]; - - // Always return pixel data, let caller decide about transparency - return [d[idx], d[idx + 1], d[idx + 2], a]; - } - - const bitmap = this.originalTiles.get(tileKey); - if (!bitmap) { - if (this.autonomousMode) { - Utils.addDebugLog(`Tile ${tileKey} not available, requesting load...`, 'warning'); - } - return null; - } - - try { - let canvas, ctx; - if (typeof OffscreenCanvas !== 'undefined') { - canvas = new OffscreenCanvas(bitmap.width, bitmap.height); - ctx = canvas.getContext('2d'); - } else { - canvas = document.createElement('canvas'); - canvas.width = bitmap.width; - canvas.height = bitmap.height; - ctx = canvas.getContext('2d'); - } - ctx.imageSmoothingEnabled = false; - ctx.drawImage(bitmap, 0, 0); - - const x = Math.max(0, Math.min(bitmap.width - 1, pixelX)); - const y = Math.max(0, Math.min(bitmap.height - 1, pixelY)); - const data = ctx.getImageData(x, y, 1, 1).data; - const a = data[3]; - - // Always return pixel data, let caller decide about transparency - return [data[0], data[1], data[2], a]; - } catch (e) { - Utils.addDebugLog(`Error reading pixel from tile ${tileKey}: ${e.message}`, 'error'); - return null; - } - } - - async waitForTiles(startRegionX, startRegionY, pixelWidth, pixelHeight, startPixelX = 0, startPixelY = 0, timeoutMs = 15000) { - const { startTileX, startTileY, endTileX, endTileY } = Utils.calculateTileRange( - startRegionX, - startRegionY, - startPixelX, - startPixelY, - pixelWidth, - pixelHeight, - this.tileSize - ); - - const requiredTiles = []; - for (let ty = startTileY; ty <= endTileY; ty++) { - for (let tx = startTileX; tx <= endTileX; tx++) { - requiredTiles.push(`${tx},${ty}`); - } - } - - if (requiredTiles.length === 0) return true; - - Utils.addDebugLog(`Waiting for ${requiredTiles.length} tiles (autonomous: ${this.autonomousMode})...`, 'info'); - - const startTime = Date.now(); - let lastProgress = 0; - - while (Date.now() - startTime < timeoutMs) { - if (state.stopFlag) { - Utils.addDebugLog('waitForTiles: stopped by user', 'warning'); - return false; - } - - const loaded = requiredTiles.filter((k) => this.originalTiles.has(k)).length; - const progress = Math.round((loaded / requiredTiles.length) * 100); - - if (progress !== lastProgress && progress % 20 === 0) { - Utils.addDebugLog(`Tile loading progress: ${loaded}/${requiredTiles.length} (${progress}%)`, 'info'); - lastProgress = progress; - } - - if (loaded === requiredTiles.length) { - Utils.addDebugLog(`All ${requiredTiles.length} required tiles are loaded`, 'success'); - return true; - } - - await Utils.sleep(this.autonomousMode ? 500 : 1000); - } - - const loaded = requiredTiles.filter((k) => this.originalTiles.has(k)).length; - Utils.addDebugLog(`Timeout waiting for tiles: ${loaded}/${requiredTiles.length} loaded`, 'warning'); - - if (this.autonomousMode && loaded > requiredTiles.length * 0.8) { - Utils.addDebugLog(`Autonomous mode: proceeding with ${loaded}/${requiredTiles.length} tiles (80%+ loaded)`, 'warning'); - return true; - } - - return loaded > 0; - } - } - - const overlayManager = new OverlayManager(); - - // Enhanced WPlace API Service with auto-batch functionality - const WPlaceService = { - async paintPixelInRegion(regionX, regionY, pixelX, pixelY, color, retryCount = 0) { - try { - await ensureToken(); - if (!turnstileToken) { - Utils.addDebugLog('No valid token available for paint request', 'error'); - return 'token_error'; - } - - const payload = { - coords: [pixelX, pixelY], - colors: [color], - t: turnstileToken, - fp: fpStr32, - }; - - const wasmToken = await createWasmToken(regionX, regionY, payload); - if (!wasmToken) { - Utils.addDebugLog('Failed to generate WASM token', 'error'); - return false; - } - - const res = await fetch(`https://backend.wplace.live/s0/pixel/${regionX}/${regionY}`, { - method: 'POST', - headers: { - 'Content-Type': 'text/plain;charset=UTF-8', - 'x-pawtect-token': wasmToken - }, - credentials: 'include', - body: JSON.stringify(payload), - }); - - if (res.status === 403) { - Utils.addDebugLog(`403 Forbidden for pixel (${pixelX},${pixelY}). Token invalid/expired.`, 'error'); - invalidateToken(); - - if (retryCount < 2 && state.autonomousMode) { - Utils.addDebugLog(`Autonomous retry ${retryCount + 1}/2 for pixel (${pixelX},${pixelY})`, 'warning'); - await Utils.sleep(1000); - return await this.paintPixelInRegion(regionX, regionY, pixelX, pixelY, color, retryCount + 1); - } - - return 'token_error'; - } - - if (!res.ok) { - Utils.addDebugLog(`Paint request failed with status ${res.status}`, 'error'); - return false; - } - - const data = await res.json(); - const success = data?.painted === 1; - - if (success) { - Utils.addDebugLog(`Paint SUCCESS for (${pixelX},${pixelY}) with color ${color}`, 'success'); - } else { - Utils.addDebugLog(`Paint FAILED for (${pixelX},${pixelY}) - server response: ${JSON.stringify(data)}`, 'error'); - } - - return success; - } catch (e) { - Utils.addDebugLog(`Paint request error for (${pixelX},${pixelY}): ${e.message}`, 'error'); - return false; - } - }, - - // NEW: Auto-batch pixel painting functionality - async paintPixelBatchInRegion(regionX, regionY, pixelBatch, retryCount = 0) { - try { - await ensureToken(); - if (!turnstileToken) { - Utils.addDebugLog('No valid token available for batch paint request', 'error'); - return 'token_error'; - } - - // Prepare batch coordinates and colors - const coords = []; - const colors = []; - - for (const pixel of pixelBatch) { - coords.push(pixel.pixelX, pixel.pixelY); - colors.push(pixel.color); - } - - const payload = { - coords: coords, - colors: colors, - t: turnstileToken, - fp: fpStr32, - }; - - const wasmToken = await createWasmToken(regionX, regionY, payload); - if (!wasmToken) { - Utils.addDebugLog('Failed to generate WASM token for batch', 'error'); - return false; - } - - const res = await fetch(`https://backend.wplace.live/s0/pixel/${regionX}/${regionY}`, { - method: 'POST', - headers: { - 'Content-Type': 'text/plain;charset=UTF-8', - 'x-pawtect-token': wasmToken - }, - credentials: 'include', - body: JSON.stringify(payload), - }); - - if (res.status === 403) { - Utils.addDebugLog(`403 Forbidden for batch (${pixelBatch.length} pixels). Token invalid/expired.`, 'error'); - invalidateToken(); - - if (retryCount < 2 && state.autonomousMode) { - Utils.addDebugLog(`Autonomous batch retry ${retryCount + 1}/2 for ${pixelBatch.length} pixels`, 'warning'); - await Utils.sleep(1000); - return await this.paintPixelBatchInRegion(regionX, regionY, pixelBatch, retryCount + 1); - } - - return 'token_error'; - } - - if (!res.ok) { - Utils.addDebugLog(`Batch paint request failed with status ${res.status}`, 'error'); - return false; - } - - const data = await res.json(); - const successCount = data?.painted || 0; - - Utils.addDebugLog(`Batch paint result: ${successCount}/${pixelBatch.length} pixels painted successfully`, - successCount === pixelBatch.length ? 'success' : 'warning'); - - return { - success: successCount > 0, - painted: successCount, - total: pixelBatch.length - }; - - } catch (e) { - Utils.addDebugLog(`Batch paint request error for ${pixelBatch.length} pixels: ${e.message}`, 'error'); - return false; - } - }, - - async getCharges() { - try { - const res = await fetch('https://backend.wplace.live/me', { - credentials: 'include', - }); - if (!res.ok) return { charges: 0, max: 1, cooldown: CONFIG.COOLDOWN_DEFAULT }; - const data = await res.json(); - return { - charges: data.charges?.count ?? 0, - max: data.charges?.max ?? 1, - cooldown: data.charges?.cooldownMs ?? CONFIG.COOLDOWN_DEFAULT, - }; - } catch (e) { - Utils.addDebugLog(`Error fetching charges: ${e.message}`, 'warning'); - return { charges: 0, max: 1, cooldown: CONFIG.COOLDOWN_DEFAULT }; - } - }, - }; - - // Enhanced anti-grief repair system with improved transparent pixel detection - async function scanForDamage() { - if (!state.imageData || !state.startPosition || !state.region) { - Utils.addDebugLog('No image data or position for scanning', 'warning'); - return []; - } - - Utils.addDebugLog('Starting enhanced damage scan with transparent pixel detection...', 'info'); - updateStatus(Utils.t('scanningForDamage')); - - const damagedPixels = []; - const { width, height, pixels } = state.imageData; - - // Check if we're working with restored data - skip tile waiting for restored saves - const isRestoredData = state.availableColors && state.availableColors.length > 0 && state.colorsChecked; - - if (!isRestoredData) { - const ready = await overlayManager.waitForTiles( - state.region.x, - state.region.y, - width, - height, - state.startPosition.x, - state.startPosition.y, - state.autonomousMode ? 20000 : 15000 - ); - - if (!ready) { - Utils.addDebugLog('Failed to load required tiles for scanning', 'error'); - if (state.autonomousMode) { - Utils.addDebugLog('Autonomous mode: will retry scan in 30 seconds', 'warning'); - setTimeout(() => { - if (state.autoRepairEnabled && !state.stopFlag) { - scanForDamage(); - } - }, 30000); - } - return []; - } - } else { - Utils.addDebugLog('Using restored data - skipping tile wait for damage scan', 'info'); - } - - Utils.addDebugLog(`Scanning ${width}x${height} image for damage (transparent detection: ${state.paintTransparentPixels})...`, 'info'); - - let scannedPixels = 0; - let transparentPixelsDetected = 0; - let wrongColorPixelsDetected = 0; - let lastProgressUpdate = Date.now(); - - for (let y = 0; y < height; y++) { - for (let x = 0; x < width; x++) { - if (state.stopFlag) break; - - const idx = (y * width + x) * 4; - const originalR = pixels[idx]; - const originalG = pixels[idx + 1]; - const originalB = pixels[idx + 2]; - const originalA = pixels[idx + 3]; - - // Enhanced transparent pixel detection - const isOriginalTransparent = originalA < state.customTransparencyThreshold; - - // Skip white pixels if configured - if (!state.paintWhitePixels && Utils.isWhitePixel(originalR, originalG, originalB)) { - continue; - } - - scannedPixels++; - - const absX = state.startPosition.x + x; - const absY = state.startPosition.y + y; - const tileX = state.region.x + Math.floor(absX / 1000); - const tileY = state.region.y + Math.floor(absY / 1000); - const pixelX = absX % 1000; - const pixelY = absY % 1000; - - try { - const currentPixel = await overlayManager.getTilePixelColor(tileX, tileY, pixelX, pixelY); - - // Handle missing current pixel data (could indicate transparent area) - if (!currentPixel) { - if (state.paintTransparentPixels && !isOriginalTransparent) { - // Original pixel is not transparent but current is missing/transparent - const targetColor = Utils.resolveColor([originalR, originalG, originalB], state.availableColors); - damagedPixels.push({ - x, - y, - originalColor: targetColor, - currentColor: { id: 63, name: 'Transparent', rgb: [0, 0, 0] }, // Transparent color - originalRgb: [originalR, originalG, originalB], - currentRgb: [0, 0, 0], - isDamagedTransparent: true - }); - transparentPixelsDetected++; - - if (!state.autonomousMode || transparentPixelsDetected <= 10) { - Utils.addDebugLog(`Missing/Transparent pixel at (${x},${y}): expected color ${targetColor.id}, found transparent/missing`, 'warning'); - } - } - continue; - } - - const currentIsTransparent = currentPixel[3] < state.customTransparencyThreshold; - - // Case 1: Original is not transparent, but current is transparent (damaged) - if (!isOriginalTransparent && currentIsTransparent && state.paintTransparentPixels) { - const targetColor = Utils.resolveColor([originalR, originalG, originalB], state.availableColors); - damagedPixels.push({ - x, - y, - originalColor: targetColor, - currentColor: { id: 63, name: 'Transparent', rgb: [0, 0, 0] }, - originalRgb: [originalR, originalG, originalB], - currentRgb: currentPixel.slice(0, 3), - isDamagedTransparent: true - }); - transparentPixelsDetected++; - - if (!state.autonomousMode || transparentPixelsDetected <= 10) { - Utils.addDebugLog(`Transparent damage at (${x},${y}): expected color ${targetColor.id}, found transparent`, 'warning'); - } - continue; - } - - // Case 2: Original is transparent, current is not transparent (wrong placement) - if (isOriginalTransparent && !currentIsTransparent && state.paintTransparentPixels) { - damagedPixels.push({ - x, - y, - originalColor: { id: 63, name: 'Transparent', rgb: null }, - currentColor: Utils.resolveColor(currentPixel.slice(0, 3), state.availableColors), - originalRgb: [originalR, originalG, originalB], - currentRgb: currentPixel.slice(0, 3), - isDamagedTransparent: true - }); - transparentPixelsDetected++; - - if (!state.autonomousMode || transparentPixelsDetected <= 10) { - Utils.addDebugLog(`Wrong placement at (${x},${y}): expected transparent, found color`, 'warning'); - } - continue; - } - - // Case 3: Both are not transparent, check color match (normal damage detection) - if (!isOriginalTransparent && !currentIsTransparent) { - const targetColor = Utils.resolveColor([originalR, originalG, originalB], state.availableColors); - const currentColor = Utils.resolveColor(currentPixel.slice(0, 3), state.availableColors); - - if (targetColor.id === currentColor.id) { - // Pixel is already correctly painted - skip it - continue; - } - - // Pixel has wrong color - mark as damaged - damagedPixels.push({ - x, - y, - originalColor: targetColor, - currentColor: currentColor, - originalRgb: [originalR, originalG, originalB], - currentRgb: currentPixel.slice(0, 3), - isDamagedTransparent: false - }); - wrongColorPixelsDetected++; - - if (!state.autonomousMode || wrongColorPixelsDetected <= 10) { - Utils.addDebugLog(`Color damage at (${x},${y}): expected color ${targetColor.id}, found color ${currentColor.id}`, 'warning'); - } - } - - } catch (e) { - if (!state.autonomousMode) { - Utils.addDebugLog(`Error checking pixel (${x},${y}): ${e.message}`, 'error'); - } - } - } - - if (state.autonomousMode && Date.now() - lastProgressUpdate > 5000) { - Utils.addDebugLog(`Scan progress: ${y}/${height} rows (${Math.round((y / height) * 100)}%), found ${damagedPixels.length} damaged (${transparentPixelsDetected} transparent, ${wrongColorPixelsDetected} wrong color)`, 'info'); - lastProgressUpdate = Date.now(); - } else if (!state.autonomousMode && y % 10 === 0) { - Utils.addDebugLog(`Scan progress: ${y}/${height} rows (${Math.round((y / height) * 100)}%)`, 'info'); - } - } - - const logLevel = damagedPixels.length > 0 ? 'warning' : 'success'; - Utils.addDebugLog(`Enhanced scan complete. Checked ${scannedPixels} pixels, found ${damagedPixels.length} damaged (${transparentPixelsDetected} transparent issues, ${wrongColorPixelsDetected} wrong colors)`, logLevel); - - // Smart auto-adjust batch size: if 6 pixels detected, batch size = 6 - if (state.autoBatchEnabled && state.batchOptimization && damagedPixels.length > 0) { - const newBatchSize = Math.min(CONFIG.MAX_BATCH_SIZE, Math.max(CONFIG.MIN_BATCH_SIZE, damagedPixels.length)); - if (newBatchSize !== state.currentBatchSize) { - state.currentBatchSize = newBatchSize; - Utils.addDebugLog(`Smart batch: Auto-adjusted batch size to ${newBatchSize} to match ${damagedPixels.length} detected damaged pixels`, 'info'); - updateBatchInfo(); // Update UI display - } - } - - return damagedPixels; - } - - async function repairDamagedPixels(damagedPixels) { - if (damagedPixels.length === 0) { - updateStatus(Utils.t('noDamageDetected')); - return 0; - } - - Utils.addDebugLog(`Starting enhanced repair of ${damagedPixels.length} pixels with auto-batch (batch size: ${state.currentBatchSize})`, 'info'); - updateStatus(Utils.t('repairingPixels', { count: damagedPixels.length })); - - let repairedCount = 0; - let consecutiveFailures = 0; - const maxConsecutiveFailures = state.autonomousMode ? 5 : 3; - - // Group pixels by region for batch processing - const pixelsByRegion = new Map(); - - for (const pixel of damagedPixels) { - const absX = state.startPosition.x + pixel.x; - const absY = state.startPosition.y + pixel.y; - const regionX = state.region.x + Math.floor(absX / 1000); - const regionY = state.region.y + Math.floor(absY / 1000); - const regionKey = `${regionX},${regionY}`; - - if (!pixelsByRegion.has(regionKey)) { - pixelsByRegion.set(regionKey, []); - } - - pixelsByRegion.get(regionKey).push({ - ...pixel, - regionX, - regionY, - pixelX: absX % 1000, - pixelY: absY % 1000, - color: pixel.isDamagedTransparent && pixel.originalColor.id === 63 ? 63 : pixel.originalColor.id - }); - } - - Utils.addDebugLog(`Grouped ${damagedPixels.length} pixels into ${pixelsByRegion.size} regions for batch processing`, 'info'); - - // Process each region - for (const [regionKey, regionPixels] of pixelsByRegion) { - if (state.stopFlag) { - Utils.addDebugLog('Repair stopped by user request', 'warning'); - break; - } - - const [regionX, regionY] = regionKey.split(',').map(Number); - Utils.addDebugLog(`Processing region ${regionKey} with ${regionPixels.length} pixels`, 'info'); - - // Process pixels in batches - for (let i = 0; i < regionPixels.length; i += state.currentBatchSize) { - if (state.stopFlag) break; - - const batch = regionPixels.slice(i, i + state.currentBatchSize); - const actualBatchSize = Math.min(batch.length, state.currentBatchSize); - - // Enhanced charge management for batch processing - await updateCharges(); - let chargeWaitAttempts = 0; - const maxChargeWaitAttempts = state.autonomousMode ? 20 : 10; - - while (state.displayCharges < actualBatchSize && !state.stopFlag && chargeWaitAttempts < maxChargeWaitAttempts) { - chargeWaitAttempts++; - const waitTime = state.autonomousMode ? Math.min(state.cooldown, 10000) : state.cooldown; - - if (chargeWaitAttempts === 1) { - Utils.addDebugLog(`Waiting for charges... (need ${actualBatchSize}, have ${state.displayCharges}/${state.maxCharges})`, 'info'); - } - - await Utils.dynamicSleep(() => { - if (state.displayCharges >= actualBatchSize) return 0; - if (state.stopFlag) return 0; - return waitTime; - }); - await updateCharges(); - } - - if (state.stopFlag) break; - - if (state.displayCharges < actualBatchSize) { - Utils.addDebugLog(`Insufficient charges for batch (need ${actualBatchSize}, have ${state.displayCharges}), falling back to single pixel repair`, 'warning'); - - // Fall back to single pixel repair - for (const pixel of batch) { - if (state.stopFlag) break; - - await updateCharges(); - if (state.displayCharges < 1) { - Utils.addDebugLog(`No charges available, skipping pixel (${pixel.x},${pixel.y})`, 'warning'); - continue; - } - - const success = await repairSinglePixel(pixel); - if (success) { - repairedCount++; - consecutiveFailures = 0; - } else { - consecutiveFailures++; - } - - if (consecutiveFailures >= maxConsecutiveFailures) break; - await Utils.sleep(100); - } - } else { - // Use batch repair - const batchResult = await repairPixelBatch(regionX, regionY, batch); - - if (batchResult && batchResult.success) { - repairedCount += batchResult.painted; - consecutiveFailures = 0; - - Utils.addDebugLog(`Batch repair: ${batchResult.painted}/${batchResult.total} pixels repaired successfully [Total: ${repairedCount}/${damagedPixels.length}]`, 'success'); - } else { - consecutiveFailures++; - Utils.addDebugLog(`Batch repair failed for ${batch.length} pixels [${consecutiveFailures}/${maxConsecutiveFailures} consecutive failures]`, 'error'); - - if (consecutiveFailures >= maxConsecutiveFailures) { - Utils.addDebugLog(`Too many consecutive batch failures (${consecutiveFailures}), pausing repair`, 'error'); - if (state.autonomousMode) { - Utils.addDebugLog('Autonomous mode: will retry repair in 60 seconds', 'warning'); - setTimeout(() => { - if (state.autoRepairEnabled && !state.stopFlag) { - performRepairCheck(); - } - }, 60000); - } - break; - } - } - } - - await updateCharges(); - - // Dynamic delay based on mode and success rate - const baseDelay = state.autonomousMode ? 100 : 200; - const adaptiveDelay = consecutiveFailures > 0 ? baseDelay * (consecutiveFailures + 1) : baseDelay; - await Utils.sleep(adaptiveDelay); - } - - if (consecutiveFailures >= maxConsecutiveFailures) break; - } - - const message = Utils.t('repairComplete', { repaired: repairedCount }); - updateStatus(message); - Utils.addDebugLog(`${message} (${damagedPixels.length - repairedCount} remaining) - Used auto-batch with size ${state.currentBatchSize}`, 'success'); - - return repairedCount; - } - - async function repairPixelBatch(regionX, regionY, pixelBatch) { - try { - Utils.addDebugLog(`Attempting batch repair of ${pixelBatch.length} pixels in region ${regionX},${regionY}`, 'info'); - - const result = await WPlaceService.paintPixelBatchInRegion(regionX, regionY, pixelBatch); - - if (result === 'token_error') { - Utils.addDebugLog('Token error during batch repair, refreshing token...', 'warning'); - await ensureToken(true); - await Utils.sleep(state.autonomousMode ? 500 : 1000); - - // Retry once with new token - const retryResult = await WPlaceService.paintPixelBatchInRegion(regionX, regionY, pixelBatch); - return retryResult; - } - - return result; - } catch (e) { - Utils.addDebugLog(`Error during batch repair: ${e.message}`, 'error'); - return false; - } - } - - async function repairSinglePixel(pixel) { - const { x, y, originalColor, regionX, regionY, pixelX, pixelY, color } = pixel; - - try { - const result = await WPlaceService.paintPixelInRegion( - regionX, - regionY, - pixelX, - pixelY, - color || originalColor.id - ); - - if (result === 'token_error') { - Utils.addDebugLog('Token error during repair, refreshing token...', 'warning'); - await ensureToken(true); - await Utils.sleep(state.autonomousMode ? 500 : 1000); - - // Retry once with new token - const retryResult = await WPlaceService.paintPixelInRegion(regionX, regionY, pixelX, pixelY, color || originalColor.id); - return retryResult === true; - } - - if (result === true) { - if (pixel.isDamagedTransparent) { - Utils.addDebugLog(`Repaired transparent pixel (${x},${y}) with color ${color || originalColor.id}`, 'success'); - } - } - - return result === true; - } catch (e) { - Utils.addDebugLog(`Error repairing pixel (${x},${y}): ${e.message}`, 'error'); - return false; - } - } - - async function performRepairCheck() { - if (state.running) { - Utils.addDebugLog('Repair check skipped - manual repair in progress', 'info'); - return; - } - - try { - Utils.addDebugLog('Autonomous repair check triggered', 'info'); - - // Ensure we have a valid token before starting - if (!isTokenValid()) { - Utils.addDebugLog('No valid token for autonomous repair, generating...', 'warning'); - await ensureToken(true); - if (!isTokenValid()) { - Utils.addDebugLog('Failed to generate token for autonomous repair, will retry next cycle', 'error'); - return; - } - } - - const damagedPixels = await scanForDamage(); - - if (damagedPixels.length > 0) { - Utils.addDebugLog(`Autonomous repair: Found ${damagedPixels.length} damaged pixels, starting repair`, 'warning'); - updateStatus(Utils.t('damageDetected', { count: damagedPixels.length })); - - // Show attack notification - showAttackNotification('attack', damagedPixels.length); - state.lastAttackState = 'attacked'; - - const repairedCount = await repairDamagedPixels(damagedPixels); - - // Show repair complete notification if all pixels were repaired - if (repairedCount === damagedPixels.length) { - showAttackNotification('repaired'); - state.lastAttackState = 'repaired'; - } - } else { - updateStatus(Utils.t('noDamageDetected')); - - // Show peaceful state if we're not under attack - if (state.lastAttackState !== 'peaceful') { - showPeacefulState(); - state.lastAttackState = 'peaceful'; - } - - if (!state.autonomousMode) { - Utils.addDebugLog('Autonomous repair: No damage detected', 'success'); - } - } - } catch (error) { - Utils.addDebugLog(`Autonomous repair error: ${error.message}`, 'error'); - - if (state.autonomousMode) { - Utils.addDebugLog('Autonomous mode: will retry repair check in 120 seconds due to error', 'warning'); - setTimeout(() => { - if (state.autoRepairEnabled && !state.stopFlag) { - performRepairCheck(); - } - }, 120000); - } - } - } - - function startAutoRepair() { - if (state.autoRepairTimer) { - clearInterval(state.autoRepairTimer); - } - - const intervalMs = state.autoRepairInterval * 1000; - state.autoRepairTimer = setInterval(performRepairCheck, intervalMs); - - const message = Utils.t('autoRepairStarted', { interval: state.autoRepairInterval }); - Utils.addDebugLog(`${message} (autonomous: ${state.autonomousMode})`, 'info'); - Utils.showAlert(message, 'success'); - - // Perform first check immediately - setTimeout(performRepairCheck, 2000); - } - - function stopAutoRepair() { - if (state.autoRepairTimer) { - clearInterval(state.autoRepairTimer); - state.autoRepairTimer = null; - } - - const message = Utils.t('autoRepairStopped'); - Utils.addDebugLog(`${message}`, 'info'); - Utils.showAlert(message, 'info'); - } - - // FUNCIONES DE UI MEJORADAS CON GESTIƓN DE CONFIGURACIƓN INICIAL - function enableFileOperations() { - state.initialSetupComplete = true; - - const loadBtn = document.querySelector('#loadFileBtn'); - if (loadBtn) { - loadBtn.disabled = false; - loadBtn.title = ''; - loadBtn.style.animation = 'pulse 0.6s ease-in-out'; - setTimeout(() => { - if (loadBtn) loadBtn.style.animation = ''; - }, 600); - Utils.addDebugLog('Load Progress button enabled after initial setup', 'success'); - } - - Utils.showAlert(Utils.t('fileOperationsAvailable'), 'success'); - } - - async function initializeTokenGenerator() { - if (isTokenValid()) { - Utils.addDebugLog('Valid token already available, skipping initialization', 'success'); - updateTokenStatus(); - enableFileOperations(); - return; - } - - try { - Utils.addDebugLog('Initializing Turnstile token generator...', 'info'); - updateStatus(Utils.t('initializingToken')); - - await Utils.loadTurnstile(); - Utils.addDebugLog('Turnstile script loaded successfully', 'success'); - - if (state.autoTokenRefresh) { - Utils.addDebugLog('Pre-generating token for autonomous operations...', 'info'); - await ensureToken(); - } - - state.initialSetupComplete = true; - Utils.addDebugLog('Enhanced token generator initialization complete', 'success'); - updateStatus(Utils.t('tokenReady')); - enableFileOperations(); - - } catch (error) { - Utils.addDebugLog('Token system initialization failed: ' + error.message, 'warning'); - Utils.addDebugLog('Manual token generation will be required', 'info'); - enableFileOperations(); - } - } - - function updateStatus(message) { - const statusEl = document.getElementById('status'); - if (statusEl) { - if (typeof message === 'string') { - statusEl.innerHTML = message; - } else { - statusEl.textContent = message; - } - } - } - - function updateDebugConsole() { - const debugConsole = document.getElementById('debugConsole'); - if (!debugConsole) return; - - const logsHtml = state.debugLogs.map(log => { - const colorClass = log.type === 'error' ? 'neon-text danger' : - log.type === 'warning' ? 'neon-text warning' : - log.type === 'success' ? 'neon-text success' : - log.type === 'info' ? 'neon-text info' : 'neon-text'; - - return `
- [${log.timestamp}] ${log.message} -
`; - }).join(''); - - debugConsole.innerHTML = logsHtml; - debugConsole.scrollTop = debugConsole.scrollHeight; - } - - async function updateCharges() { - try { - const { charges, max, cooldown } = await WPlaceService.getCharges(); - state.displayCharges = Math.floor(charges); - state.maxCharges = Math.max(1, Math.floor(max)); - state.cooldown = cooldown; - - updateChargesDisplay(); - } catch (error) { - if (!state.autonomousMode) { - Utils.addDebugLog(`Error updating charges: ${error.message}`, 'error'); - } - } - } - - function updateChargesDisplay() { - const chargesEl = document.getElementById('chargesInfo'); - if (chargesEl) { - chargesEl.textContent = `Charges: ${state.displayCharges}/${state.maxCharges} (cooldown: ${Math.round(state.cooldown / 1000)}s)`; - } - } - - function updateTokenStatus() { - const tokenEl = document.getElementById('tokenInfo'); - if (tokenEl) { - // Remove existing classes first - tokenEl.className = ''; - - if (isTokenValid()) { - const remaining = Math.round((tokenExpiryTime - Date.now()) / 1000); - tokenEl.textContent = `Token: Valid (expires in ${remaining}s)`; - tokenEl.className = 'neon-text success'; - } else { - tokenEl.textContent = 'Token: Not generated or expired'; - tokenEl.className = 'neon-text warning'; - } - } - } - - // Update batch information display for new UI - function updateBatchInfo() { - const batchSizeEl = document.getElementById('batchSize'); - const batchStatusEl = document.getElementById('batchStatus'); - const smartModeEl = document.getElementById('smartBatchMode'); - const manualModeEl = document.getElementById('manualBatchMode'); - const manualControlsEl = document.getElementById('manualBatchControls'); - - // Update radio button states - if (smartModeEl && manualModeEl) { - smartModeEl.checked = state.autoBatchEnabled && state.batchOptimization; - manualModeEl.checked = !state.autoBatchEnabled || !state.batchOptimization; - } - - // Update manual controls visibility - if (manualControlsEl) { - manualControlsEl.style.display = (!state.autoBatchEnabled || !state.batchOptimization) ? 'flex' : 'none'; - } - - // Update batch size input - if (batchSizeEl) { - batchSizeEl.value = state.currentBatchSize; - } - - // Update status display - if (batchStatusEl) { - if (state.autoBatchEnabled && state.batchOptimization) { - batchStatusEl.textContent = `🧠 Smart Batch: AUTO (Current: ${state.currentBatchSize})`; - batchStatusEl.className = 'status-info'; - } else { - batchStatusEl.textContent = `āœ‹ Manual Batch: ${state.currentBatchSize} pixels fixed`; - batchStatusEl.className = 'status-warning'; - } - } - } - - function updateSystemStatus() { - const systemEl = document.getElementById('systemInfo'); - if (systemEl) { - const autonomousStatus = state.autonomousMode ? 'AUTONOMOUS' : 'MANUAL'; - const tokenMode = state.tokenSource.toUpperCase(); - systemEl.textContent = `System: ${autonomousStatus} | Token: ${tokenMode} | Auto-refresh: ${state.autoTokenRefresh ? 'ON' : 'OFF'}`; - } - } - - // Window dragging functionality - function makeWindowDraggable() { - const container = document.getElementById('wplace-repair-tool'); - const header = document.getElementById('window-header'); - - let isDragging = false; - let currentX; - let currentY; - let initialX; - let initialY; - let xOffset = 0; - let yOffset = 0; - - function dragStart(e) { - if (e.target.tagName === 'BUTTON' || e.target.tagName === 'INPUT' || e.target.tagName === 'SELECT') { - return; - } - - initialX = e.clientX - xOffset; - initialY = e.clientY - yOffset; - - if (e.target === header || header.contains(e.target)) { - isDragging = true; - container.style.cursor = 'grabbing'; - } - } - - function dragEnd() { - initialX = currentX; - initialY = currentY; - isDragging = false; - container.style.cursor = 'default'; - } - - function drag(e) { - if (isDragging) { - e.preventDefault(); - currentX = e.clientX - initialX; - currentY = e.clientY - initialY; - xOffset = currentX; - yOffset = currentY; - - container.style.transform = `translate(${currentX}px, ${currentY}px)`; - } - } - - header.addEventListener('mousedown', dragStart); - document.addEventListener('mouseup', dragEnd); - document.addEventListener('mousemove', drag); - } - - // Enhanced UI Creation with NEW IMPLEMENTATION - function createUI() { - const existing = document.getElementById('wplace-repair-tool'); - if (existing) existing.remove(); - - const container = document.createElement('div'); - container.id = 'wplace-repair-tool'; - container.style.cssText = ` - position: fixed; top: 20px; left: 20px; z-index: 10000; - background: #1a1a2e; - border: 3px solid #00ff41; - border-radius: 0; - min-width: 600px; max-width: 700px; - box-shadow: - 0 0 30px rgb(0 255 65 / 50%), - inset 0 0 30px rgb(0 255 65 / 10%), - 0 0 0 1px #00ff41; - color: #00ff41; - font-family: 'Press Start 2P', monospace, 'Courier New'; - resize: both; overflow: hidden; - animation: neon-pulse 2s ease-in-out infinite alternate; - `; - - // Add Neon theme styles - const neonStyles = document.createElement('style'); - neonStyles.id = 'auto-repair-neon-styles'; - neonStyles.textContent = ` - @import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap'); - - #wplace-repair-tool::before { - content: ''; - position: absolute; - top: 0; - left: 0; - right: 0; - height: 2px; - background: linear-gradient(90deg, transparent, #00ff41, transparent); - z-index: 1; - pointer-events: none; - animation: scanline 3s linear infinite; - opacity: 0.7; - } - - .neon-btn { - background: #16213e !important; - border: 2px solid #00ff41 !important; - border-radius: 0 !important; - color: #00ff41 !important; - padding: 8px 15px !important; - font-family: 'Press Start 2P', monospace !important; - font-size: 8px !important; - text-transform: uppercase !important; - cursor: pointer !important; - transition: all 0.3s ease !important; - text-shadow: 0 0 8px #00ff41 !important; - letter-spacing: 1px !important; - margin: 4px !important; - } - - .neon-btn:hover { - background: rgba(0,255,65,0.1) !important; - box-shadow: 0 0 20px rgb(0 255 65 / 60%) !important; - animation: pixel-blink 0.5s infinite !important; - } - - .neon-btn:disabled { - background: #0d1117 !important; - border-color: #555 !important; - color: #555 !important; - text-shadow: none !important; - cursor: not-allowed !important; - } - - .neon-btn.danger { - border-color: #ff073a !important; - color: #ff073a !important; - text-shadow: 0 0 8px #ff073a !important; - } - - .neon-btn.danger:hover { - background: rgba(255, 7, 58, 0.1) !important; - box-shadow: 0 0 20px rgb(255 7 58 / 60%) !important; - } - - .neon-btn.warning { - border-color: #ff6b35 !important; - color: #ff6b35 !important; - text-shadow: 0 0 8px #ff6b35 !important; - } - - .neon-btn.warning:hover { - background: rgba(255, 107, 53, 0.1) !important; - box-shadow: 0 0 20px rgb(255 107 53 / 60%) !important; - } - - .neon-input { - background: #16213e !important; - border: 2px solid #00ff41 !important; - border-radius: 0 !important; - color: #00ff41 !important; - padding: 6px 10px !important; - font-family: 'Press Start 2P', monospace !important; - font-size: 8px !important; - text-shadow: 0 0 5px #00ff41 !important; - } - - .neon-input:focus { - outline: none !important; - box-shadow: 0 0 15px rgb(0 255 65 / 60%) !important; - animation: pixel-blink 0.5s infinite !important; - } - - .neon-select { - background: #16213e !important; - border: 2px solid #00ff41 !important; - border-radius: 0 !important; - color: #00ff41 !important; - padding: 4px 8px !important; - font-family: 'Press Start 2P', monospace !important; - font-size: 7px !important; - text-shadow: 0 0 5px #00ff41 !important; - } - - .neon-checkbox { - accent-color: #00ff41 !important; - transform: scale(1.2) !important; - margin-right: 8px !important; - } - - /* Enhanced Batch Control Styles */ - .batch-control-group { - display: flex !important; - align-items: center !important; - gap: 8px !important; - margin: 5px 0 !important; - padding: 8px !important; - background: rgba(0, 255, 65, 0.05) !important; - border: 1px solid rgba(0, 255, 65, 0.3) !important; - border-radius: 3px !important; - } - - .batch-control-header { - text-align: center !important; - margin-bottom: 10px !important; - padding: 5px !important; - border-bottom: 1px solid rgba(0, 255, 65, 0.3) !important; - } - - .batch-control-label { - color: #00ff41 !important; - font-family: 'Press Start 2P', monospace !important; - font-size: 6px !important; - text-shadow: 0 0 3px #00ff41 !important; - white-space: nowrap !important; - } - - .batch-mode-selection { - margin: 10px 0 !important; - } - - .batch-mode-option { - margin: 8px 0 !important; - padding: 6px !important; - background: rgba(0, 255, 65, 0.03) !important; - border: 1px solid rgba(0, 255, 65, 0.2) !important; - border-radius: 3px !important; - transition: all 0.3s ease !important; - } - - .batch-mode-option:hover { - background: rgba(0, 255, 65, 0.08) !important; - border-color: rgba(0, 255, 65, 0.4) !important; - } - - .batch-mode-label { - color: #00ff41 !important; - font-family: 'Press Start 2P', monospace !important; - font-size: 6px !important; - text-shadow: 0 0 3px #00ff41 !important; - margin-left: 8px !important; - } - - .neon-radio { - accent-color: #00ff41 !important; - transform: scale(1.2) !important; - margin-right: 8px !important; - } - - .batch-manual-controls { - display: flex !important; - align-items: center !important; - gap: 8px !important; - margin: 8px 0 !important; - padding: 8px !important; - background: rgba(0, 255, 65, 0.05) !important; - border: 1px solid rgba(0, 255, 65, 0.3) !important; - border-radius: 3px !important; - } - - .batch-size-control { - display: flex !important; - align-items: center !important; - gap: 2px !important; - border: 1px solid #00ff41 !important; - border-radius: 3px !important; - background: #16213e !important; - overflow: hidden !important; - } - - .batch-adjust-btn { - width: 20px !important; - height: 24px !important; - background: #16213e !important; - border: none !important; - color: #00ff41 !important; - font-family: 'Press Start 2P', monospace !important; - font-size: 8px !important; - cursor: pointer !important; - transition: all 0.3s ease !important; - text-shadow: 0 0 3px #00ff41 !important; - display: flex !important; - align-items: center !important; - justify-content: center !important; - } - - .batch-adjust-btn:hover { - background: rgba(0, 255, 65, 0.1) !important; - box-shadow: 0 0 5px rgba(0, 255, 65, 0.3) !important; - } - - .batch-adjust-btn:active { - background: rgba(0, 255, 65, 0.2) !important; - transform: scale(0.95) !important; - } - - .batch-size-input { - width: 50px !important; - height: 24px !important; - background: #16213e !important; - border: none !important; - color: #00ff41 !important; - padding: 0 4px !important; - font-family: 'Press Start 2P', monospace !important; - font-size: 6px !important; - text-align: center !important; - text-shadow: 0 0 3px #00ff41 !important; - outline: none !important; - /* Hide browser default spinner buttons */ - -webkit-appearance: none !important; - -moz-appearance: textfield !important; - } - - /* Hide Chrome spinner buttons */ - .batch-size-input::-webkit-outer-spin-button, - .batch-size-input::-webkit-inner-spin-button { - -webkit-appearance: none !important; - margin: 0 !important; - } - - /* Hide Firefox spinner buttons */ - .batch-size-input[type=number] { - -moz-appearance: textfield !important; - } - - .status-info { - color: #00ff41 !important; - } - - .status-warning { - color: #ff6b35 !important; - } - - .neon-panel { - background: rgba(22, 33, 62, 0.3) !important; - border: 1px solid #00ff41 !important; - border-radius: 0 !important; - padding: 15px !important; - margin: 10px 0 !important; - box-shadow: inset 0 0 15px rgba(0, 255, 65, 0.1) !important; - } - - .neon-text { - color: #00ff41 !important; - text-shadow: 0 0 5px #00ff41 !important; - font-family: 'Press Start 2P', monospace !important; - } - - .neon-text.success { - color: #39ff14 !important; - text-shadow: 0 0 5px #39ff14 !important; - } - - .neon-text.warning { - color: #ff6b35 !important; - text-shadow: 0 0 5px #ff6b35 !important; - } - - .neon-text.danger { - color: #ff073a !important; - text-shadow: 0 0 5px #ff073a !important; - } - - .neon-text.info { - color: #00ccff !important; - text-shadow: 0 0 5px #00ccff !important; - } - - /* Custom scrollbar for debug console */ - #debugConsole::-webkit-scrollbar { - width: 12px; - } - - #debugConsole::-webkit-scrollbar-track { - background: #16213e; - border: 1px solid #00ff41; - } - - #debugConsole::-webkit-scrollbar-thumb { - background: #00ff41; - border-radius: 0; - box-shadow: 0 0 10px #00ff41; - } - - #debugConsole::-webkit-scrollbar-thumb:hover { - background: #39ff14; - box-shadow: 0 0 15px #39ff14; - } - - /* Animations */ - @keyframes neon-pulse { - 0% { box-shadow: 0 0 30px rgb(0 255 65 / 50%), inset 0 0 30px rgb(0 255 65 / 10%), 0 0 0 1px #00ff41; } - 100% { box-shadow: 0 0 40px rgb(0 255 65 / 70%), inset 0 0 40px rgb(0 255 65 / 15%), 0 0 0 1px #00ff41; } - } - - @keyframes pixel-blink { - 0%, 50% { opacity: 1; } - 51%, 100% { opacity: 0.7; } - } - - @keyframes scanline { - 0% { transform: translateY(-100%); } - 100% { transform: translateY(400px); } - } - - @keyframes text-glow { - 0% { text-shadow: 0 0 15px #00ff41; } - 100% { text-shadow: 0 0 25px #00ff41, 0 0 35px #00ff41; } - } - `; - - if (!document.getElementById('auto-repair-neon-styles')) { - document.head.appendChild(neonStyles); - } - - container.innerHTML = ` -
-
-
šŸ”§
-

- WPlace Autonomous Repair Tool -

-
-
- - -
-
- -
-
-
- ${Utils.t('autonomousModeActive')} -
-
- Charges: 0/1 (cooldown: 31s) -
-
- Token: Initializing... -
-
- System: AUTONOMOUS | Token: GENERATOR | Auto-refresh: ON -
-
- Auto-Batch: ON | Size: 5 | Opt: ON -
-
- -
- - - - - -
- -
-
- -
- -
- - - seconds -
- -
- - -
- - -
-
- šŸŽÆ Pixel Batch Mode -
- - -
-
- -
- -
- -
-
- - -
- Batch Size: -
- - - -
- pixels -
-
- -
-
- -
-
Image: Not loaded
-
Position: Not set
-
Colors: 0 available
-
Batch Mode: Auto (${state.currentBatchSize} pixels)
-
-
- -
-
-

- šŸ“‹ ${Utils.t('debug')} (Enhanced System) -

- -
- -
-
[Ready] WPlace Autonomous Repair Tool v3.0
-
[Info] NEW Turnstile implementation active
-
[Info] Enhanced token management with preloading
-
[Info] ${Utils.t('tokenSystemReady')}
-
-
-
- `; - - document.body.appendChild(container); - makeWindowDraggable(); - setupEventListeners(); - - // Start typewriter effect - setTimeout(() => { - createTypewriterTitle(); - }, 1000); - - // Start autonomous systems - initializeAutonomousSystems(); - } - - function initializeAutonomousSystems() { - // Update charges periodically - updateCharges(); - setInterval(updateCharges, 10000); - - // Update token status - updateTokenStatus(); - setInterval(updateTokenStatus, 5000); - - // Update system status - updateSystemStatus(); - setInterval(updateSystemStatus, 15000); - - // Update batch information - updateBatchInfo(); - - // Initialize token generator - initializeTokenGenerator(); - } - - function setupEventListeners() { - // Window controls - document.getElementById('minimizeBtn').addEventListener('click', () => { - const content = document.getElementById('window-content'); - state.windowMinimized = !state.windowMinimized; - - if (state.windowMinimized) { - content.style.display = 'none'; - document.getElementById('minimizeBtn').textContent = '+'; - } else { - content.style.display = 'block'; - document.getElementById('minimizeBtn').textContent = 'āˆ’'; - } - }); - - document.getElementById('closeBtn').addEventListener('click', () => { - const container = document.getElementById('wplace-repair-tool'); - if (container) { - container.style.transition = 'opacity 0.3s ease, transform 0.3s ease'; - container.style.opacity = '0'; - container.style.transform = 'scale(0.8)'; - setTimeout(() => { - container.remove(); - cleanup(); - }, 300); - } - }); - - // Load file - document.getElementById('loadFileBtn').addEventListener('click', async () => { - try { - Utils.addDebugLog('Loading progress file...', 'info'); - const data = await Utils.createFileUploader(); - - if (!data || !data.state || !data.imageData) { - throw new Error(Utils.t('invalidFile')); - } - - const migratedData = migrateProgressData(data); - - Object.assign(state, migratedData.state); - state.imageData = { - ...migratedData.imageData, - pixels: new Uint8ClampedArray(migratedData.imageData.pixels), - }; - - state.imageLoaded = true; - document.getElementById('repairBtn').disabled = false; - - // Update info displays - document.getElementById('imageInfo').textContent = `${state.imageData.width}x${state.imageData.height}`; - document.getElementById('positionInfo').textContent = state.startPosition ? - `(${state.startPosition.x}, ${state.startPosition.y}) in region (${state.region.x}, ${state.region.y})` : 'Not set'; - document.getElementById('colorsInfo').textContent = `${state.availableColors?.length || 0} available`; - - updateStatus(Utils.t('fileLoaded')); - Utils.addDebugLog(`Loaded image: ${state.imageData.width}x${state.imageData.height}`, 'success'); - Utils.addDebugLog(`Position: (${state.startPosition?.x}, ${state.startPosition?.y})`, 'info'); - Utils.addDebugLog(`Region: (${state.region?.x}, ${state.region?.y})`, 'info'); - Utils.addDebugLog(`Available colors: ${state.availableColors?.length || 0}`, 'info'); - - if (migratedData.state.paintWhitePixels !== undefined) { - state.paintWhitePixels = migratedData.state.paintWhitePixels; - Utils.addDebugLog(`Paint white pixels: ${state.paintWhitePixels}`, 'info'); - } - if (migratedData.state.paintTransparentPixels !== undefined) { - state.paintTransparentPixels = migratedData.state.paintTransparentPixels; - Utils.addDebugLog(`Paint transparent pixels: ${state.paintTransparentPixels}`, 'info'); - } - - Utils.showAlert(Utils.t('fileLoaded'), 'success'); - - // If autonomous mode and auto repair is enabled, start it - if (state.autonomousMode && document.getElementById('autoRepairEnabled').checked) { - Utils.addDebugLog('Autonomous mode: starting auto repair after file load', 'info'); - setTimeout(() => { - state.autoRepairEnabled = true; - startAutoRepair(); - }, 5000); - } - - } catch (error) { - Utils.addDebugLog(`Load error: ${error.message}`, 'error'); - Utils.showAlert(error.message, 'error'); - } - }); - - // Generate Token button - document.getElementById('generateTokenBtn').addEventListener('click', async () => { - const btn = document.getElementById('generateTokenBtn'); - const originalText = btn.textContent; - - try { - btn.disabled = true; - btn.textContent = 'šŸ”„ Generating...'; - btn.style.background = 'linear-gradient(135deg, #ffa726 0%, #ff9800 100%)'; - - Utils.addDebugLog('Manual token generation requested', 'info'); - - const token = await ensureToken(true); - - if (token) { - Utils.addDebugLog('Token generation succeeded!', 'success'); - Utils.showAlert('Token generated successfully!', 'success'); - updateTokenStatus(); - } else { - throw new Error('Token generation failed'); - } - } catch (error) { - Utils.addDebugLog(`Token generation failed: ${error.message}`, 'error'); - Utils.showAlert('Token generation failed', 'error'); - playNotificationSound(); - } finally { - btn.disabled = false; - btn.textContent = originalText; - btn.style.background = 'linear-gradient(135deg, #43e97b 0%, #38f9d7 100%)'; - } - }); - - // Manual repair - let repairRunning = false; - document.getElementById('repairBtn').addEventListener('click', async () => { - if (!state.imageLoaded) { - Utils.showAlert('Please load a progress file first', 'warning'); - return; - } - - if (repairRunning) { - state.stopFlag = true; - Utils.addDebugLog('Manual repair stop requested', 'warning'); - return; - } - - repairRunning = true; - state.running = true; - state.stopFlag = false; - const repairBtn = document.getElementById('repairBtn'); - repairBtn.textContent = 'āøļø Stop Repair'; - repairBtn.style.background = 'linear-gradient(135deg, #ff6b35 0%, #f7931e 100%)'; - - try { - Utils.addDebugLog('Starting manual repair with enhanced autonomous system...', 'info'); - - if (!isTokenValid()) { - Utils.addDebugLog('No valid token, generating...', 'info'); - await ensureToken(true); - } - - const damagedPixels = await scanForDamage(); - - if (damagedPixels.length > 0) { - updateStatus(Utils.t('damageDetected', { count: damagedPixels.length })); - showAttackNotification('attack', damagedPixels.length); - - const repairedCount = await repairDamagedPixels(damagedPixels); - - if (repairedCount === damagedPixels.length) { - showAttackNotification('repaired'); - } - } else { - updateStatus(Utils.t('noDamageDetected')); - showPeacefulState(); - } - } catch (error) { - Utils.addDebugLog(`Repair error: ${error.message}`, 'error'); - Utils.showAlert('Repair failed', 'error'); - } finally { - repairRunning = false; - state.running = false; - state.stopFlag = false; - repairBtn.textContent = 'šŸ”§ ' + Utils.t('repairPixels'); - repairBtn.style.background = 'linear-gradient(135deg, #f093fb 0%, #f5576c 100%)'; - } - }); - - // Auto repair toggle - document.getElementById('autoRepairEnabled').addEventListener('change', (e) => { - state.autoRepairEnabled = e.target.checked; - - if (state.autoRepairEnabled) { - if (!state.imageLoaded) { - Utils.showAlert('Please load a progress file first', 'warning'); - e.target.checked = false; - state.autoRepairEnabled = false; - return; - } - startAutoRepair(); - } else { - stopAutoRepair(); - } - }); - - // Repair interval change - document.getElementById('repairInterval').addEventListener('change', (e) => { - const value = parseInt(e.target.value) || 30; - state.autoRepairInterval = Math.max(10, Math.min(3600, value)); - e.target.value = state.autoRepairInterval; - - Utils.addDebugLog(`Repair interval changed to ${state.autoRepairInterval} seconds`, 'info'); - - if (state.autoRepairEnabled && state.imageLoaded) { - Utils.addDebugLog('Restarting auto repair with new interval', 'info'); - startAutoRepair(); - } - }); - - // Auto token refresh toggle - document.getElementById('autoTokenRefresh').addEventListener('change', (e) => { - state.autoTokenRefresh = e.target.checked; - Utils.addDebugLog(`Auto token refresh ${state.autoTokenRefresh ? 'enabled' : 'disabled'}`, 'info'); - updateSystemStatus(); - - if (state.autoTokenRefresh && state.autonomousMode && !isTokenValid()) { - ensureToken(); - } - }); - - // Token source selection - document.getElementById('tokenSourceSelect').addEventListener('change', (e) => { - state.tokenSource = e.target.value; - Utils.addDebugLog(`Token source changed to: ${state.tokenSource}`, 'info'); - updateSystemStatus(); - - // Invalidate current token to force new generation with new method - if (isTokenValid()) { - Utils.addDebugLog('Invalidating current token due to source change', 'info'); - invalidateToken(); - } - }); - - // NEW: Enhanced batch mode controls - document.getElementById('smartBatchMode').addEventListener('change', (e) => { - if (e.target.checked) { - state.autoBatchEnabled = true; - state.batchOptimization = true; - document.getElementById('manualBatchControls').style.display = 'none'; - Utils.addDebugLog('Smart batch mode enabled - will auto-adjust to damage count', 'info'); - updateBatchInfo(); - } - }); - - document.getElementById('manualBatchMode').addEventListener('change', (e) => { - if (e.target.checked) { - state.autoBatchEnabled = false; - state.batchOptimization = false; - document.getElementById('manualBatchControls').style.display = 'flex'; - Utils.addDebugLog('Manual batch mode enabled - using fixed batch size', 'info'); - updateBatchInfo(); - } - }); - - // Manual batch size controls with +/- buttons - document.getElementById('batchSizeIncrease').addEventListener('click', () => { - const newSize = Math.min(CONFIG.MAX_BATCH_SIZE, state.currentBatchSize + 1); - if (newSize !== state.currentBatchSize) { - state.currentBatchSize = newSize; - document.getElementById('batchSize').value = state.currentBatchSize; - Utils.addDebugLog(`Increased batch size to ${state.currentBatchSize} pixels`, 'info'); - updateBatchInfo(); - } - }); - - document.getElementById('batchSizeDecrease').addEventListener('click', () => { - const newSize = Math.max(CONFIG.MIN_BATCH_SIZE, state.currentBatchSize - 1); - if (newSize !== state.currentBatchSize) { - state.currentBatchSize = newSize; - document.getElementById('batchSize').value = state.currentBatchSize; - Utils.addDebugLog(`Decreased batch size to ${state.currentBatchSize} pixels`, 'info'); - updateBatchInfo(); - } - }); - - // Manual input allows direct typing for precise control - document.getElementById('batchSize').addEventListener('input', (e) => { - const value = parseInt(e.target.value); - if (!isNaN(value)) { - const clampedValue = Math.max(CONFIG.MIN_BATCH_SIZE, Math.min(CONFIG.MAX_BATCH_SIZE, value)); - if (clampedValue !== state.currentBatchSize) { - state.currentBatchSize = clampedValue; - if (clampedValue !== value) { - e.target.value = state.currentBatchSize; - } - Utils.addDebugLog(`Manual batch size set to ${state.currentBatchSize} pixels`, 'info'); - updateBatchInfo(); - } - } - }); - - document.getElementById('paintTransparentPixels').addEventListener('change', (e) => { - state.paintTransparentPixels = e.target.checked; - Utils.addDebugLog(`Transparent pixel repair ${state.paintTransparentPixels ? 'enabled' : 'disabled'}`, 'info'); - }); - - // Clear debug - document.getElementById('clearDebugBtn').addEventListener('click', () => { - state.debugLogs = []; - updateDebugConsole(); - Utils.addDebugLog('Debug console cleared', 'info'); - Utils.addDebugLog('Enhanced WPlace Autonomous Repair Tool ready', 'info'); - }); - } - - // Enhanced data migration helper - function migrateProgressData(data) { - if (!data.version || data.version < '2.0') { - Utils.addDebugLog('Migrating old progress data format...', 'info'); - - if (!data.state) data.state = {}; - if (!data.imageData) data.imageData = {}; - - data.state.paintWhitePixels = data.state.paintWhitePixels ?? true; - data.state.paintTransparentPixels = data.state.paintTransparentPixels ?? false; - - data.version = '2.0'; - } - - // Additional migrations for autonomous features - if (data.version === '2.0') { - data.state.autonomousMode = data.state.autonomousMode ?? CONFIG.AUTONOMOUS_MODE; - data.state.autoTokenRefresh = data.state.autoTokenRefresh ?? CONFIG.AUTO_TOKEN_REFRESH; - data.state.tokenSource = data.state.tokenSource ?? CONFIG.TOKEN_SOURCE; - data.version = '3.0'; - Utils.addDebugLog('Migrated to autonomous features v3.0', 'info'); - } - - return data; - } - - // Main initialization function - async function initialize() { - Utils.addDebugLog('Starting WPlace Autonomous Repair Tool v3.0...', 'info'); - - // Create UI first - createUI(); - - Utils.addDebugLog('NEW Enhanced Features Initialized:', 'info'); - Utils.addDebugLog('• NEW Turnstile implementation with widget reuse', 'info'); - Utils.addDebugLog('• Enhanced sitekey detection from multiple sources', 'info'); - Utils.addDebugLog('• Sophisticated token management with preloading', 'info'); - Utils.addDebugLog('• Autonomous operation with smart retry logic', 'info'); - Utils.addDebugLog('• Enhanced tile interception and caching', 'info'); - Utils.addDebugLog('• Adaptive repair algorithms with failure handling', 'info'); - Utils.addDebugLog('• Visual notifications and audio alerts', 'info'); - Utils.addDebugLog('• Draggable window with minimize/close controls', 'info'); - - if (state.autonomousMode) { - Utils.addDebugLog('AUTONOMOUS MODE ACTIVE - Tool will operate independently', 'success'); - Utils.addDebugLog('Instructions for autonomous operation:', 'info'); - Utils.addDebugLog('1. Wait for initial setup to complete', 'info'); - Utils.addDebugLog('2. Load a progress file using "Load Progress File"', 'info'); - Utils.addDebugLog('3. Enable "Auto Repair" to start autonomous monitoring', 'info'); - Utils.addDebugLog('4. Tool will automatically scan and repair damage', 'info'); - Utils.addDebugLog('5. Tokens will be generated and refreshed automatically', 'info'); - Utils.addDebugLog('6. Monitor debug console for autonomous operation logs', 'info'); - } else { - Utils.addDebugLog('Manual mode active - use controls for manual operation', 'info'); - } - - // Show autonomous mode status - updateStatus(state.autonomousMode ? Utils.t('autonomousModeActive') : 'Manual mode ready - Load file and enable auto repair to start'); - - Utils.addDebugLog('WPlace Autonomous Repair Tool ready for operation', 'success'); - } - - // Enhanced cleanup function - function cleanup() { - Utils.addDebugLog('Cleaning up autonomous systems...', 'info'); - - // Clear all timers - if (state.autoRepairTimer) { - clearInterval(state.autoRepairTimer); - state.autoRepairTimer = null; - } - - if (state.tokenRetryTimer) { - clearTimeout(state.tokenRetryTimer); - state.tokenRetryTimer = null; - } - - if (state.tokenPreloadTimer) { - clearTimeout(state.tokenPreloadTimer); - state.tokenPreloadTimer = null; - } - - // Cleanup Turnstile - Utils.cleanupTurnstile(); - - // Set stop flag - state.stopFlag = true; - - Utils.addDebugLog('Cleanup completed', 'info'); - } - - // Add cleanup on page unload - window.addEventListener('beforeunload', cleanup); - - // Handle page visibility changes for autonomous mode - document.addEventListener('visibilitychange', () => { - if (state.autonomousMode) { - if (document.hidden) { - Utils.addDebugLog('Page hidden - autonomous systems continue running', 'info'); - } else { - Utils.addDebugLog('Page visible - autonomous systems active', 'info'); - // Refresh status when page becomes visible - updateCharges(); - updateTokenStatus(); - } - } - }); - - // Enhanced error handling for autonomous mode - window.addEventListener('error', (event) => { - if (state.autonomousMode) { - Utils.addDebugLog(`Global error in autonomous mode: ${event.error?.message || 'Unknown error'}`, 'error'); - } - }); - - window.addEventListener('unhandledrejection', (event) => { - if (state.autonomousMode) { - Utils.addDebugLog(`Unhandled promise rejection in autonomous mode: ${event.reason}`, 'error'); - } - }); - - // Start the enhanced autonomous application - initialize().catch(error => { - console.error('Failed to initialize autonomous repair tool:', error); - Utils.addDebugLog(`Initialization failed: ${error.message}`, 'error'); - }); - -})(); +// eslint-disable-next-line prettier/prettier +; (async () => { + // CONFIGURATION CONSTANTS + const CONFIG = { + COOLDOWN_DEFAULT: 31000, + TRANSPARENCY_THRESHOLD: 100, + WHITE_THRESHOLD: 250, + LOG_INTERVAL: 10, + PAINTING_SPEED: { + MIN: 1, + MAX: 1000, + DEFAULT: 5, + }, + TOKEN_SOURCE: 'generator', // "generator", "manual", or "hybrid" + AUTONOMOUS_MODE: true, // Enable autonomous operation + AUTO_TOKEN_REFRESH: true, // Automatically refresh tokens + TOKEN_PRELOAD_BUFFER: 60000, // Preload tokens 1 minute before expiry + MAX_RETRIES: 10, + RETRY_DELAY_BASE: 1000, + // Auto-batch configuration + AUTO_BATCH_ENABLED: true, // Enable auto pixel batch sizing + MAX_BATCH_SIZE: 500, // Maximum pixels per batch + MIN_BATCH_SIZE: 1, // Minimum pixels per batch + BATCH_OPTIMIZATION: true, // Optimize batch size based on detected damage + COLOR_MAP: { + 0: { id: 1, name: 'Black', rgb: { r: 0, g: 0, b: 0 } }, + 1: { id: 2, name: 'Dark Gray', rgb: { r: 60, g: 60, b: 60 } }, + 2: { id: 3, name: 'Gray', rgb: { r: 120, g: 120, b: 120 } }, + 3: { id: 4, name: 'Light Gray', rgb: { r: 210, g: 210, b: 210 } }, + 4: { id: 5, name: 'White', rgb: { r: 255, g: 255, b: 255 } }, + 5: { id: 6, name: 'Deep Red', rgb: { r: 96, g: 0, b: 24 } }, + 6: { id: 7, name: 'Red', rgb: { r: 237, g: 28, b: 36 } }, + 7: { id: 8, name: 'Orange', rgb: { r: 255, g: 127, b: 39 } }, + 8: { id: 9, name: 'Gold', rgb: { r: 246, g: 170, b: 9 } }, + 9: { id: 10, name: 'Yellow', rgb: { r: 249, g: 221, b: 59 } }, + 10: { id: 11, name: 'Light Yellow', rgb: { r: 255, g: 250, b: 188 } }, + 11: { id: 12, name: 'Dark Green', rgb: { r: 14, g: 185, b: 104 } }, + 12: { id: 13, name: 'Green', rgb: { r: 19, g: 230, b: 123 } }, + 13: { id: 14, name: 'Light Green', rgb: { r: 135, g: 255, b: 94 } }, + 14: { id: 15, name: 'Dark Teal', rgb: { r: 12, g: 129, b: 110 } }, + 15: { id: 16, name: 'Teal', rgb: { r: 16, g: 174, b: 166 } }, + 16: { id: 17, name: 'Light Teal', rgb: { r: 19, g: 225, b: 190 } }, + 17: { id: 20, name: 'Cyan', rgb: { r: 96, g: 247, b: 242 } }, + 18: { id: 44, name: 'Light Cyan', rgb: { r: 187, g: 250, b: 242 } }, + 19: { id: 18, name: 'Dark Blue', rgb: { r: 40, g: 80, b: 158 } }, + 20: { id: 19, name: 'Blue', rgb: { r: 64, g: 147, b: 228 } }, + 21: { id: 21, name: 'Indigo', rgb: { r: 107, g: 80, b: 246 } }, + 22: { id: 22, name: 'Light Indigo', rgb: { r: 153, g: 177, b: 251 } }, + 23: { id: 23, name: 'Dark Purple', rgb: { r: 120, g: 12, b: 153 } }, + 24: { id: 24, name: 'Purple', rgb: { r: 170, g: 56, b: 185 } }, + 25: { id: 25, name: 'Light Purple', rgb: { r: 224, g: 159, b: 249 } }, + 26: { id: 26, name: 'Dark Pink', rgb: { r: 203, g: 0, b: 122 } }, + 27: { id: 27, name: 'Pink', rgb: { r: 236, g: 31, b: 128 } }, + 28: { id: 28, name: 'Light Pink', rgb: { r: 243, g: 141, b: 169 } }, + 29: { id: 29, name: 'Dark Brown', rgb: { r: 104, g: 70, b: 52 } }, + 30: { id: 30, name: 'Brown', rgb: { r: 149, g: 104, b: 42 } }, + 31: { id: 31, name: 'Beige', rgb: { r: 248, g: 178, b: 119 } }, + 32: { id: 52, name: 'Light Beige', rgb: { r: 255, g: 197, b: 165 } }, + 33: { id: 32, name: 'Medium Gray', rgb: { r: 170, g: 170, b: 170 } }, + 34: { id: 33, name: 'Dark Red', rgb: { r: 165, g: 14, b: 30 } }, + 35: { id: 34, name: 'Light Red', rgb: { r: 250, g: 128, b: 114 } }, + 36: { id: 35, name: 'Dark Orange', rgb: { r: 228, g: 92, b: 26 } }, + 37: { id: 37, name: 'Dark Goldenrod', rgb: { r: 156, g: 132, b: 49 } }, + 38: { id: 38, name: 'Goldenrod', rgb: { r: 197, g: 173, b: 49 } }, + 39: { id: 39, name: 'Light Goldenrod', rgb: { r: 232, g: 212, b: 95 } }, + 40: { id: 40, name: 'Dark Olive', rgb: { r: 74, g: 107, b: 58 } }, + 41: { id: 41, name: 'Olive', rgb: { r: 90, g: 148, b: 74 } }, + 42: { id: 42, name: 'Light Olive', rgb: { r: 132, g: 197, b: 115 } }, + 43: { id: 43, name: 'Dark Cyan', rgb: { r: 15, g: 121, b: 159 } }, + 44: { id: 45, name: 'Light Blue', rgb: { r: 125, g: 199, b: 255 } }, + 45: { id: 46, name: 'Dark Indigo', rgb: { r: 77, g: 49, b: 184 } }, + 46: { id: 47, name: 'Dark Slate Blue', rgb: { r: 74, g: 66, b: 132 } }, + 47: { id: 48, name: 'Slate Blue', rgb: { r: 122, g: 113, b: 196 } }, + 48: { id: 49, name: 'Light Slate Blue', rgb: { r: 181, g: 174, b: 241 } }, + 49: { id: 53, name: 'Dark Peach', rgb: { r: 155, g: 82, b: 73 } }, + 50: { id: 54, name: 'Peach', rgb: { r: 209, g: 128, b: 120 } }, + 51: { id: 55, name: 'Light Peach', rgb: { r: 250, g: 182, b: 164 } }, + 52: { id: 50, name: 'Light Brown', rgb: { r: 219, g: 164, b: 99 } }, + 53: { id: 56, name: 'Dark Tan', rgb: { r: 123, g: 99, b: 82 } }, + 54: { id: 57, name: 'Tan', rgb: { r: 156, g: 132, b: 107 } }, + 55: { id: 36, name: 'Light Tan', rgb: { r: 214, g: 181, b: 148 } }, + 56: { id: 51, name: 'Dark Beige', rgb: { r: 209, g: 128, b: 81 } }, + 57: { id: 61, name: 'Dark Stone', rgb: { r: 109, g: 100, b: 63 } }, + 58: { id: 62, name: 'Stone', rgb: { r: 148, g: 140, b: 107 } }, + 59: { id: 63, name: 'Light Stone', rgb: { r: 205, g: 197, b: 158 } }, + 60: { id: 58, name: 'Dark Slate', rgb: { r: 51, g: 57, b: 65 } }, + 61: { id: 59, name: 'Slate', rgb: { r: 109, g: 117, b: 141 } }, + 62: { id: 60, name: 'Light Slate', rgb: { r: 179, g: 185, b: 209 } }, + 63: { id: 0, name: 'Transparent', rgb: null }, + }, + }; + + // GLOBAL STATE + const state = { + running: false, + imageLoaded: false, + totalPixels: 0, + paintedPixels: 0, + availableColors: [], + displayCharges: 0, + maxCharges: 1, + cooldown: CONFIG.COOLDOWN_DEFAULT, + imageData: null, + stopFlag: false, + startPosition: null, + region: null, + paintWhitePixels: true, + paintTransparentPixels: true, // Changed to true to fix transparent pixel detection + autoRepairEnabled: false, + autoRepairInterval: 30, + autoRepairTimer: null, + debugLogs: [], + customTransparencyThreshold: CONFIG.TRANSPARENCY_THRESHOLD, + customWhiteThreshold: CONFIG.WHITE_THRESHOLD, + tokenSource: CONFIG.TOKEN_SOURCE, + autonomousMode: CONFIG.AUTONOMOUS_MODE, + autoTokenRefresh: CONFIG.AUTO_TOKEN_REFRESH, + tokenPreloadBuffer: CONFIG.TOKEN_PRELOAD_BUFFER, + retryCount: 0, + tokenRetryTimer: null, + tokenPreloadTimer: null, + windowMinimized: false, + lastAttackState: null, + initialSetupComplete: false, + // Auto-batch state + autoBatchEnabled: CONFIG.AUTO_BATCH_ENABLED, + currentBatchSize: CONFIG.MIN_BATCH_SIZE, + batchOptimization: CONFIG.BATCH_OPTIMIZATION, + }; + + // Random string generator + const randStr = (len, chars = 'abcdefghijklmnopqrstuvwxyz0123456789') => + [...Array(len)].map(() => chars[(crypto?.getRandomValues?.(new Uint32Array(1))[0] % chars.length) || Math.floor(Math.random() * chars.length)]).join(''); + + const fpStr32 = randStr(32); + + // Enhanced Turnstile token handling - Actualizado con la lógica de new.txt lol + let turnstileToken = null; + let tokenExpiryTime = 0; + let tokenGenerationInProgress = false; + let _resolveToken = null; + let tokenPromise = new Promise((resolve) => { + _resolveToken = resolve; + }); + let retryCount = 0; + const MAX_RETRIES = 10; + const MAX_BATCH_RETRIES = 10; + const TOKEN_LIFETIME = 240000; // 4 minutes (tokens typically last 5 min, use 4 for safety) + + function setTurnstileToken(token) { + if (_resolveToken) { + _resolveToken(token); + _resolveToken = null; + } + turnstileToken = token; + tokenExpiryTime = Date.now() + TOKEN_LIFETIME; + console.log('āœ… Turnstile token set successfully'); + Utils.addDebugLog('Token cached successfully', 'success'); + } + + function isTokenValid() { + return turnstileToken && Date.now() < tokenExpiryTime; + } + + function invalidateToken() { + turnstileToken = null; + tokenExpiryTime = 0; + console.log('šŸ—‘ļø Token invalidated, will force fresh generation'); + Utils.addDebugLog('Token invalidated, will force fresh generation', 'warning'); + } + + async function ensureToken(forceRefresh = false) { + // Return cached token if still valid and not forcing refresh + if (isTokenValid() && !forceRefresh) { + return turnstileToken; + } + + // Invalidate token if forcing refresh + if (forceRefresh) invalidateToken(); + + // Avoid multiple simultaneous token generations + if (tokenGenerationInProgress) { + console.log('šŸ”„ Token generation already in progress, waiting...'); + await Utils.sleep(2000); + return isTokenValid() ? turnstileToken : null; + } + + tokenGenerationInProgress = true; + + try { + console.log('šŸ”„ Token expired or missing, generating new one...'); + const token = await handleCaptchaWithRetry(); + if (token && token.length > 20) { + setTurnstileToken(token); + console.log('āœ… Token captured and cached successfully'); + return token; + } + + console.log('āš ļø Invisible Turnstile failed, forcing browser automation...'); + const fallbackToken = await handleCaptchaFallback(); + if (fallbackToken && fallbackToken.length > 20) { + setTurnstileToken(fallbackToken); + console.log('āœ… Fallback token captured successfully'); + return fallbackToken; + } + + console.log('āŒ All token generation methods failed'); + return null; + } finally { + tokenGenerationInProgress = false; + } + } + + async function handleCaptchaWithRetry() { + const startTime = performance.now(); + + try { + const { sitekey, token: preGeneratedToken } = await Utils.obtainSitekeyAndToken(); + + if (!sitekey) { + throw new Error('No valid sitekey found'); + } + + console.log('šŸ”‘ Using sitekey:', sitekey); + + if (typeof window !== 'undefined' && window.navigator) { + console.log( + '🧭 UA:', + window.navigator.userAgent.substring(0, 50) + '...', + 'Platform:', + window.navigator.platform + ); + } + + let token = null; + + if ( + preGeneratedToken && + typeof preGeneratedToken === 'string' && + preGeneratedToken.length > 20 + ) { + console.log('ā™»ļø Reusing pre-generated Turnstile token'); + token = preGeneratedToken; + } else { + if (isTokenValid()) { + console.log('ā™»ļø Using existing cached token (from previous session)'); + token = turnstileToken; + } else { + console.log('šŸ” Generating new token with executeTurnstile...'); + token = await Utils.executeTurnstile(sitekey, 'paint'); + if (token) setTurnstileToken(token); + } + } + + if (token && typeof token === 'string' && token.length > 20) { + const elapsed = Math.round(performance.now() - startTime); + console.log(`āœ… Turnstile token generated successfully in ${elapsed}ms`); + return token; + } else { + throw new Error(`Invalid or empty token received - Length: ${token?.length || 0}`); + } + } catch (error) { + const elapsed = Math.round(performance.now() - startTime); + console.error(`āŒ Turnstile token generation failed after ${elapsed}ms:`, error); + throw error; + } + } + + async function handleCaptchaFallback() { + return new Promise(async (resolve, reject) => { + try { + // Ensure we have a fresh promise to await for a new token capture + if (!_resolveToken) { + tokenPromise = new Promise((res) => { + _resolveToken = res; + }); + } + const timeoutPromise = Utils.sleep(20000).then(() => + reject(new Error('Auto-CAPTCHA timed out.')) + ); + + const solvePromise = (async () => { + const mainPaintBtn = await Utils.waitForSelector( + 'button.btn.btn-primary.btn-lg, button.btn-primary.sm\\:btn-xl', + 200, + 10000 + ); + if (!mainPaintBtn) throw new Error('Could not find the main paint button.'); + mainPaintBtn.click(); + await Utils.sleep(500); + + const transBtn = await Utils.waitForSelector('button#color-0', 200, 5000); + if (!transBtn) throw new Error('Could not find the transparent color button.'); + transBtn.click(); + await Utils.sleep(500); + + const canvas = await Utils.waitForSelector('canvas', 200, 5000); + if (!canvas) throw new Error('Could not find the canvas element.'); + + canvas.setAttribute('tabindex', '0'); + canvas.focus(); + const rect = canvas.getBoundingClientRect(); + const centerX = Math.round(rect.left + rect.width / 2); + const centerY = Math.round(rect.top + rect.height / 2); + + canvas.dispatchEvent( + new MouseEvent('mousemove', { + clientX: centerX, + clientY: centerY, + bubbles: true, + }) + ); + canvas.dispatchEvent( + new KeyboardEvent('keydown', { + key: ' ', + code: 'Space', + bubbles: true, + }) + ); + await Utils.sleep(50); + canvas.dispatchEvent( + new KeyboardEvent('keyup', { + key: ' ', + code: 'Space', + bubbles: true, + }) + ); + await Utils.sleep(500); + + // 800ms delay before sending confirmation + await Utils.sleep(800); + + // Keep confirming until token is captured + const confirmLoop = async () => { + while (!turnstileToken) { + let confirmBtn = await Utils.waitForSelector( + 'button.btn.btn-primary.btn-lg, button.btn.btn-primary.sm\\:btn-xl' + ); + if (!confirmBtn) { + const allPrimary = Array.from(document.querySelectorAll('button.btn-primary')); + confirmBtn = allPrimary.length ? allPrimary[allPrimary.length - 1] : null; + } + if (confirmBtn) { + confirmBtn.click(); + } + await Utils.sleep(500); // 500ms delay between confirmation attempts + } + }; + + // Start confirmation loop and wait for token + confirmLoop(); + const token = await tokenPromise; + await Utils.sleep(300); // small delay after token is captured + resolve(token); + })(); + + await Promise.race([solvePromise, timeoutPromise]); + } catch (error) { + console.error('Auto-CAPTCHA process failed:', error); + reject(error); + } + }); + } + + // NUEVA LƓGICA DE INYECCIƓN - ACTUALIZADA DESDE new.txt + function inject(callback) { + try { + const script = document.createElement('script'); + script.textContent = `(${callback})();`; + document.documentElement?.appendChild(script); + script.remove(); + } catch (error) { + console.error('āŒ Injection error:', error); + } + } + + inject(() => { + const fetchedBlobQueue = new Map(); + + 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]; + + if (typeof url === 'string') { + if (url.includes('https://backend.wplace.live/s0/pixel/')) { + try { + const payload = JSON.parse(args[1].body); + if (payload.t) { + // šŸ“Š Debug log + console.log( + `šŸ”āœ… Turnstile Token Captured - Type: ${typeof payload.t}, Value: ${payload.t + ? typeof payload.t === 'string' + ? payload.t.length > 50 + ? payload.t.substring(0, 50) + '...' + : payload.t + : JSON.stringify(payload.t) + : 'null/undefined' + }, Length: ${payload.t?.length || 0}` + ); + window.postMessage({ source: 'turnstile-capture', token: payload.t }, '*'); + } + } catch (_) { + /* ignore */ + } + } + + 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; + }; + }); + + window.addEventListener('message', (event) => { + const { source, endpoint, blobID, blobData, token } = event.data; + + if (source === 'auto-image-tile' && endpoint && blobID && blobData) { + overlayManager.processAndRespondToTileRequest(event.data); + } + + if (source === 'turnstile-capture' && token) { + setTurnstileToken(token); + Utils.addDebugLog('Token captured from injection system', 'success'); + updateTokenStatus(); + } + }); + + // NUEVO SISTEMA WASM - ACTUALIZADO DESDE new.txt + var pawtect_chunk = null; + + // Find module if pawtect_chunk is null + pawtect_chunk ??= await findTokenModule("pawtect_wasm_bg.wasm"); + + async function createWasmToken(regionX, regionY, payload) { + try { + // Load the Pawtect module and WASM + const mod = await import(new URL('/_app/immutable/chunks/'+pawtect_chunk, location.origin).href); + let wasm; + try { + wasm = await mod._(); + console.log('āœ… WASM initialized successfully'); + } catch (wasmError) { + console.error('āŒ WASM initialization failed:', wasmError); + return null; + } + try { + try { + const me = await fetch(`https://backend.wplace.live/me`, { credentials: 'include' }).then(r => r.ok ? r.json() : null); + if (me?.id) { + mod.i(me.id); + console.log('āœ… user ID set:', me.id); + } + } catch { } + } catch (userIdError) { + console.log('āš ļø Error setting user ID:', userIdError.message); + } + try { + const testUrl = `https://backend.wplace.live/s0/pixel/${regionX}/${regionY}`; + if (mod.r) { + mod.r(testUrl); + console.log('āœ… Request URL set:', testUrl); + } else { + console.log('āš ļø request_url function (mod.r) not available'); + } + } catch (urlError) { + console.log('āš ļø Error setting request URL:', urlError.message); + } + + console.log('šŸ” payload:', payload); + + // Encode payload + const enc = new TextEncoder(); + const dec = new TextDecoder(); + const bodyStr = JSON.stringify(payload); + const bytes = enc.encode(bodyStr); + console.log('šŸ” Payload size:', bytes.length, 'bytes'); + console.log('šŸ”„ Payload string:', bodyStr); + + // Allocate WASM memory with validation + let inPtr; + try { + if (!wasm.__wbindgen_malloc) { + console.error('āŒ __wbindgen_malloc function not found'); + return null; + } + + inPtr = wasm.__wbindgen_malloc(bytes.length, 1); + console.log('āœ… WASM memory allocated, pointer:', inPtr); + + // Copy data to WASM memory + const wasmBuffer = new Uint8Array(wasm.memory.buffer, inPtr, bytes.length); + wasmBuffer.set(bytes); + console.log('āœ… Data copied to WASM memory'); + } catch (memError) { + console.error('āŒ Memory allocation error:', memError); + return null; + } + + // Call the WASM function + console.log('šŸš€ Calling get_pawtected_endpoint_payload...'); + let outPtr, outLen, token; + try { + const result = wasm.get_pawtected_endpoint_payload(inPtr, bytes.length); + console.log('āœ… Function called, result type:', typeof result, result); + + if (Array.isArray(result) && result.length === 2) { + [outPtr, outLen] = result; + console.log('āœ… Got output pointer:', outPtr, 'length:', outLen); + + // Decode the result + const outputBuffer = new Uint8Array(wasm.memory.buffer, outPtr, outLen); + token = dec.decode(outputBuffer); + console.log('āœ… Token decoded successfully'); + } else { + console.error('āŒ Unexpected function result format:', result); + return null; + } + } catch (funcError) { + console.error('āŒ Function call error:', funcError); + console.error('Stack trace:', funcError.stack); + return null; + } + + // Cleanup memory + try { + if (wasm.__wbindgen_free && outPtr && outLen) { + wasm.__wbindgen_free(outPtr, outLen, 1); + console.log('āœ… Output memory freed'); + } + if (wasm.__wbindgen_free && inPtr) { + wasm.__wbindgen_free(inPtr, bytes.length, 1); + console.log('āœ… Input memory freed'); + } + } catch (cleanupError) { + console.log('āš ļø Cleanup warning:', cleanupError.message); + } + + console.log('šŸŽ‰ SUCCESS!'); + console.log('šŸ”‘ Full token:', token); + return token; + } catch (error) { + console.error('āŒ Failed to generate fp parameter:', error); + return null; + } + } + + async function findTokenModule(str) { + console.log('šŸ”Ž Searching for wasm Module...'); + const links = Array.from(document.querySelectorAll('link[rel="modulepreload"][href$=".js"]')); + + for (const link of links) { + try { + const url = new URL(link.getAttribute("href"), location.origin).href; + const code = await fetch(url).then(r => r.text()); + if (code.includes(str)) { + console.log('āœ… Found wasm Module...'); + return url.split('/').pop(); + } + } catch { } + } + console.error(`āŒ Could not find Pawtect chunk with string: ${str}`); + } + + // Audio notification system + function playNotificationSound() { + try { + const audio = new Audio('https://cdn.pixabay.com/download/audio/2025/03/21/audio_9bec51b17f.mp3?filename=glass-break-316720.mp3'); + audio.volume = 0.5; + audio.play().catch(() => { + console.warn('Could not play notification sound'); + }); + } catch (error) { + console.warn('Audio notification failed:', error); + } + } + + // Notification system for attacks and repairs + function showAttackNotification(type, count = 0) { + const existing = document.getElementById('attack-notification'); + if (existing) existing.remove(); + + const notification = document.createElement('div'); + notification.id = 'attack-notification'; + notification.style.cssText = ` + position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); + z-index: 15000; background: rgba(0,0,0,0.9); color: white; + border-radius: 15px; padding: 20px; text-align: center; + box-shadow: 0 10px 30px rgba(0,0,0,0.8); + border: 2px solid ${type === 'attack' ? '#ff4444' : '#44ff44'}; + min-width: 350px; max-width: 450px; + `; + + if (type === 'attack') { + notification.innerHTML = ` +
+ Attack +
+

+ PIXEL ART ATTACKED! +

+

+ Detected ${count} damaged pixels! +

+ `; + } else if (type === 'repaired') { + notification.innerHTML = ` +
+ Repaired +
+

+ COMPLETELY REPAIRED! +

+

+ All pixels have been restored! +

+ `; + } + + document.body.appendChild(notification); + + setTimeout(() => { + if (notification.parentNode) { + notification.style.transition = 'opacity 0.5s ease'; + notification.style.opacity = '0'; + setTimeout(() => { + if (notification.parentNode) notification.remove(); + }, 500); + } + }, 3000); + } + + // Peaceful state display in main window + function showPeacefulState() { + const statusDiv = document.getElementById('status'); + if (statusDiv && !state.running) { + statusDiv.innerHTML = ` +
+ Peaceful + No attacks detected - All secure +
+ `; + } + } + + // Typewriter effect for title + function createTypewriterTitle() { + const titleText = "WPlace Autonomous Repair Tool"; + let currentIndex = 0; + const titleElement = document.getElementById('main-title'); + + if (!titleElement) return; + + function typeNext() { + if (currentIndex < titleText.length) { + titleElement.textContent = titleText.substring(0, currentIndex + 1); + currentIndex++; + setTimeout(typeNext, 100); + } else { + setTimeout(() => { + currentIndex = 0; + titleElement.textContent = ''; + setTimeout(typeNext, 1000); + }, 3000); + } + } + + typeNext(); + } + + // Fallback translations + const TEXTS = { + title: 'WPlace Autonomous Repair Tool', + loadFromFile: 'Load Progress File', + repairPixels: 'Repair Pixels', + enableAutoRepair: 'Enable Auto Repair', + repairInterval: 'Check Interval (seconds)', + debug: 'Debug Console', + clearDebug: 'Clear Debug', + scanningForDamage: 'Scanning for damaged pixels...', + damageDetected: 'Damage detected: {count} pixels', + noDamageDetected: 'No damage found', + repairingPixels: 'Repairing {count} damaged pixels...', + repairComplete: 'Repair completed: {repaired} pixels fixed', + autoRepairStarted: 'Auto repair started (every {interval}s)', + autoRepairStopped: 'Auto repair stopped', + fileLoaded: 'Progress file loaded successfully', + invalidFile: 'Invalid file format', + noImageData: 'No image data found in file', + turnstileInstructions: 'Complete the verification', + hideTurnstileBtn: 'Hide', + tokenCapturedSuccess: 'Token captured successfully', + autonomousModeActive: 'Autonomous mode active', + tokenSystemReady: 'Advanced token system ready', + fileOperationsAvailable: 'File operations available', + initializingToken: 'Initializing token system...', + tokenReady: 'Token system ready', + }; + + // UTILIDADES ACTUALIZADAS CON NUEVA IMPLEMENTACIƓN TURNSTILE + const Utils = { + sleep: (ms) => new Promise((r) => setTimeout(r, ms)), + + t: (key, params = {}) => { + let text = TEXTS[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; right: 20px; z-index: 10000; + padding: 12px 16px; border-radius: 8px; color: white; font-weight: 500; + background: ${type === 'success' ? '#28a745' : type === 'error' ? '#dc3545' : type === 'warning' ? '#ffc107' : '#17a2b8'}; + box-shadow: 0 4px 12px rgba(0,0,0,0.3); + max-width: 300px; word-wrap: break-word; + `; + alertDiv.textContent = message; + document.body.appendChild(alertDiv); + + setTimeout(() => { + alertDiv.style.transition = 'opacity 0.3s ease'; + alertDiv.style.opacity = '0'; + setTimeout(() => { + if (alertDiv.parentNode) alertDiv.remove(); + }, 300); + }, 4000); + }, + + addDebugLog: (message, type = 'info') => { + const timestamp = new Date().toLocaleTimeString(); + state.debugLogs.push({ timestamp, message, type }); + + if (state.debugLogs.length > 150) { + state.debugLogs = state.debugLogs.slice(-150); + } + + updateDebugConsole(); + console.log(`[${timestamp}] ${message}`); + }, + + waitForSelector: async (selector, interval = 200, timeout = 5000) => { + const start = Date.now(); + while (Date.now() - start < timeout) { + const el = document.querySelector(selector); + if (el) return el; + await Utils.sleep(interval); + } + return null; + }, + + 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(); + }), + + isWhitePixel: (r, g, b) => { + const wt = state.customWhiteThreshold || CONFIG.WHITE_THRESHOLD; + return r >= wt && g >= wt && b >= wt; + }, + + resolveColor(targetRgb, availableColors) { + if (!availableColors || availableColors.length === 0) { + return { id: null, rgb: targetRgb }; + } + + let bestId = availableColors[0].id; + let bestRgb = [...availableColors[0].rgb]; + let bestScore = Infinity; + + for (let i = 0; i < availableColors.length; i++) { + const c = availableColors[i]; + const [r, g, b] = c.rgb; + const rmean = (r + targetRgb[0]) / 2; + const rdiff = r - targetRgb[0]; + const gdiff = g - targetRgb[1]; + const bdiff = b - targetRgb[2]; + const dist = Math.sqrt( + (((512 + rmean) * rdiff * rdiff) >> 8) + + 4 * gdiff * gdiff + + (((767 - rmean) * bdiff * bdiff) >> 8) + ); + if (dist < bestScore) { + bestScore = dist; + bestId = c.id; + bestRgb = [...c.rgb]; + if (dist === 0) break; + } + } + + return { id: bestId, rgb: bestRgb }; + }, + + calculateTileRange( + startRegionX, + startRegionY, + startPixelX, + startPixelY, + width, + height, + tileSize = 1000 + ) { + const endPixelX = startPixelX + width; + const endPixelY = startPixelY + height; + + return { + startTileX: startRegionX + Math.floor(startPixelX / tileSize), + startTileY: startRegionY + Math.floor(startPixelY / tileSize), + endTileX: startRegionX + Math.floor((endPixelX - 1) / tileSize), + endTileY: startRegionY + Math.floor((endPixelY - 1) / tileSize), + }; + }, + + dynamicSleep: async function (tickAndGetRemainingMs) { + let remaining = Math.max(0, await tickAndGetRemainingMs()); + while (remaining > 0) { + const interval = remaining > 5000 ? 2000 : remaining > 1000 ? 500 : 100; + await this.sleep(Math.min(interval, remaining)); + remaining = Math.max(0, await tickAndGetRemainingMs()); + } + }, + + // NUEVA IMPLEMENTACIƓN TURNSTILE COMPLETA - ACTUALIZADA DESDE new.txt + turnstileLoaded: false, + _turnstileContainer: null, + _turnstileOverlay: null, + _turnstileWidgetId: null, + _lastSitekey: null, + _cachedSitekey: null, + + async loadTurnstile() { + if (window.turnstile) { + this.turnstileLoaded = true; + return Promise.resolve(); + } + + return new Promise((resolve, reject) => { + if (document.querySelector('script[src^="https://challenges.cloudflare.com/turnstile/v0/api.js"]')) { + const checkReady = () => { + if (window.turnstile) { + this.turnstileLoaded = true; + resolve(); + } else { + setTimeout(checkReady, 100); + } + }; + return checkReady(); + } + + const script = document.createElement('script'); + script.src = 'https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit'; + script.async = true; + script.defer = true; + script.onload = () => { + this.turnstileLoaded = true; + Utils.addDebugLog('Turnstile script loaded successfully', 'success'); + resolve(); + }; + script.onerror = () => { + Utils.addDebugLog('Failed to load Turnstile script', 'error'); + reject(new Error('Failed to load Turnstile')); + }; + document.head.appendChild(script); + }); + }, + + ensureTurnstileContainer() { + if (!this._turnstileContainer || !document.body.contains(this._turnstileContainer)) { + if (this._turnstileContainer) { + this._turnstileContainer.remove(); + } + + this._turnstileContainer = document.createElement('div'); + this._turnstileContainer.className = 'wplace-turnstile-hidden'; + this._turnstileContainer.setAttribute('aria-hidden', 'true'); + this._turnstileContainer.id = 'turnstile-widget-container'; + document.body.appendChild(this._turnstileContainer); + } + return this._turnstileContainer; + }, + + ensureTurnstileOverlayContainer() { + if (this._turnstileOverlay && document.body.contains(this._turnstileOverlay)) { + return this._turnstileOverlay; + } + + const overlay = document.createElement('div'); + overlay.id = 'turnstile-overlay-container'; + overlay.className = 'wplace-turnstile-overlay wplace-overlay-hidden'; + + const title = document.createElement('div'); + title.textContent = Utils.t('turnstileInstructions'); + title.className = 'wplace-turnstile-title'; + + const host = document.createElement('div'); + host.id = 'turnstile-overlay-host'; + host.className = 'wplace-turnstile-host'; + + const hideBtn = document.createElement('button'); + hideBtn.textContent = Utils.t('hideTurnstileBtn'); + hideBtn.className = 'wplace-turnstile-hide-btn'; + hideBtn.addEventListener('click', () => overlay.remove()); + + overlay.appendChild(title); + overlay.appendChild(host); + overlay.appendChild(hideBtn); + document.body.appendChild(overlay); + + this._turnstileOverlay = overlay; + return overlay; + }, + + async executeTurnstile(sitekey, action = 'paint') { + await this.loadTurnstile(); + + // Try reusing existing widget first if sitekey matches + if (this._turnstileWidgetId && this._lastSitekey === sitekey && window.turnstile?.execute) { + try { + console.log('šŸ”„ Reusing existing Turnstile widget...'); + const token = await Promise.race([ + window.turnstile.execute(this._turnstileWidgetId, { action }), + new Promise((_, reject) => + setTimeout(() => reject(new Error('Execute timeout')), 15000) + ), + ]); + if (token && token.length > 20) { + console.log('āœ… Token generated via widget reuse'); + return token; + } + } catch (error) { + console.log(' Widget reuse failed, will create a fresh widget:', error.message); + } + } + + // Try invisible widget first + const invisibleToken = await this.createTurnstileWidget(sitekey, action); + if (invisibleToken && invisibleToken.length > 20) { + return invisibleToken; + } + + console.log(' Falling back to interactive Turnstile (visible).'); + return await this.createTurnstileWidgetInteractive(sitekey, action); + }, + + async createTurnstileWidget(sitekey, action) { + return new Promise((resolve) => { + try { + // Force cleanup of any existing widget + if (this._turnstileWidgetId && window.turnstile?.remove) { + try { + window.turnstile.remove(this._turnstileWidgetId); + console.log('🧹 Cleaned up existing Turnstile widget'); + } catch (e) { + console.warn('āš ļø Widget cleanup warning:', e.message); + } + } + + const container = this.ensureTurnstileContainer(); + container.innerHTML = ''; + + // Verify Turnstile is available + if (!window.turnstile?.render) { + console.error('āŒ Turnstile not available for rendering'); + resolve(null); + return; + } + + console.log('šŸ”§ Creating invisible Turnstile widget...'); + const widgetId = window.turnstile.render(container, { + sitekey, + action, + size: 'invisible', + retry: 'auto', + 'retry-interval': 8000, + callback: (token) => { + console.log('āœ… Invisible Turnstile callback'); + resolve(token); + }, + 'error-callback': () => resolve(null), + 'timeout-callback': () => resolve(null), + }); + + this._turnstileWidgetId = widgetId; + this._lastSitekey = sitekey; + + if (!widgetId) { + return resolve(null); + } + + // Execute the widget and race with timeout + Promise.race([ + window.turnstile.execute(widgetId, { action }), + new Promise((_, reject) => + setTimeout(() => reject(new Error('Invisible execute timeout')), 12000) + ), + ]) + .then(resolve) + .catch(() => resolve(null)); + } catch (e) { + console.error('āŒ Invisible Turnstile creation failed:', e); + resolve(null); + } + }); + }, + + async createTurnstileWidgetInteractive(sitekey, action) { + // Create a visible widget that users can interact with if needed + console.log('šŸ”„ Creating interactive Turnstile widget (visible)'); + + return new Promise((resolve) => { + try { + // Force cleanup of any existing widget + if (this._turnstileWidgetId && window.turnstile?.remove) { + try { + window.turnstile.remove(this._turnstileWidgetId); + } catch (e) { + console.warn('āš ļø Widget cleanup warning:', e.message); + } + } + + const overlay = this.ensureTurnstileOverlayContainer(); + overlay.classList.remove('wplace-overlay-hidden'); + overlay.style.display = 'block'; + + const host = overlay.querySelector('#turnstile-overlay-host'); + host.innerHTML = ''; + + // Set a timeout for interactive mode + const timeout = setTimeout(() => { + console.warn('ā° Interactive Turnstile widget timeout'); + overlay.classList.add('wplace-overlay-hidden'); + overlay.style.display = 'none'; + resolve(null); + }, 60000); // 60 seconds for user interaction + + const widgetId = window.turnstile.render(host, { + sitekey, + action, + size: 'normal', + theme: 'light', + callback: (token) => { + clearTimeout(timeout); + overlay.classList.add('wplace-overlay-hidden'); + overlay.style.display = 'none'; + console.log('āœ… Interactive Turnstile completed successfully'); + + if (typeof token === 'string' && token.length > 20) { + resolve(token); + } else { + console.warn('āŒ Invalid token from interactive widget'); + resolve(null); + } + }, + 'error-callback': (error) => { + clearTimeout(timeout); + overlay.classList.add('wplace-overlay-hidden'); + overlay.style.display = 'none'; + console.warn('āŒ Interactive Turnstile error:', error); + resolve(null); + }, + }); + + this._turnstileWidgetId = widgetId; + this._lastSitekey = sitekey; + + if (!widgetId) { + clearTimeout(timeout); + overlay.classList.add('wplace-overlay-hidden'); + overlay.style.display = 'none'; + console.warn('āŒ Failed to create interactive Turnstile widget'); + resolve(null); + } else { + console.log('āœ… Interactive Turnstile widget created, waiting for user interaction...'); + } + } catch (e) { + console.error('āŒ Interactive Turnstile creation failed:', e); + resolve(null); + } + }); + }, + + cleanupTurnstile() { + if (this._turnstileWidgetId && window.turnstile?.remove) { + try { + window.turnstile.remove(this._turnstileWidgetId); + } catch (e) { + console.warn('Failed to cleanup Turnstile widget:', e); + } + } + + if (this._turnstileContainer && document.body.contains(this._turnstileContainer)) { + this._turnstileContainer.remove(); + } + + if (this._turnstileOverlay && document.body.contains(this._turnstileOverlay)) { + this._turnstileOverlay.remove(); + } + + this._turnstileWidgetId = null; + this._turnstileContainer = null; + this._turnstileOverlay = null; + this._lastSitekey = null; + }, + + // DETECCIƓN DE SITEKEY MEJORADA - ACTUALIZADA DESDE new.txt + async obtainSitekeyAndToken(fallback = '0x4AAAAAABpqJe8FO0N84q0F') { + // Cache sitekey to avoid repeated DOM queries + if (this._cachedSitekey) { + console.log('šŸ” Using cached sitekey:', this._cachedSitekey); + + return isTokenValid() + ? { + sitekey: this._cachedSitekey, + token: turnstileToken, + } + : { sitekey: this._cachedSitekey, token: null }; + } + + // List of potential sitekeys to try + const potentialSitekeys = [ + '0x4AAAAAABpqJe8FO0N84q0F', // WPlace common sitekey + '0x4AAAAAAAJ7xjKAp6Mt_7zw', // Alternative WPlace sitekey + '0x4AAAAAADm5QWx6Ov2LNF2g', // Another common sitekey + ]; + const trySitekey = async (sitekey, source) => { + if (!sitekey || sitekey.length < 10) return null; + + console.log(`šŸ” Testing sitekey from ${source}:`, sitekey); + const token = await this.executeTurnstile(sitekey); + + if (token && token.length >= 20) { + console.log(`āœ… Valid token generated from ${source} sitekey`); + setTurnstileToken(token); + this._cachedSitekey = sitekey; + return { sitekey, token }; + } else { + console.log(`āŒ Failed to get token from ${source} sitekey`); + return null; + } + }; + + try { + // 1ļøāƒ£ data-sitekey attribute + const sitekeySel = document.querySelector('[data-sitekey]'); + if (sitekeySel) { + const sitekey = sitekeySel.getAttribute('data-sitekey'); + const result = await trySitekey(sitekey, 'data attribute'); + if (result) { + return result; + } + } + + // 2ļøāƒ£ Turnstile element + const turnstileEl = document.querySelector('.cf-turnstile'); + if (turnstileEl?.dataset?.sitekey) { + const sitekey = turnstileEl.dataset.sitekey; + const result = await trySitekey(sitekey, 'turnstile element'); + if (result) { + return result; + } + } + + // 3ļøāƒ£ Meta tags + const metaTags = document.querySelectorAll( + 'meta[name*="turnstile"], meta[property*="turnstile"]' + ); + for (const meta of metaTags) { + const content = meta.getAttribute('content'); + const result = await trySitekey(content, 'meta tag'); + if (result) { + return result; + } + } + + // 4ļøāƒ£ Global variable + if (window.__TURNSTILE_SITEKEY) { + const result = await trySitekey(window.__TURNSTILE_SITEKEY, 'global variable'); + if (result) { + return result; + } + } + + // 5ļøāƒ£ Script tags + const scripts = document.querySelectorAll('script'); + for (const script of scripts) { + const content = script.textContent || script.innerHTML; + const match = content.match( + /(?:sitekey|data-sitekey)['"\s\[\]:\=\(]*['"]?([0-9a-zA-Z_-]{20,})['"]?/i + ); + if (match && match[1]) { + const extracted = match[1].replace(/['"]/g, ''); + const result = await trySitekey(extracted, 'script content'); + if (result) { + return result; + } + } + } + + // 6ļøāƒ£ Known potential sitekeys + console.log('šŸ” Testing known potential sitekeys...'); + for (const testSitekey of potentialSitekeys) { + const result = await trySitekey(testSitekey, 'known list'); + if (result) { + return result; + } + } + } catch (error) { + console.warn('āš ļø Error during sitekey detection:', error); + } + + // 7ļøāƒ£ Fallback + console.log('šŸ”§ Trying fallback sitekey:', fallback); + const fallbackResult = await trySitekey(fallback, 'fallback'); + if (fallbackResult) { + return fallbackResult; + } + + console.error('āŒ No working sitekey or token found.'); + return { sitekey: null, token: null }; + }, + }; + + // Enhanced Overlay Manager for pixel detection with autonomous capabilities + class OverlayManager { + constructor() { + this.originalTiles = new Map(); + this.originalTilesData = new Map(); + this.tileSize = 1000; + this.loadingPromises = new Map(); + this.autonomousMode = state.autonomousMode; + } + + async processAndRespondToTileRequest(eventData) { + const { endpoint, blobID, blobData } = eventData; + + 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}`; + + try { + const originalBitmap = await createImageBitmap(blobData); + this.originalTiles.set(tileKey, originalBitmap); + + try { + let canvas, ctx; + if (typeof OffscreenCanvas !== 'undefined') { + canvas = new OffscreenCanvas(originalBitmap.width, originalBitmap.height); + ctx = canvas.getContext('2d'); + } else { + canvas = document.createElement('canvas'); + canvas.width = originalBitmap.width; + canvas.height = originalBitmap.height; + ctx = canvas.getContext('2d'); + } + ctx.imageSmoothingEnabled = false; + ctx.drawImage(originalBitmap, 0, 0); + const imgData = ctx.getImageData(0, 0, originalBitmap.width, originalBitmap.height); + + this.originalTilesData.set(tileKey, { + w: originalBitmap.width, + h: originalBitmap.height, + data: new Uint8ClampedArray(imgData.data), + }); + + if (this.autonomousMode) { + Utils.addDebugLog(`Auto-cached tile: ${tileKey} (${originalBitmap.width}x${originalBitmap.height})`, 'info'); + } + } catch (e) { + Utils.addDebugLog(`Failed to cache tile ImageData: ${tileKey} - ${e.message}`, 'warning'); + } + } catch (e) { + Utils.addDebugLog(`Failed to create tile bitmap: ${tileKey} - ${e.message}`, 'error'); + } + } + + window.postMessage( + { + source: 'auto-image-overlay', + blobID: blobID, + blobData: blobData, + }, + '*' + ); + } + + async getTilePixelColor(tileX, tileY, pixelX, pixelY) { + const tileKey = `${tileX},${tileY}`; + const alphaThresh = state.customTransparencyThreshold || CONFIG.TRANSPARENCY_THRESHOLD; + + const cached = this.originalTilesData.get(tileKey); + if (cached && cached.data && cached.w > 0 && cached.h > 0) { + const x = Math.max(0, Math.min(cached.w - 1, pixelX)); + const y = Math.max(0, Math.min(cached.h - 1, pixelY)); + const idx = (y * cached.w + x) * 4; + const d = cached.data; + const a = d[idx + 3]; + + // Always return pixel data, let caller decide about transparency + return [d[idx], d[idx + 1], d[idx + 2], a]; + } + + const bitmap = this.originalTiles.get(tileKey); + if (!bitmap) { + if (this.autonomousMode) { + Utils.addDebugLog(`Tile ${tileKey} not available, requesting load...`, 'warning'); + } + return null; + } + + try { + let canvas, ctx; + if (typeof OffscreenCanvas !== 'undefined') { + canvas = new OffscreenCanvas(bitmap.width, bitmap.height); + ctx = canvas.getContext('2d'); + } else { + canvas = document.createElement('canvas'); + canvas.width = bitmap.width; + canvas.height = bitmap.height; + ctx = canvas.getContext('2d'); + } + ctx.imageSmoothingEnabled = false; + ctx.drawImage(bitmap, 0, 0); + + const x = Math.max(0, Math.min(bitmap.width - 1, pixelX)); + const y = Math.max(0, Math.min(bitmap.height - 1, pixelY)); + const data = ctx.getImageData(x, y, 1, 1).data; + const a = data[3]; + + // Always return pixel data, let caller decide about transparency + return [data[0], data[1], data[2], a]; + } catch (e) { + Utils.addDebugLog(`Error reading pixel from tile ${tileKey}: ${e.message}`, 'error'); + return null; + } + } + + async waitForTiles(startRegionX, startRegionY, pixelWidth, pixelHeight, startPixelX = 0, startPixelY = 0, timeoutMs = 15000) { + const { startTileX, startTileY, endTileX, endTileY } = Utils.calculateTileRange( + startRegionX, + startRegionY, + startPixelX, + startPixelY, + pixelWidth, + pixelHeight, + this.tileSize + ); + + const requiredTiles = []; + for (let ty = startTileY; ty <= endTileY; ty++) { + for (let tx = startTileX; tx <= endTileX; tx++) { + requiredTiles.push(`${tx},${ty}`); + } + } + + if (requiredTiles.length === 0) return true; + + Utils.addDebugLog(`Waiting for ${requiredTiles.length} tiles (autonomous: ${this.autonomousMode})...`, 'info'); + + const startTime = Date.now(); + let lastProgress = 0; + + while (Date.now() - startTime < timeoutMs) { + if (state.stopFlag) { + Utils.addDebugLog('waitForTiles: stopped by user', 'warning'); + return false; + } + + const loaded = requiredTiles.filter((k) => this.originalTiles.has(k)).length; + const progress = Math.round((loaded / requiredTiles.length) * 100); + + if (progress !== lastProgress && progress % 20 === 0) { + Utils.addDebugLog(`Tile loading progress: ${loaded}/${requiredTiles.length} (${progress}%)`, 'info'); + lastProgress = progress; + } + + if (loaded === requiredTiles.length) { + Utils.addDebugLog(`All ${requiredTiles.length} required tiles are loaded`, 'success'); + return true; + } + + await Utils.sleep(this.autonomousMode ? 500 : 1000); + } + + const loaded = requiredTiles.filter((k) => this.originalTiles.has(k)).length; + Utils.addDebugLog(`Timeout waiting for tiles: ${loaded}/${requiredTiles.length} loaded`, 'warning'); + + if (this.autonomousMode && loaded > requiredTiles.length * 0.8) { + Utils.addDebugLog(`Autonomous mode: proceeding with ${loaded}/${requiredTiles.length} tiles (80%+ loaded)`, 'warning'); + return true; + } + + return loaded > 0; + } + } + + const overlayManager = new OverlayManager(); + + // Enhanced WPlace API Service with auto-batch functionality + const WPlaceService = { + async paintPixelInRegion(regionX, regionY, pixelX, pixelY, color, retryCount = 0) { + try { + await ensureToken(); + if (!turnstileToken) { + Utils.addDebugLog('No valid token available for paint request', 'error'); + return 'token_error'; + } + + const payload = { + coords: [pixelX, pixelY], + colors: [color], + t: turnstileToken, + fp: fpStr32, + }; + + const wasmToken = await createWasmToken(regionX, regionY, payload); + if (!wasmToken) { + Utils.addDebugLog('Failed to generate WASM token', 'error'); + return false; + } + + const res = await fetch(`https://backend.wplace.live/s0/pixel/${regionX}/${regionY}`, { + method: 'POST', + headers: { + 'Content-Type': 'text/plain;charset=UTF-8', + 'x-pawtect-token': wasmToken + }, + credentials: 'include', + body: JSON.stringify(payload), + }); + + if (res.status === 403) { + Utils.addDebugLog(`403 Forbidden for pixel (${pixelX},${pixelY}). Token invalid/expired.`, 'error'); + invalidateToken(); + + if (retryCount < 2 && state.autonomousMode) { + Utils.addDebugLog(`Autonomous retry ${retryCount + 1}/2 for pixel (${pixelX},${pixelY})`, 'warning'); + await Utils.sleep(1000); + return await this.paintPixelInRegion(regionX, regionY, pixelX, pixelY, color, retryCount + 1); + } + + return 'token_error'; + } + + if (!res.ok) { + Utils.addDebugLog(`Paint request failed with status ${res.status}`, 'error'); + return false; + } + + const data = await res.json(); + const success = data?.painted === 1; + + if (success) { + Utils.addDebugLog(`Paint SUCCESS for (${pixelX},${pixelY}) with color ${color}`, 'success'); + } else { + Utils.addDebugLog(`Paint FAILED for (${pixelX},${pixelY}) - server response: ${JSON.stringify(data)}`, 'error'); + } + + return success; + } catch (e) { + Utils.addDebugLog(`Paint request error for (${pixelX},${pixelY}): ${e.message}`, 'error'); + return false; + } + }, + + // NEW: Auto-batch pixel painting functionality + async paintPixelBatchInRegion(regionX, regionY, pixelBatch, retryCount = 0) { + try { + await ensureToken(); + if (!turnstileToken) { + Utils.addDebugLog('No valid token available for batch paint request', 'error'); + return 'token_error'; + } + + // Prepare batch coordinates and colors + const coords = []; + const colors = []; + + for (const pixel of pixelBatch) { + coords.push(pixel.pixelX, pixel.pixelY); + colors.push(pixel.color); + } + + const payload = { + coords: coords, + colors: colors, + t: turnstileToken, + fp: fpStr32, + }; + + const wasmToken = await createWasmToken(regionX, regionY, payload); + if (!wasmToken) { + Utils.addDebugLog('Failed to generate WASM token for batch', 'error'); + return false; + } + + const res = await fetch(`https://backend.wplace.live/s0/pixel/${regionX}/${regionY}`, { + method: 'POST', + headers: { + 'Content-Type': 'text/plain;charset=UTF-8', + 'x-pawtect-token': wasmToken + }, + credentials: 'include', + body: JSON.stringify(payload), + }); + + if (res.status === 403) { + Utils.addDebugLog(`403 Forbidden for batch (${pixelBatch.length} pixels). Token invalid/expired.`, 'error'); + invalidateToken(); + + if (retryCount < 2 && state.autonomousMode) { + Utils.addDebugLog(`Autonomous batch retry ${retryCount + 1}/2 for ${pixelBatch.length} pixels`, 'warning'); + await Utils.sleep(1000); + return await this.paintPixelBatchInRegion(regionX, regionY, pixelBatch, retryCount + 1); + } + + return 'token_error'; + } + + if (!res.ok) { + Utils.addDebugLog(`Batch paint request failed with status ${res.status}`, 'error'); + return false; + } + + const data = await res.json(); + const successCount = data?.painted || 0; + + Utils.addDebugLog(`Batch paint result: ${successCount}/${pixelBatch.length} pixels painted successfully`, + successCount === pixelBatch.length ? 'success' : 'warning'); + + return { + success: successCount > 0, + painted: successCount, + total: pixelBatch.length + }; + + } catch (e) { + Utils.addDebugLog(`Batch paint request error for ${pixelBatch.length} pixels: ${e.message}`, 'error'); + return false; + } + }, + + async getCharges() { + try { + const res = await fetch('https://backend.wplace.live/me', { + credentials: 'include', + }); + if (!res.ok) return { charges: 0, max: 1, cooldown: CONFIG.COOLDOWN_DEFAULT }; + const data = await res.json(); + return { + charges: data.charges?.count ?? 0, + max: data.charges?.max ?? 1, + cooldown: data.charges?.cooldownMs ?? CONFIG.COOLDOWN_DEFAULT, + }; + } catch (e) { + Utils.addDebugLog(`Error fetching charges: ${e.message}`, 'warning'); + return { charges: 0, max: 1, cooldown: CONFIG.COOLDOWN_DEFAULT }; + } + }, + }; + + // Enhanced anti-grief repair system with improved transparent pixel detection + async function scanForDamage() { + if (!state.imageData || !state.startPosition || !state.region) { + Utils.addDebugLog('No image data or position for scanning', 'warning'); + return []; + } + + Utils.addDebugLog('Starting enhanced damage scan with transparent pixel detection...', 'info'); + updateStatus(Utils.t('scanningForDamage')); + + const damagedPixels = []; + const { width, height, pixels } = state.imageData; + + // Check if we're working with restored data - skip tile waiting for restored saves + const isRestoredData = state.availableColors && state.availableColors.length > 0 && state.colorsChecked; + + if (!isRestoredData) { + const ready = await overlayManager.waitForTiles( + state.region.x, + state.region.y, + width, + height, + state.startPosition.x, + state.startPosition.y, + state.autonomousMode ? 20000 : 15000 + ); + + if (!ready) { + Utils.addDebugLog('Failed to load required tiles for scanning', 'error'); + if (state.autonomousMode) { + Utils.addDebugLog('Autonomous mode: will retry scan in 30 seconds', 'warning'); + setTimeout(() => { + if (state.autoRepairEnabled && !state.stopFlag) { + scanForDamage(); + } + }, 30000); + } + return []; + } + } else { + Utils.addDebugLog('Using restored data - skipping tile wait for damage scan', 'info'); + } + + Utils.addDebugLog(`Scanning ${width}x${height} image for damage (transparent detection: ${state.paintTransparentPixels})...`, 'info'); + + let scannedPixels = 0; + let transparentPixelsDetected = 0; + let wrongColorPixelsDetected = 0; + let lastProgressUpdate = Date.now(); + + for (let y = 0; y < height; y++) { + for (let x = 0; x < width; x++) { + if (state.stopFlag) break; + + const idx = (y * width + x) * 4; + const originalR = pixels[idx]; + const originalG = pixels[idx + 1]; + const originalB = pixels[idx + 2]; + const originalA = pixels[idx + 3]; + + // Enhanced transparent pixel detection + const isOriginalTransparent = originalA < state.customTransparencyThreshold; + + // Skip white pixels if configured + if (!state.paintWhitePixels && Utils.isWhitePixel(originalR, originalG, originalB)) { + continue; + } + + scannedPixels++; + + const absX = state.startPosition.x + x; + const absY = state.startPosition.y + y; + const tileX = state.region.x + Math.floor(absX / 1000); + const tileY = state.region.y + Math.floor(absY / 1000); + const pixelX = absX % 1000; + const pixelY = absY % 1000; + + try { + const currentPixel = await overlayManager.getTilePixelColor(tileX, tileY, pixelX, pixelY); + + // Handle missing current pixel data (could indicate transparent area) + if (!currentPixel) { + if (state.paintTransparentPixels && !isOriginalTransparent) { + // Original pixel is not transparent but current is missing/transparent + const targetColor = Utils.resolveColor([originalR, originalG, originalB], state.availableColors); + damagedPixels.push({ + x, + y, + originalColor: targetColor, + currentColor: { id: 63, name: 'Transparent', rgb: [0, 0, 0] }, // Transparent color + originalRgb: [originalR, originalG, originalB], + currentRgb: [0, 0, 0], + isDamagedTransparent: true + }); + transparentPixelsDetected++; + + if (!state.autonomousMode || transparentPixelsDetected <= 10) { + Utils.addDebugLog(`Missing/Transparent pixel at (${x},${y}): expected color ${targetColor.id}, found transparent/missing`, 'warning'); + } + } + continue; + } + + const currentIsTransparent = currentPixel[3] < state.customTransparencyThreshold; + + // Case 1: Original is not transparent, but current is transparent (damaged) + if (!isOriginalTransparent && currentIsTransparent && state.paintTransparentPixels) { + const targetColor = Utils.resolveColor([originalR, originalG, originalB], state.availableColors); + damagedPixels.push({ + x, + y, + originalColor: targetColor, + currentColor: { id: 63, name: 'Transparent', rgb: [0, 0, 0] }, + originalRgb: [originalR, originalG, originalB], + currentRgb: currentPixel.slice(0, 3), + isDamagedTransparent: true + }); + transparentPixelsDetected++; + + if (!state.autonomousMode || transparentPixelsDetected <= 10) { + Utils.addDebugLog(`Transparent damage at (${x},${y}): expected color ${targetColor.id}, found transparent`, 'warning'); + } + continue; + } + + // Case 2: Original is transparent, current is not transparent (wrong placement) + if (isOriginalTransparent && !currentIsTransparent && state.paintTransparentPixels) { + damagedPixels.push({ + x, + y, + originalColor: { id: 63, name: 'Transparent', rgb: null }, + currentColor: Utils.resolveColor(currentPixel.slice(0, 3), state.availableColors), + originalRgb: [originalR, originalG, originalB], + currentRgb: currentPixel.slice(0, 3), + isDamagedTransparent: true + }); + transparentPixelsDetected++; + + if (!state.autonomousMode || transparentPixelsDetected <= 10) { + Utils.addDebugLog(`Wrong placement at (${x},${y}): expected transparent, found color`, 'warning'); + } + continue; + } + + // Case 3: Both are not transparent, check color match (normal damage detection) + if (!isOriginalTransparent && !currentIsTransparent) { + const targetColor = Utils.resolveColor([originalR, originalG, originalB], state.availableColors); + const currentColor = Utils.resolveColor(currentPixel.slice(0, 3), state.availableColors); + + if (targetColor.id === currentColor.id) { + // Pixel is already correctly painted - skip it + continue; + } + + // Pixel has wrong color - mark as damaged + damagedPixels.push({ + x, + y, + originalColor: targetColor, + currentColor: currentColor, + originalRgb: [originalR, originalG, originalB], + currentRgb: currentPixel.slice(0, 3), + isDamagedTransparent: false + }); + wrongColorPixelsDetected++; + + if (!state.autonomousMode || wrongColorPixelsDetected <= 10) { + Utils.addDebugLog(`Color damage at (${x},${y}): expected color ${targetColor.id}, found color ${currentColor.id}`, 'warning'); + } + } + + } catch (e) { + if (!state.autonomousMode) { + Utils.addDebugLog(`Error checking pixel (${x},${y}): ${e.message}`, 'error'); + } + } + } + + if (state.autonomousMode && Date.now() - lastProgressUpdate > 5000) { + Utils.addDebugLog(`Scan progress: ${y}/${height} rows (${Math.round((y / height) * 100)}%), found ${damagedPixels.length} damaged (${transparentPixelsDetected} transparent, ${wrongColorPixelsDetected} wrong color)`, 'info'); + lastProgressUpdate = Date.now(); + } else if (!state.autonomousMode && y % 10 === 0) { + Utils.addDebugLog(`Scan progress: ${y}/${height} rows (${Math.round((y / height) * 100)}%)`, 'info'); + } + } + + const logLevel = damagedPixels.length > 0 ? 'warning' : 'success'; + Utils.addDebugLog(`Enhanced scan complete. Checked ${scannedPixels} pixels, found ${damagedPixels.length} damaged (${transparentPixelsDetected} transparent issues, ${wrongColorPixelsDetected} wrong colors)`, logLevel); + + // Smart auto-adjust batch size: if 6 pixels detected, batch size = 6 + if (state.autoBatchEnabled && state.batchOptimization && damagedPixels.length > 0) { + const newBatchSize = Math.min(CONFIG.MAX_BATCH_SIZE, Math.max(CONFIG.MIN_BATCH_SIZE, damagedPixels.length)); + if (newBatchSize !== state.currentBatchSize) { + state.currentBatchSize = newBatchSize; + Utils.addDebugLog(`Smart batch: Auto-adjusted batch size to ${newBatchSize} to match ${damagedPixels.length} detected damaged pixels`, 'info'); + updateBatchInfo(); // Update UI display + } + } + + return damagedPixels; + } + + async function repairDamagedPixels(damagedPixels) { + if (damagedPixels.length === 0) { + updateStatus(Utils.t('noDamageDetected')); + return 0; + } + + Utils.addDebugLog(`Starting enhanced repair of ${damagedPixels.length} pixels with auto-batch (batch size: ${state.currentBatchSize})`, 'info'); + updateStatus(Utils.t('repairingPixels', { count: damagedPixels.length })); + + let repairedCount = 0; + let consecutiveFailures = 0; + const maxConsecutiveFailures = state.autonomousMode ? 5 : 3; + + // Group pixels by region for batch processing + const pixelsByRegion = new Map(); + + for (const pixel of damagedPixels) { + const absX = state.startPosition.x + pixel.x; + const absY = state.startPosition.y + pixel.y; + const regionX = state.region.x + Math.floor(absX / 1000); + const regionY = state.region.y + Math.floor(absY / 1000); + const regionKey = `${regionX},${regionY}`; + + if (!pixelsByRegion.has(regionKey)) { + pixelsByRegion.set(regionKey, []); + } + + pixelsByRegion.get(regionKey).push({ + ...pixel, + regionX, + regionY, + pixelX: absX % 1000, + pixelY: absY % 1000, + color: pixel.isDamagedTransparent && pixel.originalColor.id === 63 ? 63 : pixel.originalColor.id + }); + } + + Utils.addDebugLog(`Grouped ${damagedPixels.length} pixels into ${pixelsByRegion.size} regions for batch processing`, 'info'); + + // Process each region + for (const [regionKey, regionPixels] of pixelsByRegion) { + if (state.stopFlag) { + Utils.addDebugLog('Repair stopped by user request', 'warning'); + break; + } + + const [regionX, regionY] = regionKey.split(',').map(Number); + Utils.addDebugLog(`Processing region ${regionKey} with ${regionPixels.length} pixels`, 'info'); + + // Process pixels in batches + for (let i = 0; i < regionPixels.length; i += state.currentBatchSize) { + if (state.stopFlag) break; + + const batch = regionPixels.slice(i, i + state.currentBatchSize); + const actualBatchSize = Math.min(batch.length, state.currentBatchSize); + + // Enhanced charge management for batch processing + await updateCharges(); + let chargeWaitAttempts = 0; + const maxChargeWaitAttempts = state.autonomousMode ? 20 : 10; + + while (state.displayCharges < actualBatchSize && !state.stopFlag && chargeWaitAttempts < maxChargeWaitAttempts) { + chargeWaitAttempts++; + const waitTime = state.autonomousMode ? Math.min(state.cooldown, 10000) : state.cooldown; + + if (chargeWaitAttempts === 1) { + Utils.addDebugLog(`Waiting for charges... (need ${actualBatchSize}, have ${state.displayCharges}/${state.maxCharges})`, 'info'); + } + + await Utils.dynamicSleep(() => { + if (state.displayCharges >= actualBatchSize) return 0; + if (state.stopFlag) return 0; + return waitTime; + }); + await updateCharges(); + } + + if (state.stopFlag) break; + + if (state.displayCharges < actualBatchSize) { + Utils.addDebugLog(`Insufficient charges for batch (need ${actualBatchSize}, have ${state.displayCharges}), falling back to single pixel repair`, 'warning'); + + // Fall back to single pixel repair + for (const pixel of batch) { + if (state.stopFlag) break; + + await updateCharges(); + if (state.displayCharges < 1) { + Utils.addDebugLog(`No charges available, skipping pixel (${pixel.x},${pixel.y})`, 'warning'); + continue; + } + + const success = await repairSinglePixel(pixel); + if (success) { + repairedCount++; + consecutiveFailures = 0; + } else { + consecutiveFailures++; + } + + if (consecutiveFailures >= maxConsecutiveFailures) break; + await Utils.sleep(100); + } + } else { + // Use batch repair + const batchResult = await repairPixelBatch(regionX, regionY, batch); + + if (batchResult && batchResult.success) { + repairedCount += batchResult.painted; + consecutiveFailures = 0; + + Utils.addDebugLog(`Batch repair: ${batchResult.painted}/${batchResult.total} pixels repaired successfully [Total: ${repairedCount}/${damagedPixels.length}]`, 'success'); + } else { + consecutiveFailures++; + Utils.addDebugLog(`Batch repair failed for ${batch.length} pixels [${consecutiveFailures}/${maxConsecutiveFailures} consecutive failures]`, 'error'); + + if (consecutiveFailures >= maxConsecutiveFailures) { + Utils.addDebugLog(`Too many consecutive batch failures (${consecutiveFailures}), pausing repair`, 'error'); + if (state.autonomousMode) { + Utils.addDebugLog('Autonomous mode: will retry repair in 60 seconds', 'warning'); + setTimeout(() => { + if (state.autoRepairEnabled && !state.stopFlag) { + performRepairCheck(); + } + }, 60000); + } + break; + } + } + } + + await updateCharges(); + + // Dynamic delay based on mode and success rate + const baseDelay = state.autonomousMode ? 100 : 200; + const adaptiveDelay = consecutiveFailures > 0 ? baseDelay * (consecutiveFailures + 1) : baseDelay; + await Utils.sleep(adaptiveDelay); + } + + if (consecutiveFailures >= maxConsecutiveFailures) break; + } + + const message = Utils.t('repairComplete', { repaired: repairedCount }); + updateStatus(message); + Utils.addDebugLog(`${message} (${damagedPixels.length - repairedCount} remaining) - Used auto-batch with size ${state.currentBatchSize}`, 'success'); + + return repairedCount; + } + + async function repairPixelBatch(regionX, regionY, pixelBatch) { + try { + Utils.addDebugLog(`Attempting batch repair of ${pixelBatch.length} pixels in region ${regionX},${regionY}`, 'info'); + + const result = await WPlaceService.paintPixelBatchInRegion(regionX, regionY, pixelBatch); + + if (result === 'token_error') { + Utils.addDebugLog('Token error during batch repair, refreshing token...', 'warning'); + await ensureToken(true); + await Utils.sleep(state.autonomousMode ? 500 : 1000); + + // Retry once with new token + const retryResult = await WPlaceService.paintPixelBatchInRegion(regionX, regionY, pixelBatch); + return retryResult; + } + + return result; + } catch (e) { + Utils.addDebugLog(`Error during batch repair: ${e.message}`, 'error'); + return false; + } + } + + async function repairSinglePixel(pixel) { + const { x, y, originalColor, regionX, regionY, pixelX, pixelY, color } = pixel; + + try { + const result = await WPlaceService.paintPixelInRegion( + regionX, + regionY, + pixelX, + pixelY, + color || originalColor.id + ); + + if (result === 'token_error') { + Utils.addDebugLog('Token error during repair, refreshing token...', 'warning'); + await ensureToken(true); + await Utils.sleep(state.autonomousMode ? 500 : 1000); + + // Retry once with new token + const retryResult = await WPlaceService.paintPixelInRegion(regionX, regionY, pixelX, pixelY, color || originalColor.id); + return retryResult === true; + } + + if (result === true) { + if (pixel.isDamagedTransparent) { + Utils.addDebugLog(`Repaired transparent pixel (${x},${y}) with color ${color || originalColor.id}`, 'success'); + } + } + + return result === true; + } catch (e) { + Utils.addDebugLog(`Error repairing pixel (${x},${y}): ${e.message}`, 'error'); + return false; + } + } + + async function performRepairCheck() { + if (state.running) { + Utils.addDebugLog('Repair check skipped - manual repair in progress', 'info'); + return; + } + + try { + Utils.addDebugLog('Autonomous repair check triggered', 'info'); + + // Ensure we have a valid token before starting + if (!isTokenValid()) { + Utils.addDebugLog('No valid token for autonomous repair, generating...', 'warning'); + await ensureToken(true); + if (!isTokenValid()) { + Utils.addDebugLog('Failed to generate token for autonomous repair, will retry next cycle', 'error'); + return; + } + } + + const damagedPixels = await scanForDamage(); + + if (damagedPixels.length > 0) { + Utils.addDebugLog(`Autonomous repair: Found ${damagedPixels.length} damaged pixels, starting repair`, 'warning'); + updateStatus(Utils.t('damageDetected', { count: damagedPixels.length })); + + // Show attack notification + showAttackNotification('attack', damagedPixels.length); + state.lastAttackState = 'attacked'; + + const repairedCount = await repairDamagedPixels(damagedPixels); + + // Show repair complete notification if all pixels were repaired + if (repairedCount === damagedPixels.length) { + showAttackNotification('repaired'); + state.lastAttackState = 'repaired'; + } + } else { + updateStatus(Utils.t('noDamageDetected')); + + // Show peaceful state if we're not under attack + if (state.lastAttackState !== 'peaceful') { + showPeacefulState(); + state.lastAttackState = 'peaceful'; + } + + if (!state.autonomousMode) { + Utils.addDebugLog('Autonomous repair: No damage detected', 'success'); + } + } + } catch (error) { + Utils.addDebugLog(`Autonomous repair error: ${error.message}`, 'error'); + + if (state.autonomousMode) { + Utils.addDebugLog('Autonomous mode: will retry repair check in 120 seconds due to error', 'warning'); + setTimeout(() => { + if (state.autoRepairEnabled && !state.stopFlag) { + performRepairCheck(); + } + }, 120000); + } + } + } + + function startAutoRepair() { + if (state.autoRepairTimer) { + clearInterval(state.autoRepairTimer); + } + + const intervalMs = state.autoRepairInterval * 1000; + state.autoRepairTimer = setInterval(performRepairCheck, intervalMs); + + const message = Utils.t('autoRepairStarted', { interval: state.autoRepairInterval }); + Utils.addDebugLog(`${message} (autonomous: ${state.autonomousMode})`, 'info'); + Utils.showAlert(message, 'success'); + + // Perform first check immediately + setTimeout(performRepairCheck, 2000); + } + + function stopAutoRepair() { + if (state.autoRepairTimer) { + clearInterval(state.autoRepairTimer); + state.autoRepairTimer = null; + } + + const message = Utils.t('autoRepairStopped'); + Utils.addDebugLog(`${message}`, 'info'); + Utils.showAlert(message, 'info'); + } + + // FUNCIONES DE UI MEJORADAS CON GESTIƓN DE CONFIGURACIƓN INICIAL + function enableFileOperations() { + state.initialSetupComplete = true; + + const loadBtn = document.querySelector('#loadFileBtn'); + if (loadBtn) { + loadBtn.disabled = false; + loadBtn.title = ''; + loadBtn.style.animation = 'pulse 0.6s ease-in-out'; + setTimeout(() => { + if (loadBtn) loadBtn.style.animation = ''; + }, 600); + Utils.addDebugLog('Load Progress button enabled after initial setup', 'success'); + } + + Utils.showAlert(Utils.t('fileOperationsAvailable'), 'success'); + } + + async function initializeTokenGenerator() { + if (isTokenValid()) { + Utils.addDebugLog('Valid token already available, skipping initialization', 'success'); + updateTokenStatus(); + enableFileOperations(); + return; + } + + try { + Utils.addDebugLog('Initializing Turnstile token generator...', 'info'); + updateStatus(Utils.t('initializingToken')); + + await Utils.loadTurnstile(); + Utils.addDebugLog('Turnstile script loaded successfully', 'success'); + + if (state.autoTokenRefresh) { + Utils.addDebugLog('Pre-generating token for autonomous operations...', 'info'); + await ensureToken(); + } + + state.initialSetupComplete = true; + Utils.addDebugLog('Enhanced token generator initialization complete', 'success'); + updateStatus(Utils.t('tokenReady')); + enableFileOperations(); + + } catch (error) { + Utils.addDebugLog('Token system initialization failed: ' + error.message, 'warning'); + Utils.addDebugLog('Manual token generation will be required', 'info'); + enableFileOperations(); + } + } + + function updateStatus(message) { + const statusEl = document.getElementById('status'); + if (statusEl) { + if (typeof message === 'string') { + statusEl.innerHTML = message; + } else { + statusEl.textContent = message; + } + } + } + + function updateDebugConsole() { + const debugConsole = document.getElementById('debugConsole'); + if (!debugConsole) return; + + const logsHtml = state.debugLogs.map(log => { + const colorClass = log.type === 'error' ? 'neon-text danger' : + log.type === 'warning' ? 'neon-text warning' : + log.type === 'success' ? 'neon-text success' : + log.type === 'info' ? 'neon-text info' : 'neon-text'; + + return `
+ [${log.timestamp}] ${log.message} +
`; + }).join(''); + + debugConsole.innerHTML = logsHtml; + debugConsole.scrollTop = debugConsole.scrollHeight; + } + + async function updateCharges() { + try { + const { charges, max, cooldown } = await WPlaceService.getCharges(); + state.displayCharges = Math.floor(charges); + state.maxCharges = Math.max(1, Math.floor(max)); + state.cooldown = cooldown; + + updateChargesDisplay(); + } catch (error) { + if (!state.autonomousMode) { + Utils.addDebugLog(`Error updating charges: ${error.message}`, 'error'); + } + } + } + + function updateChargesDisplay() { + const chargesEl = document.getElementById('chargesInfo'); + if (chargesEl) { + chargesEl.textContent = `Charges: ${state.displayCharges}/${state.maxCharges} (cooldown: ${Math.round(state.cooldown / 1000)}s)`; + } + } + + function updateTokenStatus() { + const tokenEl = document.getElementById('tokenInfo'); + if (tokenEl) { + // Remove existing classes first + tokenEl.className = ''; + + if (isTokenValid()) { + const remaining = Math.round((tokenExpiryTime - Date.now()) / 1000); + tokenEl.textContent = `Token: Valid (expires in ${remaining}s)`; + tokenEl.className = 'neon-text success'; + } else { + tokenEl.textContent = 'Token: Not generated or expired'; + tokenEl.className = 'neon-text warning'; + } + } + } + + // Update batch information display for new UI + function updateBatchInfo() { + const batchSizeEl = document.getElementById('batchSize'); + const batchStatusEl = document.getElementById('batchStatus'); + const smartModeEl = document.getElementById('smartBatchMode'); + const manualModeEl = document.getElementById('manualBatchMode'); + const manualControlsEl = document.getElementById('manualBatchControls'); + + // Update radio button states + if (smartModeEl && manualModeEl) { + smartModeEl.checked = state.autoBatchEnabled && state.batchOptimization; + manualModeEl.checked = !state.autoBatchEnabled || !state.batchOptimization; + } + + // Update manual controls visibility + if (manualControlsEl) { + manualControlsEl.style.display = (!state.autoBatchEnabled || !state.batchOptimization) ? 'flex' : 'none'; + } + + // Update batch size input + if (batchSizeEl) { + batchSizeEl.value = state.currentBatchSize; + } + + // Update status display + if (batchStatusEl) { + if (state.autoBatchEnabled && state.batchOptimization) { + batchStatusEl.textContent = `🧠 Smart Batch: AUTO (Current: ${state.currentBatchSize})`; + batchStatusEl.className = 'status-info'; + } else { + batchStatusEl.textContent = `āœ‹ Manual Batch: ${state.currentBatchSize} pixels fixed`; + batchStatusEl.className = 'status-warning'; + } + } + } + + function updateSystemStatus() { + const systemEl = document.getElementById('systemInfo'); + if (systemEl) { + const autonomousStatus = state.autonomousMode ? 'AUTONOMOUS' : 'MANUAL'; + const tokenMode = state.tokenSource.toUpperCase(); + systemEl.textContent = `System: ${autonomousStatus} | Token: ${tokenMode} | Auto-refresh: ${state.autoTokenRefresh ? 'ON' : 'OFF'}`; + } + } + + // Window dragging functionality + function makeWindowDraggable() { + const container = document.getElementById('wplace-repair-tool'); + const header = document.getElementById('window-header'); + + let isDragging = false; + let currentX; + let currentY; + let initialX; + let initialY; + let xOffset = 0; + let yOffset = 0; + + function dragStart(e) { + if (e.target.tagName === 'BUTTON' || e.target.tagName === 'INPUT' || e.target.tagName === 'SELECT') { + return; + } + + initialX = e.clientX - xOffset; + initialY = e.clientY - yOffset; + + if (e.target === header || header.contains(e.target)) { + isDragging = true; + container.style.cursor = 'grabbing'; + } + } + + function dragEnd() { + initialX = currentX; + initialY = currentY; + isDragging = false; + container.style.cursor = 'default'; + } + + function drag(e) { + if (isDragging) { + e.preventDefault(); + currentX = e.clientX - initialX; + currentY = e.clientY - initialY; + xOffset = currentX; + yOffset = currentY; + + container.style.transform = `translate(${currentX}px, ${currentY}px)`; + } + } + + header.addEventListener('mousedown', dragStart); + document.addEventListener('mouseup', dragEnd); + document.addEventListener('mousemove', drag); + } + + // Enhanced UI Creation with NEW IMPLEMENTATION + function createUI() { + const existing = document.getElementById('wplace-repair-tool'); + if (existing) existing.remove(); + + const container = document.createElement('div'); + container.id = 'wplace-repair-tool'; + container.style.cssText = ` + position: fixed; top: 20px; left: 20px; z-index: 10000; + background: #1a1a2e; + border: 3px solid #00ff41; + border-radius: 0; + min-width: 600px; max-width: 700px; + box-shadow: + 0 0 30px rgb(0 255 65 / 50%), + inset 0 0 30px rgb(0 255 65 / 10%), + 0 0 0 1px #00ff41; + color: #00ff41; + font-family: 'Press Start 2P', monospace, 'Courier New'; + resize: both; overflow: hidden; + animation: neon-pulse 2s ease-in-out infinite alternate; + `; + + // Add Neon theme styles + const neonStyles = document.createElement('style'); + neonStyles.id = 'auto-repair-neon-styles'; + neonStyles.textContent = ` + @import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap'); + + #wplace-repair-tool::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 2px; + background: linear-gradient(90deg, transparent, #00ff41, transparent); + z-index: 1; + pointer-events: none; + animation: scanline 3s linear infinite; + opacity: 0.7; + } + + .neon-btn { + background: #16213e !important; + border: 2px solid #00ff41 !important; + border-radius: 0 !important; + color: #00ff41 !important; + padding: 8px 15px !important; + font-family: 'Press Start 2P', monospace !important; + font-size: 8px !important; + text-transform: uppercase !important; + cursor: pointer !important; + transition: all 0.3s ease !important; + text-shadow: 0 0 8px #00ff41 !important; + letter-spacing: 1px !important; + margin: 4px !important; + } + + .neon-btn:hover { + background: rgba(0,255,65,0.1) !important; + box-shadow: 0 0 20px rgb(0 255 65 / 60%) !important; + animation: pixel-blink 0.5s infinite !important; + } + + .neon-btn:disabled { + background: #0d1117 !important; + border-color: #555 !important; + color: #555 !important; + text-shadow: none !important; + cursor: not-allowed !important; + } + + .neon-btn.danger { + border-color: #ff073a !important; + color: #ff073a !important; + text-shadow: 0 0 8px #ff073a !important; + } + + .neon-btn.danger:hover { + background: rgba(255, 7, 58, 0.1) !important; + box-shadow: 0 0 20px rgb(255 7 58 / 60%) !important; + } + + .neon-btn.warning { + border-color: #ff6b35 !important; + color: #ff6b35 !important; + text-shadow: 0 0 8px #ff6b35 !important; + } + + .neon-btn.warning:hover { + background: rgba(255, 107, 53, 0.1) !important; + box-shadow: 0 0 20px rgb(255 107 53 / 60%) !important; + } + + .neon-input { + background: #16213e !important; + border: 2px solid #00ff41 !important; + border-radius: 0 !important; + color: #00ff41 !important; + padding: 6px 10px !important; + font-family: 'Press Start 2P', monospace !important; + font-size: 8px !important; + text-shadow: 0 0 5px #00ff41 !important; + } + + .neon-input:focus { + outline: none !important; + box-shadow: 0 0 15px rgb(0 255 65 / 60%) !important; + animation: pixel-blink 0.5s infinite !important; + } + + .neon-select { + background: #16213e !important; + border: 2px solid #00ff41 !important; + border-radius: 0 !important; + color: #00ff41 !important; + padding: 4px 8px !important; + font-family: 'Press Start 2P', monospace !important; + font-size: 7px !important; + text-shadow: 0 0 5px #00ff41 !important; + } + + .neon-checkbox { + accent-color: #00ff41 !important; + transform: scale(1.2) !important; + margin-right: 8px !important; + } + + /* Enhanced Batch Control Styles */ + .batch-control-group { + display: flex !important; + align-items: center !important; + gap: 8px !important; + margin: 5px 0 !important; + padding: 8px !important; + background: rgba(0, 255, 65, 0.05) !important; + border: 1px solid rgba(0, 255, 65, 0.3) !important; + border-radius: 3px !important; + } + + .batch-control-header { + text-align: center !important; + margin-bottom: 10px !important; + padding: 5px !important; + border-bottom: 1px solid rgba(0, 255, 65, 0.3) !important; + } + + .batch-control-label { + color: #00ff41 !important; + font-family: 'Press Start 2P', monospace !important; + font-size: 6px !important; + text-shadow: 0 0 3px #00ff41 !important; + white-space: nowrap !important; + } + + .batch-mode-selection { + margin: 10px 0 !important; + } + + .batch-mode-option { + margin: 8px 0 !important; + padding: 6px !important; + background: rgba(0, 255, 65, 0.03) !important; + border: 1px solid rgba(0, 255, 65, 0.2) !important; + border-radius: 3px !important; + transition: all 0.3s ease !important; + } + + .batch-mode-option:hover { + background: rgba(0, 255, 65, 0.08) !important; + border-color: rgba(0, 255, 65, 0.4) !important; + } + + .batch-mode-label { + color: #00ff41 !important; + font-family: 'Press Start 2P', monospace !important; + font-size: 6px !important; + text-shadow: 0 0 3px #00ff41 !important; + margin-left: 8px !important; + } + + .neon-radio { + accent-color: #00ff41 !important; + transform: scale(1.2) !important; + margin-right: 8px !important; + } + + .batch-manual-controls { + display: flex !important; + align-items: center !important; + gap: 8px !important; + margin: 8px 0 !important; + padding: 8px !important; + background: rgba(0, 255, 65, 0.05) !important; + border: 1px solid rgba(0, 255, 65, 0.3) !important; + border-radius: 3px !important; + } + + .batch-size-control { + display: flex !important; + align-items: center !important; + gap: 2px !important; + border: 1px solid #00ff41 !important; + border-radius: 3px !important; + background: #16213e !important; + overflow: hidden !important; + } + + .batch-adjust-btn { + width: 20px !important; + height: 24px !important; + background: #16213e !important; + border: none !important; + color: #00ff41 !important; + font-family: 'Press Start 2P', monospace !important; + font-size: 8px !important; + cursor: pointer !important; + transition: all 0.3s ease !important; + text-shadow: 0 0 3px #00ff41 !important; + display: flex !important; + align-items: center !important; + justify-content: center !important; + } + + .batch-adjust-btn:hover { + background: rgba(0, 255, 65, 0.1) !important; + box-shadow: 0 0 5px rgba(0, 255, 65, 0.3) !important; + } + + .batch-adjust-btn:active { + background: rgba(0, 255, 65, 0.2) !important; + transform: scale(0.95) !important; + } + + .batch-size-input { + width: 50px !important; + height: 24px !important; + background: #16213e !important; + border: none !important; + color: #00ff41 !important; + padding: 0 4px !important; + font-family: 'Press Start 2P', monospace !important; + font-size: 6px !important; + text-align: center !important; + text-shadow: 0 0 3px #00ff41 !important; + outline: none !important; + /* Hide browser default spinner buttons */ + -webkit-appearance: none !important; + -moz-appearance: textfield !important; + } + + /* Hide Chrome spinner buttons */ + .batch-size-input::-webkit-outer-spin-button, + .batch-size-input::-webkit-inner-spin-button { + -webkit-appearance: none !important; + margin: 0 !important; + } + + /* Hide Firefox spinner buttons */ + .batch-size-input[type=number] { + -moz-appearance: textfield !important; + } + + .status-info { + color: #00ff41 !important; + } + + .status-warning { + color: #ff6b35 !important; + } + + .neon-panel { + background: rgba(22, 33, 62, 0.3) !important; + border: 1px solid #00ff41 !important; + border-radius: 0 !important; + padding: 15px !important; + margin: 10px 0 !important; + box-shadow: inset 0 0 15px rgba(0, 255, 65, 0.1) !important; + } + + .neon-text { + color: #00ff41 !important; + text-shadow: 0 0 5px #00ff41 !important; + font-family: 'Press Start 2P', monospace !important; + } + + .neon-text.success { + color: #39ff14 !important; + text-shadow: 0 0 5px #39ff14 !important; + } + + .neon-text.warning { + color: #ff6b35 !important; + text-shadow: 0 0 5px #ff6b35 !important; + } + + .neon-text.danger { + color: #ff073a !important; + text-shadow: 0 0 5px #ff073a !important; + } + + .neon-text.info { + color: #00ccff !important; + text-shadow: 0 0 5px #00ccff !important; + } + + /* Custom scrollbar for debug console */ + #debugConsole::-webkit-scrollbar { + width: 12px; + } + + #debugConsole::-webkit-scrollbar-track { + background: #16213e; + border: 1px solid #00ff41; + } + + #debugConsole::-webkit-scrollbar-thumb { + background: #00ff41; + border-radius: 0; + box-shadow: 0 0 10px #00ff41; + } + + #debugConsole::-webkit-scrollbar-thumb:hover { + background: #39ff14; + box-shadow: 0 0 15px #39ff14; + } + + /* Animations */ + @keyframes neon-pulse { + 0% { box-shadow: 0 0 30px rgb(0 255 65 / 50%), inset 0 0 30px rgb(0 255 65 / 10%), 0 0 0 1px #00ff41; } + 100% { box-shadow: 0 0 40px rgb(0 255 65 / 70%), inset 0 0 40px rgb(0 255 65 / 15%), 0 0 0 1px #00ff41; } + } + + @keyframes pixel-blink { + 0%, 50% { opacity: 1; } + 51%, 100% { opacity: 0.7; } + } + + @keyframes scanline { + 0% { transform: translateY(-100%); } + 100% { transform: translateY(400px); } + } + + @keyframes text-glow { + 0% { text-shadow: 0 0 15px #00ff41; } + 100% { text-shadow: 0 0 25px #00ff41, 0 0 35px #00ff41; } + } + `; + + if (!document.getElementById('auto-repair-neon-styles')) { + document.head.appendChild(neonStyles); + } + + container.innerHTML = ` +
+
+
šŸ”§
+

+ WPlace Autonomous Repair Tool +

+
+
+ + +
+
+ +
+
+
+ ${Utils.t('autonomousModeActive')} +
+
+ Charges: 0/1 (cooldown: 31s) +
+
+ Token: Initializing... +
+
+ System: AUTONOMOUS | Token: GENERATOR | Auto-refresh: ON +
+
+ Auto-Batch: ON | Size: 5 | Opt: ON +
+
+ +
+ + + + + +
+ +
+
+ +
+ +
+ + + seconds +
+ +
+ + +
+ + +
+
+ šŸŽÆ Pixel Batch Mode +
+ + +
+
+ +
+ +
+ +
+
+ + +
+ Batch Size: +
+ + + +
+ pixels +
+
+ +
+
+ +
+
Image: Not loaded
+
Position: Not set
+
Colors: 0 available
+
Batch Mode: Auto (${state.currentBatchSize} pixels)
+
+
+ +
+
+

+ šŸ“‹ ${Utils.t('debug')} (Enhanced System) +

+ +
+ +
+
[Ready] WPlace Autonomous Repair Tool v3.0
+
[Info] NEW Turnstile implementation active
+
[Info] Enhanced token management with preloading
+
[Info] ${Utils.t('tokenSystemReady')}
+
+
+
+ `; + + document.body.appendChild(container); + makeWindowDraggable(); + setupEventListeners(); + + // Start typewriter effect + setTimeout(() => { + createTypewriterTitle(); + }, 1000); + + // Start autonomous systems + initializeAutonomousSystems(); + } + + function initializeAutonomousSystems() { + // Update charges periodically + updateCharges(); + setInterval(updateCharges, 10000); + + // Update token status + updateTokenStatus(); + setInterval(updateTokenStatus, 5000); + + // Update system status + updateSystemStatus(); + setInterval(updateSystemStatus, 15000); + + // Update batch information + updateBatchInfo(); + + // Initialize token generator + initializeTokenGenerator(); + } + + function setupEventListeners() { + // Window controls + document.getElementById('minimizeBtn').addEventListener('click', () => { + const content = document.getElementById('window-content'); + state.windowMinimized = !state.windowMinimized; + + if (state.windowMinimized) { + content.style.display = 'none'; + document.getElementById('minimizeBtn').textContent = '+'; + } else { + content.style.display = 'block'; + document.getElementById('minimizeBtn').textContent = 'āˆ’'; + } + }); + + document.getElementById('closeBtn').addEventListener('click', () => { + const container = document.getElementById('wplace-repair-tool'); + if (container) { + container.style.transition = 'opacity 0.3s ease, transform 0.3s ease'; + container.style.opacity = '0'; + container.style.transform = 'scale(0.8)'; + setTimeout(() => { + container.remove(); + cleanup(); + }, 300); + } + }); + + // Load file + document.getElementById('loadFileBtn').addEventListener('click', async () => { + try { + Utils.addDebugLog('Loading progress file...', 'info'); + const data = await Utils.createFileUploader(); + + if (!data || !data.state || !data.imageData) { + throw new Error(Utils.t('invalidFile')); + } + + const migratedData = migrateProgressData(data); + + Object.assign(state, migratedData.state); + state.imageData = { + ...migratedData.imageData, + pixels: new Uint8ClampedArray(migratedData.imageData.pixels), + }; + + state.imageLoaded = true; + document.getElementById('repairBtn').disabled = false; + + // Update info displays + document.getElementById('imageInfo').textContent = `${state.imageData.width}x${state.imageData.height}`; + document.getElementById('positionInfo').textContent = state.startPosition ? + `(${state.startPosition.x}, ${state.startPosition.y}) in region (${state.region.x}, ${state.region.y})` : 'Not set'; + document.getElementById('colorsInfo').textContent = `${state.availableColors?.length || 0} available`; + + updateStatus(Utils.t('fileLoaded')); + Utils.addDebugLog(`Loaded image: ${state.imageData.width}x${state.imageData.height}`, 'success'); + Utils.addDebugLog(`Position: (${state.startPosition?.x}, ${state.startPosition?.y})`, 'info'); + Utils.addDebugLog(`Region: (${state.region?.x}, ${state.region?.y})`, 'info'); + Utils.addDebugLog(`Available colors: ${state.availableColors?.length || 0}`, 'info'); + + if (migratedData.state.paintWhitePixels !== undefined) { + state.paintWhitePixels = migratedData.state.paintWhitePixels; + Utils.addDebugLog(`Paint white pixels: ${state.paintWhitePixels}`, 'info'); + } + if (migratedData.state.paintTransparentPixels !== undefined) { + state.paintTransparentPixels = migratedData.state.paintTransparentPixels; + Utils.addDebugLog(`Paint transparent pixels: ${state.paintTransparentPixels}`, 'info'); + } + + Utils.showAlert(Utils.t('fileLoaded'), 'success'); + + // If autonomous mode and auto repair is enabled, start it + if (state.autonomousMode && document.getElementById('autoRepairEnabled').checked) { + Utils.addDebugLog('Autonomous mode: starting auto repair after file load', 'info'); + setTimeout(() => { + state.autoRepairEnabled = true; + startAutoRepair(); + }, 5000); + } + + } catch (error) { + Utils.addDebugLog(`Load error: ${error.message}`, 'error'); + Utils.showAlert(error.message, 'error'); + } + }); + + // Generate Token button + document.getElementById('generateTokenBtn').addEventListener('click', async () => { + const btn = document.getElementById('generateTokenBtn'); + const originalText = btn.textContent; + + try { + btn.disabled = true; + btn.textContent = 'šŸ”„ Generating...'; + btn.style.background = 'linear-gradient(135deg, #ffa726 0%, #ff9800 100%)'; + + Utils.addDebugLog('Manual token generation requested', 'info'); + + const token = await ensureToken(true); + + if (token) { + Utils.addDebugLog('Token generation succeeded!', 'success'); + Utils.showAlert('Token generated successfully!', 'success'); + updateTokenStatus(); + } else { + throw new Error('Token generation failed'); + } + } catch (error) { + Utils.addDebugLog(`Token generation failed: ${error.message}`, 'error'); + Utils.showAlert('Token generation failed', 'error'); + playNotificationSound(); + } finally { + btn.disabled = false; + btn.textContent = originalText; + btn.style.background = 'linear-gradient(135deg, #43e97b 0%, #38f9d7 100%)'; + } + }); + + // Manual repair + let repairRunning = false; + document.getElementById('repairBtn').addEventListener('click', async () => { + if (!state.imageLoaded) { + Utils.showAlert('Please load a progress file first', 'warning'); + return; + } + + if (repairRunning) { + state.stopFlag = true; + Utils.addDebugLog('Manual repair stop requested', 'warning'); + return; + } + + repairRunning = true; + state.running = true; + state.stopFlag = false; + const repairBtn = document.getElementById('repairBtn'); + repairBtn.textContent = 'āøļø Stop Repair'; + repairBtn.style.background = 'linear-gradient(135deg, #ff6b35 0%, #f7931e 100%)'; + + try { + Utils.addDebugLog('Starting manual repair with enhanced autonomous system...', 'info'); + + if (!isTokenValid()) { + Utils.addDebugLog('No valid token, generating...', 'info'); + await ensureToken(true); + } + + const damagedPixels = await scanForDamage(); + + if (damagedPixels.length > 0) { + updateStatus(Utils.t('damageDetected', { count: damagedPixels.length })); + showAttackNotification('attack', damagedPixels.length); + + const repairedCount = await repairDamagedPixels(damagedPixels); + + if (repairedCount === damagedPixels.length) { + showAttackNotification('repaired'); + } + } else { + updateStatus(Utils.t('noDamageDetected')); + showPeacefulState(); + } + } catch (error) { + Utils.addDebugLog(`Repair error: ${error.message}`, 'error'); + Utils.showAlert('Repair failed', 'error'); + } finally { + repairRunning = false; + state.running = false; + state.stopFlag = false; + repairBtn.textContent = 'šŸ”§ ' + Utils.t('repairPixels'); + repairBtn.style.background = 'linear-gradient(135deg, #f093fb 0%, #f5576c 100%)'; + } + }); + + // Auto repair toggle + document.getElementById('autoRepairEnabled').addEventListener('change', (e) => { + state.autoRepairEnabled = e.target.checked; + + if (state.autoRepairEnabled) { + if (!state.imageLoaded) { + Utils.showAlert('Please load a progress file first', 'warning'); + e.target.checked = false; + state.autoRepairEnabled = false; + return; + } + startAutoRepair(); + } else { + stopAutoRepair(); + } + }); + + // Repair interval change + document.getElementById('repairInterval').addEventListener('change', (e) => { + const value = parseInt(e.target.value) || 30; + state.autoRepairInterval = Math.max(10, Math.min(3600, value)); + e.target.value = state.autoRepairInterval; + + Utils.addDebugLog(`Repair interval changed to ${state.autoRepairInterval} seconds`, 'info'); + + if (state.autoRepairEnabled && state.imageLoaded) { + Utils.addDebugLog('Restarting auto repair with new interval', 'info'); + startAutoRepair(); + } + }); + + // Auto token refresh toggle + document.getElementById('autoTokenRefresh').addEventListener('change', (e) => { + state.autoTokenRefresh = e.target.checked; + Utils.addDebugLog(`Auto token refresh ${state.autoTokenRefresh ? 'enabled' : 'disabled'}`, 'info'); + updateSystemStatus(); + + if (state.autoTokenRefresh && state.autonomousMode && !isTokenValid()) { + ensureToken(); + } + }); + + // Token source selection + document.getElementById('tokenSourceSelect').addEventListener('change', (e) => { + state.tokenSource = e.target.value; + Utils.addDebugLog(`Token source changed to: ${state.tokenSource}`, 'info'); + updateSystemStatus(); + + // Invalidate current token to force new generation with new method + if (isTokenValid()) { + Utils.addDebugLog('Invalidating current token due to source change', 'info'); + invalidateToken(); + } + }); + + // NEW: Enhanced batch mode controls + document.getElementById('smartBatchMode').addEventListener('change', (e) => { + if (e.target.checked) { + state.autoBatchEnabled = true; + state.batchOptimization = true; + document.getElementById('manualBatchControls').style.display = 'none'; + Utils.addDebugLog('Smart batch mode enabled - will auto-adjust to damage count', 'info'); + updateBatchInfo(); + } + }); + + document.getElementById('manualBatchMode').addEventListener('change', (e) => { + if (e.target.checked) { + state.autoBatchEnabled = false; + state.batchOptimization = false; + document.getElementById('manualBatchControls').style.display = 'flex'; + Utils.addDebugLog('Manual batch mode enabled - using fixed batch size', 'info'); + updateBatchInfo(); + } + }); + + // Manual batch size controls with +/- buttons + document.getElementById('batchSizeIncrease').addEventListener('click', () => { + const newSize = Math.min(CONFIG.MAX_BATCH_SIZE, state.currentBatchSize + 1); + if (newSize !== state.currentBatchSize) { + state.currentBatchSize = newSize; + document.getElementById('batchSize').value = state.currentBatchSize; + Utils.addDebugLog(`Increased batch size to ${state.currentBatchSize} pixels`, 'info'); + updateBatchInfo(); + } + }); + + document.getElementById('batchSizeDecrease').addEventListener('click', () => { + const newSize = Math.max(CONFIG.MIN_BATCH_SIZE, state.currentBatchSize - 1); + if (newSize !== state.currentBatchSize) { + state.currentBatchSize = newSize; + document.getElementById('batchSize').value = state.currentBatchSize; + Utils.addDebugLog(`Decreased batch size to ${state.currentBatchSize} pixels`, 'info'); + updateBatchInfo(); + } + }); + + // Manual input allows direct typing for precise control + document.getElementById('batchSize').addEventListener('input', (e) => { + const value = parseInt(e.target.value); + if (!isNaN(value)) { + const clampedValue = Math.max(CONFIG.MIN_BATCH_SIZE, Math.min(CONFIG.MAX_BATCH_SIZE, value)); + if (clampedValue !== state.currentBatchSize) { + state.currentBatchSize = clampedValue; + if (clampedValue !== value) { + e.target.value = state.currentBatchSize; + } + Utils.addDebugLog(`Manual batch size set to ${state.currentBatchSize} pixels`, 'info'); + updateBatchInfo(); + } + } + }); + + document.getElementById('paintTransparentPixels').addEventListener('change', (e) => { + state.paintTransparentPixels = e.target.checked; + Utils.addDebugLog(`Transparent pixel repair ${state.paintTransparentPixels ? 'enabled' : 'disabled'}`, 'info'); + }); + + // Clear debug + document.getElementById('clearDebugBtn').addEventListener('click', () => { + state.debugLogs = []; + updateDebugConsole(); + Utils.addDebugLog('Debug console cleared', 'info'); + Utils.addDebugLog('Enhanced WPlace Autonomous Repair Tool ready', 'info'); + }); + } + + // Enhanced data migration helper + function migrateProgressData(data) { + if (!data.version || data.version < '2.0') { + Utils.addDebugLog('Migrating old progress data format...', 'info'); + + if (!data.state) data.state = {}; + if (!data.imageData) data.imageData = {}; + + data.state.paintWhitePixels = data.state.paintWhitePixels ?? true; + data.state.paintTransparentPixels = data.state.paintTransparentPixels ?? false; + + data.version = '2.0'; + } + + // Additional migrations for autonomous features + if (data.version === '2.0') { + data.state.autonomousMode = data.state.autonomousMode ?? CONFIG.AUTONOMOUS_MODE; + data.state.autoTokenRefresh = data.state.autoTokenRefresh ?? CONFIG.AUTO_TOKEN_REFRESH; + data.state.tokenSource = data.state.tokenSource ?? CONFIG.TOKEN_SOURCE; + data.version = '3.0'; + Utils.addDebugLog('Migrated to autonomous features v3.0', 'info'); + } + + return data; + } + + // Main initialization function + async function initialize() { + Utils.addDebugLog('Starting WPlace Autonomous Repair Tool v3.0...', 'info'); + + // Create UI first + createUI(); + + Utils.addDebugLog('NEW Enhanced Features Initialized:', 'info'); + Utils.addDebugLog('• NEW Turnstile implementation with widget reuse', 'info'); + Utils.addDebugLog('• Enhanced sitekey detection from multiple sources', 'info'); + Utils.addDebugLog('• Sophisticated token management with preloading', 'info'); + Utils.addDebugLog('• Autonomous operation with smart retry logic', 'info'); + Utils.addDebugLog('• Enhanced tile interception and caching', 'info'); + Utils.addDebugLog('• Adaptive repair algorithms with failure handling', 'info'); + Utils.addDebugLog('• Visual notifications and audio alerts', 'info'); + Utils.addDebugLog('• Draggable window with minimize/close controls', 'info'); + + if (state.autonomousMode) { + Utils.addDebugLog('AUTONOMOUS MODE ACTIVE - Tool will operate independently', 'success'); + Utils.addDebugLog('Instructions for autonomous operation:', 'info'); + Utils.addDebugLog('1. Wait for initial setup to complete', 'info'); + Utils.addDebugLog('2. Load a progress file using "Load Progress File"', 'info'); + Utils.addDebugLog('3. Enable "Auto Repair" to start autonomous monitoring', 'info'); + Utils.addDebugLog('4. Tool will automatically scan and repair damage', 'info'); + Utils.addDebugLog('5. Tokens will be generated and refreshed automatically', 'info'); + Utils.addDebugLog('6. Monitor debug console for autonomous operation logs', 'info'); + } else { + Utils.addDebugLog('Manual mode active - use controls for manual operation', 'info'); + } + + // Show autonomous mode status + updateStatus(state.autonomousMode ? Utils.t('autonomousModeActive') : 'Manual mode ready - Load file and enable auto repair to start'); + + Utils.addDebugLog('WPlace Autonomous Repair Tool ready for operation', 'success'); + } + + // Enhanced cleanup function + function cleanup() { + Utils.addDebugLog('Cleaning up autonomous systems...', 'info'); + + // Clear all timers + if (state.autoRepairTimer) { + clearInterval(state.autoRepairTimer); + state.autoRepairTimer = null; + } + + if (state.tokenRetryTimer) { + clearTimeout(state.tokenRetryTimer); + state.tokenRetryTimer = null; + } + + if (state.tokenPreloadTimer) { + clearTimeout(state.tokenPreloadTimer); + state.tokenPreloadTimer = null; + } + + // Cleanup Turnstile + Utils.cleanupTurnstile(); + + // Set stop flag + state.stopFlag = true; + + Utils.addDebugLog('Cleanup completed', 'info'); + } + + // Add cleanup on page unload + window.addEventListener('beforeunload', cleanup); + + // Handle page visibility changes for autonomous mode + document.addEventListener('visibilitychange', () => { + if (state.autonomousMode) { + if (document.hidden) { + Utils.addDebugLog('Page hidden - autonomous systems continue running', 'info'); + } else { + Utils.addDebugLog('Page visible - autonomous systems active', 'info'); + // Refresh status when page becomes visible + updateCharges(); + updateTokenStatus(); + } + } + }); + + // Enhanced error handling for autonomous mode + window.addEventListener('error', (event) => { + if (state.autonomousMode) { + Utils.addDebugLog(`Global error in autonomous mode: ${event.error?.message || 'Unknown error'}`, 'error'); + } + }); + + window.addEventListener('unhandledrejection', (event) => { + if (state.autonomousMode) { + Utils.addDebugLog(`Unhandled promise rejection in autonomous mode: ${event.reason}`, 'error'); + } + }); + + // Start the enhanced autonomous application + initialize().catch(error => { + console.error('Failed to initialize autonomous repair tool:', error); + Utils.addDebugLog(`Initialization failed: ${error.message}`, 'error'); + }); + +})(); diff --git a/Extension/scripts/token-manager.js b/Extension/scripts/token-manager.js index 874bfe4..3e56c22 100644 --- a/Extension/scripts/token-manager.js +++ b/Extension/scripts/token-manager.js @@ -39,8 +39,23 @@ class TokenManager { // Retry counter this.retryCount = 0; + // Widget pooling for faster token generation + this.widgetPool = []; + this.poolQueue = []; // Resolvers waiting for free widgets + this.poolSize = 2; // Default pool size + this.poolInitialized = false; + this.poolMetrics = { + tokensGenerated: 0, + avgCreationTime: 0, + poolHits: 0, + poolMisses: 0 + }; + // Initialize message listener for token capture this._initializeTokenCapture(); + + // Initialize widget pool after a short delay to ensure DOM is ready + setTimeout(() => this.initPool(this.poolSize), 100); } /** @@ -334,7 +349,7 @@ class TokenManager { script.src = 'https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit'; script.async = true; script.defer = true; - script.onload = () => { + script.onload = async () => { this.turnstileLoaded = true; console.log('āœ… Turnstile script loaded successfully'); resolve(); @@ -411,6 +426,60 @@ class TokenManager { async executeTurnstile(sitekey, action = 'paint') { await this.loadTurnstile(); + // Initialize widget pool lazily on first use + if (!this.poolInitialized && window.turnstile && false) { // Temporarily disabled + try { + console.log('šŸŠ Initializing widget pool on first use...'); + // Use the current sitekey if available, otherwise skip pool for this request + if (sitekey && sitekey.length > 10 && sitekey.startsWith('0x')) { + this._cachedSitekey = sitekey; // Cache the valid sitekey + await this.initPool(); + } else { + console.warn('āš ļø Invalid sitekey, skipping pool initialization for now'); + } + } catch (error) { + console.warn('āš ļø Failed to initialize widget pool:', error.message); + } + } + + // Skip widget pool for now (temporarily disabled) + // Try widget pool first for faster generation (only if we have a valid pool) + if (false && this.poolInitialized && this.widgetPool.length > 0 && sitekey && sitekey.length > 10) { + try { + console.log('šŸŠ Using widget pool for token generation...'); + const poolStartTime = performance.now(); + + const token = await this.withWidget(async (widget) => { + if (!widget) { + throw new Error('No widget available from pool'); + } + + // Reset the widget for fresh token generation + if (window.turnstile?.reset) { + window.turnstile.reset(widget.widgetId); + } + + // Generate token using pooled widget with built-in timeout + const token = await widget.createNewTokenPromise(); + + return token; + }, 12000); // Increased widget borrow timeout + + if (token && token.length > 20) { + const poolTime = performance.now() - poolStartTime; + console.log(`āœ… Token generated via pool in ${poolTime.toFixed(2)}ms`); + return token; + } + } catch (error) { + console.log('āš ļø Widget pool failed, falling back to direct creation:', error.message); + // Don't try pool again for this sitekey if it's failing + if (error.message.includes('401') || error.message.includes('unauthorized')) { + console.log('🚫 Sitekey might be invalid, disabling pool for this session'); + this.poolInitialized = false; + } + } + } + // Try reusing existing widget first if sitekey matches if (this._turnstileWidgetId && this._lastSitekey === sitekey && window.turnstile?.execute) { try { @@ -829,6 +898,328 @@ class TokenManager { ) .join(''); } + + // ============ WIDGET POOLING METHODS ============ + + /** + * Initialize widget pool for faster token generation + * @param {number} size - Pool size (default 2) + */ + async initPool(size = 2) { + if (this.poolInitialized) return; + + console.log(`šŸŠ Initializing Turnstile widget pool with ${size} widgets...`); + const startTime = performance.now(); + + try { + // Ensure Turnstile is loaded first + await this.loadTurnstile(); + + // Use a fallback sitekey for pool initialization to avoid circular dependency + let sitekey = this._cachedSitekey || '0x4AAAAAABpqJe8FO0N84q0F'; + + // Validate sitekey format + if (!sitekey || sitekey.length < 10 || !sitekey.startsWith('0x')) { + console.warn('āš ļø Invalid cached sitekey, using fallback'); + sitekey = '0x4AAAAAABpqJe8FO0N84q0F'; + } + + console.log('šŸ” Using sitekey for pool initialization:', sitekey); + + // Create pool entries + const promises = []; + for (let i = 0; i < size; i++) { + promises.push(this._createWidgetEntry(sitekey, i)); + } + + const results = await Promise.allSettled(promises); + + // Process results + results.forEach((result, index) => { + if (result.status === 'fulfilled' && result.value) { + this.widgetPool.push(result.value); + console.log(`āœ… Pool widget ${index} created successfully`); + } else { + console.warn(`āŒ Pool widget ${index} failed:`, result.reason); + } + }); + + const initTime = performance.now() - startTime; + this.poolMetrics.avgCreationTime = initTime / this.widgetPool.length; + this.poolInitialized = true; + + console.log(`šŸŠ Widget pool initialized: ${this.widgetPool.length}/${size} widgets ready (${initTime.toFixed(2)}ms)`); + + } catch (error) { + console.error('āŒ Widget pool initialization failed:', error); + this.poolInitialized = false; + } + } + + /** + * Create a single widget entry for the pool + * @param {string} sitekey - Turnstile sitekey + * @param {number} index - Widget index for identification + * @returns {Promise} Widget entry object + */ + async _createWidgetEntry(sitekey, index = 0) { + const startTime = performance.now(); + + try { + // Create hidden container + const container = document.createElement('div'); + container.id = `turnstile-pool-widget-${index}-${this._randStr(8)}`; + container.style.cssText = ` + position: fixed !important; + left: -9999px !important; + top: -9999px !important; + width: 300px !important; + height: 65px !important; + visibility: hidden !important; + pointer-events: none !important; + z-index: -1 !important; + `; + + document.body.appendChild(container); + + // Create widget + let widgetId = null; + let tokenPromise = null; + + const widgetPromise = new Promise((resolve, reject) => { + const timeout = setTimeout(() => { + reject(new Error('Widget creation timeout')); + }, 10000); + + try { + widgetId = window.turnstile.render(container, { + sitekey: sitekey, + size: 'normal', + theme: 'light', + callback: (token) => { + clearTimeout(timeout); + if (tokenPromise) { + tokenPromise.resolve(token); + } + }, + 'error-callback': (error) => { + clearTimeout(timeout); + if (tokenPromise) { + tokenPromise.reject(new Error(`Turnstile error: ${error}`)); + } else { + reject(new Error(`Turnstile error: ${error}`)); + } + }, + 'expired-callback': () => { + console.log(`šŸ”„ Pool widget ${index} token expired`); + } + }); + + if (widgetId) { + clearTimeout(timeout); + resolve(); + } else { + clearTimeout(timeout); + reject(new Error('Failed to create widget')); + } + } catch (error) { + clearTimeout(timeout); + reject(error); + } + }); + + await widgetPromise; + + const entry = { + id: `pool-widget-${index}`, + index, + container, + widgetId, + sitekey, + state: 'idle', // 'idle' | 'in-use' | 'error' | 'initializing' + createdAt: Date.now(), + lastUsedAt: 0, + generationCount: 0, + createNewTokenPromise() { + tokenPromise = {}; + tokenPromise.promise = new Promise((resolve, reject) => { + tokenPromise.resolve = resolve; + tokenPromise.reject = reject; + }); + return tokenPromise.promise; + } + }; + + const creationTime = performance.now() - startTime; + console.log(`āœ… Pool widget ${index} created in ${creationTime.toFixed(2)}ms`); + + return entry; + + } catch (error) { + console.error(`āŒ Failed to create pool widget ${index}:`, error); + throw error; + } + } + + /** + * Get an available widget from the pool + * @param {number} timeoutMs - Timeout in milliseconds + * @returns {Promise} Widget entry + */ + async getWidget(timeoutMs = 5000) { + // Try to find an idle widget + const idleWidget = this.widgetPool.find(entry => entry.state === 'idle'); + if (idleWidget) { + idleWidget.state = 'in-use'; + idleWidget.lastUsedAt = Date.now(); + this.poolMetrics.poolHits++; + return idleWidget; + } + + // No idle widget available, add to queue and wait + this.poolMetrics.poolMisses++; + + return new Promise((resolve, reject) => { + const timeout = setTimeout(() => { + // Remove from queue + const index = this.poolQueue.findIndex(item => item.resolve === resolve); + if (index > -1) { + this.poolQueue.splice(index, 1); + } + reject(new Error('Widget pool timeout: no widget available')); + }, timeoutMs); + + this.poolQueue.push({ + resolve: (widget) => { + clearTimeout(timeout); + resolve(widget); + }, + reject: (error) => { + clearTimeout(timeout); + reject(error); + } + }); + }); + } + + /** + * Release a widget back to the pool + * @param {Object} entry - Widget entry to release + */ + releaseWidget(entry) { + if (!entry || entry.state !== 'in-use') { + console.warn('āš ļø Attempted to release widget that is not in use'); + return; + } + + entry.state = 'idle'; + entry.generationCount++; + + // Check if widget needs rotation (after many uses) + if (entry.generationCount > 50) { + console.log(`šŸ”„ Rotating pool widget ${entry.index} after ${entry.generationCount} uses`); + this.destroyWidget(entry); + // Recreate widget in background + this._createWidgetEntry(entry.sitekey, entry.index).then(newEntry => { + if (newEntry) { + this.widgetPool.push(newEntry); + } + }).catch(error => { + console.error('Failed to recreate rotated widget:', error); + }); + return; + } + + // Serve next waiting request if any + if (this.poolQueue.length > 0) { + const waiter = this.poolQueue.shift(); + entry.state = 'in-use'; + entry.lastUsedAt = Date.now(); + waiter.resolve(entry); + } + } + + /** + * Destroy a widget and remove from pool + * @param {Object} entry - Widget entry to destroy + */ + destroyWidget(entry) { + if (!entry) return; + + try { + // Reset widget if possible + if (entry.widgetId && window.turnstile?.reset) { + window.turnstile.reset(entry.widgetId); + } + + // Remove container + if (entry.container && entry.container.parentNode) { + entry.container.parentNode.removeChild(entry.container); + } + + // Remove from pool + const index = this.widgetPool.findIndex(w => w.id === entry.id); + if (index > -1) { + this.widgetPool.splice(index, 1); + } + + console.log(`šŸ—‘ļø Destroyed pool widget ${entry.index}`); + + } catch (error) { + console.error('Error destroying widget:', error); + } + } + + /** + * Execute a function with a pooled widget (automatic borrow/return) + * @param {Function} fn - Async function that receives widget entry + * @param {number} timeoutMs - Widget acquisition timeout + * @returns {Promise} Result of the function + */ + async withWidget(fn, timeoutMs = 5000) { + let widget = null; + + try { + // Fallback to direct creation if pool not ready + if (!this.poolInitialized || this.widgetPool.length === 0) { + console.log('šŸ“ Pool not ready, falling back to direct widget creation'); + return await fn(null); // Let the function handle direct creation + } + + widget = await this.getWidget(timeoutMs); + const result = await fn(widget); + + this.poolMetrics.tokensGenerated++; + return result; + + } catch (error) { + console.error('āŒ Error in withWidget:', error); + throw error; + } finally { + if (widget) { + this.releaseWidget(widget); + } + } + } + + /** + * Get pool statistics + * @returns {Object} Pool metrics and status + */ + getPoolStats() { + const idleCount = this.widgetPool.filter(w => w.state === 'idle').length; + const inUseCount = this.widgetPool.filter(w => w.state === 'in-use').length; + + return { + initialized: this.poolInitialized, + totalWidgets: this.widgetPool.length, + idleWidgets: idleCount, + inUseWidgets: inUseCount, + queueLength: this.poolQueue.length, + metrics: { ...this.poolMetrics }, + hitRate: this.poolMetrics.poolHits / Math.max(1, this.poolMetrics.poolHits + this.poolMetrics.poolMisses) + }; + } } // Create global instance diff --git a/Extension/themes/neon-cyan.css b/Extension/themes/neon-cyan.css index 6f6a155..e62c5e9 100644 --- a/Extension/themes/neon-cyan.css +++ b/Extension/themes/neon-cyan.css @@ -1,815 +1,815 @@ -/* WPlace Auto-Image Bot - Neon Cyan Theme */ -/* Retro cyberpunk aesthetic with cyan neon glow effects */ - -.wplace-theme-neon-cyan { - /* Neon theme colors - matching upstream main neon theme */ - --wplace-primary: #1959A1; - --wplace-secondary: #3C74AF; - --wplace-accent: #538CC0; - --wplace-text: #81DCF7; - --wplace-highlight: #EA9C00; - --wplace-success: #39ff14; - --wplace-error: #ff073a; - --wplace-warning: #ff0; - - /* Neon UI properties */ - --wplace-radius: 0; - --wplace-border-style: solid; - --wplace-border-width: 2px; - --wplace-border-color: #81DCF7; - --wplace-shadow: 0 0 20px rgb(234 156 0 / 40%), inset 0 0 20px rgb(234 156 0 / 10%); - --wplace-backdrop: none; - --wplace-font: 'Press Start 2P', monospace, 'Courier New'; - - /* Z-index layers */ - --wplace-z-overlay: 10000; - --wplace-z-alert: 10002; - --wplace-z-settings: 10002; - - /* Feature toggles */ - --wplace-scanline: 1; - --wplace-pixel-blink: 1; - - /* Icon colors - neon variants */ - --wplace-icon-primary: #81DCF7; - --wplace-icon-secondary: #39ff14; - --wplace-icon-palette: #EA9C00; - - /* Additional UI colors - neon variants */ - --wplace-danger: #ff073a; - --wplace-danger-dark: #cc0531; - --wplace-muted-text: #00ff4180; - --wplace-highlight-secondary: #fa0; - - /* Text variants - neon style */ - --wplace-text-secondary: #3C74AF; - --wplace-text-muted: #00ff41; - --wplace-text-dim: #00ff41; - --wplace-text-faded: #00ff41cc; - - /* Background variants - neon style */ - --wplace-bg-input: rgb(234 156 0 / 15%); - --wplace-bg-subtle: rgb(234 156 0 / 8%); - --wplace-bg-faint: rgb(234 156 0 / 5%); - --wplace-bg-ghost: rgb(234 156 0 / 3%); - --wplace-bg-whisper: rgb(234 156 0 / 2%); - - /* Border variants - neon style */ - --wplace-border-subtle: rgb(234 156 0 / 40%); - --wplace-border-faint: rgb(234 156 0 / 25%); - --wplace-border-ghost: rgb(234 156 0 / 15%); - --wplace-border-ultra-faint: rgb(234 156 0 / 8%); - - /* Shadow variants - neon style */ - --wplace-shadow-drag: 0 12px 40px rgb(234 156 0 / 60%), 0 0 0 2px rgb(234 156 0 / 80%), 0 0 20px rgb(234 156 0 / 30%); - --wplace-shadow-notification: 0 4px 12px rgb(234 156 0 / 40%), 0 0 15px rgb(234 156 0 / 20%); - --wplace-shadow-slider-thumb: 0 3px 6px rgb(234 156 0 / 50%), 0 0 0 2px var(--wplace-icon-primary), 0 0 10px rgb(234 156 0 / 30%); - --wplace-shadow-slider-hover: 0 4px 8px rgb(234 156 0 / 60%), 0 0 0 3px var(--wplace-icon-primary), 0 0 15px rgb(234 156 0 / 40%); - - /* Animation colors - neon style */ - --wplace-pulse-start: rgb(234 156 0 / 80%); - --wplace-pulse-mid: rgb(234 156 0 / 0%); - --wplace-pulse-end: rgb(234 156 0 / 0%); - - /* Slider colors - neon style */ - --wplace-slider-thumb-bg: #81DCF7; -} - -/* Neon-specific styling overrides */ -.wplace-theme-neon-cyan #wplace-image-bot-container { - background: var(--wplace-primary) !important; - border: 2px solid var(--wplace-text) !important; - border-radius: 0 !important; - box-shadow: 0 0 20px var(--wplace-text), inset 0 0 20px rgb(234 156 0 / 10%) !important; - font-family: var(--wplace-font) !important; -} - -.wplace-theme-neon-cyan #wplace-stats-container { - background: var(--wplace-primary) !important; - border: 2px solid var(--wplace-text) !important; - border-radius: 0 !important; - box-shadow: 0 0 20px var(--wplace-text), inset 0 0 20px rgb(234 156 0 / 10%) !important; - font-family: var(--wplace-font) !important; - position: fixed !important; - overflow: hidden !important; - z-index: 9998 !important; -} - -.wplace-theme-neon-cyan .wplace-header { - background: var(--wplace-secondary) !important; - border-bottom: 1px solid var(--wplace-text) !important; - color: var(--wplace-text) !important; - text-shadow: 0 0 10px var(--wplace-text) !important; - font-family: var(--wplace-font) !important; -} - -.wplace-theme-neon-cyan .wplace-section { - background: rgb(22 33 62 / 50%) !important; - border: 1px solid var(--wplace-text) !important; - border-radius: 0 !important; -} - -.wplace-theme-neon-cyan .wplace-status-section { - background: rgb(22 33 62 / 50%) !important; - border: 1px solid var(--wplace-text) !important; - border-radius: 0 !important; -} - -.wplace-theme-neon-cyan .wplace-section-title { - color: var(--wplace-text) !important; - text-shadow: 0 0 8px var(--wplace-text) !important; - text-transform: uppercase !important; - font-family: var(--wplace-font) !important; - font-size: 10px !important; -} - -.wplace-theme-neon-cyan .wplace-btn { - background: var(--wplace-secondary) !important; - border: 1px solid var(--wplace-text) !important; - border-radius: 0 !important; - color: var(--wplace-text) !important; - text-shadow: 0 0 5px var(--wplace-text) !important; - font-family: var(--wplace-font) !important; - font-size: 9px !important; - text-transform: uppercase !important; -} - -/* Pixel blinking and neon glow animation for buttons */ -.wplace-theme-neon-cyan .wplace-btn:hover:not(:disabled) { - box-shadow: 0 0 15px var(--wplace-text), inset 0 0 15px rgb(234 156 0 / 10%) !important; - animation: pixel-blink 0.5s infinite, neon-glow 1s ease-in-out infinite alternate !important; -} - -.wplace-theme-neon-cyan .wplace-btn-start { - background: var(--wplace-secondary) !important; - border-color: var(--wplace-success) !important; - color: var(--wplace-success) !important; - text-shadow: 0 0 8px var(--wplace-success) !important; -} - -.wplace-theme-neon-cyan .wplace-btn-stop { - background: var(--wplace-secondary) !important; - border-color: var(--wplace-error) !important; - color: var(--wplace-error) !important; - text-shadow: 0 0 8px var(--wplace-error) !important; -} - -.wplace-theme-neon-cyan .wplace-btn-upload { - background: var(--wplace-secondary) !important; - border: 1px dashed var(--wplace-highlight) !important; - color: var(--wplace-highlight) !important; - text-shadow: 0 0 8px var(--wplace-highlight) !important; -} - -.wplace-theme-neon-cyan .wplace-btn-select { - background: var(--wplace-secondary) !important; - border-color: var(--wplace-highlight) !important; - color: var(--wplace-highlight) !important; - text-shadow: 0 0 8px var(--wplace-highlight) !important; -} - -.wplace-theme-neon-cyan .wplace-btn-file { - background: var(--wplace-secondary) !important; - border-color: var(--wplace-warning) !important; - color: var(--wplace-warning) !important; - text-shadow: 0 0 8px var(--wplace-warning) !important; -} - -.wplace-theme-neon-cyan .wplace-progress { - background: rgb(0 0 0 / 80%) !important; - border: 1px solid var(--wplace-text) !important; - border-radius: 0 !important; -} - -.wplace-theme-neon-cyan .wplace-progress-bar { - background: linear-gradient(90deg, var(--wplace-success) 0%, var(--wplace-text) 100%) !important; - box-shadow: 0 0 10px var(--wplace-success) !important; -} - -.wplace-theme-neon-cyan .wplace-stat-value { - color: var(--wplace-text) !important; - text-shadow: 0 0 5px var(--wplace-text) !important; -} - -.wplace-theme-neon-cyan .status-success { - background: rgb(57 255 20 / 10%) !important; - border-color: var(--wplace-success) !important; - color: var(--wplace-success) !important; - box-shadow: 0 0 15px var(--wplace-success) !important; - text-shadow: 0 0 8px var(--wplace-success) !important; -} - -.wplace-theme-neon-cyan .status-error { - background: rgb(255 7 58 / 10%) !important; - border-color: var(--wplace-error) !important; - color: var(--wplace-error) !important; - box-shadow: 0 0 15px var(--wplace-error) !important; - text-shadow: 0 0 8px var(--wplace-error) !important; -} - -.wplace-theme-neon-cyan .status-default { - background: rgb(234 156 0 / 10%) !important; - border-color: var(--wplace-text) !important; - color: var(--wplace-text) !important; - text-shadow: 0 0 5px var(--wplace-text) !important; -} - -/* Settings dialog neon styling */ -.wplace-theme-neon-cyan #wplace-settings-container { - background: var(--wplace-primary) !important; - border: 2px solid var(--wplace-text) !important; - border-radius: 0 !important; - box-shadow: 0 0 30px var(--wplace-text), inset 0 0 30px rgb(234 156 0 / 10%) !important; - font-family: var(--wplace-font) !important; -} - -.wplace-theme-neon-cyan .wplace-settings-header { - background: var(--wplace-secondary) !important; - border-bottom: 1px solid var(--wplace-text) !important; -} - -.wplace-theme-neon-cyan .wplace-settings-header h3 { - color: var(--wplace-text) !important; - text-shadow: 0 0 10px var(--wplace-text) !important; - font-family: var(--wplace-font) !important; - font-size: 16px !important; - text-transform: uppercase !important; -} - -.wplace-theme-neon-cyan .wplace-settings-close-btn { - background: var(--wplace-secondary) !important; - border: 1px solid var(--wplace-text) !important; - border-radius: 0 !important; - color: var(--wplace-text) !important; -} - -.wplace-theme-neon-cyan .wplace-settings-close-btn:hover { - background: var(--wplace-error) !important; - box-shadow: 0 0 15px var(--wplace-error) !important; -} - -.wplace-theme-neon-cyan .wplace-settings-section-wrapper { - background: rgb(22 33 62 / 30%) !important; - border: 1px solid var(--wplace-text) !important; - border-radius: 0 !important; -} - -.wplace-theme-neon-cyan .wplace-settings-select { - background: var(--wplace-secondary) !important; - border: 1px solid var(--wplace-text) !important; - border-radius: 0 !important; - color: var(--wplace-text) !important; - font-family: var(--wplace-font) !important; - font-size: 11px !important; -} - -.wplace-theme-neon-cyan .wplace-settings-section-label { - color: var(--wplace-text) !important; - text-shadow: 0 0 8px var(--wplace-text) !important; - font-family: var(--wplace-font) !important; - font-size: 12px !important; - text-transform: uppercase !important; -} - -.wplace-theme-neon-cyan .wplace-speed-value { - background: var(--wplace-secondary) !important; - border: 1px solid var(--wplace-text) !important; - border-radius: 0 !important; - color: var(--wplace-text) !important; - text-shadow: 0 0 8px var(--wplace-text) !important; - font-family: var(--wplace-font) !important; - box-shadow: 0 0 10px rgb(234 156 0 / 30%) !important; -} - -.wplace-theme-neon-cyan .wplace-overlay-opacity-value { - background: var(--wplace-secondary) !important; - border: 1px solid var(--wplace-text) !important; - border-radius: 0 !important; - color: var(--wplace-text) !important; - text-shadow: 0 0 5px var(--wplace-text) !important; -} - -/* Neon slider styling */ -.wplace-theme-neon-cyan .wplace-slider, -.wplace-theme-neon-cyan .wplace-speed-slider, -.wplace-theme-neon-cyan .wplace-overlay-opacity-slider { - background: var(--wplace-secondary) !important; - border: 1px solid var(--wplace-text) !important; - border-radius: 0 !important; - box-shadow: 0 0 10px rgb(234 156 0 / 30%) !important; -} - -.wplace-theme-neon-cyan .wplace-slider::-webkit-slider-thumb, -.wplace-theme-neon-cyan .wplace-speed-slider::-webkit-slider-thumb, -.wplace-theme-neon-cyan .wplace-overlay-opacity-slider::-webkit-slider-thumb { - -webkit-appearance: none; - width: 16px; - height: 16px; - border-radius: 0 !important; - background: var(--wplace-slider-thumb-bg) !important; - border: 2px solid var(--wplace-text) !important; - cursor: pointer; - transition: all 0.2s ease; - box-shadow: 0 0 8px rgb(234 156 0 / 50%) !important; -} - -.wplace-theme-neon-cyan .wplace-slider::-webkit-slider-thumb:hover, -.wplace-theme-neon-cyan .wplace-speed-slider::-webkit-slider-thumb:hover, -.wplace-theme-neon-cyan .wplace-overlay-opacity-slider::-webkit-slider-thumb:hover { - transform: scale(1.1); - box-shadow: 0 0 15px var(--wplace-text) !important; -} - -/* Scanline animation for neon theme */ -.wplace-theme-neon-cyan #wplace-image-bot-container::before { - content: ''; - position: absolute; - top: 0; - left: 0; - right: 0; - height: 2px; - background: linear-gradient(90deg, transparent, var(--wplace-text), transparent); - z-index: 1; - pointer-events: none; - animation: scanline 3s linear infinite; - opacity: 0.7; -} - - -.wplace-theme-neon-cyan #wplace-stats-container::before { - content: ''; - position: absolute; - top: 0; - left: 0; - right: 0; - height: 2px; - background: linear-gradient(90deg, transparent, var(--wplace-text), transparent); - z-index: 1; - pointer-events: none; - animation: scanline-stats 4s linear infinite; - opacity: 0.7; -} - - -/* Text glow animations */ -@keyframes neon-glow { - 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; - } -} - -@keyframes pixel-blink { - 0%, 50% { - opacity: 1; - } - - 51%, 100% { - opacity: 0.7; - } -} - -@keyframes scanline { - 0% { - transform: translateY(-100%); - } - - 100% { - transform: translateY(400px); - } -} - -@keyframes scanline-stats { - 0% { - transform: translateY(-100%); - } - - 100% { - transform: translateY(300px); - } -} - -/* Dual control layout styling for neon theme */ -.wplace-theme-neon-cyan .wplace-dual-control-compact { - display: flex; - flex-direction: row; - align-items: center; - gap: 8px; - margin: 6px 0; - flex-wrap: wrap; -} - -.wplace-theme-neon-cyan .wplace-slider-container-compact { - display: flex; - align-items: center; - gap: 6px; - flex: 1; - min-width: 160px; -} - -.wplace-theme-neon-cyan .wplace-speed-slider-container-compact { - display: flex; - align-items: center; - gap: 6px; - flex: 1; - min-width: 160px; -} - -.wplace-theme-neon-cyan .wplace-input-group-compact { - display: flex; - align-items: center; - gap: 3px; - flex-shrink: 0; -} - -.wplace-theme-neon-cyan .wplace-input-btn-compact { - background: var(--wplace-secondary) !important; - border: 1px solid var(--wplace-text) !important; - border-radius: 0 !important; - color: var(--wplace-text) !important; - width: 18px; - height: 18px; - cursor: pointer; - font-weight: bold; - font-size: 10px; - display: flex; - align-items: center; - justify-content: center; - transition: all 0.2s ease; - text-shadow: 0 0 5px var(--wplace-text) !important; - font-family: var(--wplace-font) !important; -} - -.wplace-theme-neon-cyan .wplace-input-btn-compact:hover { - box-shadow: 0 0 10px var(--wplace-text) !important; - animation: pixel-blink 0.5s infinite !important; -} - -.wplace-theme-neon-cyan .wplace-number-input-compact { - background: var(--wplace-secondary) !important; - border: 1px solid var(--wplace-text) !important; - border-radius: 0 !important; - color: var(--wplace-text) !important; - padding: 2px 4px; - font-size: 10px; - width: 65px; - text-align: center; - transition: all 0.2s ease; - height: 18px; - font-family: var(--wplace-font) !important; - text-shadow: 0 0 5px var(--wplace-text) !important; -} - -.wplace-theme-neon-cyan .wplace-number-input-compact:focus { - outline: none; - box-shadow: 0 0 10px var(--wplace-text) !important; - background: rgb(234 156 0 / 15%) !important; -} - -.wplace-theme-neon-cyan .wplace-input-label-compact { - color: var(--wplace-text) !important; - font-size: 9px; - margin-left: 3px; - white-space: nowrap; - text-shadow: 0 0 5px var(--wplace-text) !important; - font-family: var(--wplace-font) !important; - text-transform: uppercase; -} - -.wplace-theme-neon-cyan .wplace-batch-size-header { - margin-bottom: 6px; -} - -.wplace-theme-neon-cyan .wplace-batch-size-label { - color: var(--wplace-text) !important; - font-weight: 500; - font-size: 11px; - text-shadow: 0 0 8px var(--wplace-text) !important; - font-family: var(--wplace-font) !important; - text-transform: uppercase; -} - -/* Cooldown control styling for neon theme */ -.wplace-theme-neon-cyan .wplace-cooldown-control { - margin: 8px 0; -} - -.wplace-theme-neon-cyan .wplace-cooldown-control label { - color: var(--wplace-text) !important; - font-weight: 500; - font-size: 11px; - text-shadow: 0 0 8px var(--wplace-text) !important; - font-family: var(--wplace-font) !important; - text-transform: uppercase; - margin-bottom: 6px; - display: block; -} - -/* Resize dialog styling for neon theme */ -.wplace-theme-neon-cyan .resize-container { - background-color: #1959A1 !important; /* Solid neon blue */ - opacity: 1 !important; /* Fully opaque */ - backdrop-filter: none !important; /* Remove blur effects */ - box-shadow: none !important; /* Optional: remove glows */ - font-family: 'Press Start 2P', monospace !important; -} - -/* Account switching UI styles */ -.wplace-theme-neon-cyan .wplace-section { - margin: 15px 0; - border-radius: 0; - background: rgba(0, 255, 255, 0.05); - padding: 15px; - border: 2px solid #00ffff; - box-shadow: 0 0 20px rgba(0, 255, 255, 0.3); - font-family: var(--wplace-font); -} - -.wplace-theme-neon-cyan .wplace-section-title { - display: flex; - align-items: center; - gap: 8px; - font-weight: 600; - color: var(--wplace-text); - margin-bottom: 10px; - font-size: 12px; - text-transform: uppercase; - text-shadow: 0 0 8px var(--wplace-text); - font-family: var(--wplace-font); -} - -/* Auto-swap toggle switch */ -.wplace-theme-neon-cyan .wplace-switch { - position: relative; - display: inline-block; - width: 50px; - height: 24px; -} - -.wplace-theme-neon-cyan .wplace-switch input { - opacity: 0; - width: 0; - height: 0; -} - -.wplace-theme-neon-cyan .wplace-slider-round { - position: absolute; - cursor: pointer; - top: 0; - left: 0; - right: 0; - bottom: 0; - background-color: #1a1a2e; - transition: 0.4s; - border-radius: 0; - border: 2px solid #333; - box-shadow: 0 0 10px rgba(0, 255, 255, 0.2); -} - -.wplace-theme-neon-cyan .wplace-slider-round:before { - position: absolute; - content: ""; - height: 14px; - width: 14px; - left: 3px; - bottom: 3px; - background-color: #fff; - transition: 0.4s; - border-radius: 0; - box-shadow: 0 0 8px rgba(255, 255, 255, 0.5); -} - -.wplace-theme-neon-cyan .wplace-switch input:checked + .wplace-slider-round { - background-color: #00ffff; - box-shadow: 0 0 20px rgba(0, 255, 255, 0.6); -} - -.wplace-theme-neon-cyan .wplace-switch input:checked + .wplace-slider-round:before { - transform: translateX(26px); - box-shadow: 0 0 12px rgba(0, 255, 255, 0.8); -} - -/* Account list container */ -.wplace-theme-neon-cyan .accounts-list-container { - max-height: 200px; - overflow-y: auto; - margin-top: 10px; - padding: 0 6px; -} - -/* Individual account item */ -.wplace-theme-neon-cyan .wplace-account-item { - display: flex; - justify-content: space-between; - align-items: center; - padding: 6px 8px; - margin: 4px 0; - background: rgba(0, 255, 255, 0.03); - border-radius: 0; - border: 1px solid #00ffff; - transition: all 0.3s ease; - box-shadow: 0 0 5px rgba(0, 255, 255, 0.2); - position: relative; -} - -/* Account ordering number */ -.wplace-theme-neon-cyan .wplace-account-number { - position: absolute; - top: 3px; - left: 3px; - width: 8x; - height: 8px; - background: #00ffff; - color: #1a1a2e; - border-radius: 0; - display: flex; - align-items: center; - justify-content: center; - font-size: 8px; - font-weight: bold; - z-index: 1; - border: 1px solid #00ffff; - box-shadow: 0 0 8px rgba(0, 255, 255, 0.6); - font-family: var(--wplace-font); -} - -.wplace-theme-neon-cyan .wplace-account-item:hover { - background: rgba(0, 255, 255, 0.08); - border-color: #00ffff; - box-shadow: 0 0 15px rgba(0, 255, 255, 0.4); - transform: translateX(2px); -} - -.wplace-theme-neon-cyan .wplace-account-item.is-current { - background: rgba(0, 255, 255, 0.15); - border-color: #00ffff; - box-shadow: 0 0 25px rgba(0, 255, 255, 0.6); - animation: neon-cyan-pulse 2s infinite; -} - -@keyframes neon-cyan-pulse { - 0%, 100% { box-shadow: 0 0 25px rgba(0, 255, 255, 0.6); } - 50% { box-shadow: 0 0 35px rgba(0, 255, 255, 0.8); } -} - -/* Account details */ -.wplace-theme-neon-cyan .wplace-account-details { - flex: 1; - margin-left: 20px; - min-width: 0; -} - -.wplace-theme-neon-cyan .wplace-account-name { - font-weight: 600; - color: var(--wplace-text); - font-size: 8px; - margin-bottom: 1px; - text-transform: uppercase; - text-shadow: 0 0 5px var(--wplace-text); - font-family: var(--wplace-font); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -/* Account statistics */ -.wplace-theme-neon-cyan .wplace-account-stats { - display: flex; - gap: 8px; - align-items: center; - font-size: 8px; - color: rgba(0, 255, 255, 0.8); - text-transform: uppercase; - font-family: var(--wplace-font); - flex-shrink: 0; -} - -.wplace-theme-neon-cyan .wplace-account-stats i { - margin-right: 4px; - text-shadow: 0 0 5px currentColor; -} - -.wplace-theme-neon-cyan .wplace-account-stats .fas.fa-bolt { - color: #ffff00; - text-shadow: 0 0 8px #ffff00; -} - -.wplace-theme-neon-cyan .wplace-account-stats .fas.fa-tint { - color: #00ffff; - text-shadow: 0 0 8px #00ffff; -} - -/* Move Artwork Panel Styles for Neon Cyan Theme */ -.wplace-theme-neon-cyan .wplace-move-panel { - position: fixed; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - background: var(--wplace-primary) !important; - border: 2px solid var(--wplace-text) !important; - border-radius: 0 !important; - padding: 20px; - z-index: 10000; - box-shadow: 0 0 20px var(--wplace-text), inset 0 0 20px rgba(129, 220, 247, 0.1) !important; - min-width: 200px; - text-align: center; - font-family: var(--wplace-font) !important; - color: var(--wplace-text) !important; -} - -.wplace-theme-neon-cyan .wplace-move-panel-title { - color: var(--wplace-text) !important; - margin: 0 0 15px 0; - font-size: 12px !important; - text-transform: uppercase; - text-shadow: 0 0 10px var(--wplace-text) !important; - font-family: var(--wplace-font) !important; - animation: cyan-pulse 2s infinite; -} - -.wplace-theme-neon-cyan .wplace-move-controls { - display: grid; - grid-template-columns: 1fr 1fr 1fr; - gap: 4px; - margin-bottom: 15px; -} - -.wplace-theme-neon-cyan .wplace-move-btn { - padding: 8px !important; - background: var(--wplace-secondary) !important; - color: var(--wplace-text) !important; - border: 1px solid var(--wplace-text) !important; - border-radius: 0 !important; - cursor: pointer; - font-family: var(--wplace-font) !important; - font-size: 10px !important; - text-shadow: 0 0 5px var(--wplace-text) !important; - transition: all 0.2s ease; - text-transform: uppercase; -} - -.wplace-theme-neon-cyan .wplace-move-btn:hover { - background: var(--wplace-accent) !important; - box-shadow: 0 0 15px var(--wplace-text) !important; - animation: cyan-flicker 0.5s infinite; -} - -.wplace-theme-neon-cyan .wplace-move-center { - color: var(--wplace-highlight) !important; - padding: 8px; - font-size: 9px !important; - font-weight: bold; - display: flex; - align-items: center; - justify-content: center; - text-shadow: 0 0 5px var(--wplace-highlight) !important; - text-transform: uppercase; - font-family: var(--wplace-font) !important; -} - -.wplace-theme-neon-cyan .wplace-move-close-btn { - background: var(--wplace-error) !important; - color: white !important; - border: 1px solid var(--wplace-error) !important; - padding: 8px 12px; - border-radius: 0 !important; - cursor: pointer; - font-family: var(--wplace-font) !important; - font-size: 9px !important; - text-transform: uppercase; - text-shadow: 0 0 5px white !important; - transition: all 0.2s ease; -} - -.wplace-theme-neon-cyan .wplace-move-close-btn:hover { - box-shadow: 0 0 15px var(--wplace-error) !important; - animation: error-pulse 0.5s infinite; -} - -.wplace-theme-neon-cyan .wplace-move-overlay { - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - background: rgba(25, 89, 161, 0.9) !important; - z-index: 9999; -} - -@keyframes cyan-pulse { - 0%, 100% { text-shadow: 0 0 5px var(--wplace-text), 0 0 10px var(--wplace-text); } - 50% { text-shadow: 0 0 10px var(--wplace-text), 0 0 20px var(--wplace-text); } -} - -@keyframes cyan-flicker { - 0%, 50% { opacity: 1; } - 51%, 100% { opacity: 0.8; } -} - -@keyframes error-pulse { - 0%, 100% { box-shadow: 0 0 5px var(--wplace-error); } - 50% { box-shadow: 0 0 15px var(--wplace-error); } -} +/* WPlace Auto-Image Bot - Neon Cyan Theme */ +/* Retro cyberpunk aesthetic with cyan neon glow effects */ + +.wplace-theme-neon-cyan { + /* Neon theme colors - matching upstream main neon theme */ + --wplace-primary: #1959A1; + --wplace-secondary: #3C74AF; + --wplace-accent: #538CC0; + --wplace-text: #81DCF7; + --wplace-highlight: #EA9C00; + --wplace-success: #39ff14; + --wplace-error: #ff073a; + --wplace-warning: #ff0; + + /* Neon UI properties */ + --wplace-radius: 0; + --wplace-border-style: solid; + --wplace-border-width: 2px; + --wplace-border-color: #81DCF7; + --wplace-shadow: 0 0 20px rgb(234 156 0 / 40%), inset 0 0 20px rgb(234 156 0 / 10%); + --wplace-backdrop: none; + --wplace-font: 'Press Start 2P', monospace, 'Courier New'; + + /* Z-index layers */ + --wplace-z-overlay: 10000; + --wplace-z-alert: 10002; + --wplace-z-settings: 10002; + + /* Feature toggles */ + --wplace-scanline: 1; + --wplace-pixel-blink: 1; + + /* Icon colors - neon variants */ + --wplace-icon-primary: #81DCF7; + --wplace-icon-secondary: #39ff14; + --wplace-icon-palette: #EA9C00; + + /* Additional UI colors - neon variants */ + --wplace-danger: #ff073a; + --wplace-danger-dark: #cc0531; + --wplace-muted-text: #00ff4180; + --wplace-highlight-secondary: #fa0; + + /* Text variants - neon style */ + --wplace-text-secondary: #3C74AF; + --wplace-text-muted: #00ff41; + --wplace-text-dim: #00ff41; + --wplace-text-faded: #00ff41cc; + + /* Background variants - neon style */ + --wplace-bg-input: rgb(234 156 0 / 15%); + --wplace-bg-subtle: rgb(234 156 0 / 8%); + --wplace-bg-faint: rgb(234 156 0 / 5%); + --wplace-bg-ghost: rgb(234 156 0 / 3%); + --wplace-bg-whisper: rgb(234 156 0 / 2%); + + /* Border variants - neon style */ + --wplace-border-subtle: rgb(234 156 0 / 40%); + --wplace-border-faint: rgb(234 156 0 / 25%); + --wplace-border-ghost: rgb(234 156 0 / 15%); + --wplace-border-ultra-faint: rgb(234 156 0 / 8%); + + /* Shadow variants - neon style */ + --wplace-shadow-drag: 0 12px 40px rgb(234 156 0 / 60%), 0 0 0 2px rgb(234 156 0 / 80%), 0 0 20px rgb(234 156 0 / 30%); + --wplace-shadow-notification: 0 4px 12px rgb(234 156 0 / 40%), 0 0 15px rgb(234 156 0 / 20%); + --wplace-shadow-slider-thumb: 0 3px 6px rgb(234 156 0 / 50%), 0 0 0 2px var(--wplace-icon-primary), 0 0 10px rgb(234 156 0 / 30%); + --wplace-shadow-slider-hover: 0 4px 8px rgb(234 156 0 / 60%), 0 0 0 3px var(--wplace-icon-primary), 0 0 15px rgb(234 156 0 / 40%); + + /* Animation colors - neon style */ + --wplace-pulse-start: rgb(234 156 0 / 80%); + --wplace-pulse-mid: rgb(234 156 0 / 0%); + --wplace-pulse-end: rgb(234 156 0 / 0%); + + /* Slider colors - neon style */ + --wplace-slider-thumb-bg: #81DCF7; +} + +/* Neon-specific styling overrides */ +.wplace-theme-neon-cyan #wplace-image-bot-container { + background: var(--wplace-primary) !important; + border: 2px solid var(--wplace-text) !important; + border-radius: 0 !important; + box-shadow: 0 0 20px var(--wplace-text), inset 0 0 20px rgb(234 156 0 / 10%) !important; + font-family: var(--wplace-font) !important; +} + +.wplace-theme-neon-cyan #wplace-stats-container { + background: var(--wplace-primary) !important; + border: 2px solid var(--wplace-text) !important; + border-radius: 0 !important; + box-shadow: 0 0 20px var(--wplace-text), inset 0 0 20px rgb(234 156 0 / 10%) !important; + font-family: var(--wplace-font) !important; + position: fixed !important; + overflow: hidden !important; + z-index: 9998 !important; +} + +.wplace-theme-neon-cyan .wplace-header { + background: var(--wplace-secondary) !important; + border-bottom: 1px solid var(--wplace-text) !important; + color: var(--wplace-text) !important; + text-shadow: 0 0 10px var(--wplace-text) !important; + font-family: var(--wplace-font) !important; +} + +.wplace-theme-neon-cyan .wplace-section { + background: rgb(22 33 62 / 50%) !important; + border: 1px solid var(--wplace-text) !important; + border-radius: 0 !important; +} + +.wplace-theme-neon-cyan .wplace-status-section { + background: rgb(22 33 62 / 50%) !important; + border: 1px solid var(--wplace-text) !important; + border-radius: 0 !important; +} + +.wplace-theme-neon-cyan .wplace-section-title { + color: var(--wplace-text) !important; + text-shadow: 0 0 8px var(--wplace-text) !important; + text-transform: uppercase !important; + font-family: var(--wplace-font) !important; + font-size: 10px !important; +} + +.wplace-theme-neon-cyan .wplace-btn { + background: var(--wplace-secondary) !important; + border: 1px solid var(--wplace-text) !important; + border-radius: 0 !important; + color: var(--wplace-text) !important; + text-shadow: 0 0 5px var(--wplace-text) !important; + font-family: var(--wplace-font) !important; + font-size: 9px !important; + text-transform: uppercase !important; +} + +/* Pixel blinking and neon glow animation for buttons */ +.wplace-theme-neon-cyan .wplace-btn:hover:not(:disabled) { + box-shadow: 0 0 15px var(--wplace-text), inset 0 0 15px rgb(234 156 0 / 10%) !important; + animation: pixel-blink 0.5s infinite, neon-glow 1s ease-in-out infinite alternate !important; +} + +.wplace-theme-neon-cyan .wplace-btn-start { + background: var(--wplace-secondary) !important; + border-color: var(--wplace-success) !important; + color: var(--wplace-success) !important; + text-shadow: 0 0 8px var(--wplace-success) !important; +} + +.wplace-theme-neon-cyan .wplace-btn-stop { + background: var(--wplace-secondary) !important; + border-color: var(--wplace-error) !important; + color: var(--wplace-error) !important; + text-shadow: 0 0 8px var(--wplace-error) !important; +} + +.wplace-theme-neon-cyan .wplace-btn-upload { + background: var(--wplace-secondary) !important; + border: 1px dashed var(--wplace-highlight) !important; + color: var(--wplace-highlight) !important; + text-shadow: 0 0 8px var(--wplace-highlight) !important; +} + +.wplace-theme-neon-cyan .wplace-btn-select { + background: var(--wplace-secondary) !important; + border-color: var(--wplace-highlight) !important; + color: var(--wplace-highlight) !important; + text-shadow: 0 0 8px var(--wplace-highlight) !important; +} + +.wplace-theme-neon-cyan .wplace-btn-file { + background: var(--wplace-secondary) !important; + border-color: var(--wplace-warning) !important; + color: var(--wplace-warning) !important; + text-shadow: 0 0 8px var(--wplace-warning) !important; +} + +.wplace-theme-neon-cyan .wplace-progress { + background: rgb(0 0 0 / 80%) !important; + border: 1px solid var(--wplace-text) !important; + border-radius: 0 !important; +} + +.wplace-theme-neon-cyan .wplace-progress-bar { + background: linear-gradient(90deg, var(--wplace-success) 0%, var(--wplace-text) 100%) !important; + box-shadow: 0 0 10px var(--wplace-success) !important; +} + +.wplace-theme-neon-cyan .wplace-stat-value { + color: var(--wplace-text) !important; + text-shadow: 0 0 5px var(--wplace-text) !important; +} + +.wplace-theme-neon-cyan .status-success { + background: rgb(57 255 20 / 10%) !important; + border-color: var(--wplace-success) !important; + color: var(--wplace-success) !important; + box-shadow: 0 0 15px var(--wplace-success) !important; + text-shadow: 0 0 8px var(--wplace-success) !important; +} + +.wplace-theme-neon-cyan .status-error { + background: rgb(255 7 58 / 10%) !important; + border-color: var(--wplace-error) !important; + color: var(--wplace-error) !important; + box-shadow: 0 0 15px var(--wplace-error) !important; + text-shadow: 0 0 8px var(--wplace-error) !important; +} + +.wplace-theme-neon-cyan .status-default { + background: rgb(234 156 0 / 10%) !important; + border-color: var(--wplace-text) !important; + color: var(--wplace-text) !important; + text-shadow: 0 0 5px var(--wplace-text) !important; +} + +/* Settings dialog neon styling */ +.wplace-theme-neon-cyan #wplace-settings-container { + background: var(--wplace-primary) !important; + border: 2px solid var(--wplace-text) !important; + border-radius: 0 !important; + box-shadow: 0 0 30px var(--wplace-text), inset 0 0 30px rgb(234 156 0 / 10%) !important; + font-family: var(--wplace-font) !important; +} + +.wplace-theme-neon-cyan .wplace-settings-header { + background: var(--wplace-secondary) !important; + border-bottom: 1px solid var(--wplace-text) !important; +} + +.wplace-theme-neon-cyan .wplace-settings-header h3 { + color: var(--wplace-text) !important; + text-shadow: 0 0 10px var(--wplace-text) !important; + font-family: var(--wplace-font) !important; + font-size: 16px !important; + text-transform: uppercase !important; +} + +.wplace-theme-neon-cyan .wplace-settings-close-btn { + background: var(--wplace-secondary) !important; + border: 1px solid var(--wplace-text) !important; + border-radius: 0 !important; + color: var(--wplace-text) !important; +} + +.wplace-theme-neon-cyan .wplace-settings-close-btn:hover { + background: var(--wplace-error) !important; + box-shadow: 0 0 15px var(--wplace-error) !important; +} + +.wplace-theme-neon-cyan .wplace-settings-section-wrapper { + background: rgb(22 33 62 / 30%) !important; + border: 1px solid var(--wplace-text) !important; + border-radius: 0 !important; +} + +.wplace-theme-neon-cyan .wplace-settings-select { + background: var(--wplace-secondary) !important; + border: 1px solid var(--wplace-text) !important; + border-radius: 0 !important; + color: var(--wplace-text) !important; + font-family: var(--wplace-font) !important; + font-size: 11px !important; +} + +.wplace-theme-neon-cyan .wplace-settings-section-label { + color: var(--wplace-text) !important; + text-shadow: 0 0 8px var(--wplace-text) !important; + font-family: var(--wplace-font) !important; + font-size: 12px !important; + text-transform: uppercase !important; +} + +.wplace-theme-neon-cyan .wplace-speed-value { + background: var(--wplace-secondary) !important; + border: 1px solid var(--wplace-text) !important; + border-radius: 0 !important; + color: var(--wplace-text) !important; + text-shadow: 0 0 8px var(--wplace-text) !important; + font-family: var(--wplace-font) !important; + box-shadow: 0 0 10px rgb(234 156 0 / 30%) !important; +} + +.wplace-theme-neon-cyan .wplace-overlay-opacity-value { + background: var(--wplace-secondary) !important; + border: 1px solid var(--wplace-text) !important; + border-radius: 0 !important; + color: var(--wplace-text) !important; + text-shadow: 0 0 5px var(--wplace-text) !important; +} + +/* Neon slider styling */ +.wplace-theme-neon-cyan .wplace-slider, +.wplace-theme-neon-cyan .wplace-speed-slider, +.wplace-theme-neon-cyan .wplace-overlay-opacity-slider { + background: var(--wplace-secondary) !important; + border: 1px solid var(--wplace-text) !important; + border-radius: 0 !important; + box-shadow: 0 0 10px rgb(234 156 0 / 30%) !important; +} + +.wplace-theme-neon-cyan .wplace-slider::-webkit-slider-thumb, +.wplace-theme-neon-cyan .wplace-speed-slider::-webkit-slider-thumb, +.wplace-theme-neon-cyan .wplace-overlay-opacity-slider::-webkit-slider-thumb { + -webkit-appearance: none; + width: 16px; + height: 16px; + border-radius: 0 !important; + background: var(--wplace-slider-thumb-bg) !important; + border: 2px solid var(--wplace-text) !important; + cursor: pointer; + transition: all 0.2s ease; + box-shadow: 0 0 8px rgb(234 156 0 / 50%) !important; +} + +.wplace-theme-neon-cyan .wplace-slider::-webkit-slider-thumb:hover, +.wplace-theme-neon-cyan .wplace-speed-slider::-webkit-slider-thumb:hover, +.wplace-theme-neon-cyan .wplace-overlay-opacity-slider::-webkit-slider-thumb:hover { + transform: scale(1.1); + box-shadow: 0 0 15px var(--wplace-text) !important; +} + +/* Scanline animation for neon theme */ +.wplace-theme-neon-cyan #wplace-image-bot-container::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 2px; + background: linear-gradient(90deg, transparent, var(--wplace-text), transparent); + z-index: 1; + pointer-events: none; + animation: scanline 3s linear infinite; + opacity: 0.7; +} + + +.wplace-theme-neon-cyan #wplace-stats-container::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 2px; + background: linear-gradient(90deg, transparent, var(--wplace-text), transparent); + z-index: 1; + pointer-events: none; + animation: scanline-stats 4s linear infinite; + opacity: 0.7; +} + + +/* Text glow animations */ +@keyframes neon-glow { + 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; + } +} + +@keyframes pixel-blink { + 0%, 50% { + opacity: 1; + } + + 51%, 100% { + opacity: 0.7; + } +} + +@keyframes scanline { + 0% { + transform: translateY(-100%); + } + + 100% { + transform: translateY(400px); + } +} + +@keyframes scanline-stats { + 0% { + transform: translateY(-100%); + } + + 100% { + transform: translateY(300px); + } +} + +/* Dual control layout styling for neon theme */ +.wplace-theme-neon-cyan .wplace-dual-control-compact { + display: flex; + flex-direction: row; + align-items: center; + gap: 8px; + margin: 6px 0; + flex-wrap: wrap; +} + +.wplace-theme-neon-cyan .wplace-slider-container-compact { + display: flex; + align-items: center; + gap: 6px; + flex: 1; + min-width: 160px; +} + +.wplace-theme-neon-cyan .wplace-speed-slider-container-compact { + display: flex; + align-items: center; + gap: 6px; + flex: 1; + min-width: 160px; +} + +.wplace-theme-neon-cyan .wplace-input-group-compact { + display: flex; + align-items: center; + gap: 3px; + flex-shrink: 0; +} + +.wplace-theme-neon-cyan .wplace-input-btn-compact { + background: var(--wplace-secondary) !important; + border: 1px solid var(--wplace-text) !important; + border-radius: 0 !important; + color: var(--wplace-text) !important; + width: 18px; + height: 18px; + cursor: pointer; + font-weight: bold; + font-size: 10px; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.2s ease; + text-shadow: 0 0 5px var(--wplace-text) !important; + font-family: var(--wplace-font) !important; +} + +.wplace-theme-neon-cyan .wplace-input-btn-compact:hover { + box-shadow: 0 0 10px var(--wplace-text) !important; + animation: pixel-blink 0.5s infinite !important; +} + +.wplace-theme-neon-cyan .wplace-number-input-compact { + background: var(--wplace-secondary) !important; + border: 1px solid var(--wplace-text) !important; + border-radius: 0 !important; + color: var(--wplace-text) !important; + padding: 2px 4px; + font-size: 10px; + width: 65px; + text-align: center; + transition: all 0.2s ease; + height: 18px; + font-family: var(--wplace-font) !important; + text-shadow: 0 0 5px var(--wplace-text) !important; +} + +.wplace-theme-neon-cyan .wplace-number-input-compact:focus { + outline: none; + box-shadow: 0 0 10px var(--wplace-text) !important; + background: rgb(234 156 0 / 15%) !important; +} + +.wplace-theme-neon-cyan .wplace-input-label-compact { + color: var(--wplace-text) !important; + font-size: 9px; + margin-left: 3px; + white-space: nowrap; + text-shadow: 0 0 5px var(--wplace-text) !important; + font-family: var(--wplace-font) !important; + text-transform: uppercase; +} + +.wplace-theme-neon-cyan .wplace-batch-size-header { + margin-bottom: 6px; +} + +.wplace-theme-neon-cyan .wplace-batch-size-label { + color: var(--wplace-text) !important; + font-weight: 500; + font-size: 11px; + text-shadow: 0 0 8px var(--wplace-text) !important; + font-family: var(--wplace-font) !important; + text-transform: uppercase; +} + +/* Cooldown control styling for neon theme */ +.wplace-theme-neon-cyan .wplace-cooldown-control { + margin: 8px 0; +} + +.wplace-theme-neon-cyan .wplace-cooldown-control label { + color: var(--wplace-text) !important; + font-weight: 500; + font-size: 11px; + text-shadow: 0 0 8px var(--wplace-text) !important; + font-family: var(--wplace-font) !important; + text-transform: uppercase; + margin-bottom: 6px; + display: block; +} + +/* Resize dialog styling for neon theme */ +.wplace-theme-neon-cyan .resize-container { + background-color: #1959A1 !important; /* Solid neon blue */ + opacity: 1 !important; /* Fully opaque */ + backdrop-filter: none !important; /* Remove blur effects */ + box-shadow: none !important; /* Optional: remove glows */ + font-family: 'Press Start 2P', monospace !important; +} + +/* Account switching UI styles */ +.wplace-theme-neon-cyan .wplace-section { + margin: 15px 0; + border-radius: 0; + background: rgba(0, 255, 255, 0.05); + padding: 15px; + border: 2px solid #00ffff; + box-shadow: 0 0 20px rgba(0, 255, 255, 0.3); + font-family: var(--wplace-font); +} + +.wplace-theme-neon-cyan .wplace-section-title { + display: flex; + align-items: center; + gap: 8px; + font-weight: 600; + color: var(--wplace-text); + margin-bottom: 10px; + font-size: 12px; + text-transform: uppercase; + text-shadow: 0 0 8px var(--wplace-text); + font-family: var(--wplace-font); +} + +/* Auto-swap toggle switch */ +.wplace-theme-neon-cyan .wplace-switch { + position: relative; + display: inline-block; + width: 50px; + height: 24px; +} + +.wplace-theme-neon-cyan .wplace-switch input { + opacity: 0; + width: 0; + height: 0; +} + +.wplace-theme-neon-cyan .wplace-slider-round { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: #1a1a2e; + transition: 0.4s; + border-radius: 0; + border: 2px solid #333; + box-shadow: 0 0 10px rgba(0, 255, 255, 0.2); +} + +.wplace-theme-neon-cyan .wplace-slider-round:before { + position: absolute; + content: ""; + height: 14px; + width: 14px; + left: 3px; + bottom: 3px; + background-color: #fff; + transition: 0.4s; + border-radius: 0; + box-shadow: 0 0 8px rgba(255, 255, 255, 0.5); +} + +.wplace-theme-neon-cyan .wplace-switch input:checked + .wplace-slider-round { + background-color: #00ffff; + box-shadow: 0 0 20px rgba(0, 255, 255, 0.6); +} + +.wplace-theme-neon-cyan .wplace-switch input:checked + .wplace-slider-round:before { + transform: translateX(26px); + box-shadow: 0 0 12px rgba(0, 255, 255, 0.8); +} + +/* Account list container */ +.wplace-theme-neon-cyan .accounts-list-container { + max-height: 200px; + overflow-y: auto; + margin-top: 10px; + padding: 0 6px; +} + +/* Individual account item */ +.wplace-theme-neon-cyan .wplace-account-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 6px 8px; + margin: 4px 0; + background: rgba(0, 255, 255, 0.03); + border-radius: 0; + border: 1px solid #00ffff; + transition: all 0.3s ease; + box-shadow: 0 0 5px rgba(0, 255, 255, 0.2); + position: relative; +} + +/* Account ordering number */ +.wplace-theme-neon-cyan .wplace-account-number { + position: absolute; + top: 3px; + left: 3px; + width: 8x; + height: 8px; + background: #00ffff; + color: #1a1a2e; + border-radius: 0; + display: flex; + align-items: center; + justify-content: center; + font-size: 8px; + font-weight: bold; + z-index: 1; + border: 1px solid #00ffff; + box-shadow: 0 0 8px rgba(0, 255, 255, 0.6); + font-family: var(--wplace-font); +} + +.wplace-theme-neon-cyan .wplace-account-item:hover { + background: rgba(0, 255, 255, 0.08); + border-color: #00ffff; + box-shadow: 0 0 15px rgba(0, 255, 255, 0.4); + transform: translateX(2px); +} + +.wplace-theme-neon-cyan .wplace-account-item.is-current { + background: rgba(0, 255, 255, 0.15); + border-color: #00ffff; + box-shadow: 0 0 25px rgba(0, 255, 255, 0.6); + animation: neon-cyan-pulse 2s infinite; +} + +@keyframes neon-cyan-pulse { + 0%, 100% { box-shadow: 0 0 25px rgba(0, 255, 255, 0.6); } + 50% { box-shadow: 0 0 35px rgba(0, 255, 255, 0.8); } +} + +/* Account details */ +.wplace-theme-neon-cyan .wplace-account-details { + flex: 1; + margin-left: 20px; + min-width: 0; +} + +.wplace-theme-neon-cyan .wplace-account-name { + font-weight: 600; + color: var(--wplace-text); + font-size: 8px; + margin-bottom: 1px; + text-transform: uppercase; + text-shadow: 0 0 5px var(--wplace-text); + font-family: var(--wplace-font); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +/* Account statistics */ +.wplace-theme-neon-cyan .wplace-account-stats { + display: flex; + gap: 8px; + align-items: center; + font-size: 8px; + color: rgba(0, 255, 255, 0.8); + text-transform: uppercase; + font-family: var(--wplace-font); + flex-shrink: 0; +} + +.wplace-theme-neon-cyan .wplace-account-stats i { + margin-right: 4px; + text-shadow: 0 0 5px currentColor; +} + +.wplace-theme-neon-cyan .wplace-account-stats .fas.fa-bolt { + color: #ffff00; + text-shadow: 0 0 8px #ffff00; +} + +.wplace-theme-neon-cyan .wplace-account-stats .fas.fa-tint { + color: #00ffff; + text-shadow: 0 0 8px #00ffff; +} + +/* Move Artwork Panel Styles for Neon Cyan Theme */ +.wplace-theme-neon-cyan .wplace-move-panel { + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background: var(--wplace-primary) !important; + border: 2px solid var(--wplace-text) !important; + border-radius: 0 !important; + padding: 20px; + z-index: 10000; + box-shadow: 0 0 20px var(--wplace-text), inset 0 0 20px rgba(129, 220, 247, 0.1) !important; + min-width: 200px; + text-align: center; + font-family: var(--wplace-font) !important; + color: var(--wplace-text) !important; +} + +.wplace-theme-neon-cyan .wplace-move-panel-title { + color: var(--wplace-text) !important; + margin: 0 0 15px 0; + font-size: 12px !important; + text-transform: uppercase; + text-shadow: 0 0 10px var(--wplace-text) !important; + font-family: var(--wplace-font) !important; + animation: cyan-pulse 2s infinite; +} + +.wplace-theme-neon-cyan .wplace-move-controls { + display: grid; + grid-template-columns: 1fr 1fr 1fr; + gap: 4px; + margin-bottom: 15px; +} + +.wplace-theme-neon-cyan .wplace-move-btn { + padding: 8px !important; + background: var(--wplace-secondary) !important; + color: var(--wplace-text) !important; + border: 1px solid var(--wplace-text) !important; + border-radius: 0 !important; + cursor: pointer; + font-family: var(--wplace-font) !important; + font-size: 10px !important; + text-shadow: 0 0 5px var(--wplace-text) !important; + transition: all 0.2s ease; + text-transform: uppercase; +} + +.wplace-theme-neon-cyan .wplace-move-btn:hover { + background: var(--wplace-accent) !important; + box-shadow: 0 0 15px var(--wplace-text) !important; + animation: cyan-flicker 0.5s infinite; +} + +.wplace-theme-neon-cyan .wplace-move-center { + color: var(--wplace-highlight) !important; + padding: 8px; + font-size: 9px !important; + font-weight: bold; + display: flex; + align-items: center; + justify-content: center; + text-shadow: 0 0 5px var(--wplace-highlight) !important; + text-transform: uppercase; + font-family: var(--wplace-font) !important; +} + +.wplace-theme-neon-cyan .wplace-move-close-btn { + background: var(--wplace-error) !important; + color: white !important; + border: 1px solid var(--wplace-error) !important; + padding: 8px 12px; + border-radius: 0 !important; + cursor: pointer; + font-family: var(--wplace-font) !important; + font-size: 9px !important; + text-transform: uppercase; + text-shadow: 0 0 5px white !important; + transition: all 0.2s ease; +} + +.wplace-theme-neon-cyan .wplace-move-close-btn:hover { + box-shadow: 0 0 15px var(--wplace-error) !important; + animation: error-pulse 0.5s infinite; +} + +.wplace-theme-neon-cyan .wplace-move-overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(25, 89, 161, 0.9) !important; + z-index: 9999; +} + +@keyframes cyan-pulse { + 0%, 100% { text-shadow: 0 0 5px var(--wplace-text), 0 0 10px var(--wplace-text); } + 50% { text-shadow: 0 0 10px var(--wplace-text), 0 0 20px var(--wplace-text); } +} + +@keyframes cyan-flicker { + 0%, 50% { opacity: 1; } + 51%, 100% { opacity: 0.8; } +} + +@keyframes error-pulse { + 0%, 100% { box-shadow: 0 0 5px var(--wplace-error); } + 50% { box-shadow: 0 0 15px var(--wplace-error); } +} diff --git a/Extension/themes/neon-light.css b/Extension/themes/neon-light.css index fbc2316..f2ac42f 100644 --- a/Extension/themes/neon-light.css +++ b/Extension/themes/neon-light.css @@ -1,881 +1,881 @@ -/* WPlace Auto-Image Bot - Neon Light Theme */ -/* Retro cyberpunk aesthetic with light neon glow effects */ - -.wplace-theme-neon-light { - /* Neon theme colors - matching upstream main neon theme */ - --wplace-primary: #E0E0E1; - --wplace-secondary: #FBFBFB; - --wplace-accent: #203C5D; - --wplace-text: #203C5D; - --wplace-highlight: #203C5D; - --wplace-success: #39ff14; - --wplace-error: #ff073a; - --wplace-warning: #ff0; - - /* Neon UI properties */ - --wplace-radius: 0; - --wplace-border-style: solid; - --wplace-border-width: 2px; - --wplace-border-color: #203C5D; - --wplace-shadow: 0 0 20px rgb(234 156 0 / 40%), inset 0 0 20px rgb(234 156 0 / 10%); - --wplace-backdrop: none; - --wplace-font: 'Press Start 2P', monospace, 'Courier New'; - - /* Z-index layers */ - --wplace-z-overlay: 10000; - --wplace-z-alert: 10002; - --wplace-z-settings: 10002; - - /* Feature toggles */ - --wplace-scanline: 1; - --wplace-pixel-blink: 1; - - /* Icon colors - neon variants */ - --wplace-icon-primary: #203C5D; - --wplace-icon-secondary: #39ff14; - --wplace-icon-palette: #203C5D; - - /* Additional UI colors - neon variants */ - --wplace-danger: #ff073a; - --wplace-danger-dark: #cc0531; - --wplace-muted-text: #00ff4180; - --wplace-highlight-secondary: #fa0; - - /* Text variants - neon style */ - --wplace-text-secondary: #3C74AF; - --wplace-text-muted: #00ff41; - --wplace-text-dim: #00ff41; - --wplace-text-faded: #00ff41cc; - - /* Background variants - neon style */ - --wplace-bg-input: rgb(234 156 0 / 15%); - --wplace-bg-subtle: rgb(234 156 0 / 8%); - --wplace-bg-faint: rgb(234 156 0 / 5%); - --wplace-bg-ghost: rgb(234 156 0 / 3%); - --wplace-bg-whisper: rgb(234 156 0 / 2%); - - /* Border variants - neon style */ - --wplace-border-subtle: rgb(234 156 0 / 40%); - --wplace-border-faint: rgb(234 156 0 / 25%); - --wplace-border-ghost: rgb(234 156 0 / 15%); - --wplace-border-ultra-faint: rgb(234 156 0 / 8%); - - /* Shadow variants - neon style */ - --wplace-shadow-drag: 0 12px 40px rgb(234 156 0 / 60%), 0 0 0 2px rgb(234 156 0 / 80%), 0 0 20px rgb(234 156 0 / 30%); - --wplace-shadow-notification: 0 4px 12px rgb(234 156 0 / 40%), 0 0 15px rgb(234 156 0 / 20%); - --wplace-shadow-slider-thumb: 0 3px 6px rgb(234 156 0 / 50%), 0 0 0 2px var(--wplace-icon-primary), 0 0 10px rgb(234 156 0 / 30%); - --wplace-shadow-slider-hover: 0 4px 8px rgb(234 156 0 / 60%), 0 0 0 3px var(--wplace-icon-primary), 0 0 15px rgb(234 156 0 / 40%); - - /* Animation colors - neon style */ - --wplace-pulse-start: rgb(234 156 0 / 80%); - --wplace-pulse-mid: rgb(234 156 0 / 0%); - --wplace-pulse-end: rgb(234 156 0 / 0%); - - /* Slider colors - neon style */ - --wplace-slider-thumb-bg: #203C5D; -} - -/* Neon-specific styling overrides */ -.wplace-theme-neon-light #wplace-image-bot-container { - background: var(--wplace-primary) !important; - border: 2px solid var(--wplace-text) !important; - border-radius: 0 !important; - box-shadow: 0 0 20px var(--wplace-text), inset 0 0 20px rgb(234 156 0 / 10%) !important; - font-family: var(--wplace-font) !important; -} - -.wplace-theme-neon-light #wplace-stats-container { - background: var(--wplace-primary) !important; - border: 2px solid var(--wplace-text) !important; - border-radius: 0 !important; - box-shadow: 0 0 20px var(--wplace-text), inset 0 0 20px rgb(234 156 0 / 10%) !important; - font-family: var(--wplace-font) !important; - position: fixed !important; - overflow: hidden !important; - z-index: 9998 !important; -} - -.wplace-theme-neon-light .wplace-header { - background: var(--wplace-secondary) !important; - border-bottom: 1px solid var(--wplace-text) !important; - color: var(--wplace-text) !important; - text-shadow: 0 0 10px var(--wplace-text) !important; - font-family: var(--wplace-font) !important; -} - -.wplace-theme-neon-light .wplace-section { - background: rgb(22 33 62 / 30%) !important; - border: 1px solid var(--wplace-text) !important; - border-radius: 0 !important; -} - -.wplace-theme-neon-light .wplace-status-section { - background: rgb(22 33 62 / 30%) !important; - border: 1px solid var(--wplace-text) !important; - border-radius: 0 !important; -} - -.wplace-theme-neon-light .wplace-section-title { - color: var(--wplace-text) !important; - text-shadow: 0 0 8px var(--wplace-text) !important; - text-transform: uppercase !important; - font-family: var(--wplace-font) !important; - font-size: 10px !important; -} - -.wplace-theme-neon-light .wplace-btn { - background: var(--wplace-secondary) !important; - border: 1px solid var(--wplace-text) !important; - border-radius: 0 !important; - color: var(--wplace-text) !important; - text-shadow: 0 0 5px var(--wplace-text) !important; - font-family: var(--wplace-font) !important; - font-size: 9px !important; - text-transform: uppercase !important; -} - -/* Pixel blinking and neon glow animation for buttons */ -.wplace-theme-neon-light .wplace-btn:hover:not(:disabled) { - box-shadow: 0 0 15px var(--wplace-text), inset 0 0 15px rgb(234 156 0 / 10%) !important; - animation: pixel-blink 0.5s infinite, neon-glow 1s ease-in-out infinite alternate !important; -} - -.wplace-theme-neon-light .wplace-btn-start { - background: var(--wplace-secondary) !important; - border-color: var(--wplace-success) !important; - color: var(--wplace-success) !important; - text-shadow: 0 0 8px var(--wplace-success) !important; -} - -.wplace-theme-neon-light .wplace-btn-stop { - background: var(--wplace-secondary) !important; - border-color: var(--wplace-error) !important; - color: var(--wplace-error) !important; - text-shadow: 0 0 8px var(--wplace-error) !important; -} - -.wplace-theme-neon-light .wplace-btn-upload { - background: var(--wplace-secondary) !important; - border: 1px dashed var(--wplace-highlight) !important; - color: var(--wplace-highlight) !important; - text-shadow: 0 0 8px var(--wplace-highlight) !important; -} - -.wplace-theme-neon-light .wplace-btn-select { - background: var(--wplace-secondary) !important; - border-color: var(--wplace-highlight) !important; - color: var(--wplace-highlight) !important; - text-shadow: 0 0 8px var(--wplace-highlight) !important; -} - -.wplace-theme-neon-light .wplace-btn-file { - background: var(--wplace-secondary) !important; - border-color: var(--wplace-warning) !important; - color: var(--wplace-warning) !important; - text-shadow: 0 0 8px var(--wplace-warning) !important; -} - -.wplace-theme-neon-light .wplace-progress { - background: rgb(0 0 0 / 80%) !important; - border: 1px solid var(--wplace-text) !important; - border-radius: 0 !important; -} - -.wplace-theme-neon-light .wplace-progress-bar { - background: linear-gradient(90deg, var(--wplace-success) 0%, var(--wplace-text) 100%) !important; - box-shadow: 0 0 10px var(--wplace-success) !important; -} - -.wplace-theme-neon-light .wplace-stat-value { - color: var(--wplace-text) !important; - text-shadow: 0 0 5px var(--wplace-text) !important; -} - -.wplace-theme-neon-light .status-success { - background: rgb(57 255 20 / 10%) !important; - border-color: var(--wplace-success) !important; - color: var(--wplace-success) !important; - box-shadow: 0 0 15px var(--wplace-success) !important; - text-shadow: 0 0 8px var(--wplace-success) !important; -} - -.wplace-theme-neon-light .status-error { - background: rgb(255 7 58 / 10%) !important; - border-color: var(--wplace-error) !important; - color: var(--wplace-error) !important; - box-shadow: 0 0 15px var(--wplace-error) !important; - text-shadow: 0 0 8px var(--wplace-error) !important; -} - -.wplace-theme-neon-light .status-default { - background: rgb(234 156 0 / 10%) !important; - border-color: var(--wplace-text) !important; - color: var(--wplace-text) !important; - text-shadow: 0 0 5px var(--wplace-text) !important; -} - -/* Settings dialog neon styling */ -.wplace-theme-neon-light #wplace-settings-container { - background: var(--wplace-primary) !important; - border: 2px solid var(--wplace-text) !important; - border-radius: 0 !important; - box-shadow: 0 0 30px var(--wplace-text), inset 0 0 30px rgb(234 156 0 / 10%) !important; - font-family: var(--wplace-font) !important; -} - -.wplace-theme-neon-light .wplace-settings-header { - background: var(--wplace-secondary) !important; - border-bottom: 1px solid var(--wplace-text) !important; -} - -.wplace-theme-neon-light .wplace-settings-header h3 { - color: var(--wplace-text) !important; - text-shadow: 0 0 10px var(--wplace-text) !important; - font-family: var(--wplace-font) !important; - font-size: 16px !important; - text-transform: uppercase !important; -} - -.wplace-theme-neon-light .wplace-settings-close-btn { - background: var(--wplace-secondary) !important; - border: 1px solid var(--wplace-text) !important; - border-radius: 0 !important; - color: var(--wplace-text) !important; -} - -.wplace-theme-neon-light .wplace-settings-close-btn:hover { - background: var(--wplace-error) !important; - box-shadow: 0 0 15px var(--wplace-error) !important; -} - -.wplace-theme-neon-light .wplace-settings-section-wrapper { - background: rgb(22 33 62 / 10%) !important; - border: 1px solid var(--wplace-text) !important; - border-radius: 0 !important; -} - -.wplace-theme-neon-light .wplace-settings-select { - background: var(--wplace-secondary) !important; - border: 1px solid var(--wplace-text) !important; - border-radius: 0 !important; - color: var(--wplace-text) !important; - font-family: var(--wplace-font) !important; - font-size: 11px !important; -} - -.wplace-theme-neon-light .wplace-settings-section-label { - color: var(--wplace-text) !important; - text-shadow: 0 0 8px var(--wplace-text) !important; - font-family: var(--wplace-font) !important; - font-size: 12px !important; - text-transform: uppercase !important; -} - -.wplace-theme-neon-light .wplace-speed-value { - background: var(--wplace-secondary) !important; - border: 1px solid var(--wplace-text) !important; - border-radius: 0 !important; - color: var(--wplace-text) !important; - text-shadow: 0 0 8px var(--wplace-text) !important; - font-family: var(--wplace-font) !important; - box-shadow: 0 0 10px rgb(234 156 0 / 30%) !important; -} - -.wplace-theme-neon-light .wplace-overlay-opacity-value { - background: var(--wplace-secondary) !important; - border: 1px solid var(--wplace-text) !important; - border-radius: 0 !important; - color: var(--wplace-text) !important; - text-shadow: 0 0 5px var(--wplace-text) !important; -} - -/* Neon slider styling */ -.wplace-theme-neon-light .wplace-slider, -.wplace-theme-neon-light .wplace-speed-slider, -.wplace-theme-neon-light .wplace-overlay-opacity-slider { - background: var(--wplace-secondary) !important; - border: 1px solid var(--wplace-text) !important; - border-radius: 0 !important; - box-shadow: 0 0 10px rgb(234 156 0 / 30%) !important; -} - -.wplace-theme-neon-light .wplace-slider::-webkit-slider-thumb, -.wplace-theme-neon-light .wplace-speed-slider::-webkit-slider-thumb, -.wplace-theme-neon-light .wplace-overlay-opacity-slider::-webkit-slider-thumb { - -webkit-appearance: none; - width: 16px; - height: 16px; - border-radius: 0 !important; - background: var(--wplace-slider-thumb-bg) !important; - border: 2px solid var(--wplace-text) !important; - cursor: pointer; - transition: all 0.2s ease; - box-shadow: 0 0 8px rgb(234 156 0 / 50%) !important; -} - -.wplace-theme-neon-light .wplace-slider::-webkit-slider-thumb:hover, -.wplace-theme-neon-light .wplace-speed-slider::-webkit-slider-thumb:hover, -.wplace-theme-neon-light .wplace-overlay-opacity-slider::-webkit-slider-thumb:hover { - transform: scale(1.1); - box-shadow: 0 0 15px var(--wplace-text) !important; -} - -/* Scanline animation for neon theme */ -.wplace-theme-neon-light #wplace-image-bot-container::before { - content: ''; - position: absolute; - top: 0; - left: 0; - right: 0; - height: 2px; - background: linear-gradient(90deg, transparent, var(--wplace-text), transparent); - z-index: 1; - pointer-events: none; - animation: scanline 3s linear infinite; - opacity: 0.7; -} - - -.wplace-theme-neon-light #wplace-stats-container::before { - content: ''; - position: absolute; - top: 0; - left: 0; - right: 0; - height: 2px; - background: linear-gradient(90deg, transparent, var(--wplace-text), transparent); - z-index: 1; - pointer-events: none; - animation: scanline-stats 4s linear infinite; - opacity: 0.7; -} - - -/* Text glow animations */ -@keyframes neon-glow { - 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; - } -} - -@keyframes pixel-blink { - 0%, 50% { - opacity: 1; - } - - 51%, 100% { - opacity: 0.7; - } -} - -@keyframes scanline { - 0% { - transform: translateY(-100%); - } - - 100% { - transform: translateY(400px); - } -} - -@keyframes scanline-stats { - 0% { - transform: translateY(-100%); - } - - 100% { - transform: translateY(300px); - } -} - -/* Dual control layout styling for neon theme */ -.wplace-theme-neon-light .wplace-dual-control-compact { - display: flex; - flex-direction: row; - align-items: center; - gap: 8px; - margin: 6px 0; - flex-wrap: wrap; -} - -.wplace-theme-neon-light .wplace-slider-container-compact { - display: flex; - align-items: center; - gap: 6px; - flex: 1; - min-width: 160px; -} - -.wplace-theme-neon-light .wplace-speed-slider-container-compact { - display: flex; - align-items: center; - gap: 6px; - flex: 1; - min-width: 160px; -} - -.wplace-theme-neon-light .wplace-input-group-compact { - display: flex; - align-items: center; - gap: 3px; - flex-shrink: 0; -} - -.wplace-theme-neon-light .wplace-input-btn-compact { - background: var(--wplace-secondary) !important; - border: 1px solid var(--wplace-text) !important; - border-radius: 0 !important; - color: var(--wplace-text) !important; - width: 18px; - height: 18px; - cursor: pointer; - font-weight: bold; - font-size: 10px; - display: flex; - align-items: center; - justify-content: center; - transition: all 0.2s ease; - text-shadow: 0 0 5px var(--wplace-text) !important; - font-family: var(--wplace-font) !important; -} - -.wplace-theme-neon-light .wplace-input-btn-compact:hover { - box-shadow: 0 0 10px var(--wplace-text) !important; - animation: pixel-blink 0.5s infinite !important; -} - -.wplace-theme-neon-light .wplace-number-input-compact { - background: var(--wplace-secondary) !important; - border: 1px solid var(--wplace-text) !important; - border-radius: 0 !important; - color: var(--wplace-text) !important; - padding: 2px 4px; - font-size: 10px; - width: 65px; - text-align: center; - transition: all 0.2s ease; - height: 18px; - font-family: var(--wplace-font) !important; - text-shadow: 0 0 5px var(--wplace-text) !important; -} - -.wplace-theme-neon-light .wplace-number-input-compact:focus { - outline: none; - box-shadow: 0 0 10px var(--wplace-text) !important; - background: rgb(234 156 0 / 15%) !important; -} - -.wplace-theme-neon-light .wplace-input-label-compact { - color: var(--wplace-text) !important; - font-size: 9px; - margin-left: 3px; - white-space: nowrap; - text-shadow: 0 0 5px var(--wplace-text) !important; - font-family: var(--wplace-font) !important; - text-transform: uppercase; -} - -.wplace-theme-neon-light .wplace-batch-size-header { - margin-bottom: 6px; -} - -.wplace-theme-neon-light .wplace-batch-size-label { - color: var(--wplace-text) !important; - font-weight: 500; - font-size: 11px; - text-shadow: 0 0 8px var(--wplace-text) !important; - font-family: var(--wplace-font) !important; - text-transform: uppercase; -} - -/* Cooldown control styling for neon theme */ -.wplace-theme-neon-light .wplace-cooldown-control { - margin: 8px 0; -} - -.wplace-theme-neon-light .wplace-cooldown-control label { - color: var(--wplace-text) !important; - font-weight: 500; - font-size: 11px; - text-shadow: 0 0 8px var(--wplace-text) !important; - font-family: var(--wplace-font) !important; - text-transform: uppercase; - margin-bottom: 6px; - display: block; -} - -/* Resize dialog styling for neon theme */ -.wplace-theme-neon-light .resize-container { - background-color: #E0E0E1 !important; /* Solid neon white */ - border: 3px solid #203C5D !important; - border-radius: 0 !important; - backdrop-filter: none !important; /* Remove blur effects */ - box-shadow: none !important; /* Optional: remove glows */ - font-family: 'Press Start 2P', monospace !important; -} - -/* Account switching UI styles */ -.wplace-theme-neon-light .wplace-section { - margin: 15px 0; - border-radius: 0; - background: rgba(32, 60, 93, 0.05); - padding: 15px; - border: 2px solid #203C5D; - box-shadow: 0 0 20px rgba(32, 60, 93, 0.2); - font-family: var(--wplace-font); -} - -.wplace-theme-neon-light .wplace-section-title { - display: flex; - align-items: center; - gap: 8px; - font-weight: 600; - color: var(--wplace-text); - margin-bottom: 10px; - font-size: 12px; - text-transform: uppercase; - text-shadow: 0 0 5px var(--wplace-text); - font-family: var(--wplace-font); -} - -/* Auto-swap toggle switch */ -.wplace-theme-neon-light .wplace-switch { - position: relative; - display: inline-block; - width: 50px; - height: 24px; -} - -.wplace-theme-neon-light .wplace-switch input { - opacity: 0; - width: 0; - height: 0; -} - -.wplace-theme-neon-light .wplace-slider-round { - position: absolute; - cursor: pointer; - top: 0; - left: 0; - right: 0; - bottom: 0; - background-color: #f0f0f0; - transition: 0.4s; - border-radius: 0; - border: 2px solid #ccc; - box-shadow: 0 0 8px rgba(32, 60, 93, 0.15); -} - -.wplace-theme-neon-light .wplace-slider-round:before { - position: absolute; - content: ""; - height: 14px; - width: 14px; - left: 3px; - bottom: 3px; - background-color: #203C5D; - transition: 0.4s; - border-radius: 0; - box-shadow: 0 0 8px rgba(32, 60, 93, 0.3); -} - -.wplace-theme-neon-light .wplace-switch input:checked + .wplace-slider-round { - background-color: #203C5D; - box-shadow: 0 0 15px rgba(32, 60, 93, 0.4); -} - -.wplace-theme-neon-light .wplace-switch input:checked + .wplace-slider-round:before { - transform: translateX(26px); - background-color: #fff; - box-shadow: 0 0 10px rgba(32, 60, 93, 0.5); -} - -/* Account list container */ -.wplace-theme-neon-light .accounts-list-container { - max-height: 200px; - overflow-y: auto; - margin-top: 10px; - padding: 0 6px; -} - -/* Individual account item */ -.wplace-theme-neon-light .wplace-account-item { - display: flex; - justify-content: space-between; - align-items: center; - padding: 6px 8px; - margin: 4px 0; - background: rgba(32, 60, 93, 0.03); - border-radius: 0; - border: 1px solid #203C5D; - transition: all 0.3s ease; - box-shadow: 0 0 5px rgba(32, 60, 93, 0.1); - position: relative; -} - -/* Account ordering number */ -.wplace-theme-neon-light .wplace-account-number { - position: absolute; - top: 3px; - left: 3px; - width: 8px; - height: 8px; - background: #203C5D; - color: white; - border-radius: 0; - display: flex; - align-items: center; - justify-content: center; - font-size: 8px; - font-weight: bold; - z-index: 1; - border: 1px solid #203C5D; - box-shadow: 0 0 6px rgba(32, 60, 93, 0.4); - font-family: var(--wplace-font); -} - -.wplace-theme-neon-light .wplace-account-item:hover { - background: rgba(32, 60, 93, 0.08); - border-color: #203C5D; - box-shadow: 0 0 12px rgba(32, 60, 93, 0.2); - transform: translateX(2px); -} - -.wplace-theme-neon-light .wplace-account-item.is-current { - background: rgba(32, 60, 93, 0.15); - border-color: #203C5D; - box-shadow: 0 0 20px rgba(32, 60, 93, 0.3); - animation: neon-light-pulse 2s infinite; -} - -@keyframes neon-light-pulse { - 0%, 100% { box-shadow: 0 0 20px rgba(32, 60, 93, 0.3); } - 50% { box-shadow: 0 0 25px rgba(32, 60, 93, 0.4); } -} - -/* Account details */ -.wplace-theme-neon-light .wplace-account-details { - flex: 1; - margin-left: 20px; - min-width: 0; -} - -.wplace-theme-neon-light .wplace-account-name { - font-weight: 600; - color: var(--wplace-text); - font-size: 8px; - margin-bottom: 1px; - text-transform: uppercase; - text-shadow: 0 0 3px var(--wplace-text); - font-family: var(--wplace-font); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -/* Account statistics */ -.wplace-theme-neon-light .wplace-account-stats { - display: flex; - gap: 8px; - align-items: center; - font-size: 8px; - color: rgba(32, 60, 93, 0.8); - text-transform: uppercase; - font-family: var(--wplace-font); - flex-shrink: 0; -} - -.wplace-theme-neon-light .wplace-account-stats i { - margin-right: 4px; - text-shadow: 0 0 3px currentColor; -} - -.wplace-theme-neon-light .wplace-account-stats .fas.fa-bolt { - color: #ff6b35; - text-shadow: 0 0 5px #ff6b35; -} - -.wplace-theme-neon-light .wplace-account-stats .fas.fa-tint { - color: #203C5D; - text-shadow: 0 0 5px #203C5D; -} - -/* Move Artwork Panel */ -.wplace-theme-neon-light .wplace-move-overlay { - position: fixed; - top: 0; - left: 0; - width: 100vw; - height: 100vh; - background: rgba(32, 60, 93, 0.3); - backdrop-filter: blur(4px); - z-index: 10000; - display: flex; - align-items: center; - justify-content: center; - animation: light-fade-in 0.3s ease-out; -} - -.wplace-theme-neon-light .wplace-move-panel { - background: var(--wplace-secondary); - border: 2px solid var(--wplace-accent); - border-radius: var(--wplace-radius); - padding: 20px; - box-shadow: var(--wplace-shadow), - 0 0 25px rgba(234, 156, 0, 0.6), - inset 0 0 15px rgba(234, 156, 0, 0.1); - color: var(--wplace-text); - font-family: var(--wplace-font); - text-align: center; - min-width: 200px; - animation: light-panel-pulse 2s ease-in-out infinite alternate; -} - -.wplace-theme-neon-light .wplace-move-panel h3 { - margin: 0 0 15px 0; - color: var(--wplace-accent); - text-shadow: 0 0 8px var(--wplace-accent); - font-size: 14px; - font-weight: bold; - letter-spacing: 1px; - text-transform: uppercase; -} - -.wplace-theme-neon-light .wplace-move-controls { - display: grid; - grid-template-columns: 1fr 1fr 1fr; - grid-template-rows: 1fr 1fr 1fr; - gap: 8px; - width: 150px; - height: 150px; - margin: 0 auto 15px; -} - -.wplace-theme-neon-light .wplace-move-btn { - background: linear-gradient(135deg, var(--wplace-primary) 0%, var(--wplace-secondary) 100%); - border: 2px solid var(--wplace-accent); - border-radius: var(--wplace-radius); - color: var(--wplace-accent); - font-size: 16px; - font-weight: bold; - cursor: pointer; - transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); - display: flex; - align-items: center; - justify-content: center; - text-shadow: 0 0 5px currentColor; - box-shadow: 0 0 10px rgba(234, 156, 0, 0.3); - font-family: var(--wplace-font); -} - -.wplace-theme-neon-light .wplace-move-btn:hover { - background: linear-gradient(135deg, var(--wplace-accent) 0%, var(--wplace-highlight) 100%); - color: var(--wplace-secondary); - text-shadow: 0 0 10px currentColor; - box-shadow: 0 0 20px rgba(234, 156, 0, 0.7), - inset 0 0 15px rgba(234, 156, 0, 0.2); - transform: scale(1.05); - animation: light-glow 0.5s ease-in-out; -} - -.wplace-theme-neon-light .wplace-move-btn:active { - transform: scale(0.95); - box-shadow: 0 0 15px rgba(234, 156, 0, 0.9), - inset 0 0 20px rgba(234, 156, 0, 0.3); -} - -.wplace-theme-neon-light .wplace-move-btn[data-direction="up"] { - grid-column: 2; - grid-row: 1; -} - -.wplace-theme-neon-light .wplace-move-btn[data-direction="left"] { - grid-column: 1; - grid-row: 2; -} - -.wplace-theme-neon-light .wplace-move-btn[data-direction="right"] { - grid-column: 3; - grid-row: 2; -} - -.wplace-theme-neon-light .wplace-move-btn[data-direction="down"] { - grid-column: 2; - grid-row: 3; -} - -.wplace-theme-neon-light .wplace-move-close { - background: linear-gradient(135deg, var(--wplace-error) 0%, #ff6b6b 100%); - border: 2px solid var(--wplace-error); - border-radius: var(--wplace-radius); - color: var(--wplace-secondary); - padding: 8px 16px; - font-size: 12px; - font-weight: bold; - cursor: pointer; - transition: all 0.2s ease; - text-shadow: 0 0 5px currentColor; - box-shadow: 0 0 10px rgba(255, 7, 58, 0.3); - font-family: var(--wplace-font); - text-transform: uppercase; - letter-spacing: 0.5px; -} - -.wplace-theme-neon-light .wplace-move-close:hover { - background: linear-gradient(135deg, #ff6b6b 0%, var(--wplace-error) 100%); - box-shadow: 0 0 20px rgba(255, 7, 58, 0.7), - inset 0 0 10px rgba(255, 7, 58, 0.2); - transform: scale(1.05); - animation: light-error-pulse 0.5s ease-in-out; -} - -/* Neon Light Theme Animations */ -@keyframes light-fade-in { - from { - opacity: 0; - backdrop-filter: blur(0px); - } - to { - opacity: 1; - backdrop-filter: blur(4px); - } -} - -@keyframes light-panel-pulse { - 0% { - box-shadow: var(--wplace-shadow), - 0 0 25px rgba(234, 156, 0, 0.6), - inset 0 0 15px rgba(234, 156, 0, 0.1); - } - 100% { - box-shadow: var(--wplace-shadow), - 0 0 35px rgba(234, 156, 0, 0.8), - inset 0 0 20px rgba(234, 156, 0, 0.15); - } -} - -@keyframes light-glow { - 0%, 100% { - box-shadow: 0 0 20px rgba(234, 156, 0, 0.7), - inset 0 0 15px rgba(234, 156, 0, 0.2); - } - 50% { - box-shadow: 0 0 30px rgba(234, 156, 0, 0.9), - inset 0 0 25px rgba(234, 156, 0, 0.3); - } -} - -@keyframes light-error-pulse { - 0%, 100% { - box-shadow: 0 0 20px rgba(255, 7, 58, 0.7), - inset 0 0 10px rgba(255, 7, 58, 0.2); - } - 50% { - box-shadow: 0 0 30px rgba(255, 7, 58, 0.9), - inset 0 0 15px rgba(255, 7, 58, 0.3); - } -} +/* WPlace Auto-Image Bot - Neon Light Theme */ +/* Retro cyberpunk aesthetic with light neon glow effects */ + +.wplace-theme-neon-light { + /* Neon theme colors - matching upstream main neon theme */ + --wplace-primary: #E0E0E1; + --wplace-secondary: #FBFBFB; + --wplace-accent: #203C5D; + --wplace-text: #203C5D; + --wplace-highlight: #203C5D; + --wplace-success: #39ff14; + --wplace-error: #ff073a; + --wplace-warning: #ff0; + + /* Neon UI properties */ + --wplace-radius: 0; + --wplace-border-style: solid; + --wplace-border-width: 2px; + --wplace-border-color: #203C5D; + --wplace-shadow: 0 0 20px rgb(234 156 0 / 40%), inset 0 0 20px rgb(234 156 0 / 10%); + --wplace-backdrop: none; + --wplace-font: 'Press Start 2P', monospace, 'Courier New'; + + /* Z-index layers */ + --wplace-z-overlay: 10000; + --wplace-z-alert: 10002; + --wplace-z-settings: 10002; + + /* Feature toggles */ + --wplace-scanline: 1; + --wplace-pixel-blink: 1; + + /* Icon colors - neon variants */ + --wplace-icon-primary: #203C5D; + --wplace-icon-secondary: #39ff14; + --wplace-icon-palette: #203C5D; + + /* Additional UI colors - neon variants */ + --wplace-danger: #ff073a; + --wplace-danger-dark: #cc0531; + --wplace-muted-text: #00ff4180; + --wplace-highlight-secondary: #fa0; + + /* Text variants - neon style */ + --wplace-text-secondary: #3C74AF; + --wplace-text-muted: #00ff41; + --wplace-text-dim: #00ff41; + --wplace-text-faded: #00ff41cc; + + /* Background variants - neon style */ + --wplace-bg-input: rgb(234 156 0 / 15%); + --wplace-bg-subtle: rgb(234 156 0 / 8%); + --wplace-bg-faint: rgb(234 156 0 / 5%); + --wplace-bg-ghost: rgb(234 156 0 / 3%); + --wplace-bg-whisper: rgb(234 156 0 / 2%); + + /* Border variants - neon style */ + --wplace-border-subtle: rgb(234 156 0 / 40%); + --wplace-border-faint: rgb(234 156 0 / 25%); + --wplace-border-ghost: rgb(234 156 0 / 15%); + --wplace-border-ultra-faint: rgb(234 156 0 / 8%); + + /* Shadow variants - neon style */ + --wplace-shadow-drag: 0 12px 40px rgb(234 156 0 / 60%), 0 0 0 2px rgb(234 156 0 / 80%), 0 0 20px rgb(234 156 0 / 30%); + --wplace-shadow-notification: 0 4px 12px rgb(234 156 0 / 40%), 0 0 15px rgb(234 156 0 / 20%); + --wplace-shadow-slider-thumb: 0 3px 6px rgb(234 156 0 / 50%), 0 0 0 2px var(--wplace-icon-primary), 0 0 10px rgb(234 156 0 / 30%); + --wplace-shadow-slider-hover: 0 4px 8px rgb(234 156 0 / 60%), 0 0 0 3px var(--wplace-icon-primary), 0 0 15px rgb(234 156 0 / 40%); + + /* Animation colors - neon style */ + --wplace-pulse-start: rgb(234 156 0 / 80%); + --wplace-pulse-mid: rgb(234 156 0 / 0%); + --wplace-pulse-end: rgb(234 156 0 / 0%); + + /* Slider colors - neon style */ + --wplace-slider-thumb-bg: #203C5D; +} + +/* Neon-specific styling overrides */ +.wplace-theme-neon-light #wplace-image-bot-container { + background: var(--wplace-primary) !important; + border: 2px solid var(--wplace-text) !important; + border-radius: 0 !important; + box-shadow: 0 0 20px var(--wplace-text), inset 0 0 20px rgb(234 156 0 / 10%) !important; + font-family: var(--wplace-font) !important; +} + +.wplace-theme-neon-light #wplace-stats-container { + background: var(--wplace-primary) !important; + border: 2px solid var(--wplace-text) !important; + border-radius: 0 !important; + box-shadow: 0 0 20px var(--wplace-text), inset 0 0 20px rgb(234 156 0 / 10%) !important; + font-family: var(--wplace-font) !important; + position: fixed !important; + overflow: hidden !important; + z-index: 9998 !important; +} + +.wplace-theme-neon-light .wplace-header { + background: var(--wplace-secondary) !important; + border-bottom: 1px solid var(--wplace-text) !important; + color: var(--wplace-text) !important; + text-shadow: 0 0 10px var(--wplace-text) !important; + font-family: var(--wplace-font) !important; +} + +.wplace-theme-neon-light .wplace-section { + background: rgb(22 33 62 / 30%) !important; + border: 1px solid var(--wplace-text) !important; + border-radius: 0 !important; +} + +.wplace-theme-neon-light .wplace-status-section { + background: rgb(22 33 62 / 30%) !important; + border: 1px solid var(--wplace-text) !important; + border-radius: 0 !important; +} + +.wplace-theme-neon-light .wplace-section-title { + color: var(--wplace-text) !important; + text-shadow: 0 0 8px var(--wplace-text) !important; + text-transform: uppercase !important; + font-family: var(--wplace-font) !important; + font-size: 10px !important; +} + +.wplace-theme-neon-light .wplace-btn { + background: var(--wplace-secondary) !important; + border: 1px solid var(--wplace-text) !important; + border-radius: 0 !important; + color: var(--wplace-text) !important; + text-shadow: 0 0 5px var(--wplace-text) !important; + font-family: var(--wplace-font) !important; + font-size: 9px !important; + text-transform: uppercase !important; +} + +/* Pixel blinking and neon glow animation for buttons */ +.wplace-theme-neon-light .wplace-btn:hover:not(:disabled) { + box-shadow: 0 0 15px var(--wplace-text), inset 0 0 15px rgb(234 156 0 / 10%) !important; + animation: pixel-blink 0.5s infinite, neon-glow 1s ease-in-out infinite alternate !important; +} + +.wplace-theme-neon-light .wplace-btn-start { + background: var(--wplace-secondary) !important; + border-color: var(--wplace-success) !important; + color: var(--wplace-success) !important; + text-shadow: 0 0 8px var(--wplace-success) !important; +} + +.wplace-theme-neon-light .wplace-btn-stop { + background: var(--wplace-secondary) !important; + border-color: var(--wplace-error) !important; + color: var(--wplace-error) !important; + text-shadow: 0 0 8px var(--wplace-error) !important; +} + +.wplace-theme-neon-light .wplace-btn-upload { + background: var(--wplace-secondary) !important; + border: 1px dashed var(--wplace-highlight) !important; + color: var(--wplace-highlight) !important; + text-shadow: 0 0 8px var(--wplace-highlight) !important; +} + +.wplace-theme-neon-light .wplace-btn-select { + background: var(--wplace-secondary) !important; + border-color: var(--wplace-highlight) !important; + color: var(--wplace-highlight) !important; + text-shadow: 0 0 8px var(--wplace-highlight) !important; +} + +.wplace-theme-neon-light .wplace-btn-file { + background: var(--wplace-secondary) !important; + border-color: var(--wplace-warning) !important; + color: var(--wplace-warning) !important; + text-shadow: 0 0 8px var(--wplace-warning) !important; +} + +.wplace-theme-neon-light .wplace-progress { + background: rgb(0 0 0 / 80%) !important; + border: 1px solid var(--wplace-text) !important; + border-radius: 0 !important; +} + +.wplace-theme-neon-light .wplace-progress-bar { + background: linear-gradient(90deg, var(--wplace-success) 0%, var(--wplace-text) 100%) !important; + box-shadow: 0 0 10px var(--wplace-success) !important; +} + +.wplace-theme-neon-light .wplace-stat-value { + color: var(--wplace-text) !important; + text-shadow: 0 0 5px var(--wplace-text) !important; +} + +.wplace-theme-neon-light .status-success { + background: rgb(57 255 20 / 10%) !important; + border-color: var(--wplace-success) !important; + color: var(--wplace-success) !important; + box-shadow: 0 0 15px var(--wplace-success) !important; + text-shadow: 0 0 8px var(--wplace-success) !important; +} + +.wplace-theme-neon-light .status-error { + background: rgb(255 7 58 / 10%) !important; + border-color: var(--wplace-error) !important; + color: var(--wplace-error) !important; + box-shadow: 0 0 15px var(--wplace-error) !important; + text-shadow: 0 0 8px var(--wplace-error) !important; +} + +.wplace-theme-neon-light .status-default { + background: rgb(234 156 0 / 10%) !important; + border-color: var(--wplace-text) !important; + color: var(--wplace-text) !important; + text-shadow: 0 0 5px var(--wplace-text) !important; +} + +/* Settings dialog neon styling */ +.wplace-theme-neon-light #wplace-settings-container { + background: var(--wplace-primary) !important; + border: 2px solid var(--wplace-text) !important; + border-radius: 0 !important; + box-shadow: 0 0 30px var(--wplace-text), inset 0 0 30px rgb(234 156 0 / 10%) !important; + font-family: var(--wplace-font) !important; +} + +.wplace-theme-neon-light .wplace-settings-header { + background: var(--wplace-secondary) !important; + border-bottom: 1px solid var(--wplace-text) !important; +} + +.wplace-theme-neon-light .wplace-settings-header h3 { + color: var(--wplace-text) !important; + text-shadow: 0 0 10px var(--wplace-text) !important; + font-family: var(--wplace-font) !important; + font-size: 16px !important; + text-transform: uppercase !important; +} + +.wplace-theme-neon-light .wplace-settings-close-btn { + background: var(--wplace-secondary) !important; + border: 1px solid var(--wplace-text) !important; + border-radius: 0 !important; + color: var(--wplace-text) !important; +} + +.wplace-theme-neon-light .wplace-settings-close-btn:hover { + background: var(--wplace-error) !important; + box-shadow: 0 0 15px var(--wplace-error) !important; +} + +.wplace-theme-neon-light .wplace-settings-section-wrapper { + background: rgb(22 33 62 / 10%) !important; + border: 1px solid var(--wplace-text) !important; + border-radius: 0 !important; +} + +.wplace-theme-neon-light .wplace-settings-select { + background: var(--wplace-secondary) !important; + border: 1px solid var(--wplace-text) !important; + border-radius: 0 !important; + color: var(--wplace-text) !important; + font-family: var(--wplace-font) !important; + font-size: 11px !important; +} + +.wplace-theme-neon-light .wplace-settings-section-label { + color: var(--wplace-text) !important; + text-shadow: 0 0 8px var(--wplace-text) !important; + font-family: var(--wplace-font) !important; + font-size: 12px !important; + text-transform: uppercase !important; +} + +.wplace-theme-neon-light .wplace-speed-value { + background: var(--wplace-secondary) !important; + border: 1px solid var(--wplace-text) !important; + border-radius: 0 !important; + color: var(--wplace-text) !important; + text-shadow: 0 0 8px var(--wplace-text) !important; + font-family: var(--wplace-font) !important; + box-shadow: 0 0 10px rgb(234 156 0 / 30%) !important; +} + +.wplace-theme-neon-light .wplace-overlay-opacity-value { + background: var(--wplace-secondary) !important; + border: 1px solid var(--wplace-text) !important; + border-radius: 0 !important; + color: var(--wplace-text) !important; + text-shadow: 0 0 5px var(--wplace-text) !important; +} + +/* Neon slider styling */ +.wplace-theme-neon-light .wplace-slider, +.wplace-theme-neon-light .wplace-speed-slider, +.wplace-theme-neon-light .wplace-overlay-opacity-slider { + background: var(--wplace-secondary) !important; + border: 1px solid var(--wplace-text) !important; + border-radius: 0 !important; + box-shadow: 0 0 10px rgb(234 156 0 / 30%) !important; +} + +.wplace-theme-neon-light .wplace-slider::-webkit-slider-thumb, +.wplace-theme-neon-light .wplace-speed-slider::-webkit-slider-thumb, +.wplace-theme-neon-light .wplace-overlay-opacity-slider::-webkit-slider-thumb { + -webkit-appearance: none; + width: 16px; + height: 16px; + border-radius: 0 !important; + background: var(--wplace-slider-thumb-bg) !important; + border: 2px solid var(--wplace-text) !important; + cursor: pointer; + transition: all 0.2s ease; + box-shadow: 0 0 8px rgb(234 156 0 / 50%) !important; +} + +.wplace-theme-neon-light .wplace-slider::-webkit-slider-thumb:hover, +.wplace-theme-neon-light .wplace-speed-slider::-webkit-slider-thumb:hover, +.wplace-theme-neon-light .wplace-overlay-opacity-slider::-webkit-slider-thumb:hover { + transform: scale(1.1); + box-shadow: 0 0 15px var(--wplace-text) !important; +} + +/* Scanline animation for neon theme */ +.wplace-theme-neon-light #wplace-image-bot-container::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 2px; + background: linear-gradient(90deg, transparent, var(--wplace-text), transparent); + z-index: 1; + pointer-events: none; + animation: scanline 3s linear infinite; + opacity: 0.7; +} + + +.wplace-theme-neon-light #wplace-stats-container::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 2px; + background: linear-gradient(90deg, transparent, var(--wplace-text), transparent); + z-index: 1; + pointer-events: none; + animation: scanline-stats 4s linear infinite; + opacity: 0.7; +} + + +/* Text glow animations */ +@keyframes neon-glow { + 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; + } +} + +@keyframes pixel-blink { + 0%, 50% { + opacity: 1; + } + + 51%, 100% { + opacity: 0.7; + } +} + +@keyframes scanline { + 0% { + transform: translateY(-100%); + } + + 100% { + transform: translateY(400px); + } +} + +@keyframes scanline-stats { + 0% { + transform: translateY(-100%); + } + + 100% { + transform: translateY(300px); + } +} + +/* Dual control layout styling for neon theme */ +.wplace-theme-neon-light .wplace-dual-control-compact { + display: flex; + flex-direction: row; + align-items: center; + gap: 8px; + margin: 6px 0; + flex-wrap: wrap; +} + +.wplace-theme-neon-light .wplace-slider-container-compact { + display: flex; + align-items: center; + gap: 6px; + flex: 1; + min-width: 160px; +} + +.wplace-theme-neon-light .wplace-speed-slider-container-compact { + display: flex; + align-items: center; + gap: 6px; + flex: 1; + min-width: 160px; +} + +.wplace-theme-neon-light .wplace-input-group-compact { + display: flex; + align-items: center; + gap: 3px; + flex-shrink: 0; +} + +.wplace-theme-neon-light .wplace-input-btn-compact { + background: var(--wplace-secondary) !important; + border: 1px solid var(--wplace-text) !important; + border-radius: 0 !important; + color: var(--wplace-text) !important; + width: 18px; + height: 18px; + cursor: pointer; + font-weight: bold; + font-size: 10px; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.2s ease; + text-shadow: 0 0 5px var(--wplace-text) !important; + font-family: var(--wplace-font) !important; +} + +.wplace-theme-neon-light .wplace-input-btn-compact:hover { + box-shadow: 0 0 10px var(--wplace-text) !important; + animation: pixel-blink 0.5s infinite !important; +} + +.wplace-theme-neon-light .wplace-number-input-compact { + background: var(--wplace-secondary) !important; + border: 1px solid var(--wplace-text) !important; + border-radius: 0 !important; + color: var(--wplace-text) !important; + padding: 2px 4px; + font-size: 10px; + width: 65px; + text-align: center; + transition: all 0.2s ease; + height: 18px; + font-family: var(--wplace-font) !important; + text-shadow: 0 0 5px var(--wplace-text) !important; +} + +.wplace-theme-neon-light .wplace-number-input-compact:focus { + outline: none; + box-shadow: 0 0 10px var(--wplace-text) !important; + background: rgb(234 156 0 / 15%) !important; +} + +.wplace-theme-neon-light .wplace-input-label-compact { + color: var(--wplace-text) !important; + font-size: 9px; + margin-left: 3px; + white-space: nowrap; + text-shadow: 0 0 5px var(--wplace-text) !important; + font-family: var(--wplace-font) !important; + text-transform: uppercase; +} + +.wplace-theme-neon-light .wplace-batch-size-header { + margin-bottom: 6px; +} + +.wplace-theme-neon-light .wplace-batch-size-label { + color: var(--wplace-text) !important; + font-weight: 500; + font-size: 11px; + text-shadow: 0 0 8px var(--wplace-text) !important; + font-family: var(--wplace-font) !important; + text-transform: uppercase; +} + +/* Cooldown control styling for neon theme */ +.wplace-theme-neon-light .wplace-cooldown-control { + margin: 8px 0; +} + +.wplace-theme-neon-light .wplace-cooldown-control label { + color: var(--wplace-text) !important; + font-weight: 500; + font-size: 11px; + text-shadow: 0 0 8px var(--wplace-text) !important; + font-family: var(--wplace-font) !important; + text-transform: uppercase; + margin-bottom: 6px; + display: block; +} + +/* Resize dialog styling for neon theme */ +.wplace-theme-neon-light .resize-container { + background-color: #E0E0E1 !important; /* Solid neon white */ + border: 3px solid #203C5D !important; + border-radius: 0 !important; + backdrop-filter: none !important; /* Remove blur effects */ + box-shadow: none !important; /* Optional: remove glows */ + font-family: 'Press Start 2P', monospace !important; +} + +/* Account switching UI styles */ +.wplace-theme-neon-light .wplace-section { + margin: 15px 0; + border-radius: 0; + background: rgba(32, 60, 93, 0.05); + padding: 15px; + border: 2px solid #203C5D; + box-shadow: 0 0 20px rgba(32, 60, 93, 0.2); + font-family: var(--wplace-font); +} + +.wplace-theme-neon-light .wplace-section-title { + display: flex; + align-items: center; + gap: 8px; + font-weight: 600; + color: var(--wplace-text); + margin-bottom: 10px; + font-size: 12px; + text-transform: uppercase; + text-shadow: 0 0 5px var(--wplace-text); + font-family: var(--wplace-font); +} + +/* Auto-swap toggle switch */ +.wplace-theme-neon-light .wplace-switch { + position: relative; + display: inline-block; + width: 50px; + height: 24px; +} + +.wplace-theme-neon-light .wplace-switch input { + opacity: 0; + width: 0; + height: 0; +} + +.wplace-theme-neon-light .wplace-slider-round { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: #f0f0f0; + transition: 0.4s; + border-radius: 0; + border: 2px solid #ccc; + box-shadow: 0 0 8px rgba(32, 60, 93, 0.15); +} + +.wplace-theme-neon-light .wplace-slider-round:before { + position: absolute; + content: ""; + height: 14px; + width: 14px; + left: 3px; + bottom: 3px; + background-color: #203C5D; + transition: 0.4s; + border-radius: 0; + box-shadow: 0 0 8px rgba(32, 60, 93, 0.3); +} + +.wplace-theme-neon-light .wplace-switch input:checked + .wplace-slider-round { + background-color: #203C5D; + box-shadow: 0 0 15px rgba(32, 60, 93, 0.4); +} + +.wplace-theme-neon-light .wplace-switch input:checked + .wplace-slider-round:before { + transform: translateX(26px); + background-color: #fff; + box-shadow: 0 0 10px rgba(32, 60, 93, 0.5); +} + +/* Account list container */ +.wplace-theme-neon-light .accounts-list-container { + max-height: 200px; + overflow-y: auto; + margin-top: 10px; + padding: 0 6px; +} + +/* Individual account item */ +.wplace-theme-neon-light .wplace-account-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 6px 8px; + margin: 4px 0; + background: rgba(32, 60, 93, 0.03); + border-radius: 0; + border: 1px solid #203C5D; + transition: all 0.3s ease; + box-shadow: 0 0 5px rgba(32, 60, 93, 0.1); + position: relative; +} + +/* Account ordering number */ +.wplace-theme-neon-light .wplace-account-number { + position: absolute; + top: 3px; + left: 3px; + width: 8px; + height: 8px; + background: #203C5D; + color: white; + border-radius: 0; + display: flex; + align-items: center; + justify-content: center; + font-size: 8px; + font-weight: bold; + z-index: 1; + border: 1px solid #203C5D; + box-shadow: 0 0 6px rgba(32, 60, 93, 0.4); + font-family: var(--wplace-font); +} + +.wplace-theme-neon-light .wplace-account-item:hover { + background: rgba(32, 60, 93, 0.08); + border-color: #203C5D; + box-shadow: 0 0 12px rgba(32, 60, 93, 0.2); + transform: translateX(2px); +} + +.wplace-theme-neon-light .wplace-account-item.is-current { + background: rgba(32, 60, 93, 0.15); + border-color: #203C5D; + box-shadow: 0 0 20px rgba(32, 60, 93, 0.3); + animation: neon-light-pulse 2s infinite; +} + +@keyframes neon-light-pulse { + 0%, 100% { box-shadow: 0 0 20px rgba(32, 60, 93, 0.3); } + 50% { box-shadow: 0 0 25px rgba(32, 60, 93, 0.4); } +} + +/* Account details */ +.wplace-theme-neon-light .wplace-account-details { + flex: 1; + margin-left: 20px; + min-width: 0; +} + +.wplace-theme-neon-light .wplace-account-name { + font-weight: 600; + color: var(--wplace-text); + font-size: 8px; + margin-bottom: 1px; + text-transform: uppercase; + text-shadow: 0 0 3px var(--wplace-text); + font-family: var(--wplace-font); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +/* Account statistics */ +.wplace-theme-neon-light .wplace-account-stats { + display: flex; + gap: 8px; + align-items: center; + font-size: 8px; + color: rgba(32, 60, 93, 0.8); + text-transform: uppercase; + font-family: var(--wplace-font); + flex-shrink: 0; +} + +.wplace-theme-neon-light .wplace-account-stats i { + margin-right: 4px; + text-shadow: 0 0 3px currentColor; +} + +.wplace-theme-neon-light .wplace-account-stats .fas.fa-bolt { + color: #ff6b35; + text-shadow: 0 0 5px #ff6b35; +} + +.wplace-theme-neon-light .wplace-account-stats .fas.fa-tint { + color: #203C5D; + text-shadow: 0 0 5px #203C5D; +} + +/* Move Artwork Panel */ +.wplace-theme-neon-light .wplace-move-overlay { + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + background: rgba(32, 60, 93, 0.3); + backdrop-filter: blur(4px); + z-index: 10000; + display: flex; + align-items: center; + justify-content: center; + animation: light-fade-in 0.3s ease-out; +} + +.wplace-theme-neon-light .wplace-move-panel { + background: var(--wplace-secondary); + border: 2px solid var(--wplace-accent); + border-radius: var(--wplace-radius); + padding: 20px; + box-shadow: var(--wplace-shadow), + 0 0 25px rgba(234, 156, 0, 0.6), + inset 0 0 15px rgba(234, 156, 0, 0.1); + color: var(--wplace-text); + font-family: var(--wplace-font); + text-align: center; + min-width: 200px; + animation: light-panel-pulse 2s ease-in-out infinite alternate; +} + +.wplace-theme-neon-light .wplace-move-panel h3 { + margin: 0 0 15px 0; + color: var(--wplace-accent); + text-shadow: 0 0 8px var(--wplace-accent); + font-size: 14px; + font-weight: bold; + letter-spacing: 1px; + text-transform: uppercase; +} + +.wplace-theme-neon-light .wplace-move-controls { + display: grid; + grid-template-columns: 1fr 1fr 1fr; + grid-template-rows: 1fr 1fr 1fr; + gap: 8px; + width: 150px; + height: 150px; + margin: 0 auto 15px; +} + +.wplace-theme-neon-light .wplace-move-btn { + background: linear-gradient(135deg, var(--wplace-primary) 0%, var(--wplace-secondary) 100%); + border: 2px solid var(--wplace-accent); + border-radius: var(--wplace-radius); + color: var(--wplace-accent); + font-size: 16px; + font-weight: bold; + cursor: pointer; + transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); + display: flex; + align-items: center; + justify-content: center; + text-shadow: 0 0 5px currentColor; + box-shadow: 0 0 10px rgba(234, 156, 0, 0.3); + font-family: var(--wplace-font); +} + +.wplace-theme-neon-light .wplace-move-btn:hover { + background: linear-gradient(135deg, var(--wplace-accent) 0%, var(--wplace-highlight) 100%); + color: var(--wplace-secondary); + text-shadow: 0 0 10px currentColor; + box-shadow: 0 0 20px rgba(234, 156, 0, 0.7), + inset 0 0 15px rgba(234, 156, 0, 0.2); + transform: scale(1.05); + animation: light-glow 0.5s ease-in-out; +} + +.wplace-theme-neon-light .wplace-move-btn:active { + transform: scale(0.95); + box-shadow: 0 0 15px rgba(234, 156, 0, 0.9), + inset 0 0 20px rgba(234, 156, 0, 0.3); +} + +.wplace-theme-neon-light .wplace-move-btn[data-direction="up"] { + grid-column: 2; + grid-row: 1; +} + +.wplace-theme-neon-light .wplace-move-btn[data-direction="left"] { + grid-column: 1; + grid-row: 2; +} + +.wplace-theme-neon-light .wplace-move-btn[data-direction="right"] { + grid-column: 3; + grid-row: 2; +} + +.wplace-theme-neon-light .wplace-move-btn[data-direction="down"] { + grid-column: 2; + grid-row: 3; +} + +.wplace-theme-neon-light .wplace-move-close { + background: linear-gradient(135deg, var(--wplace-error) 0%, #ff6b6b 100%); + border: 2px solid var(--wplace-error); + border-radius: var(--wplace-radius); + color: var(--wplace-secondary); + padding: 8px 16px; + font-size: 12px; + font-weight: bold; + cursor: pointer; + transition: all 0.2s ease; + text-shadow: 0 0 5px currentColor; + box-shadow: 0 0 10px rgba(255, 7, 58, 0.3); + font-family: var(--wplace-font); + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.wplace-theme-neon-light .wplace-move-close:hover { + background: linear-gradient(135deg, #ff6b6b 0%, var(--wplace-error) 100%); + box-shadow: 0 0 20px rgba(255, 7, 58, 0.7), + inset 0 0 10px rgba(255, 7, 58, 0.2); + transform: scale(1.05); + animation: light-error-pulse 0.5s ease-in-out; +} + +/* Neon Light Theme Animations */ +@keyframes light-fade-in { + from { + opacity: 0; + backdrop-filter: blur(0px); + } + to { + opacity: 1; + backdrop-filter: blur(4px); + } +} + +@keyframes light-panel-pulse { + 0% { + box-shadow: var(--wplace-shadow), + 0 0 25px rgba(234, 156, 0, 0.6), + inset 0 0 15px rgba(234, 156, 0, 0.1); + } + 100% { + box-shadow: var(--wplace-shadow), + 0 0 35px rgba(234, 156, 0, 0.8), + inset 0 0 20px rgba(234, 156, 0, 0.15); + } +} + +@keyframes light-glow { + 0%, 100% { + box-shadow: 0 0 20px rgba(234, 156, 0, 0.7), + inset 0 0 15px rgba(234, 156, 0, 0.2); + } + 50% { + box-shadow: 0 0 30px rgba(234, 156, 0, 0.9), + inset 0 0 25px rgba(234, 156, 0, 0.3); + } +} + +@keyframes light-error-pulse { + 0%, 100% { + box-shadow: 0 0 20px rgba(255, 7, 58, 0.7), + inset 0 0 10px rgba(255, 7, 58, 0.2); + } + 50% { + box-shadow: 0 0 30px rgba(255, 7, 58, 0.9), + inset 0 0 15px rgba(255, 7, 58, 0.3); + } +}