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 = `
-
-
-
-
- PIXEL ART ATTACKED!
-
-
- Detected ${count} damaged pixels!
-
- `;
- } else if (type === 'repaired') {
- notification.innerHTML = `
-
-
-
-
- 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 = `
-
-
-
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 = `
-
-
-
-
-
- ${Utils.t('autonomousModeActive')}
-
-
- Charges: 0/1 (cooldown: 31s)
-
-
- Token: Initializing...
-
-
- System: AUTONOMOUS | Token: GENERATOR | Auto-refresh: ON
-
-
- Auto-Batch: ON | Size: 5 | Opt: ON
-
-
-
-
-
- š ${Utils.t('loadFromFile')}
-
-
-
- š§ ${Utils.t('repairPixels')}
-
-
-
- šÆ Force Token
-
-
-
-
-
-
-
- ${Utils.t('enableAutoRepair')} (Enhanced)
-
-
-
-
- ${Utils.t('repairInterval')}:
-
- seconds
-
-
-
-
-
- Auto Token Refresh
-
-
- Generator
- Hybrid
- Manual
-
-
-
-
-
-
-
-
-
-
-
-
-
Batch Size:
-
- -
-
- +
-
-
pixels
-
-
-
-
- š§ Repair Transparent Pixels
-
-
-
-
-
-
Image: Not loaded
-
Position: Not set
-
Colors: 0 available
-
Batch Mode: Auto (${state.currentBatchSize} pixels)
-
-
-
-
-
-
- š ${Utils.t('debug')} (Enhanced System)
-
-
- ${Utils.t('clearDebug')}
-
-
-
-
-
[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 = `
+
+
+
+
+ PIXEL ART ATTACKED!
+
+
+ Detected ${count} damaged pixels!
+
+ `;
+ } else if (type === 'repaired') {
+ notification.innerHTML = `
+
+
+
+
+ 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 = `
+
+
+
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 = `
+
+
+
+
+
+ ${Utils.t('autonomousModeActive')}
+
+
+ Charges: 0/1 (cooldown: 31s)
+
+
+ Token: Initializing...
+
+
+ System: AUTONOMOUS | Token: GENERATOR | Auto-refresh: ON
+
+
+ Auto-Batch: ON | Size: 5 | Opt: ON
+
+
+
+
+
+ š ${Utils.t('loadFromFile')}
+
+
+
+ š§ ${Utils.t('repairPixels')}
+
+
+
+ šÆ Force Token
+
+
+
+
+
+
+
+ ${Utils.t('enableAutoRepair')} (Enhanced)
+
+
+
+
+ ${Utils.t('repairInterval')}:
+
+ seconds
+
+
+
+
+
+ Auto Token Refresh
+
+
+ Generator
+ Hybrid
+ Manual
+
+
+
+
+
+
+
+
+
+
+
+
+
Batch Size:
+
+ -
+
+ +
+
+
pixels
+
+
+
+
+ š§ Repair Transparent Pixels
+
+
+
+
+
+
Image: Not loaded
+
Position: Not set
+
Colors: 0 available
+
Batch Mode: Auto (${state.currentBatchSize} pixels)
+
+
+
+
+
+
+ š ${Utils.t('debug')} (Enhanced System)
+
+
+ ${Utils.t('clearDebug')}
+
+
+
+
+
[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);
+ }
+}