From 498e0176797265f8e598aa3679a7bf5942fd97a8 Mon Sep 17 00:00:00 2001
From: Staninna <26458805+Staninna@users.noreply.github.com>
Date: Mon, 25 Aug 2025 20:11:39 +0200
Subject: [PATCH 1/7] idk if this gonna fuck me over later but i started
`decoupled-css` on main and this is currently upstream main
---
Auto-Image.js | 2027 ++++++++++++++++++++++++++++++++++---------------
1 file changed, 1396 insertions(+), 631 deletions(-)
diff --git a/Auto-Image.js b/Auto-Image.js
index 49a8b95..6fae8ba 100644
--- a/Auto-Image.js
+++ b/Auto-Image.js
@@ -21,10 +21,10 @@
COOLDOWN_CHARGE_THRESHOLD: 1, // Default wait threshold
// Desktop Notifications (defaults)
NOTIFICATIONS: {
- ENABLED: true,
- ON_CHARGES_REACHED: true,
- ONLY_WHEN_UNFOCUSED: true,
- REPEAT_MINUTES: 5, // repeat reminder while threshold condition holds
+ ENABLED: true,
+ ON_CHARGES_REACHED: true,
+ ONLY_WHEN_UNFOCUSED: true,
+ REPEAT_MINUTES: 5, // repeat reminder while threshold condition holds
},
OVERLAY: {
OPACITY_DEFAULT: 0.6,
@@ -1072,28 +1072,28 @@
initialSetupComplete: false, // Track if initial startup setup is complete (only happens once)
overlayOpacity: CONFIG.OVERLAY.OPACITY_DEFAULT,
blueMarbleEnabled: CONFIG.OVERLAY.BLUE_MARBLE_DEFAULT,
- ditheringEnabled: true,
- // Advanced color matching settings
- colorMatchingAlgorithm: 'lab',
- enableChromaPenalty: true,
- chromaPenaltyWeight: 0.15,
- customTransparencyThreshold: CONFIG.TRANSPARENCY_THRESHOLD,
- customWhiteThreshold: CONFIG.WHITE_THRESHOLD,
- resizeSettings: null,
- originalImage: null,
- resizeIgnoreMask: null,
- // Notification prefs and runtime bookkeeping
- notificationsEnabled: CONFIG.NOTIFICATIONS.ENABLED,
- notifyOnChargesReached: CONFIG.NOTIFICATIONS.ON_CHARGES_REACHED,
- notifyOnlyWhenUnfocused: CONFIG.NOTIFICATIONS.ONLY_WHEN_UNFOCUSED,
- notificationIntervalMinutes: CONFIG.NOTIFICATIONS.REPEAT_MINUTES,
- _lastChargesNotifyAt: 0,
- _lastChargesBelow: true,
- // Smart save tracking
- _lastSavePixelCount: 0,
- _lastSaveTime: 0,
- _saveInProgress: false,
- paintedMap: null,
+ ditheringEnabled: true,
+ // Advanced color matching settings
+ colorMatchingAlgorithm: 'lab',
+ enableChromaPenalty: true,
+ chromaPenaltyWeight: 0.15,
+ customTransparencyThreshold: CONFIG.TRANSPARENCY_THRESHOLD,
+ customWhiteThreshold: CONFIG.WHITE_THRESHOLD,
+ resizeSettings: null,
+ originalImage: null,
+ resizeIgnoreMask: null,
+ // Notification prefs and runtime bookkeeping
+ notificationsEnabled: CONFIG.NOTIFICATIONS.ENABLED,
+ notifyOnChargesReached: CONFIG.NOTIFICATIONS.ON_CHARGES_REACHED,
+ notifyOnlyWhenUnfocused: CONFIG.NOTIFICATIONS.ONLY_WHEN_UNFOCUSED,
+ notificationIntervalMinutes: CONFIG.NOTIFICATIONS.REPEAT_MINUTES,
+ _lastChargesNotifyAt: 0,
+ _lastChargesBelow: true,
+ // Smart save tracking
+ _lastSavePixelCount: 0,
+ _lastSaveTime: 0,
+ _saveInProgress: false,
+ paintedMap: null,
}
let _updateResizePreview = () => { };
@@ -1126,8 +1126,8 @@
this.disable();
this.imageBitmap = null;
this.chunkedTiles.clear();
- this.originalTiles.clear();
- this.originalTilesData.clear();
+ this.originalTiles.clear();
+ this.originalTilesData.clear();
this.lastProcessedHash = null;
if (this.processPromise) {
this.processPromise = null;
@@ -1194,7 +1194,7 @@
async _doProcessImageIntoChunks() {
const startTime = performance.now();
this.chunkedTiles.clear();
-
+
const { width: imageWidth, height: imageHeight } = this.imageBitmap;
const { x: startPixelX, y: startPixelY } = this.startCoords.pixel;
const { x: startRegionX, y: startRegionY } = this.startCoords.region;
@@ -1223,7 +1223,7 @@
// Process tiles in batches with yielding
for (let i = 0; i < tilesToProcess.length; i += batchSize) {
const batch = tilesToProcess.slice(i, i + batchSize);
-
+
await Promise.all(batch.map(async ({ tx, ty }) => {
const tileKey = `${tx},${ty}`;
const chunkBitmap = await this._processTile(tx, ty, imageWidth, imageHeight, startPixelX, startPixelY, startRegionX, startRegionY);
@@ -1271,18 +1271,18 @@
if (state.blueMarbleEnabled) {
const imageData = chunkCtx.getImageData(dX, dY, sW, sH);
const data = imageData.data;
-
+
// Faster pixel manipulation using typed arrays
for (let i = 0; i < data.length; i += 4) {
const pixelIndex = i / 4;
const pixelY = Math.floor(pixelIndex / sW);
const pixelX = pixelIndex % sW;
-
+
if ((pixelX + pixelY) % 2 === 0 && data[i + 3] > 0) {
data[i + 3] = 0; // Set alpha to 0
}
}
-
+
chunkCtx.putImageData(imageData, dX, dY);
}
@@ -1411,7 +1411,7 @@
const originalBitmap = await createImageBitmap(originalBlob);
const canvas = new OffscreenCanvas(originalBitmap.width, originalBitmap.height);
const ctx = canvas.getContext('2d');
-
+
// Disable antialiasing for pixel-perfect rendering
ctx.imageSmoothingEnabled = false;
@@ -1424,7 +1424,7 @@
ctx.drawImage(overlayBitmap, 0, 0);
// Use faster blob conversion with compression settings
- return await canvas.convertToBlob({
+ return await canvas.convertToBlob({
type: 'image/png',
quality: 0.95 // Slight compression for faster processing
});
@@ -1481,7 +1481,7 @@
}
tokenGenerationInProgress = true;
-
+
try {
console.log("๐ Token expired or missing, generating new one...");
const token = await handleCaptchaWithRetry();
@@ -1659,7 +1659,7 @@
this.turnstileLoaded = true;
return Promise.resolve();
}
-
+
return new Promise((resolve, reject) => {
// Avoid adding the script twice
if (document.querySelector('script[src^="https://challenges.cloudflare.com/turnstile/v0/api.js"]')) {
@@ -1673,7 +1673,7 @@
};
return checkReady();
}
-
+
const script = document.createElement('script');
script.src = 'https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit';
script.async = true;
@@ -1698,7 +1698,7 @@
if (this._turnstileContainer) {
this._turnstileContainer.remove();
}
-
+
this._turnstileContainer = document.createElement('div');
this._turnstileContainer.style.cssText = `
position: fixed !important;
@@ -1802,24 +1802,24 @@
try {
// Force cleanup of any existing widget
if (this._turnstileWidgetId && window.turnstile?.remove) {
- try {
- window.turnstile.remove(this._turnstileWidgetId);
+ 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,
@@ -1834,10 +1834,10 @@
'error-callback': () => resolve(null),
'timeout-callback': () => resolve(null)
});
-
+
this._turnstileWidgetId = widgetId;
this._lastSitekey = sitekey;
-
+
if (!widgetId) {
return resolve(null);
}
@@ -1857,31 +1857,31 @@
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);
+ try {
+ window.turnstile.remove(this._turnstileWidgetId);
} catch (e) {
console.warn('โ ๏ธ Widget cleanup warning:', e.message);
}
}
-
+
const overlay = this.ensureTurnstileOverlayContainer();
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.style.display = 'none';
resolve(null);
}, 60000); // 60 seconds for user interaction
-
+
const widgetId = window.turnstile.render(host, {
sitekey,
action,
@@ -1891,7 +1891,7 @@
clearTimeout(timeout);
overlay.style.display = 'none';
console.log('โ
Interactive Turnstile completed successfully');
-
+
if (typeof token === 'string' && token.length > 20) {
resolve(token);
} else {
@@ -1906,10 +1906,10 @@
resolve(null);
},
});
-
+
this._turnstileWidgetId = widgetId;
this._lastSitekey = sitekey;
-
+
if (!widgetId) {
clearTimeout(timeout);
overlay.style.display = 'none';
@@ -1938,15 +1938,15 @@
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;
@@ -2024,11 +2024,11 @@
this._cachedSitekey = testSitekey;
return testSitekey;
}
-
+
} catch (error) {
console.warn('Error detecting sitekey:', error);
}
-
+
console.log("๐ Using fallback sitekey:", fallback);
this._cachedSitekey = fallback;
return fallback;
@@ -2168,7 +2168,7 @@
// Use provided palette or derive from COLOR_MAP
if (!palette || palette.length === 0) {
palette = Object.values(CONFIG.COLOR_MAP)
- .filter(c => c.rgb)
+ .filter(c => c.rgb)
.map(c => [c.rgb.r, c.rgb.g, c.rgb.b]);
}
if (state.colorMatchingAlgorithm === 'legacy') {
@@ -2222,17 +2222,17 @@
},
createImageUploader: () =>
- new Promise((resolve) => {
- const input = document.createElement("input")
- input.type = "file"
- input.accept = "image/png,image/jpeg"
- input.onchange = () => {
- const fr = new FileReader()
- fr.onload = () => resolve(fr.result)
- fr.readAsDataURL(input.files[0])
- }
- input.click()
- }),
+ new Promise((resolve) => {
+ const input = document.createElement("input")
+ input.type = "file"
+ input.accept = "image/png,image/jpeg"
+ input.onchange = () => {
+ const fr = new FileReader()
+ fr.onload = () => resolve(fr.result)
+ fr.readAsDataURL(input.files[0])
+ }
+ input.click()
+ }),
createFileDownloader: (data, filename) => {
const blob = new Blob([data], { type: "application/json" })
@@ -2247,30 +2247,30 @@
},
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"))
+ 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"))
}
- reader.onerror = () => reject(new Error("File reading error"))
- reader.readAsText(file)
- } else {
- reject(new Error("No file selected"))
}
- }
- input.click()
- }),
+ input.click()
+ }),
extractAvailableColors: () => {
const colorElements = document.querySelectorAll('[id^="color-"]')
@@ -2363,8 +2363,8 @@
markPixelPainted: (x, y, regionX = 0, regionY = 0) => {
const actualX = x + regionX;
const actualY = y + regionY;
-
- if (state.paintedMap && state.paintedMap[actualY] &&
+
+ if (state.paintedMap && state.paintedMap[actualY] &&
actualX >= 0 && actualX < state.paintedMap[actualY].length) {
state.paintedMap[actualY][actualX] = true;
}
@@ -2373,8 +2373,8 @@
isPixelPainted: (x, y, regionX = 0, regionY = 0) => {
const actualX = x + regionX;
const actualY = y + regionY;
-
- if (state.paintedMap && state.paintedMap[actualY] &&
+
+ if (state.paintedMap && state.paintedMap[actualY] &&
actualX >= 0 && actualX < state.paintedMap[actualY].length) {
return state.paintedMap[actualY][actualX];
}
@@ -2386,28 +2386,28 @@
const now = Date.now();
const pixelsSinceLastSave = state.paintedPixels - state._lastSavePixelCount;
const timeSinceLastSave = now - state._lastSaveTime;
-
+
// Save conditions:
// 1. Every 25 pixels (reduced from 50 for more frequent saves)
// 2. At least 30 seconds since last save (prevent spam)
// 3. Not already saving
- return !state._saveInProgress &&
- pixelsSinceLastSave >= 25 &&
- timeSinceLastSave >= 30000;
+ return !state._saveInProgress &&
+ pixelsSinceLastSave >= 25 &&
+ timeSinceLastSave >= 30000;
},
performSmartSave: () => {
if (!Utils.shouldAutoSave()) return false;
-
+
state._saveInProgress = true;
const success = Utils.saveProgress();
-
+
if (success) {
state._lastSavePixelCount = state.paintedPixels;
state._lastSaveTime = Date.now();
console.log(`๐พ Auto-saved at ${state.paintedPixels} pixels`);
}
-
+
state._saveInProgress = false;
return success;
},
@@ -2461,7 +2461,7 @@
migrateProgressToV2: (saved) => {
if (!saved) return saved;
const isV1 = !saved.version || saved.version === '1' || saved.version === '1.0' || saved.version === '1.1';
- if (!isV1) return saved;
+ if (!isV1) return saved;
try {
const migrated = { ...saved };
@@ -2506,7 +2506,7 @@
}
},
- saveProgress: () => {
+ saveProgress: () => {
try {
// Pack painted map if available
let paintedMapPacked = null;
@@ -2535,13 +2535,13 @@
availableColors: state.availableColors,
},
imageData: state.imageData
- ? {
- width: state.imageData.width,
- height: state.imageData.height,
- pixels: Array.from(state.imageData.pixels),
- totalPixels: state.imageData.totalPixels,
- }
- : null,
+ ? {
+ width: state.imageData.width,
+ height: state.imageData.height,
+ pixels: Array.from(state.imageData.pixels),
+ totalPixels: state.imageData.totalPixels,
+ }
+ : null,
paintedMapPacked: paintedMapPacked,
}
@@ -2611,7 +2611,7 @@
const imageData = new ImageData(state.imageData.pixels, state.imageData.width, state.imageData.height);
ctx.putImageData(imageData, 0, 0);
const proc = new ImageProcessor('');
- proc.img = canvas;
+ proc.img = canvas;
proc.canvas = canvas;
proc.ctx = ctx;
state.imageData.processor = proc;
@@ -2635,7 +2635,7 @@
}
},
- saveProgressToFile: () => {
+ saveProgressToFile: () => {
try {
// Pack painted map if available
let paintedMapPacked = null;
@@ -2664,13 +2664,13 @@
availableColors: state.availableColors,
},
imageData: state.imageData
- ? {
- width: state.imageData.width,
- height: state.imageData.height,
- pixels: Array.from(state.imageData.pixels),
- totalPixels: state.imageData.totalPixels,
- }
- : null,
+ ? {
+ width: state.imageData.width,
+ height: state.imageData.height,
+ pixels: Array.from(state.imageData.pixels),
+ totalPixels: state.imageData.totalPixels,
+ }
+ : null,
paintedMapPacked: paintedMapPacked,
}
@@ -2683,7 +2683,7 @@
}
},
- loadProgressFromFile: async () => {
+ loadProgressFromFile: async () => {
try {
const data = await Utils.createFileUploader()
if (!data || !data.state) {
@@ -2697,7 +2697,7 @@
} else {
migrated = Utils.migrateProgressToV21(data) || data;
}
- const success = Utils.restoreProgress(migrated)
+ const success = Utils.restoreProgress(migrated)
return success
} catch (error) {
console.error("Error loading from file:", error)
@@ -2714,9 +2714,9 @@
try {
// Recreate ImageBitmap from loaded pixel data
const imageData = new ImageData(
- state.imageData.pixels,
- state.imageData.width,
- state.imageData.height
+ state.imageData.pixels,
+ state.imageData.width,
+ state.imageData.height
);
const canvas = new OffscreenCanvas(state.imageData.width, state.imageData.height);
@@ -2865,96 +2865,96 @@
// Desktop Notification Manager
const NotificationManager = {
- pollTimer: null,
- pollIntervalMs: 60_000,
- icon() {
- const link = document.querySelector("link[rel~='icon']");
- return link?.href || (location.origin + "/favicon.ico");
- },
- async requestPermission() {
- if (!("Notification" in window)) {
- Utils.showAlert("Notifications are not supported in this browser.", "warning");
- return "denied";
- }
- if (Notification.permission === "granted") return "granted";
- try {
- const perm = await Notification.requestPermission();
- return perm;
- } catch {
- return Notification.permission;
- }
- },
- canNotify() {
- return state.notificationsEnabled &&
- typeof Notification !== "undefined" &&
- Notification.permission === "granted";
- },
- notify(title, body, tag = "wplace-charges", force = false) {
- if (!this.canNotify()) return false;
- if (!force && state.notifyOnlyWhenUnfocused && document.hasFocus()) return false;
- try {
- new Notification(title, {
- body,
- tag,
- renotify: true,
- icon: this.icon(),
- badge: this.icon(),
- silent: false,
- });
- return true;
- } catch {
- // Graceful fallback
- Utils.showAlert(body, "info");
- return false;
- }
- },
- resetEdgeTracking() {
- state._lastChargesBelow = state.currentCharges < state.cooldownChargeThreshold;
- state._lastChargesNotifyAt = 0;
- },
- maybeNotifyChargesReached(force = false) {
- if (!state.notificationsEnabled || !state.notifyOnChargesReached) return;
- const reached = state.currentCharges >= state.cooldownChargeThreshold;
- const now = Date.now();
- const repeatMs = Math.max(1, Number(state.notificationIntervalMinutes || 5)) * 60_000;
- if (reached) {
- const shouldEdge = state._lastChargesBelow || force;
- const shouldRepeat = now - (state._lastChargesNotifyAt || 0) >= repeatMs;
- if (shouldEdge || shouldRepeat) {
- const msg = `Charges ready: ${Math.floor(state.currentCharges)} / ${state.maxCharges}. Threshold: ${state.cooldownChargeThreshold}.`;
- this.notify("WPlace โ Charges Ready", msg, "wplace-notify-charges");
- state._lastChargesNotifyAt = now;
- }
- state._lastChargesBelow = false;
- } else {
- state._lastChargesBelow = true;
- }
- },
- startPolling() {
- this.stopPolling();
- if (!state.notificationsEnabled || !state.notifyOnChargesReached) return;
- // lightweight background polling
- this.pollTimer = setInterval(async () => {
- try {
- const { charges, cooldown, max } = await WPlaceService.getCharges();
- state.currentCharges = Math.floor(charges);
- state.cooldown = cooldown;
- state.maxCharges = Math.max(1, Math.floor(max));
- this.maybeNotifyChargesReached();
- } catch { /* ignore */ }
- }, this.pollIntervalMs);
- },
- stopPolling() {
- if (this.pollTimer) {
- clearInterval(this.pollTimer);
- this.pollTimer = null;
- }
- },
- syncFromState() {
- this.resetEdgeTracking();
- if (state.notificationsEnabled && state.notifyOnChargesReached) this.startPolling();
- else this.stopPolling();
- },
+ pollTimer: null,
+ pollIntervalMs: 60_000,
+ icon() {
+ const link = document.querySelector("link[rel~='icon']");
+ return link?.href || (location.origin + "/favicon.ico");
+ },
+ async requestPermission() {
+ if (!("Notification" in window)) {
+ Utils.showAlert("Notifications are not supported in this browser.", "warning");
+ return "denied";
+ }
+ if (Notification.permission === "granted") return "granted";
+ try {
+ const perm = await Notification.requestPermission();
+ return perm;
+ } catch {
+ return Notification.permission;
+ }
+ },
+ canNotify() {
+ return state.notificationsEnabled &&
+ typeof Notification !== "undefined" &&
+ Notification.permission === "granted";
+ },
+ notify(title, body, tag = "wplace-charges", force = false) {
+ if (!this.canNotify()) return false;
+ if (!force && state.notifyOnlyWhenUnfocused && document.hasFocus()) return false;
+ try {
+ new Notification(title, {
+ body,
+ tag,
+ renotify: true,
+ icon: this.icon(),
+ badge: this.icon(),
+ silent: false,
+ });
+ return true;
+ } catch {
+ // Graceful fallback
+ Utils.showAlert(body, "info");
+ return false;
+ }
+ },
+ resetEdgeTracking() {
+ state._lastChargesBelow = state.currentCharges < state.cooldownChargeThreshold;
+ state._lastChargesNotifyAt = 0;
+ },
+ maybeNotifyChargesReached(force = false) {
+ if (!state.notificationsEnabled || !state.notifyOnChargesReached) return;
+ const reached = state.currentCharges >= state.cooldownChargeThreshold;
+ const now = Date.now();
+ const repeatMs = Math.max(1, Number(state.notificationIntervalMinutes || 5)) * 60_000;
+ if (reached) {
+ const shouldEdge = state._lastChargesBelow || force;
+ const shouldRepeat = now - (state._lastChargesNotifyAt || 0) >= repeatMs;
+ if (shouldEdge || shouldRepeat) {
+ const msg = `Charges ready: ${Math.floor(state.currentCharges)} / ${state.maxCharges}. Threshold: ${state.cooldownChargeThreshold}.`;
+ this.notify("WPlace โ Charges Ready", msg, "wplace-notify-charges");
+ state._lastChargesNotifyAt = now;
+ }
+ state._lastChargesBelow = false;
+ } else {
+ state._lastChargesBelow = true;
+ }
+ },
+ startPolling() {
+ this.stopPolling();
+ if (!state.notificationsEnabled || !state.notifyOnChargesReached) return;
+ // lightweight background polling
+ this.pollTimer = setInterval(async () => {
+ try {
+ const { charges, cooldown, max } = await WPlaceService.getCharges();
+ state.currentCharges = Math.floor(charges);
+ state.cooldown = cooldown;
+ state.maxCharges = Math.max(1, Math.floor(max));
+ this.maybeNotifyChargesReached();
+ } catch { /* ignore */ }
+ }, this.pollIntervalMs);
+ },
+ stopPolling() {
+ if (this.pollTimer) {
+ clearInterval(this.pollTimer);
+ this.pollTimer = null;
+ }
+ },
+ syncFromState() {
+ this.resetEdgeTracking();
+ if (state.notificationsEnabled && state.notifyOnChargesReached) this.startPolling();
+ else this.stopPolling();
+ },
};
// COLOR MATCHING FUNCTION - Optimized with caching
@@ -3078,7 +3078,7 @@
// Check if this color is available in the captured colors
const isAvailable = state.availableColors.some(c =>
- c.rgb[0] === rgb.r && c.rgb[1] === rgb.g && c.rgb[2] === rgb.b
+ c.rgb[0] === rgb.r && c.rgb[1] === rgb.g && c.rgb[2] === rgb.b
);
// If not showing all colors and this color is not available, skip it
@@ -3143,29 +3143,29 @@
}
async function handleCaptcha() {
const startTime = performance.now();
-
+
// Check user's token source preference
if (state.tokenSource === "manual") {
console.log("๐ฏ Manual token source selected - using pixel placement automation");
return await handleCaptchaFallback();
}
-
+
// Generator mode (pure) or Hybrid mode - try generator first
try {
// Use optimized token generation with automatic sitekey detection
const sitekey = Utils.detectSitekey();
console.log("๐ Generating Turnstile token for sitekey:", sitekey);
console.log('๐งญ UA:', navigator.userAgent.substring(0, 50) + '...', 'Platform:', navigator.platform);
-
+
// Add additional checks before token generation
if (!window.turnstile) {
await Utils.loadTurnstile();
}
-
+
const token = await Utils.generatePaintToken(sitekey);
-
+
console.log(`๐ Token received - Type: ${typeof token}, Value: ${token ? (typeof token === 'string' ? (token.length > 50 ? token.substring(0, 50) + '...' : token) : JSON.stringify(token)) : 'null/undefined'}, Length: ${token?.length || 0}`);
-
+
if (typeof token === 'string' && token.length > 20) {
const duration = Math.round(performance.now() - startTime);
console.log(`โ
Turnstile token generated successfully in ${duration}ms`);
@@ -3176,7 +3176,7 @@
} catch (error) {
const duration = Math.round(performance.now() - startTime);
console.error(`โ Turnstile token generation failed after ${duration}ms:`, error);
-
+
// Fallback to manual pixel placement for hybrid mode
if (state.tokenSource === "hybrid") {
console.log("๐ Hybrid mode: Generator failed, automatically switching to manual pixel placement...");
@@ -3292,222 +3292,406 @@
document.head.appendChild(googleFonts)
}
- // Link external CSS file
- const cssLink = document.createElement('link');
- cssLink.rel = 'stylesheet';
- cssLink.href = 'https://staninna.github.io/WPlace-AutoBOT/auto-image-styles.css';
- cssLink.setAttribute('data-wplace-theme', 'true');
- document.head.appendChild(cssLink);
-
- // Apply theme-specific dynamic styles
const style = document.createElement("style")
- style.setAttribute("data-wplace-theme-dynamic", "true")
+ style.setAttribute("data-wplace-theme", "true")
style.textContent = `
- /* Dynamic theme-specific styles */
- ${theme.animations.glow ? `
- .wplace-header { animation: neonGlow 2s ease-in-out infinite alternate; }
- .fas, .fa { filter: drop-shadow(0 0 3px currentColor); }
- ` : ""}
-
- ${theme.animations.pixelBlink ? `
- .wplace-btn:hover:not(:disabled) { animation: pixelBlink 0.5s infinite; }
- .status-error { animation: pixelBlink 0.5s infinite; }
- .wplace-progress-bar::after { animation: pixelBlink 1s infinite; }
- ` : ""}
-
- ${theme.animations.scanline ? `
- #wplace-image-bot-container::before {
- content: '';
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- height: 2px;
- background: linear-gradient(90deg, transparent, ${theme.neon}, transparent);
- animation: scanline 3s linear infinite;
- z-index: 1;
- pointer-events: none;
+ ${theme.animations.glow
+ ? `
+ @keyframes neonGlow {
+ 0%, 100% {
+ text-shadow: 0 0 5px currentColor, 0 0 10px currentColor, 0 0 15px currentColor;
}
- ` : ""}
+ 50% {
+ text-shadow: 0 0 2px currentColor, 0 0 5px currentColor, 0 0 8px currentColor;
+ }
+ }`
+ : ""
+ }
+
+ ${theme.animations.pixelBlink
+ ? `
+ @keyframes pixelBlink {
+ 0%, 50% { opacity: 1; }
+ 51%, 100% { opacity: 0.7; }
+ }`
+ : ""
+ }
+
+ ${theme.animations.scanline
+ ? `
+ @keyframes scanline {
+ 0% { transform: translateY(-100%); }
+ 100% { transform: translateY(400px); }
+ }`
+ : ""
+ }
+
+ @keyframes pulse {
+ 0% { box-shadow: 0 0 0 0 rgba(0, 255, 0, 0.7); }
+ 70% { box-shadow: 0 0 0 10px rgba(0, 255, 0, 0); }
+ 100% { box-shadow: 0 0 0 0 rgba(0, 255, 0, 0); }
+ }
+ @keyframes slideIn {
+ from { transform: translateY(-10px); opacity: 0; }
+ to { transform: translateY(0); opacity: 1; }
+ }
+ @keyframes shimmer {
+ 0% { transform: translateX(-100%); }
+ 100% { transform: translateX(100%); }
+ }
- /* Theme-specific container styles */
#wplace-image-bot-container {
+ position: fixed;
+ top: 20px;
+ left: 20px;
+ width: ${CONFIG.currentTheme === "Neon Retro" ? "280px" : "280px"};
+ max-height: calc(100vh - 40px);
background: ${CONFIG.currentTheme === "Classic Autobot"
- ? `linear-gradient(135deg, ${theme.primary} 0%, #1a1a1a 100%)`
- : theme.primary
- };
+ ? `linear-gradient(135deg, ${theme.primary} 0%, #1a1a1a 100%)`
+ : theme.primary
+ };
border: ${theme.borderWidth} ${theme.borderStyle} ${CONFIG.currentTheme === "Classic Autobot" ? theme.accent : theme.text};
border-radius: ${theme.borderRadius};
+ padding: 0;
box-shadow: ${theme.boxShadow};
+ z-index: 9998;
font-family: ${theme.fontFamily};
color: ${theme.text};
+ animation: slideIn 0.4s ease-out;
+ overflow-y: auto; /* Allow scrolling for main panel */
+ overflow-x: hidden;
${theme.backdropFilter ? `backdrop-filter: ${theme.backdropFilter};` : ""}
+ transition: all 0.3s ease;
+ user-select: none;
${CONFIG.currentTheme === "Neon Retro" ? "image-rendering: pixelated;" : ""}
}
- ${CONFIG.currentTheme === "Neon Retro" ? `
- #wplace-image-bot-container::after {
- content: '';
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background:
- repeating-linear-gradient(
- 0deg,
- transparent,
- transparent 2px,
- rgba(0, 255, 65, 0.03) 2px,
- rgba(0, 255, 65, 0.03) 4px
- );
- pointer-events: none;
- z-index: 1;
- }
- ` : ""}
+ ${theme.animations.scanline
+ ? `
+ #wplace-image-bot-container::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ height: 2px;
+ background: linear-gradient(90deg, transparent, ${theme.neon}, transparent);
+ animation: scanline 3s linear infinite;
+ z-index: 1;
+ pointer-events: none;
+ }`
+ : ""
+ }
+ ${CONFIG.currentTheme === "Neon Retro"
+ ? `
+ #wplace-image-bot-container::after {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background:
+ repeating-linear-gradient(
+ 0deg,
+ transparent,
+ transparent 2px,
+ rgba(0, 255, 65, 0.03) 2px,
+ rgba(0, 255, 65, 0.03) 4px
+ );
+ pointer-events: none;
+ z-index: 1;
+ }`
+ : ""
+ }
+
+ #wplace-image-bot-container.wplace-dragging {
+ transition: none;
+ box-shadow: 0 12px 40px rgba(0,0,0,0.8), 0 0 0 2px rgba(255,255,255,0.2);
+ transform: scale(1.02);
+ z-index: 9999;
+ }
+ #wplace-image-bot-container.wplace-minimized {
+ width: 200px;
+ height: auto;
+ overflow: hidden;
+ }
+ #wplace-image-bot-container.wplace-compact {
+ width: 240px;
+ }
+
+ /* Stats Container */
#wplace-stats-container {
+ position: fixed;
+ top: 20px;
+ left: 330px;
+ width: ${CONFIG.currentTheme === "Neon Retro" ? "280px" : "280px"};
+ max-height: calc(100vh - 40px);
background: ${CONFIG.currentTheme === "Classic Autobot"
- ? `linear-gradient(135deg, ${theme.primary} 0%, #1a1a1a 100%)`
- : theme.primary
- };
+ ? `linear-gradient(135deg, ${theme.primary} 0%, #1a1a1a 100%)`
+ : theme.primary
+ };
border: ${theme.borderWidth} ${theme.borderStyle} ${CONFIG.currentTheme === "Classic Autobot" ? theme.accent : theme.text};
border-radius: ${theme.borderRadius};
+ padding: 0;
box-shadow: ${theme.boxShadow};
+ z-index: 9997;
font-family: ${theme.fontFamily};
color: ${theme.text};
+ animation: slideIn 0.4s ease-out;
+ overflow-y: auto; /* Make stats panel scrollable */
${theme.backdropFilter ? `backdrop-filter: ${theme.backdropFilter};` : ""}
+ transition: all 0.3s ease;
+ user-select: none;
${CONFIG.currentTheme === "Neon Retro" ? "image-rendering: pixelated;" : ""}
}
+ /* FIX: Disable transition during drag to prevent lag */
+ #wplace-stats-container.wplace-dragging {
+ transition: none;
+ }
+
.wplace-header {
padding: ${CONFIG.currentTheme === "Neon Retro" ? "8px 12px" : "8px 12px"};
background: ${CONFIG.currentTheme === "Classic Autobot"
- ? `linear-gradient(135deg, ${theme.secondary} 0%, #2a2a2a 100%)`
- : theme.secondary
- };
+ ? `linear-gradient(135deg, ${theme.secondary} 0%, #2a2a2a 100%)`
+ : theme.secondary
+ };
color: ${theme.highlight};
font-size: ${CONFIG.currentTheme === "Neon Retro" ? "11px" : "13px"};
font-weight: ${CONFIG.currentTheme === "Neon Retro" ? "normal" : "700"};
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ cursor: move;
+ user-select: none;
border-bottom: ${CONFIG.currentTheme === "Neon Retro" ? "2px" : "1px"} solid ${CONFIG.currentTheme === "Classic Autobot" ? "rgba(255,255,255,0.1)" : theme.text};
${CONFIG.currentTheme === "Classic Autobot" ? "text-shadow: 0 1px 2px rgba(0,0,0,0.5);" : "text-transform: uppercase; letter-spacing: 1px;"}
- font-family: ${theme.fontFamily};
+ transition: background 0.2s ease;
+ position: relative;
+ z-index: 2;
+ ${theme.animations.glow ? "animation: neonGlow 2s ease-in-out infinite alternate;" : ""}
}
- .wplace-header-title { gap: ${CONFIG.currentTheme === "Neon Retro" ? "6px" : "6px"}; }
- .wplace-header-controls { gap: ${CONFIG.currentTheme === "Neon Retro" ? "6px" : "6px"}; }
+ .wplace-header-title {
+ display: flex;
+ align-items: center;
+ gap: ${CONFIG.currentTheme === "Neon Retro" ? "6px" : "6px"};
+ }
+
+ .wplace-header-controls {
+ display: flex;
+ gap: ${CONFIG.currentTheme === "Neon Retro" ? "6px" : "6px"};
+ }
.wplace-header-btn {
background: ${CONFIG.currentTheme === "Classic Autobot" ? "rgba(255,255,255,0.1)" : theme.accent};
border: ${CONFIG.currentTheme === "Neon Retro" ? `2px solid ${theme.text}` : "none"};
color: ${theme.text};
+ cursor: pointer;
border-radius: ${CONFIG.currentTheme === "Classic Autobot" ? "4px" : "0"};
width: ${CONFIG.currentTheme === "Classic Autobot" ? "18px" : "auto"};
height: ${CONFIG.currentTheme === "Classic Autobot" ? "18px" : "auto"};
padding: ${CONFIG.currentTheme === "Neon Retro" ? "4px 6px" : "0"};
font-size: ${CONFIG.currentTheme === "Neon Retro" ? "8px" : "10px"};
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition: all 0.2s;
font-family: ${theme.fontFamily};
${CONFIG.currentTheme === "Neon Retro" ? "image-rendering: pixelated;" : ""}
}
.wplace-header-btn:hover {
background: ${CONFIG.currentTheme === "Classic Autobot" ? theme.accent : theme.text};
color: ${CONFIG.currentTheme === "Classic Autobot" ? theme.text : theme.primary};
+ transform: ${CONFIG.currentTheme === "Classic Autobot" ? "scale(1.1)" : "none"};
${CONFIG.currentTheme === "Neon Retro" ? `box-shadow: 0 0 10px ${theme.text};` : ""}
}
- .wplace-content { padding: ${CONFIG.currentTheme === "Neon Retro" ? "12px" : "12px"}; }
- .wplace-section { border-radius: ${theme.borderRadius}; }
- .wplace-section-title { color: ${theme.highlight}; }
+ .wplace-content {
+ padding: ${CONFIG.currentTheme === "Neon Retro" ? "12px" : "12px"};
+ display: block;
+ position: relative;
+ z-index: 2;
+ }
+ .wplace-content.wplace-hidden {
+ display: none;
+ }
+
+ .wplace-status-section {
+ margin-bottom: 12px;
+ padding: 8px;
+ background: rgba(255,255,255,0.03);
+ border-radius: ${theme.borderRadius};
+ border: 1px solid rgba(255,255,255,0.1);
+ }
+
+ .wplace-section {
+ margin-bottom: ${CONFIG.currentTheme === "Neon Retro" ? "12px" : "12px"};
+ padding: 12px;
+ background: rgba(255,255,255,0.03);
+ border-radius: ${theme.borderRadius};
+ border: 1px solid rgba(255,255,255,0.1);
+ }
+
+ .wplace-section-title {
+ font-size: 11px;
+ font-weight: 600;
+ margin-bottom: 8px;
+ color: ${theme.highlight};
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+ }
+
+ .wplace-controls {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ }
+ .wplace-row {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 8px;
+ }
+ .wplace-row.single {
+ grid-template-columns: 1fr;
+ }
.wplace-btn {
padding: ${CONFIG.currentTheme === "Neon Retro" ? "12px 8px" : "8px 12px"};
border: ${CONFIG.currentTheme === "Neon Retro" ? "2px solid" : "none"};
border-radius: ${theme.borderRadius};
font-weight: ${CONFIG.currentTheme === "Neon Retro" ? "normal" : "500"};
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ justify-content: center;
gap: ${CONFIG.currentTheme === "Neon Retro" ? "8px" : "6px"};
font-size: ${CONFIG.currentTheme === "Neon Retro" ? "8px" : "11px"};
+ transition: all 0.3s ease;
+ position: relative;
+ overflow: hidden;
font-family: ${theme.fontFamily};
${CONFIG.currentTheme === "Neon Retro" ? "text-transform: uppercase; letter-spacing: 1px; image-rendering: pixelated;" : ""}
background: ${CONFIG.currentTheme === "Classic Autobot"
- ? `linear-gradient(135deg, ${theme.accent} 0%, #4a4a4a 100%)`
- : theme.accent
- };
+ ? `linear-gradient(135deg, ${theme.accent} 0%, #4a4a4a 100%)`
+ : theme.accent
+ };
${CONFIG.currentTheme === "Classic Autobot" ? "border: 1px solid rgba(255,255,255,0.1);" : ""}
}
- ${CONFIG.currentTheme === "Classic Autobot" ? `
- .wplace-btn::before {
- background: linear-gradient(90deg, transparent, rgba(255,255,255,0.1), transparent);
- transition: left 0.5s ease;
- }
- .wplace-btn:hover:not(:disabled)::before { left: 100%; }
- .wplace-btn:hover:not(:disabled) {
- transform: translateY(-1px);
- box-shadow: 0 4px 12px rgba(0,0,0,0.4);
- }
- ` : `
- .wplace-btn:hover:not(:disabled) { box-shadow: 0 0 15px currentColor; }
- `}
+ ${CONFIG.currentTheme === "Classic Autobot"
+ ? `
+ .wplace-btn::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: -100%;
+ width: 100%;
+ height: 100%;
+ background: linear-gradient(90deg, transparent, rgba(255,255,255,0.1), transparent);
+ transition: left 0.5s ease;
+ }
+ .wplace-btn:hover:not(:disabled)::before {
+ left: 100%;
+ }`
+ : `
+ .wplace-btn::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: -100%;
+ width: 100%;
+ height: 100%;
+ background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
+ transition: left 0.5s;
+ }
+ .wplace-btn:hover::before {
+ left: 100%;
+ }`
+ }
+
+ .wplace-btn:hover:not(:disabled) {
+ transform: ${CONFIG.currentTheme === "Classic Autobot" ? "translateY(-1px)" : "none"};
+ box-shadow: ${CONFIG.currentTheme === "Classic Autobot" ? "0 4px 12px rgba(0,0,0,0.4)" : "0 0 15px currentColor"
+ };
+ ${theme.animations.pixelBlink ? "animation: pixelBlink 0.5s infinite;" : ""}
+ }
+ .wplace-btn:active:not(:disabled) {
+ transform: translateY(0);
+ }
.wplace-btn-primary {
background: ${CONFIG.currentTheme === "Classic Autobot"
- ? `linear-gradient(135deg, ${theme.accent} 0%, #6a5acd 100%)`
- : theme.accent
- };
+ ? `linear-gradient(135deg, ${theme.accent} 0%, #6a5acd 100%)`
+ : theme.accent
+ };
color: ${theme.text};
${CONFIG.currentTheme === "Neon Retro" ? `border-color: ${theme.text};` : ""}
}
-
.wplace-btn-upload {
background: ${CONFIG.currentTheme === "Classic Autobot"
- ? `linear-gradient(135deg, ${theme.secondary} 0%, #4a4a4a 100%)`
- : theme.purple
- };
+ ? `linear-gradient(135deg, ${theme.secondary} 0%, #4a4a4a 100%)`
+ : theme.purple
+ };
color: ${theme.text};
${CONFIG.currentTheme === "Classic Autobot"
- ? `border: 1px dashed ${theme.highlight};`
- : `border-color: ${theme.text}; border-style: dashed;`
- }
+ ? `border: 1px dashed ${theme.highlight};`
+ : `border-color: ${theme.text}; border-style: dashed;`
+ }
}
-
.wplace-btn-start {
background: ${CONFIG.currentTheme === "Classic Autobot"
- ? `linear-gradient(135deg, ${theme.success} 0%, #228b22 100%)`
- : theme.success
- };
+ ? `linear-gradient(135deg, ${theme.success} 0%, #228b22 100%)`
+ : theme.success
+ };
color: ${CONFIG.currentTheme === "Classic Autobot" ? "white" : theme.primary};
${CONFIG.currentTheme === "Neon Retro" ? `border-color: ${theme.success};` : ""}
}
-
.wplace-btn-stop {
background: ${CONFIG.currentTheme === "Classic Autobot"
- ? `linear-gradient(135deg, ${theme.error} 0%, #dc143c 100%)`
- : theme.error
- };
+ ? `linear-gradient(135deg, ${theme.error} 0%, #dc143c 100%)`
+ : theme.error
+ };
color: ${CONFIG.currentTheme === "Classic Autobot" ? "white" : theme.text};
${CONFIG.currentTheme === "Neon Retro" ? `border-color: ${theme.error};` : ""}
}
-
.wplace-btn-select {
background: ${CONFIG.currentTheme === "Classic Autobot"
- ? `linear-gradient(135deg, ${theme.highlight} 0%, #9370db 100%)`
- : theme.highlight
- };
+ ? `linear-gradient(135deg, ${theme.highlight} 0%, #9370db 100%)`
+ : theme.highlight
+ };
color: ${CONFIG.currentTheme === "Classic Autobot" ? "white" : theme.primary};
${CONFIG.currentTheme === "Neon Retro" ? `border-color: ${theme.highlight};` : ""}
}
-
.wplace-btn-file {
background: ${CONFIG.currentTheme === "Classic Autobot"
- ? "linear-gradient(135deg, #ff8c00 0%, #ff7f50 100%)"
- : theme.warning
- };
+ ? "linear-gradient(135deg, #ff8c00 0%, #ff7f50 100%)"
+ : theme.warning
+ };
color: ${CONFIG.currentTheme === "Classic Autobot" ? "white" : theme.primary};
${CONFIG.currentTheme === "Neon Retro" ? `border-color: ${theme.warning};` : ""}
}
-
- .wplace-btn:disabled { opacity: ${CONFIG.currentTheme === "Classic Autobot" ? "0.5" : "0.3"}; }
+ .wplace-btn:disabled {
+ opacity: ${CONFIG.currentTheme === "Classic Autobot" ? "0.5" : "0.3"};
+ cursor: not-allowed;
+ transform: none !important;
+ ${theme.animations.pixelBlink ? "animation: none !important;" : ""}
+ box-shadow: none !important;
+ }
+ .wplace-btn:disabled::before {
+ display: none;
+ }
+
+ .wplace-btn-overlay.active {
+ background: linear-gradient(135deg, #29b6f6 0%, #8e2de2 100%);
+ box-shadow: 0 0 15px #8e2de2;
+ }
.wplace-stats {
background: ${CONFIG.currentTheme === "Classic Autobot" ? "rgba(255,255,255,0.03)" : theme.secondary};
@@ -3519,68 +3703,132 @@
}
.wplace-stat-item {
+ display: flex;
+ justify-content: space-between;
padding: ${CONFIG.currentTheme === "Neon Retro" ? "6px 0" : "4px 0"};
font-size: ${CONFIG.currentTheme === "Neon Retro" ? "8px" : "11px"};
+ border-bottom: 1px solid rgba(255,255,255,0.05);
${CONFIG.currentTheme === "Neon Retro" ? "text-transform: uppercase; letter-spacing: 1px;" : ""}
}
- .wplace-stat-label { font-size: ${CONFIG.currentTheme === "Neon Retro" ? "8px" : "10px"}; }
- .wplace-stat-value { color: ${theme.highlight}; }
+ .wplace-stat-item:last-child {
+ border-bottom: none;
+ }
+ .wplace-stat-label {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ opacity: 0.9;
+ font-size: ${CONFIG.currentTheme === "Neon Retro" ? "8px" : "10px"};
+ }
+ .wplace-stat-value {
+ font-weight: 600;
+ color: ${theme.highlight};
+ }
+
+ .wplace-colors-section {
+ margin-top: 10px;
+ padding-top: 8px;
+ border-top: 1px solid rgba(255,255,255,0.05);
+ }
+
+ .wplace-stat-colors-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(16px, 1fr));
+ gap: 4px;
+ margin-top: 8px;
+ padding: 4px;
+ background: rgba(0,0,0,0.2);
+ border-radius: 4px;
+ max-height: 80px; /* Limit height and allow scrolling */
+ overflow-y: auto;
+ }
+
+ .wplace-stat-color-swatch {
+ width: 16px;
+ height: 16px;
+ border-radius: 3px;
+ border: 1px solid rgba(255,255,255,0.1);
+ box-shadow: inset 0 0 2px rgba(0,0,0,0.5);
+ }
.wplace-progress {
+ width: 100%;
background: ${CONFIG.currentTheme === "Classic Autobot" ? "rgba(0,0,0,0.3)" : theme.secondary};
border: ${CONFIG.currentTheme === "Neon Retro" ? `2px solid ${theme.text}` : "1px solid rgba(255,255,255,0.1)"};
border-radius: ${theme.borderRadius};
margin: ${CONFIG.currentTheme === "Neon Retro" ? "10px 0" : "8px 0"};
+ overflow: hidden;
height: ${CONFIG.currentTheme === "Neon Retro" ? "16px" : "6px"};
+ position: relative;
}
- ${CONFIG.currentTheme === "Neon Retro" ? `
- .wplace-progress::before {
- content: '';
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background:
- repeating-linear-gradient(
- 45deg,
- transparent,
- transparent 2px,
- rgba(0, 255, 65, 0.1) 2px,
- rgba(0, 255, 65, 0.1) 4px
- );
- pointer-events: none;
- }
- ` : ""}
+ ${CONFIG.currentTheme === "Neon Retro"
+ ? `
+ .wplace-progress::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background:
+ repeating-linear-gradient(
+ 45deg,
+ transparent,
+ transparent 2px,
+ rgba(0, 255, 65, 0.1) 2px,
+ rgba(0, 255, 65, 0.1) 4px
+ );
+ pointer-events: none;
+ }`
+ : ""
+ }
.wplace-progress-bar {
height: ${CONFIG.currentTheme === "Neon Retro" ? "100%" : "6px"};
background: ${CONFIG.currentTheme === "Classic Autobot"
- ? `linear-gradient(135deg, ${theme.highlight} 0%, #9370db 100%)`
- : `linear-gradient(90deg, ${theme.success}, ${theme.neon})`
- };
+ ? `linear-gradient(135deg, ${theme.highlight} 0%, #9370db 100%)`
+ : `linear-gradient(90deg, ${theme.success}, ${theme.neon})`
+ };
transition: width ${CONFIG.currentTheme === "Neon Retro" ? "0.3s" : "0.5s"} ease;
+ position: relative;
${CONFIG.currentTheme === "Neon Retro" ? `box-shadow: 0 0 10px ${theme.success};` : ""}
}
- ${CONFIG.currentTheme === "Classic Autobot" ? `
- .wplace-progress-bar::after {
- background: linear-gradient(90deg, transparent, rgba(255,255,255,0.3), transparent);
- animation: shimmer 2s infinite;
- }
- ` : `
- .wplace-progress-bar::after {
- background: ${theme.text};
- }
- `}
+ ${CONFIG.currentTheme === "Classic Autobot"
+ ? `
+ .wplace-progress-bar::after {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: linear-gradient(90deg, transparent, rgba(255,255,255,0.3), transparent);
+ animation: shimmer 2s infinite;
+ }`
+ : `
+ .wplace-progress-bar::after {
+ content: '';
+ position: absolute;
+ top: 0;
+ right: 0;
+ width: 4px;
+ height: 100%;
+ background: ${theme.text};
+ animation: pixelBlink 1s infinite;
+ }`
+ }
.wplace-status {
padding: ${CONFIG.currentTheme === "Neon Retro" ? "10px" : "6px"};
border: ${CONFIG.currentTheme === "Neon Retro" ? "2px solid" : "1px solid"};
border-radius: ${theme.borderRadius};
+ text-align: center;
font-size: ${CONFIG.currentTheme === "Neon Retro" ? "8px" : "11px"};
${CONFIG.currentTheme === "Neon Retro" ? "text-transform: uppercase; letter-spacing: 1px;" : ""}
+ position: relative;
+ overflow: hidden;
}
.status-default {
@@ -3599,6 +3847,7 @@
border-color: ${theme.error};
color: ${CONFIG.currentTheme === "Classic Autobot" ? theme.error : theme.text};
box-shadow: 0 0 15px ${theme.error};
+ ${theme.animations.pixelBlink ? "animation: pixelBlink 0.5s infinite;" : ""}
}
.status-warning {
background: ${CONFIG.currentTheme === "Classic Autobot" ? "rgba(255, 165, 0, 0.1)" : theme.warning};
@@ -3607,17 +3856,71 @@
box-shadow: 0 0 15px ${theme.warning};
}
- /* Settings and other dynamic elements */
.resize-container {
+ display: none;
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
background: ${theme.primary};
+ padding: 20px;
border: ${theme.borderWidth} ${theme.borderStyle} ${theme.text};
border-radius: ${theme.borderRadius};
- box-shadow: ${CONFIG.currentTheme === "Classic Autobot" ? "0 0 20px rgba(0,0,0,0.5)" : "0 0 30px rgba(0, 255, 65, 0.5)"};
+ z-index: 10000;
+ box-shadow: ${CONFIG.currentTheme === "Classic Autobot" ? "0 0 20px rgba(0,0,0,0.5)" : "0 0 30px rgba(0, 255, 65, 0.5)"
+ };
+ width: 90%;
+ max-width: 700px;
+ max-height: 90%;
+ overflow: auto;
font-family: ${theme.fontFamily};
}
- .resize-preview-wrapper { border: 1px solid ${theme.accent}; }
-
+ .resize-preview-wrapper {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ border: 1px solid ${theme.accent};
+ background: rgba(0,0,0,0.2);
+ margin: 15px 0;
+ height: 300px;
+ overflow: hidden;
+ }
+
+ .resize-canvas-stack { position: relative; transform-origin: center center; display: inline-block; }
+ .resize-base-canvas, .resize-mask-canvas {
+ position: absolute; left: 0; top: 0;
+ image-rendering: pixelated;
+ image-rendering: -moz-crisp-edges;
+ image-rendering: crisp-edges;
+ }
+ .resize-mask-canvas { pointer-events: auto; }
+ .resize-tools { display:flex; gap:8px; align-items:center; margin-top:8px; font-size:12px; }
+ .resize-tools button { padding:6px 10px; border-radius:6px; border:1px solid rgba(255,255,255,0.2); background: rgba(255,255,255,0.06); color:#fff; cursor:pointer; }
+ .wplace-btn.active,
+ .wplace-btn[aria-pressed="true"] {
+ background: ${theme.highlight} !important;
+ color: ${theme.primary} !important;
+ border-color: ${theme.text} !important;
+ box-shadow: 0 0 8px rgba(0,0,0,0.25) inset, 0 0 6px rgba(0,0,0,0.2) !important;
+ }
+ .wplace-btn.active i,
+ .wplace-btn[aria-pressed="true"] i { filter: drop-shadow(0 0 3px ${theme.primary}); }
+ .mask-mode-group .wplace-btn.active,
+ .mask-mode-group .wplace-btn[aria-pressed="true"] {
+ background: ${theme.highlight};
+ color: ${theme.primary};
+ border-color: ${theme.text};
+ box-shadow: 0 0 8px rgba(0,0,0,0.25) inset, 0 0 6px rgba(0,0,0,0.2);
+ }
+
+ .resize-controls {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 15px;
+ align-items: center;
+ }
+
.resize-controls label {
font-size: ${CONFIG.currentTheme === "Neon Retro" ? "8px" : "12px"};
${CONFIG.currentTheme === "Neon Retro" ? "text-transform: uppercase; letter-spacing: 1px;" : ""}
@@ -3625,55 +3928,517 @@
}
.resize-slider {
+ width: 100%;
height: ${CONFIG.currentTheme === "Neon Retro" ? "8px" : "4px"};
background: ${CONFIG.currentTheme === "Classic Autobot" ? "#ccc" : theme.secondary};
border: ${CONFIG.currentTheme === "Neon Retro" ? `2px solid ${theme.text}` : "none"};
border-radius: ${theme.borderRadius};
+ outline: none;
+ -webkit-appearance: none;
}
- ${CONFIG.currentTheme === "Neon Retro" ? `
- .resize-slider::-webkit-slider-thumb {
- width: 16px;
- height: 16px;
- background: ${theme.highlight};
- border: 2px solid ${theme.text};
- border-radius: 0;
- box-shadow: 0 0 5px ${theme.highlight};
- }
-
- .resize-slider::-moz-range-thumb {
- width: 16px;
- height: 16px;
- background: ${theme.highlight};
- border: 2px solid ${theme.text};
- border-radius: 0;
- box-shadow: 0 0 5px ${theme.highlight};
- }
- ` : ""}
-
- .wplace-slider::-webkit-slider-thumb { background: ${theme.highlight}; }
-
- ${CONFIG.currentTheme === "Neon Retro" ? `
- input[type="checkbox"] {
- width: 16px;
- height: 16px;
- border: 2px solid ${theme.text};
- background: ${theme.secondary};
- }
- input[type="checkbox"]:checked { background: ${theme.success}; }
- input[type="checkbox"]:checked::after { color: ${theme.primary}; }
- ` : ""}
-
- /* Additional theme-specific styles */
- .wplace-btn.active,
- .wplace-btn[aria-pressed="true"] {
- background: ${theme.highlight} !important;
- color: ${theme.primary} !important;
- border-color: ${theme.text} !important;
+ ${CONFIG.currentTheme === "Neon Retro"
+ ? `
+ .resize-slider::-webkit-slider-thumb {
+ -webkit-appearance: none;
+ width: 16px;
+ height: 16px;
+ background: ${theme.highlight};
+ border: 2px solid ${theme.text};
+ border-radius: 0;
+ cursor: pointer;
+ box-shadow: 0 0 5px ${theme.highlight};
}
+
+ .resize-slider::-moz-range-thumb {
+ width: 16px;
+ height: 16px;
+ background: ${theme.highlight};
+ border: 2px solid ${theme.text};
+ border-radius: 0;
+ cursor: pointer;
+ box-shadow: 0 0 5px ${theme.highlight};
+ }`
+ : ""
+ }
- .wplace-btn.active i,
- .wplace-btn[aria-pressed="true"] i { filter: drop-shadow(0 0 3px ${theme.primary}); }
+ .resize-zoom-controls {
+ grid-column: 1 / -1;
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ margin-top: 15px;
+ }
+
+ .resize-buttons {
+ display: flex;
+ gap: 10px;
+ justify-content: center;
+ margin-top: 20px;
+ }
+
+ .resize-overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: rgba(0, 0, 0, 0.8);
+ z-index: 9999;
+ display: none;
+ }
+ .wplace-color-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(60px, 1fr));
+ gap: 10px;
+ padding-top: 8px;
+ max-height: 300px;
+ overflow-y: auto;
+ }
+ .wplace-color-item {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 4px;
+ }
+ .wplace-color-item-name {
+ font-size: 9px;
+ color: #ccc;
+ text-align: center;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ width: 100%;
+ }
+ .wplace-color-swatch {
+ width: 22px;
+ height: 22px;
+ border: 1px solid rgba(255,255,255,0.2);
+ border-radius: 4px;
+ cursor: pointer;
+ transition: transform 0.1s ease, box-shadow 0.2s ease;
+ position: relative;
+ margin: 0 auto;
+ }
+ .wplace-color-swatch.unavailable {
+ border-color: #666;
+ border-style: dashed;
+ cursor: not-allowed;
+ }
+ .wplace-color-swatch:hover {
+ transform: scale(1.1);
+ z-index: 1;
+ }
+ .wplace-color-swatch:not(.active) {
+ opacity: 0.3;
+ filter: grayscale(80%);
+ }
+ .wplace-color-swatch.unavailable:not(.active) {
+ opacity: 0.2;
+ filter: grayscale(90%);
+ }
+ .wplace-color-swatch.active::after {
+ content: 'โ';
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ color: white;
+ font-size: 12px;
+ font-weight: bold;
+ text-shadow: 0 0 3px black;
+ }
+ .wplace-color-divider {
+ border: none;
+ height: 1px;
+ background: rgba(255,255,255,0.1);
+ margin: 8px 0;
+ }
+
+ .wplace-cooldown-control {
+ margin-top: 8px;
+ }
+ .wplace-cooldown-control label {
+ font-size: 11px;
+ margin-bottom: 4px;
+ display: block;
+ }
+ .wplace-slider-container {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ }
+ .wplace-slider {
+ flex: 1;
+ -webkit-appearance: none;
+ appearance: none;
+ height: 4px;
+ background: #444;
+ border-radius: 2px;
+ outline: none;
+ }
+ .wplace-slider::-webkit-slider-thumb {
+ -webkit-appearance: none;
+ width: 14px;
+ height: 14px;
+ background: ${theme.highlight};
+ border-radius: 50%;
+ cursor: pointer;
+ }
+
+
+ ${CONFIG.currentTheme === "Neon Retro"
+ ? `
+ input[type="checkbox"] {
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ appearance: none;
+ width: 16px;
+ height: 16px;
+ border: 2px solid ${theme.text};
+ background: ${theme.secondary};
+ margin-right: 8px;
+ position: relative;
+ cursor: pointer;
+ }
+
+ input[type="checkbox"]:checked {
+ background: ${theme.success};
+ }
+
+ input[type="checkbox"]:checked::after {
+ content: 'โ';
+ position: absolute;
+ top: -2px;
+ left: 1px;
+ color: ${theme.primary};
+ font-size: 12px;
+ font-weight: bold;
+ }
+
+ .fas, .fa {
+ filter: drop-shadow(0 0 3px currentColor);
+ }
+
+ .wplace-speed-control {
+ margin-top: 12px;
+ padding: 12px;
+ background: ${theme.secondary};
+ border: ${theme.borderWidth} ${theme.borderStyle} ${theme.accent};
+ border-radius: ${theme.borderRadius};
+ backdrop-filter: ${theme.backdropFilter};
+ }
+
+ .wplace-speed-label {
+ display: flex;
+ align-items: center;
+ margin-bottom: 8px;
+ color: ${theme.text};
+ font-size: 13px;
+ font-weight: 600;
+ }
+
+ .wplace-speed-label i {
+ margin-right: 6px;
+ color: ${theme.highlight};
+ }
+
+ .wplace-speed-slider-container {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ }
+
+ .wplace-speed-slider {
+ flex: 1;
+ height: 6px;
+ border-radius: 3px;
+ background: ${theme.primary};
+ outline: none;
+ cursor: pointer;
+ -webkit-appearance: none;
+ appearance: none;
+ }
+
+ .wplace-speed-slider::-webkit-slider-thumb {
+ -webkit-appearance: none;
+ appearance: none;
+ width: 18px;
+ height: 18px;
+ border-radius: 50%;
+ background: ${theme.highlight};
+ cursor: pointer;
+ border: 2px solid ${theme.text};
+ box-shadow: ${theme.boxShadow};
+ }
+
+ .wplace-speed-slider::-moz-range-thumb {
+ width: 18px;
+ height: 18px;
+ border-radius: 50%;
+ background: ${theme.highlight};
+ cursor: pointer;
+ border: 2px solid ${theme.text};
+ box-shadow: ${theme.boxShadow};
+ }
+
+ .wplace-speed-display {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ min-width: 90px;
+ justify-content: flex-end;
+ }
+
+ #speedValue {
+ color: ${theme.highlight};
+ font-weight: 600;
+ font-size: 14px;
+ }
+
+ .wplace-speed-unit {
+ color: ${theme.text};
+ font-size: 11px;
+ opacity: 0.8;
+ }
+
+ #wplace-settings-container {
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ z-index: 10001;
+ min-width: 400px;
+ max-width: 500px;
+ background: ${theme.primary};
+ border: ${theme.borderWidth} ${theme.borderStyle} ${theme.accent};
+ border-radius: ${theme.borderRadius};
+ box-shadow: ${theme.boxShadow};
+ backdrop-filter: ${theme.backdropFilter};
+ }
+
+ .wplace-settings {
+ padding: 16px;
+ max-height: 400px;
+ overflow-y: auto;
+ }
+
+ .wplace-setting-section {
+ margin-bottom: 20px;
+ padding: 12px;
+ background: ${theme.secondary};
+ border: ${theme.borderWidth} ${theme.borderStyle} ${theme.accent};
+ border-radius: ${theme.borderRadius};
+ }
+
+ .wplace-setting-title {
+ display: flex;
+ align-items: center;
+ margin-bottom: 12px;
+ color: ${theme.text};
+ font-size: 14px;
+ font-weight: 600;
+ }
+
+ .wplace-setting-title i {
+ margin-right: 8px;
+ color: ${theme.highlight};
+ }
+
+ .wplace-setting-content {
+ color: ${theme.text};
+ }
+
+ .wplace-section {
+ margin-bottom: 20px;
+ padding: 15px;
+ background: ${theme.secondary};
+ border: ${theme.borderWidth} ${theme.borderStyle} ${theme.accent};
+ border-radius: ${theme.borderRadius};
+ }
+
+ .wplace-section-title {
+ display: flex;
+ align-items: center;
+ margin-bottom: 15px;
+ color: ${theme.text};
+ font-size: 14px;
+ font-weight: 600;
+ }
+
+ .wplace-section-title i {
+ margin-right: 8px;
+ color: ${theme.highlight};
+ }
+
+ .wplace-speed-container {
+ display: flex;
+ align-items: center;
+ gap: 15px;
+ margin-bottom: 10px;
+ }
+
+ .wplace-slider {
+ flex: 1;
+ height: 6px;
+ background: ${theme.accent};
+ border-radius: 3px;
+ outline: none;
+ -webkit-appearance: none;
+ }
+
+ .wplace-slider::-webkit-slider-thumb {
+ -webkit-appearance: none;
+ width: 18px;
+ height: 18px;
+ background: ${theme.highlight};
+ border-radius: 50%;
+ cursor: pointer;
+ border: 2px solid ${theme.primary};
+ }
+
+ .wplace-speed-display {
+ background: ${theme.accent};
+ padding: 5px 10px;
+ border-radius: 4px;
+ color: ${theme.text};
+ font-weight: 600;
+ min-width: 80px;
+ text-align: center;
+ border: ${theme.borderWidth} ${theme.borderStyle} ${theme.highlight};
+ }
+
+ .wplace-select {
+ width: 100%;
+ padding: 8px 12px;
+ background: ${theme.secondary};
+ border: ${theme.borderWidth} ${theme.borderStyle} ${theme.accent};
+ border-radius: ${theme.borderRadius};
+ color: ${theme.text};
+ font-size: 14px;
+ margin-bottom: 10px;
+ }
+
+ .wplace-select:focus {
+ outline: none;
+ border-color: ${theme.highlight};
+ }
+
+ .wplace-description {
+ color: ${theme.text};
+ font-size: 12px;
+ opacity: 0.8;
+ line-height: 1.4;
+ }
+
+ .wplace-theme-custom {
+ margin-top: 15px;
+ padding: 15px;
+ background: ${theme.accent};
+ border-radius: ${theme.borderRadius};
+ border: ${theme.borderWidth} ${theme.borderStyle} ${theme.highlight};
+ }
+
+ .wplace-custom-group {
+ margin-bottom: 15px;
+ }
+
+ .wplace-custom-label {
+ display: flex;
+ align-items: center;
+ margin-bottom: 8px;
+ color: ${theme.text};
+ font-size: 13px;
+ font-weight: 600;
+ }
+
+ .wplace-custom-label i {
+ margin-right: 8px;
+ color: ${theme.highlight};
+ width: 16px;
+ }
+
+ .wplace-color-input-group {
+ display: flex;
+ gap: 8px;
+ align-items: center;
+ }
+
+ .wplace-color-input {
+ width: 50px;
+ height: 30px;
+ border: none;
+ border-radius: 4px;
+ cursor: pointer;
+ background: transparent;
+ }
+
+ .wplace-color-text {
+ flex: 1;
+ padding: 6px 10px;
+ background: ${theme.secondary};
+ border: ${theme.borderWidth} ${theme.borderStyle} ${theme.accent};
+ border-radius: 4px;
+ color: ${theme.text};
+ font-size: 12px;
+ font-family: monospace;
+ }
+
+ .wplace-animation-controls {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ }
+
+ .wplace-checkbox-label {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ color: ${theme.text};
+ font-size: 12px;
+ cursor: pointer;
+ }
+
+ .wplace-checkbox-label input[type="checkbox"] {
+ accent-color: ${theme.highlight};
+ }
+
+ .wplace-slider-container {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ }
+
+ .wplace-slider-container .wplace-slider {
+ flex: 1;
+ }
+
+ .wplace-slider-container span {
+ color: ${theme.text};
+ font-size: 12px;
+ font-weight: 600;
+ min-width: 40px;
+ }
+
+ .wplace-custom-actions {
+ display: flex;
+ gap: 10px;
+ margin-top: 20px;
+ border-top: 1px solid ${theme.accent};
+ padding-top: 15px;
+ }
+
+ .wplace-btn-secondary {
+ background: ${theme.accent};
+ color: ${theme.text};
+ border: ${theme.borderWidth} ${theme.borderStyle} ${theme.highlight};
+ }
+
+ .wplace-btn-secondary:hover {
+ background: ${theme.secondary};
+ }`
+ : ""
+ }
`
document.head.appendChild(style)
@@ -3832,12 +4597,12 @@
// Use the theme variable already declared at the top of createUI function
const settingsContainer = document.createElement("div")
settingsContainer.id = "wplace-settings-container"
-
+
// Apply theme-based styling
- const themeBackground = theme.primary ?
- `linear-gradient(135deg, ${theme.primary} 0%, ${theme.secondary || theme.primary} 100%)` :
- `linear-gradient(135deg, #667eea 0%, #764ba2 100%)`
-
+ const themeBackground = theme.primary ?
+ `linear-gradient(135deg, ${theme.primary} 0%, ${theme.secondary || theme.primary} 100%)` :
+ `linear-gradient(135deg, #667eea 0%, #764ba2 100%)`
+
settingsContainer.style.cssText = `
position: fixed;
top: 50%;
@@ -4194,7 +4959,7 @@
box-shadow: 0 3px 10px rgba(0,0,0,0.1);
">
${Object.keys(CONFIG.THEMES).map(themeName =>
- ``
+ ``
).join('')}
@@ -4518,7 +5283,7 @@
const loadBtn = container.querySelector("#loadBtn")
const saveToFileBtn = container.querySelector("#saveToFileBtn")
const loadFromFileBtn = container.querySelector("#loadFromFileBtn")
-
+
// Disable load/upload buttons until initial setup is complete (startup only)
if (loadBtn) {
loadBtn.disabled = !state.initialSetupComplete;
@@ -4532,7 +5297,7 @@
uploadBtn.disabled = !state.initialSetupComplete;
uploadBtn.title = state.initialSetupComplete ? "" : "๐ Waiting for initial setup to complete...";
}
-
+
const minimizeBtn = container.querySelector("#minimizeBtn")
const compactBtn = container.querySelector("#compactBtn")
const statsBtn = container.querySelector("#statsBtn")
@@ -4569,9 +5334,9 @@
function makeDraggable(element) {
let pos1 = 0,
- pos2 = 0,
- pos3 = 0,
- pos4 = 0
+ pos2 = 0,
+ pos3 = 0,
+ pos4 = 0
let isDragging = false
const header = element.querySelector(".wplace-header") || element.querySelector(".wplace-settings-header")
@@ -4742,8 +5507,8 @@
if (notifOnChargesToggle) state.notifyOnChargesReached = !!notifOnChargesToggle.checked;
if (notifOnlyUnfocusedToggle) state.notifyOnlyWhenUnfocused = !!notifOnlyUnfocusedToggle.checked;
if (notifIntervalInput) {
- const v = parseInt(notifIntervalInput.value, 10);
- if (!isNaN(v) && v >= 1 && v <= 60) state.notificationIntervalMinutes = v;
+ const v = parseInt(notifIntervalInput.value, 10);
+ if (!isNaN(v) && v >= 1 && v <= 60) state.notificationIntervalMinutes = v;
}
saveBotSettings();
Utils.showAlert(Utils.t("settingsSaved"), "success");
@@ -4774,11 +5539,11 @@
const randomBatchControls = settingsContainer.querySelector("#randomBatchControls")
const randomBatchMin = settingsContainer.querySelector("#randomBatchMin")
const randomBatchMax = settingsContainer.querySelector("#randomBatchMax")
-
+
if (batchModeSelect) {
batchModeSelect.addEventListener("change", (e) => {
state.batchMode = e.target.value
-
+
// Switch between normal and random controls
if (normalBatchControls && randomBatchControls) {
if (e.target.value === 'random') {
@@ -4789,13 +5554,13 @@
randomBatchControls.style.display = 'none'
}
}
-
+
saveBotSettings()
console.log(`๐ฆ Batch mode changed to: ${state.batchMode}`)
Utils.showAlert(`Batch mode set to: ${state.batchMode === 'random' ? 'Random Range' : 'Normal Fixed Size'}`, "success")
})
}
-
+
if (randomBatchMin) {
randomBatchMin.addEventListener("input", (e) => {
const min = parseInt(e.target.value)
@@ -4810,7 +5575,7 @@
}
})
}
-
+
if (randomBatchMax) {
randomBatchMax.addEventListener("input", (e) => {
const max = parseInt(e.target.value)
@@ -4883,7 +5648,7 @@
});
}
- // (Advanced color listeners moved outside to work with resize dialog)
+ // (Advanced color listeners moved outside to work with resize dialog)
// (Advanced color listeners moved outside to work with resize dialog)
// Notifications listeners
const notifPermBtn = settingsContainer.querySelector("#notifRequestPermBtn");
@@ -4909,23 +5674,23 @@
const heightValue = resizeContainer.querySelector("#heightValue")
const keepAspect = resizeContainer.querySelector("#keepAspect")
const paintWhiteToggle = resizeContainer.querySelector("#paintWhiteToggle");
- const zoomSlider = resizeContainer.querySelector("#zoomSlider");
- const zoomValue = resizeContainer.querySelector('#zoomValue');
- const zoomInBtn = resizeContainer.querySelector('#zoomInBtn');
- const zoomOutBtn = resizeContainer.querySelector('#zoomOutBtn');
- const zoomFitBtn = resizeContainer.querySelector('#zoomFitBtn');
- const zoomActualBtn = resizeContainer.querySelector('#zoomActualBtn');
- const panModeBtn = resizeContainer.querySelector('#panModeBtn');
- const panStage = resizeContainer.querySelector('#resizePanStage');
- const canvasStack = resizeContainer.querySelector('#resizeCanvasStack');
- const baseCanvas = resizeContainer.querySelector('#resizeCanvas');
- const maskCanvas = resizeContainer.querySelector('#maskCanvas');
- const baseCtx = baseCanvas.getContext('2d');
- const maskCtx = maskCanvas.getContext('2d');
+ const zoomSlider = resizeContainer.querySelector("#zoomSlider");
+ const zoomValue = resizeContainer.querySelector('#zoomValue');
+ const zoomInBtn = resizeContainer.querySelector('#zoomInBtn');
+ const zoomOutBtn = resizeContainer.querySelector('#zoomOutBtn');
+ const zoomFitBtn = resizeContainer.querySelector('#zoomFitBtn');
+ const zoomActualBtn = resizeContainer.querySelector('#zoomActualBtn');
+ const panModeBtn = resizeContainer.querySelector('#panModeBtn');
+ const panStage = resizeContainer.querySelector('#resizePanStage');
+ const canvasStack = resizeContainer.querySelector('#resizeCanvasStack');
+ const baseCanvas = resizeContainer.querySelector('#resizeCanvas');
+ const maskCanvas = resizeContainer.querySelector('#maskCanvas');
+ const baseCtx = baseCanvas.getContext('2d');
+ const maskCtx = maskCanvas.getContext('2d');
const confirmResize = resizeContainer.querySelector("#confirmResize")
const cancelResize = resizeContainer.querySelector("#cancelResize")
- const downloadPreviewBtn = resizeContainer.querySelector("#downloadPreviewBtn");
- const clearIgnoredBtn = resizeContainer.querySelector('#clearIgnoredBtn');
+ const downloadPreviewBtn = resizeContainer.querySelector("#downloadPreviewBtn");
+ const clearIgnoredBtn = resizeContainer.querySelector('#clearIgnoredBtn');
if (compactBtn) {
compactBtn.addEventListener("click", () => {
@@ -4962,9 +5727,9 @@
if (toggleOverlayBtn) {
toggleOverlayBtn.addEventListener('click', () => {
- const isEnabled = overlayManager.toggle();
- toggleOverlayBtn.classList.toggle('active', isEnabled);
- toggleOverlayBtn.setAttribute('aria-pressed', isEnabled ? 'true' : 'false');
+ const isEnabled = overlayManager.toggle();
+ toggleOverlayBtn.classList.toggle('active', isEnabled);
+ toggleOverlayBtn.setAttribute('aria-pressed', isEnabled ? 'true' : 'false');
Utils.showAlert(`Overlay ${isEnabled ? 'enabled' : 'disabled'}.`, 'info');
});
}
@@ -5009,7 +5774,7 @@
Utils.showAlert("๐ Please wait for the initial setup to complete before loading progress.", "warning");
return;
}
-
+
const savedData = Utils.loadProgress()
if (!savedData) {
updateUI("noSavedData", "warning")
@@ -5018,9 +5783,9 @@
}
const confirmLoad = confirm(
- `${Utils.t("savedDataFound")}\n\n` +
- `Saved: ${new Date(savedData.timestamp).toLocaleString()}\n` +
- `Progress: ${savedData.state.paintedPixels}/${savedData.state.totalPixels} pixels`,
+ `${Utils.t("savedDataFound")}\n\n` +
+ `Saved: ${new Date(savedData.timestamp).toLocaleString()}\n` +
+ `Progress: ${savedData.state.paintedPixels}/${savedData.state.totalPixels} pixels`,
)
if (confirmLoad) {
@@ -5073,7 +5838,7 @@
Utils.showAlert("๐ Please wait for the initial setup to complete before loading from file.", "warning");
return;
}
-
+
try {
const success = await Utils.loadProgressFromFile()
if (success) {
@@ -5201,31 +5966,31 @@
}
const aspectRatio = width / height;
- const rs = state.resizeSettings;
- widthSlider.max = width * 2;
- heightSlider.max = height * 2;
- let initialW = width;
- let initialH = height;
- if (rs && Number.isFinite(rs.width) && Number.isFinite(rs.height) && rs.width > 0 && rs.height > 0) {
- initialW = rs.width;
- initialH = rs.height;
- }
- // Clamp to slider ranges
- initialW = Math.max(parseInt(widthSlider.min, 10) || 10, Math.min(initialW, parseInt(widthSlider.max, 10)));
- initialH = Math.max(parseInt(heightSlider.min, 10) || 10, Math.min(initialH, parseInt(heightSlider.max, 10)));
- widthSlider.value = initialW;
- heightSlider.value = initialH;
- widthValue.textContent = initialW;
- heightValue.textContent = initialH;
- zoomSlider.value = 1;
- if (zoomValue) zoomValue.textContent = '100%';
+ const rs = state.resizeSettings;
+ widthSlider.max = width * 2;
+ heightSlider.max = height * 2;
+ let initialW = width;
+ let initialH = height;
+ if (rs && Number.isFinite(rs.width) && Number.isFinite(rs.height) && rs.width > 0 && rs.height > 0) {
+ initialW = rs.width;
+ initialH = rs.height;
+ }
+ // Clamp to slider ranges
+ initialW = Math.max(parseInt(widthSlider.min, 10) || 10, Math.min(initialW, parseInt(widthSlider.max, 10)));
+ initialH = Math.max(parseInt(heightSlider.min, 10) || 10, Math.min(initialH, parseInt(heightSlider.max, 10)));
+ widthSlider.value = initialW;
+ heightSlider.value = initialH;
+ widthValue.textContent = initialW;
+ heightValue.textContent = initialH;
+ zoomSlider.value = 1;
+ if (zoomValue) zoomValue.textContent = '100%';
paintWhiteToggle.checked = state.paintWhitePixels;
let _previewTimer = null;
let _previewJobId = 0;
let _isDraggingSize = false;
let _zoomLevel = 1;
- let _ditherWorkBuf = null;
+ let _ditherWorkBuf = null;
let _ditherEligibleBuf = null;
const ensureDitherBuffers = (n) => {
if (!_ditherWorkBuf || _ditherWorkBuf.length !== n * 3) _ditherWorkBuf = new Float32Array(n * 3);
@@ -5288,9 +6053,9 @@
widthValue.textContent = newWidth;
heightValue.textContent = newHeight;
- ensureMaskSize(newWidth, newHeight);
- canvasStack.style.width = newWidth + 'px';
- canvasStack.style.height = newHeight + 'px';
+ ensureMaskSize(newWidth, newHeight);
+ canvasStack.style.width = newWidth + 'px';
+ canvasStack.style.height = newHeight + 'px';
baseCtx.imageSmoothingEnabled = false;
if (!state.availableColors || state.availableColors.length === 0) {
if (baseProcessor !== processor && (!baseProcessor.img || !baseProcessor.canvas)) {
@@ -5304,7 +6069,7 @@
updateZoomLayout();
return;
}
- if (baseProcessor !== processor && (!baseProcessor.img || !baseProcessor.canvas)) {
+ if (baseProcessor !== processor && (!baseProcessor.img || !baseProcessor.canvas)) {
await baseProcessor.load();
}
baseCtx.clearRect(0,0,newWidth,newHeight);
@@ -5312,7 +6077,7 @@
const imgData = baseCtx.getImageData(0, 0, newWidth, newHeight);
const data = imgData.data;
- const tThresh = state.customTransparencyThreshold || CONFIG.TRANSPARENCY_THRESHOLD;
+ const tThresh = state.customTransparencyThreshold || CONFIG.TRANSPARENCY_THRESHOLD;
const applyFSDither = () => {
const w = newWidth, h = newHeight;
@@ -5389,9 +6154,9 @@
if (jobId !== _previewJobId) return;
baseCtx.putImageData(imgData, 0, 0);
- maskCtx.clearRect(0,0,maskCanvas.width,maskCanvas.height);
- if (_maskImageData) maskCtx.putImageData(_maskImageData, 0, 0);
- updateZoomLayout();
+ maskCtx.clearRect(0,0,maskCanvas.width,maskCanvas.height);
+ if (_maskImageData) maskCtx.putImageData(_maskImageData, 0, 0);
+ updateZoomLayout();
};
const onWidthInput = () => {
@@ -5399,13 +6164,13 @@
heightSlider.value = Math.round(parseInt(widthSlider.value, 10) / aspectRatio);
}
_updateResizePreview();
- const curW = parseInt(widthSlider.value, 10);
- const curH = parseInt(heightSlider.value, 10);
- state.resizeSettings = { baseWidth: width, baseHeight: height, width: curW, height: curH };
- saveBotSettings();
+ const curW = parseInt(widthSlider.value, 10);
+ const curH = parseInt(heightSlider.value, 10);
+ state.resizeSettings = { baseWidth: width, baseHeight: height, width: curW, height: curH };
+ saveBotSettings();
// Auto-fit after size changes
const fit = (typeof computeFitZoom === 'function') ? computeFitZoom() : 1;
- if (!isNaN(fit) && isFinite(fit)) applyZoom(fit);
+ if (!isNaN(fit) && isFinite(fit)) applyZoom(fit);
};
const onHeightInput = () => {
@@ -5413,13 +6178,13 @@
widthSlider.value = Math.round(parseInt(heightSlider.value, 10) * aspectRatio);
}
_updateResizePreview();
- const curW = parseInt(widthSlider.value, 10);
- const curH = parseInt(heightSlider.value, 10);
- state.resizeSettings = { baseWidth: width, baseHeight: height, width: curW, height: curH };
- saveBotSettings();
+ const curW = parseInt(widthSlider.value, 10);
+ const curH = parseInt(heightSlider.value, 10);
+ state.resizeSettings = { baseWidth: width, baseHeight: height, width: curW, height: curH };
+ saveBotSettings();
// Auto-fit after size changes
const fit = (typeof computeFitZoom === 'function') ? computeFitZoom() : 1;
- if (!isNaN(fit) && isFinite(fit)) applyZoom(fit);
+ if (!isNaN(fit) && isFinite(fit)) applyZoom(fit);
};
paintWhiteToggle.onchange = (e) => {
@@ -5474,8 +6239,8 @@
zoomSlider.addEventListener('input', () => {
applyZoom(parseFloat(zoomSlider.value));
});
- if (zoomInBtn) zoomInBtn.addEventListener('click', () => applyZoom(parseFloat(zoomSlider.value) + 0.1));
- if (zoomOutBtn) zoomOutBtn.addEventListener('click', () => applyZoom(parseFloat(zoomSlider.value) - 0.1));
+ if (zoomInBtn) zoomInBtn.addEventListener('click', () => applyZoom(parseFloat(zoomSlider.value) + 0.1));
+ if (zoomOutBtn) zoomOutBtn.addEventListener('click', () => applyZoom(parseFloat(zoomSlider.value) - 0.1));
const computeFitZoom = () => {
const wrapRect = panStage?.getBoundingClientRect();
if (!wrapRect) return 1;
@@ -5486,8 +6251,8 @@
const scaleY = (wrapRect.height - margin) / h;
return Math.max(0.05, Math.min(20, Math.min(scaleX, scaleY)));
};
- if (zoomFitBtn) zoomFitBtn.addEventListener('click', () => { applyZoom(computeFitZoom()); centerInView(); });
- if (zoomActualBtn) zoomActualBtn.addEventListener('click', () => { applyZoom(1); centerInView(); });
+ if (zoomFitBtn) zoomFitBtn.addEventListener('click', () => { applyZoom(computeFitZoom()); centerInView(); });
+ if (zoomActualBtn) zoomActualBtn.addEventListener('click', () => { applyZoom(1); centerInView(); });
const centerInView = () => {
if (!panStage) return;
@@ -5499,12 +6264,12 @@
applyPan();
};
- let isPanning = false; let startX = 0, startY = 0, startPanX = 0, startPanY = 0;
- let allowPan = false; // Space key
- let panMode = false; // Explicit pan mode toggle for touch/one-button mice
+ let isPanning = false; let startX = 0, startY = 0, startPanX = 0, startPanY = 0;
+ let allowPan = false; // Space key
+ let panMode = false; // Explicit pan mode toggle for touch/one-button mice
const isPanMouseButton = (e) => e.button === 1 || e.button === 2;
- const setCursor = (val) => { if (panStage) panStage.style.cursor = val; };
- const isPanActive = (e) => panMode || allowPan || isPanMouseButton(e);
+ const setCursor = (val) => { if (panStage) panStage.style.cursor = val; };
+ const isPanActive = (e) => panMode || allowPan || isPanMouseButton(e);
const updatePanModeBtn = () => {
if (!panModeBtn) return;
panModeBtn.classList.toggle('active', panMode);
@@ -5530,7 +6295,7 @@
panX = startPanX + dx; panY = startPanY + dy; applyPan();
});
window.addEventListener('mouseup', () => { if (isPanning) { isPanning = false; setCursor(allowPan ? 'grab' : ''); }});
- panStage.addEventListener('wheel', (e) => {
+ panStage.addEventListener('wheel', (e) => {
if (!e.ctrlKey && !e.metaKey) return;
e.preventDefault();
const rect = panStage.getBoundingClientRect();
@@ -5616,8 +6381,8 @@
heightSlider.addEventListener('pointerdown', markDragStart);
widthSlider.addEventListener('pointerup', markDragEnd);
heightSlider.addEventListener('pointerup', markDragEnd);
- widthSlider.addEventListener("input", () => { onWidthInput(); schedulePreview(); });
- heightSlider.addEventListener("input", () => { onHeightInput(); schedulePreview(); });
+ widthSlider.addEventListener("input", () => { onWidthInput(); schedulePreview(); });
+ heightSlider.addEventListener("input", () => { onHeightInput(); schedulePreview(); });
// Mask painting UX: brush size, modes, row/column fills, and precise coords
let draggingMask = false;
@@ -5659,11 +6424,11 @@
rowColSizeValEl.textContent = rowColSizeEl.value;
rowColSize = parseInt(rowColSizeEl.value, 10) || 1;
}
- if (btnIgnore) btnIgnore.addEventListener('click', () => setMode('ignore'));
- if (btnUnignore) btnUnignore.addEventListener('click', () => setMode('unignore'));
- if (btnToggle) btnToggle.addEventListener('click', () => setMode('toggle'));
- // Initialize button state (default to toggle mode)
- updateModeButtons();
+ if (btnIgnore) btnIgnore.addEventListener('click', () => setMode('ignore'));
+ if (btnUnignore) btnUnignore.addEventListener('click', () => setMode('unignore'));
+ if (btnToggle) btnToggle.addEventListener('click', () => setMode('toggle'));
+ // Initialize button state (default to toggle mode)
+ updateModeButtons();
const mapClientToPixel = (clientX, clientY) => {
// Compute without rounding until final step to avoid drift at higher zoom
@@ -5718,12 +6483,12 @@
const w = baseCanvas.width, h = baseCanvas.height;
ensureMask(w, h);
if (y < 0 || y >= h) return;
-
+
// Paint multiple rows based on rowColSize
const halfSize = Math.floor(rowColSize / 2);
const startY = Math.max(0, y - halfSize);
const endY = Math.min(h - 1, y + halfSize);
-
+
for (let rowY = startY; rowY <= endY; rowY++) {
for (let x = 0; x < w; x++) {
const idx = rowY * w + x;
@@ -5750,12 +6515,12 @@
const w = baseCanvas.width, h = baseCanvas.height;
ensureMask(w, h);
if (x < 0 || x >= w) return;
-
+
// Paint multiple columns based on rowColSize
const halfSize = Math.floor(rowColSize / 2);
const startX = Math.max(0, x - halfSize);
const endX = Math.min(w - 1, x + halfSize);
-
+
for (let colX = startX; colX <= endX; colX++) {
for (let y = 0; y < h; y++) {
const idx = y * w + colX;
@@ -5801,14 +6566,14 @@
redrawMaskOverlay();
};
- maskCanvas.addEventListener('mousedown', (e) => {
+ maskCanvas.addEventListener('mousedown', (e) => {
if (e.button === 1 || e.button === 2 || allowPan) return; // let pan handler manage
draggingMask = true; handlePaint(e);
});
- // Avoid hijacking touch gestures for panning/zooming
- maskCanvas.addEventListener('touchstart', (e) => { /* let panStage handle */ }, { passive: true });
- maskCanvas.addEventListener('touchmove', (e) => { /* let panStage handle */ }, { passive: true });
- maskCanvas.addEventListener('touchend', (e) => { /* let panStage handle */ }, { passive: true });
+ // Avoid hijacking touch gestures for panning/zooming
+ maskCanvas.addEventListener('touchstart', (e) => { /* let panStage handle */ }, { passive: true });
+ maskCanvas.addEventListener('touchmove', (e) => { /* let panStage handle */ }, { passive: true });
+ maskCanvas.addEventListener('touchend', (e) => { /* let panStage handle */ }, { passive: true });
window.addEventListener('mousemove', (e) => { if (draggingMask) handlePaint(e); });
window.addEventListener('mouseup', () => { if (draggingMask) { draggingMask = false; saveBotSettings(); }});
@@ -5839,15 +6604,15 @@
tempCanvas.width = newWidth;
tempCanvas.height = newHeight;
tempCtx.imageSmoothingEnabled = false;
- if (baseProcessor !== processor && (!baseProcessor.img || !baseProcessor.canvas)) {
+ if (baseProcessor !== processor && (!baseProcessor.img || !baseProcessor.canvas)) {
await baseProcessor.load();
}
tempCtx.drawImage(baseProcessor.img, 0, 0, newWidth, newHeight);
- const imgData = tempCtx.getImageData(0, 0, newWidth, newHeight);
+ const imgData = tempCtx.getImageData(0, 0, newWidth, newHeight);
const data = imgData.data;
const tThresh2 = state.customTransparencyThreshold || CONFIG.TRANSPARENCY_THRESHOLD;
let totalValidPixels = 0;
- const mask = (state.resizeIgnoreMask && state.resizeIgnoreMask.length === newWidth * newHeight) ? state.resizeIgnoreMask : null;
+ const mask = (state.resizeIgnoreMask && state.resizeIgnoreMask.length === newWidth * newHeight) ? state.resizeIgnoreMask : null;
const applyFSDitherFinal = async () => {
const w = newWidth, h = newHeight;
@@ -5942,16 +6707,16 @@
state.totalPixels = totalValidPixels;
state.paintedPixels = 0;
- state.resizeSettings = { baseWidth: width, baseHeight: height, width: newWidth, height: newHeight };
+ state.resizeSettings = { baseWidth: width, baseHeight: height, width: newWidth, height: newHeight };
saveBotSettings();
const finalImageBitmap = await createImageBitmap(tempCanvas);
await overlayManager.setImage(finalImageBitmap);
- overlayManager.enable();
- toggleOverlayBtn.classList.add('active');
- toggleOverlayBtn.setAttribute('aria-pressed', 'true');
+ overlayManager.enable();
+ toggleOverlayBtn.classList.add('active');
+ toggleOverlayBtn.setAttribute('aria-pressed', 'true');
- // Keep state.imageData.processor as the original-based source; painting uses paletted pixels already stored
+ // Keep state.imageData.processor as the original-based source; painting uses paletted pixels already stored
updateStats();
updateUI("resizeSuccess", "success", { width: newWidth, height: newHeight });
@@ -6002,7 +6767,7 @@
}
function closeResizeDialog() {
- try { if (typeof _resizeDialogCleanup === 'function') { _resizeDialogCleanup(); } } catch {}
+ try { if (typeof _resizeDialogCleanup === 'function') { _resizeDialogCleanup(); } } catch {}
resizeOverlay.style.display = "none";
resizeContainer.style.display = "none";
_updateResizePreview = () => { };
@@ -6010,7 +6775,7 @@
try { if (_previewTimer) { clearTimeout(_previewTimer); _previewTimer = null; } } catch {}
_maskImageData = null; _maskData = null; _dirty = null;
_ditherWorkBuf = null; _ditherEligibleBuf = null;
- _resizeDialogCleanup = null;
+ _resizeDialogCleanup = null;
}
if (uploadBtn) {
@@ -6069,10 +6834,10 @@
state.paintedPixels = 0
state.imageLoaded = true
state.lastPosition = { x: 0, y: 0 }
-
+
// Initialize painted map for tracking
Utils.initializePaintedMap(width, height);
-
+
// New image: clear previous resize settings
state.resizeSettings = null;
// Also clear any previous ignore mask
@@ -6132,9 +6897,9 @@
const tempFetch = async (url, options) => {
if (
- typeof url === "string" &&
- url.includes("https://backend.wplace.live/s0/pixel/") &&
- options?.method?.toUpperCase() === "POST"
+ typeof url === "string" &&
+ url.includes("https://backend.wplace.live/s0/pixel/") &&
+ options?.method?.toUpperCase() === "POST"
) {
try {
const response = await originalFetch(url, options)
@@ -6260,11 +7025,11 @@
const progress = Math.round((savedData.state.paintedPixels / savedData.state.totalPixels) * 100)
Utils.showAlert(
- `${Utils.t("savedDataFound")}\n\n` +
- `Saved: ${savedDate}\n` +
- `Progress: ${savedData.state.paintedPixels}/${savedData.state.totalPixels} pixels (${progress}%)\n` +
- `${Utils.t("clickLoadToContinue")}`,
- "info",
+ `${Utils.t("savedDataFound")}\n\n` +
+ `Saved: ${savedDate}\n` +
+ `Progress: ${savedData.state.paintedPixels}/${savedData.state.totalPixels} pixels (${progress}%)\n` +
+ `${Utils.t("clickLoadToContinue")}`,
+ "info",
)
}
}
@@ -6335,7 +7100,7 @@
break outerLoop
}
-
+
const idx = (y * width + x) * 4
const r = pixels[idx]
const g = pixels[idx + 1]
@@ -6370,15 +7135,15 @@
let pixelY = absY % 1000;
if (!pixelBatch ||
- pixelBatch.regionX !== regionX + adderX ||
- pixelBatch.regionY !== regionY + adderY) {
+ pixelBatch.regionX !== regionX + adderX ||
+ pixelBatch.regionY !== regionY + adderY) {
if (pixelBatch && pixelBatch.pixels.length > 0) {
console.log(`๐ Sending region-change batch with ${pixelBatch.pixels.length} pixels (switching to region ${regionX + adderX},${regionY + adderY})`);
const success = await sendBatchWithRetry(pixelBatch.pixels, pixelBatch.regionX, pixelBatch.regionY);
-
+
if (success) {
- pixelBatch.pixels.forEach((p) => {
+ pixelBatch.pixels.forEach((p) => {
state.paintedPixels++;
// Mark pixel as painted in map
Utils.markPixelPainted(p.x, p.y, pixelBatch.regionX, pixelBatch.regionY);
@@ -6414,7 +7179,7 @@
};
}
-
+
try {
const tileRegionX = pixelBatch ? (pixelBatch.regionX) : (regionX + adderX);
const tileRegionY = pixelBatch ? (pixelBatch.regionY) : (regionY + adderY);
@@ -6502,13 +7267,13 @@
current: state.currentCharges
});
await updateStats();
-
+
// Allow auto save during cooldown
Utils.performSmartSave();
-
+
await Utils.sleep(state.cooldown);
}
-
+
// Disable save button again after exiting wait loop (back to normal painting)
if (!state.stopFlag) {
saveBtn.disabled = true;
@@ -6530,7 +7295,7 @@
state.currentCharges -= pixelBatch.pixels.length;
// Final save with painted map
Utils.saveProgress();
-
+
if (CONFIG.PAINTING_SPEED_ENABLED && state.paintingSpeed > 0 && pixelBatch.pixels.length > 0) {
const delayPerPixel = 1000 / state.paintingSpeed // ms per pixel
const totalDelay = Math.max(100, delayPerPixel * pixelBatch.pixels.length) // minimum 100ms
@@ -6578,7 +7343,7 @@
// Helper function to calculate batch size based on mode
function calculateBatchSize() {
let targetBatchSize;
-
+
if (state.batchMode === 'random') {
// Generate random batch size within the specified range
const min = Math.max(1, state.randomBatchMin);
@@ -6589,11 +7354,11 @@
// Normal mode - use the fixed paintingSpeed value
targetBatchSize = state.paintingSpeed;
}
-
+
// Always limit by available charges
const maxAllowed = Math.floor(state.currentCharges);
const finalBatchSize = Math.min(targetBatchSize, maxAllowed);
-
+
return finalBatchSize;
}
@@ -6603,9 +7368,9 @@
while (attempt < maxRetries && !state.stopFlag) {
attempt++;
console.log(`๐ Attempting to send batch (attempt ${attempt}/${maxRetries}) for region ${regionX},${regionY} with ${pixels.length} pixels`);
-
+
const result = await sendPixelBatch(pixels, regionX, regionY);
-
+
if (result === true) {
console.log(`โ
Batch succeeded on attempt ${attempt}`);
return true;
@@ -6631,19 +7396,19 @@
await Utils.sleep(baseDelay + jitter);
}
}
-
+
if (attempt >= maxRetries) {
console.error(`โ Batch failed after ${maxRetries} attempts (MAX_BATCH_RETRIES=${MAX_BATCH_RETRIES}). This will stop painting to prevent infinite loops.`);
updateUI("paintingError", "error");
return false;
}
-
+
return false;
}
async function sendPixelBatch(pixelBatch, regionX, regionY) {
let token = turnstileToken;
-
+
// Generate new token if we don't have one
if (!token) {
try {
@@ -6680,13 +7445,13 @@
let data = null
try { data = await res.json() } catch (_) { }
console.error("โ 403 Forbidden. Turnstile token might be invalid or expired.")
-
+
// Try to generate a new token and retry once
try {
console.log("๐ Regenerating Turnstile token after 403...");
token = await handleCaptcha();
turnstileToken = token;
-
+
// Retry the request with new token
const retryPayload = { coords, colors, t: token };
const retryRes = await fetch(`https://backend.wplace.live/s0/pixel/${regionX}/${regionY}`, {
@@ -6695,16 +7460,16 @@
credentials: "include",
body: JSON.stringify(retryPayload),
});
-
+
if (retryRes.status === 403) {
turnstileToken = null;
tokenPromise = new Promise((resolve) => { _resolveToken = resolve });
return "token_error";
}
-
+
const retryData = await retryRes.json();
return retryData?.painted === pixelBatch.length;
-
+
} catch (retryError) {
console.error("โ Token regeneration failed:", retryError);
turnstileToken = null;
@@ -6712,7 +7477,7 @@
return "token_error";
}
}
-
+
const data = await res.json()
return data?.painted === pixelBatch.length
} catch (e) {
@@ -6734,19 +7499,19 @@
minimized: state.minimized,
overlayOpacity: state.overlayOpacity,
blueMarbleEnabled: document.getElementById('enableBlueMarbleToggle')?.checked,
- ditheringEnabled: state.ditheringEnabled,
- colorMatchingAlgorithm: state.colorMatchingAlgorithm,
- enableChromaPenalty: state.enableChromaPenalty,
- chromaPenaltyWeight: state.chromaPenaltyWeight,
- customTransparencyThreshold: state.customTransparencyThreshold,
- customWhiteThreshold: state.customWhiteThreshold,
- paintWhitePixels: state.paintWhitePixels,
- resizeSettings: state.resizeSettings,
- originalImage: state.originalImage,
- // Save ignore mask (as base64) with its dimensions
- resizeIgnoreMask: (state.resizeIgnoreMask && state.resizeSettings && state.resizeSettings.width * state.resizeSettings.height === state.resizeIgnoreMask.length)
- ? { w: state.resizeSettings.width, h: state.resizeSettings.height, data: btoa(String.fromCharCode(...state.resizeIgnoreMask)) }
- : null,
+ ditheringEnabled: state.ditheringEnabled,
+ colorMatchingAlgorithm: state.colorMatchingAlgorithm,
+ enableChromaPenalty: state.enableChromaPenalty,
+ chromaPenaltyWeight: state.chromaPenaltyWeight,
+ customTransparencyThreshold: state.customTransparencyThreshold,
+ customWhiteThreshold: state.customWhiteThreshold,
+ paintWhitePixels: state.paintWhitePixels,
+ resizeSettings: state.resizeSettings,
+ originalImage: state.originalImage,
+ // Save ignore mask (as base64) with its dimensions
+ resizeIgnoreMask: (state.resizeIgnoreMask && state.resizeSettings && state.resizeSettings.width * state.resizeSettings.height === state.resizeIgnoreMask.length)
+ ? { w: state.resizeSettings.width, h: state.resizeSettings.height, data: btoa(String.fromCharCode(...state.resizeIgnoreMask)) }
+ : null,
// Notifications
notificationsEnabled: state.notificationsEnabled,
notifyOnChargesReached: state.notifyOnChargesReached,
@@ -6779,31 +7544,31 @@
CONFIG.AUTO_CAPTCHA_ENABLED = settings.autoCaptchaEnabled ?? false;
state.overlayOpacity = settings.overlayOpacity ?? CONFIG.OVERLAY.OPACITY_DEFAULT;
state.blueMarbleEnabled = settings.blueMarbleEnabled ?? CONFIG.OVERLAY.BLUE_MARBLE_DEFAULT;
- state.ditheringEnabled = settings.ditheringEnabled ?? false;
- state.colorMatchingAlgorithm = settings.colorMatchingAlgorithm || 'lab';
- state.enableChromaPenalty = settings.enableChromaPenalty ?? true;
- state.chromaPenaltyWeight = settings.chromaPenaltyWeight ?? 0.15;
- state.customTransparencyThreshold = settings.customTransparencyThreshold ?? CONFIG.TRANSPARENCY_THRESHOLD;
- state.customWhiteThreshold = settings.customWhiteThreshold ?? CONFIG.WHITE_THRESHOLD;
- state.paintWhitePixels = settings.paintWhitePixels ?? true;
- state.resizeSettings = settings.resizeSettings ?? null;
- state.originalImage = settings.originalImage ?? null;
+ state.ditheringEnabled = settings.ditheringEnabled ?? false;
+ state.colorMatchingAlgorithm = settings.colorMatchingAlgorithm || 'lab';
+ state.enableChromaPenalty = settings.enableChromaPenalty ?? true;
+ state.chromaPenaltyWeight = settings.chromaPenaltyWeight ?? 0.15;
+ state.customTransparencyThreshold = settings.customTransparencyThreshold ?? CONFIG.TRANSPARENCY_THRESHOLD;
+ state.customWhiteThreshold = settings.customWhiteThreshold ?? CONFIG.WHITE_THRESHOLD;
+ state.paintWhitePixels = settings.paintWhitePixels ?? true;
+ state.resizeSettings = settings.resizeSettings ?? null;
+ state.originalImage = settings.originalImage ?? null;
// Notifications
state.notificationsEnabled = settings.notificationsEnabled ?? CONFIG.NOTIFICATIONS.ENABLED;
state.notifyOnChargesReached = settings.notifyOnChargesReached ?? CONFIG.NOTIFICATIONS.ON_CHARGES_REACHED;
state.notifyOnlyWhenUnfocused = settings.notifyOnlyWhenUnfocused ?? CONFIG.NOTIFICATIONS.ONLY_WHEN_UNFOCUSED;
state.notificationIntervalMinutes = settings.notificationIntervalMinutes ?? CONFIG.NOTIFICATIONS.REPEAT_MINUTES;
- // Restore ignore mask if dims match current resizeSettings
- if (settings.resizeIgnoreMask && settings.resizeIgnoreMask.data && state.resizeSettings && settings.resizeIgnoreMask.w === state.resizeSettings.width && settings.resizeIgnoreMask.h === state.resizeSettings.height) {
- try {
- const bin = atob(settings.resizeIgnoreMask.data);
- const arr = new Uint8Array(bin.length);
- for (let i=0;i
+ WPlace AutoBOT
+Este proyecto es un fork de https://github.com/DarkModde/WPlace-AutoBOT
+
+ Considera unirte a nuestro discord: https://discord.gg/knkNRYyQcm
+
+ ยกScripts prรกcticos con menรบs intuitivos para hacer tu vida mรกs fรกcil en WPlace!
+ Si te preguntas, ยกsรญ! Estos scripts fueron desarrollados por un equipo conformado por brasileรฑos, vietnamitas e indonesios. Jajajaj!
+
+
+
+ Perfectos para quienes quieren subir de nivel automรกticamente o construir pixel arts masivos โ sin perder tiempo.
+
+ Advertencia: Este es un script puramente educativo para demostrar lo que se puede hacer en wplace.live, no somos responsables de lo que suceda si usas el script.
+
+
+
+
+
+
+
+
๐โCรณmo usar los scripts:
+ +
+ Es sรบper fรกcil: copia uno de los cรณdigos de abajo, pรฉgalo en la barra de marcadores de tu navegador,
+ y ejecรบtalo mientras estรฉs en wplace.live.
+
1. Ninguno de los scripts funcionarรก si cierras la pestaรฑa del navegador. Debes mantener la pestaรฑa de WPlace abierta, incluso si estรก en segundo plano.
+>2. Nunca ejecutes ambos scripts en la misma pรกgina โ esto puede causar errores en tu WPlace. Si sucede, ยกsolo refresca la pรกgina!
+>3. Si el script no detecta los colores disponibles, solo haz clic en el botรณn "Pintar" para mostrarlos โ solo entonces inicia el Auto-Image.
+ +
+
+
+
+
WPlace AutoBOT
++ะฆะตะน ะฟัะพัะบั ั ัะพัะบะพะผ ะท https://github.com/DarkModde/WPlace-AutoBOT +
++ ะะฐะฒััะฐะนัะต ะฝะฐ ะฝะฐั Discord: https://discord.gg/knkNRYyQcm +
+
+ ะัะฐะบัะธัะฝั ัะบัะธะฟัะธ ะท ัะฝััััะธะฒะฝะธะผ ะผะตะฝั, ัะพะฑ ะทัะพะฑะธัะธ ะฒะฐัะต ะถะธััั ะฝะฐ WPlace ะฟัะพััััะธะผ!
+ ะะดะตะฐะปัะฝะพ ะดะปั ัะธั
, ั
ัะพ ั
ะพัะต ะฐะฒัะพะผะฐัะธัะฝะพ ะฟัะพะบะฐััะฒะฐัะธ ััะฒะตะฝั ะฐะฑะพ ะฑัะดัะฒะฐัะธ ะผะฐัะธะฒะฝั ะฟัะบัะตะปั-ะฐััะธ โ ะฑะตะท ะฒะธััะฐัะธ ัะฐัั.
+
+ โ ๏ธ ะฃะฒะฐะณะฐ: ะฆะต ะพัะฒััะฝัะน ัะบัะธะฟั, ััะฒะพัะตะฝะธะน ะดะปั ะดะตะผะพะฝัััะฐััั ะผะพะถะปะธะฒะพััะตะน ะฝะฐ wplace.live. ะะธ ะฝะต ะฝะตัะตะผะพ ะฒัะดะฟะพะฒัะดะฐะปัะฝะพััั ะทะฐ ะฝะฐัะปัะดะบะธ ะนะพะณะพ ะฒะธะบะพัะธััะฐะฝะฝั.
+
+ ะฏะบัะพ ะฒะธ ััะบะฐะฒะธัะตัั โ ัะฐะบ! ะกะบัะธะฟัะธ ะฑัะปะธ ัะพะทัะพะฑะปะตะฝั ะบะพะผะฐะฝะดะพั ะท ะัะฐะทะธะปัั, ะโััะฝะฐะผั ัะฐ ะะฝะดะพะฝะตะทัั. HUEHUE! +
+
+
+
+
+
+
๐โะฏะบ ะบะพัะธัััะฒะฐัะธัั ัะบัะธะฟัะฐะผะธ:
+ +
+ ะฆะต ะดัะถะต ะฟัะพััะพ: ัะบะพะฟััะนัะต ะพะดะธะฝ ะท ะบะพะดัะฒ ะฝะธะถัะต, ะฒััะฐะฒัะต ะนะพะณะพ ั ะฟะฐะฝะตะปั ะทะฐะบะปะฐะดะพะบ ะฒะฐัะพะณะพ ะฑัะฐัะทะตัะฐ,
+ ัะฐ ะทะฐะฟัััััั, ะบะพะปะธ ะฟะตัะตะฑัะฒะฐััะต ะฝะฐ wplace.live.
+
1. ะะพะดะตะฝ ัะบัะธะฟั ะฝะต ะฟัะฐััะฒะฐัะธะผะต, ัะบัะพ ะฒะธ ะทะฐะบัะธััะต ะฒะบะปะฐะดะบั ะฑัะฐัะทะตัะฐ. ะะบะปะฐะดะบะฐ WPlace ะผะฐั ะทะฐะปะธัะฐัะธัั ะฒัะดะบัะธัะพั, ะฝะฐะฒััั ั ัะพะฝั.
+>2. ะัะบะพะปะธ ะฝะต ะทะฐะฟััะบะฐะนัะต ะพะฑะธะดะฒะฐ ัะบัะธะฟัะธ ะฝะฐ ะพะดะฝัะน ััะพััะฝัั โ ัะต ะผะพะถะต ะทะปะฐะผะฐัะธ WPlace. ะฏะบัะพ ัะต ััะฐะปะพัั โ ะฟัะพััะพ ะพะฝะพะฒััั ััะพััะฝะบั!
+>3. ะฏะบัะพ ัะบัะธะฟั ะฝะต ะฒะธะทะฝะฐัะฐั ะดะพัััะฟะฝั ะบะพะปัะพัะธ, ะฝะฐัะธัะฝััั ะบะฝะพะฟะบั ยซPaintยป, ัะพะฑ ะฒะพะฝะธ ะทโัะฒะธะปะธัั โ ะปะธัะต ัะพะดั ะทะฐะฟััะบะฐะนัะต Auto-Imager.
+ +
+
+
-
-
WPlace AutoBOT
--Este proyecto es un fork de https://github.com/DarkModde/WPlace-AutoBOT -
-- Considera unirte a nuestro discord: https://discord.gg/knkNRYyQcm -
-
- ยกScripts prรกcticos con menรบs intuitivos para hacer tu vida mรกs fรกcil en WPlace!
- Perfectos para quienes quieren subir de nivel automรกticamente o construir pixel arts masivos โ sin perder tiempo.
-
- Advertencia: Este es un script puramente educativo para demostrar lo que se puede hacer en wplace.live, no somos responsables de lo que suceda si usas el script.
-
- Si te preguntas, ยกsรญ! Estos scripts fueron desarrollados por un equipo conformado por brasileรฑos, vietnamitas e indonesios. Jajajaj! -
-
-
-
-
-
-
๐โCรณmo usar los scripts:
- -
- Es sรบper fรกcil: copia uno de los cรณdigos de abajo, pรฉgalo en la barra de marcadores de tu navegador,
- y ejecรบtalo mientras estรฉs en wplace.live.
-
1. Ninguno de los scripts funcionarรก si cierras la pestaรฑa del navegador. Debes mantener la pestaรฑa de WPlace abierta, incluso si estรก en segundo plano.
->2. Nunca ejecutes ambos scripts en la misma pรกgina โ esto puede causar errores en tu WPlace. Si sucede, ยกsolo refresca la pรกgina!
->3. Si el script no detecta los colores disponibles, solo haz clic en el botรณn "Pintar" para mostrarlos โ solo entonces inicia el Auto-Image.
- -
-
-