diff --git a/Auto-Image.js b/Auto-Image.js
index d05e62a..eca8c56 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,
@@ -146,6 +146,27 @@
pixelBlink: false,
},
},
+ "Classic Light": {
+ primary: "#ffffff",
+ secondary: "#f8f9fa",
+ accent: "#e9ecef",
+ text: "#212529",
+ highlight: "#6f42c1",
+ success: "#28a745",
+ error: "#dc3545",
+ warning: "#ffc107",
+ fontFamily: "'Segoe UI', Roboto, sans-serif",
+ borderRadius: "12px",
+ borderStyle: "solid",
+ borderWidth: "1px",
+ boxShadow: "0 8px 32px rgba(0,0,0,0.15), 0 0 0 1px rgba(0,0,0,0.08)",
+ backdropFilter: "blur(10px)",
+ animations: {
+ glow: false,
+ scanline: false,
+ pixelBlink: false,
+ },
+ },
"Neon Retro": {
primary: "#1a1a2e",
secondary: "#16213e",
@@ -177,20 +198,56 @@
const getCurrentTheme = () => CONFIG.THEMES[CONFIG.currentTheme]
const switchTheme = (themeName) => {
- if (CONFIG.THEMES[themeName]) {
- CONFIG.currentTheme = themeName
- saveThemePreference()
+ if (CONFIG.THEMES[themeName]) {
+ CONFIG.currentTheme = themeName
+ saveThemePreference()
- // Remove existing theme styles
- const existingStyle = document.querySelector('style[data-wplace-theme="true"]')
- if (existingStyle) {
- existingStyle.remove()
- }
+ // APPLY THEME VARS/CLASS (new)
+ applyTheme()
- // Recreate UI with new theme (cleanup is handled in createUI)
- createUI()
- }
+ // Recreate UI (kept for now)
+ createUI()
}
+}
+
+// Add this helper (place it after getCurrentTheme/switchTheme definitions)
+function applyTheme() {
+ const theme = getCurrentTheme();
+ // Toggle theme class on documentElement so CSS vars cascade to our UI
+ document.documentElement.classList.remove('wplace-theme--classic', 'wplace-theme--classic-light', 'wplace-theme--neon');
+
+ let themeClass = 'wplace-theme--classic'; // default
+ if (CONFIG.currentTheme === 'Neon Retro') {
+ themeClass = 'wplace-theme--neon';
+ } else if (CONFIG.currentTheme === 'Classic Light') {
+ themeClass = 'wplace-theme--classic-light';
+ }
+
+ document.documentElement.classList.add(themeClass);
+
+ // Also set CSS variables explicitly in case you want runtime overrides
+ const root = document.documentElement;
+ const setVar = (k, v) => { try { root.style.setProperty(k, v); } catch {} };
+
+ setVar('--wplace-primary', theme.primary);
+ setVar('--wplace-secondary', theme.secondary);
+ setVar('--wplace-accent', theme.accent);
+ setVar('--wplace-text', theme.text);
+ setVar('--wplace-highlight', theme.highlight);
+ setVar('--wplace-success', theme.success);
+ setVar('--wplace-error', theme.error);
+ setVar('--wplace-warning', theme.warning);
+
+ // Typography + look
+ setVar('--wplace-font', theme.fontFamily || "'Segoe UI', Roboto, sans-serif");
+ setVar('--wplace-radius', ('' + (theme.borderRadius || '12px')));
+ setVar('--wplace-border-style', (('' + (theme.borderStyle || 'solid'))));
+ setVar('--wplace-border-width', (('' + (theme.borderWidth || '1px'))));
+ setVar('--wplace-backdrop', (('' + (theme.backdropFilter || 'blur(10px)'))));
+ setVar('--wplace-border-color', 'rgba(255,255,255,0.1)');
+}
+
+
const saveThemePreference = () => {
try {
@@ -329,28 +386,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 = () => { };
@@ -383,8 +440,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;
@@ -451,7 +508,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;
@@ -480,7 +537,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);
@@ -528,18 +585,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);
}
@@ -668,7 +725,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;
@@ -681,7 +738,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
});
@@ -738,7 +795,7 @@
}
tokenGenerationInProgress = true;
-
+
try {
console.log("๐ Token expired or missing, generating new one...");
const token = await handleCaptchaWithRetry();
@@ -877,6 +934,18 @@
}
});
+ async function detectLanguage() {
+ try {
+ const response = await fetch("https://backend.wplace.live/me", {
+ credentials: "include",
+ })
+ const data = await response.json()
+ state.language = data.language === "pt" ? "pt" : "en"
+ } catch {
+ state.language = navigator.language.startsWith("pt") ? "pt" : "en"
+ }
+ }
+
// UTILITY FUNCTIONS
const Utils = {
sleep: (ms) => new Promise((r) => setTimeout(r, ms)),
@@ -904,7 +973,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"]')) {
@@ -918,7 +987,7 @@
};
return checkReady();
}
-
+
const script = document.createElement('script');
script.src = 'https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit';
script.async = true;
@@ -943,20 +1012,9 @@
if (this._turnstileContainer) {
this._turnstileContainer.remove();
}
-
+
this._turnstileContainer = document.createElement('div');
- this._turnstileContainer.style.cssText = `
- position: fixed !important;
- left: -99999px !important;
- top: -99999px !important;
- width: 1px !important;
- height: 1px !important;
- pointer-events: none !important;
- opacity: 0 !important;
- visibility: hidden !important;
- z-index: -99999 !important;
- overflow: hidden !important;
- `;
+ this._turnstileContainer.className = 'wplace-turnstile-hidden';
this._turnstileContainer.setAttribute('aria-hidden', 'true');
this._turnstileContainer.id = 'turnstile-widget-container';
document.body.appendChild(this._turnstileContainer);
@@ -972,23 +1030,7 @@
const overlay = document.createElement('div');
overlay.id = 'turnstile-overlay-container';
- overlay.style.cssText = `
- position: fixed !important;
- bottom: 20px !important;
- right: 20px !important;
- z-index: 99999 !important;
- background: rgba(0,0,0,0.9) !important;
- border-radius: 12px !important;
- padding: 20px !important;
- box-shadow: 0 8px 32px rgba(0,0,0,0.4) !important;
- backdrop-filter: blur(10px) !important;
- border: 1px solid rgba(255,255,255,0.2) !important;
- color: white !important;
- font-family: 'Segoe UI', sans-serif !important;
- display: none !important;
- max-width: 350px !important;
- min-width: 300px !important;
- `;
+ overlay.className = 'wplace-turnstile-overlay wplace-overlay-hidden';
const title = document.createElement('div');
title.textContent = Utils.t('turnstileInstructions');
@@ -996,11 +1038,11 @@
const host = document.createElement('div');
host.id = 'turnstile-overlay-host';
- host.style.cssText = 'width: 100%; min-height: 70px;';
+ host.className = 'wplace-turnstile-host';
const hideBtn = document.createElement('button');
hideBtn.textContent = Utils.t('hideTurnstileBtn');
- hideBtn.style.cssText = 'position:absolute; top:6px; right:6px; font-size:11px; background:transparent; color:#fff; border:1px solid rgba(255,255,255,0.2); border-radius:6px; padding:2px 6px; cursor:pointer;';
+ hideBtn.className = 'wplace-turnstile-hide-btn';
hideBtn.addEventListener('click', () => overlay.remove());
overlay.appendChild(title);
@@ -1047,24 +1089,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,
@@ -1079,10 +1121,10 @@
'error-callback': () => resolve(null),
'timeout-callback': () => resolve(null)
});
-
+
this._turnstileWidgetId = widgetId;
this._lastSitekey = sitekey;
-
+
if (!widgetId) {
return resolve(null);
}
@@ -1102,31 +1144,33 @@
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.classList.remove('wplace-overlay-hidden');
overlay.style.display = 'block';
-
+
const host = overlay.querySelector('#turnstile-overlay-host');
host.innerHTML = '';
-
+
// Set a timeout for interactive mode
const timeout = setTimeout(() => {
console.warn('โฐ Interactive Turnstile widget timeout');
+ overlay.classList.add('wplace-overlay-hidden');
overlay.style.display = 'none';
resolve(null);
}, 60000); // 60 seconds for user interaction
-
+
const widgetId = window.turnstile.render(host, {
sitekey,
action,
@@ -1134,9 +1178,10 @@
theme: 'light',
callback: (token) => {
clearTimeout(timeout);
- overlay.style.display = 'none';
+ overlay.classList.add('wplace-overlay-hidden');
+ overlay.style.display = 'none';
console.log('โ
Interactive Turnstile completed successfully');
-
+
if (typeof token === 'string' && token.length > 20) {
resolve(token);
} else {
@@ -1146,17 +1191,19 @@
},
'error-callback': (error) => {
clearTimeout(timeout);
- overlay.style.display = 'none';
+ overlay.classList.add('wplace-overlay-hidden');
+ overlay.style.display = 'none';
console.warn('โ Interactive Turnstile error:', error);
resolve(null);
},
});
-
+
this._turnstileWidgetId = widgetId;
this._lastSitekey = sitekey;
-
+
if (!widgetId) {
clearTimeout(timeout);
+ overlay.classList.add('wplace-overlay-hidden');
overlay.style.display = 'none';
console.warn('โ Failed to create interactive Turnstile widget');
resolve(null);
@@ -1183,15 +1230,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;
@@ -1269,11 +1316,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;
@@ -1361,40 +1408,7 @@
showAlert: (message, type = "info") => {
const alertDiv = document.createElement("div")
- alertDiv.style.cssText = `
- position: fixed;
- top: 20px;
- left: 50%;
- transform: translateX(-50%);
- padding: 12px 20px;
- border-radius: 8px;
- color: white;
- font-weight: 600;
- z-index: 10001;
- max-width: 400px;
- text-align: center;
- box-shadow: 0 4px 12px rgba(0,0,0,0.3);
- animation: slideDown 0.3s ease-out;
- font-family: 'Segoe UI', sans-serif;
- `
-
- const colors = {
- info: "background: linear-gradient(135deg, #3498db, #2980b9);",
- success: "background: linear-gradient(135deg, #27ae60, #229954);",
- warning: "background: linear-gradient(135deg, #f39c12, #e67e22);",
- error: "background: linear-gradient(135deg, #e74c3c, #c0392b);",
- }
-
- alertDiv.style.cssText += colors[type] || colors.info
-
- const style = document.createElement("style")
- style.textContent = `
- @keyframes slideDown {
- from { transform: translateX(-50%) translateY(-20px); opacity: 0; }
- to { transform: translateX(-50%) translateY(0); opacity: 1; }
- }
- `
- document.head.appendChild(style)
+ alertDiv.className = `wplace-alert-base wplace-alert-${type}`
alertDiv.textContent = message
document.body.appendChild(alertDiv)
@@ -1403,7 +1417,6 @@
alertDiv.style.animation = "slideDown 0.3s ease-out reverse"
setTimeout(() => {
document.body.removeChild(alertDiv)
- document.head.removeChild(style)
}, 300)
}, 4000)
},
@@ -1445,7 +1458,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') {
@@ -1499,17 +1512,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" })
@@ -1524,30 +1537,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-"]')
@@ -1618,15 +1631,15 @@
},
calculateEstimatedTime: (remainingPixels, charges, cooldown) => {
- if (remainingPixels <= 0) return 0
+ if (remainingPixels <= 0) return 0;
- const paintingSpeedDelay = state.paintingSpeed > 0 ? (1000 / state.paintingSpeed) : 1000
- const timeFromSpeed = remainingPixels * paintingSpeedDelay
+ const paintingSpeedDelay = state.paintingSpeed > 0 ? (1000 / state.paintingSpeed) : 1000;
+ const timeFromSpeed = remainingPixels * paintingSpeedDelay;
- const cyclesNeeded = Math.ceil(remainingPixels / Math.max(charges, 1))
- const timeFromCharges = cyclesNeeded * cooldown
+ const cyclesNeeded = Math.ceil(remainingPixels / Math.max(charges, 1));
+ const timeFromCharges = cyclesNeeded * cooldown;
- return Math.max(timeFromSpeed, timeFromCharges)
+ return timeFromSpeed + timeFromCharges; // combine instead of taking max
},
// --- Painted pixel tracking helpers ---
@@ -1640,8 +1653,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;
}
@@ -1650,8 +1663,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];
}
@@ -1663,28 +1676,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;
},
@@ -1738,7 +1751,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 };
@@ -1783,7 +1796,7 @@
}
},
- saveProgress: () => {
+ saveProgress: () => {
try {
// Pack painted map if available
let paintedMapPacked = null;
@@ -1812,13 +1825,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,
}
@@ -1888,7 +1901,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;
@@ -1912,7 +1925,7 @@
}
},
- saveProgressToFile: () => {
+ saveProgressToFile: () => {
try {
// Pack painted map if available
let paintedMapPacked = null;
@@ -1941,13 +1954,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,
}
@@ -1960,7 +1973,7 @@
}
},
- loadProgressFromFile: async () => {
+ loadProgressFromFile: async () => {
try {
const data = await Utils.createFileUploader()
if (!data || !data.state) {
@@ -1974,7 +1987,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)
@@ -1991,9 +2004,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);
@@ -2336,7 +2349,7 @@
// Don't re-fetch colors here, use what was captured when user clicked upload
if (!state.availableColors || state.availableColors.length === 0) {
// If no colors have been captured yet, show message
- colorsContainer.innerHTML = `
${Utils.t('uploadImageFirst')}
`;
+ colorsContainer.innerHTML = `${Utils.t('uploadImageFirst')}
`;
return;
}
@@ -2355,7 +2368,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
@@ -2420,29 +2433,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`);
@@ -2453,7 +2466,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...");
@@ -2538,6 +2551,8 @@
async function createUI() {
+ await detectLanguage()
+
const existingContainer = document.getElementById("wplace-image-bot-container")
const existingStats = document.getElementById("wplace-stats-container")
const existingSettings = document.getElementById("wplace-settings-container")
@@ -2554,6 +2569,7 @@
await loadLanguagePreference()
const theme = getCurrentTheme()
+ applyTheme() // <- new: set CSS vars and theme class before building UI
const fontAwesome = document.createElement("link")
fontAwesome.rel = "stylesheet"
@@ -2567,1155 +2583,12 @@
document.head.appendChild(googleFonts)
}
- const style = document.createElement("style")
- style.setAttribute("data-wplace-theme", "true")
-
- style.textContent = `
- ${theme.animations.glow
- ? `
- @keyframes neonGlow {
- 0%, 100% {
- text-shadow: 0 0 5px currentColor, 0 0 10px currentColor, 0 0 15px currentColor;
- }
- 50% {
- text-shadow: 0 0 2px currentColor, 0 0 5px currentColor, 0 0 8px currentColor;
- }
- }`
- : ""
- }
-
- ${theme.animations.pixelBlink
- ? `
- @keyframes pixelBlink {
- 0%, 50% { opacity: 1; }
- 51%, 100% { opacity: 0.7; }
- }`
- : ""
- }
-
- ${theme.animations.scanline
- ? `
- @keyframes scanline {
- 0% { transform: translateY(-100%); }
- 100% { transform: translateY(400px); }
- }`
- : ""
- }
-
- @keyframes pulse {
- 0% { box-shadow: 0 0 0 0 rgba(0, 255, 0, 0.7); }
- 70% { box-shadow: 0 0 0 10px rgba(0, 255, 0, 0); }
- 100% { box-shadow: 0 0 0 0 rgba(0, 255, 0, 0); }
- }
- @keyframes slideIn {
- from { transform: translateY(-10px); opacity: 0; }
- to { transform: translateY(0); opacity: 1; }
- }
- @keyframes shimmer {
- 0% { transform: translateX(-100%); }
- 100% { transform: translateX(100%); }
- }
-
- #wplace-image-bot-container {
- position: fixed;
- top: 20px;
- left: 20px;
- width: ${CONFIG.currentTheme === "Neon Retro" ? "280px" : "280px"};
- max-height: calc(100vh - 40px);
- background: ${CONFIG.currentTheme === "Classic Autobot"
- ? `linear-gradient(135deg, ${theme.primary} 0%, #1a1a1a 100%)`
- : theme.primary
- };
- border: ${theme.borderWidth} ${theme.borderStyle} ${CONFIG.currentTheme === "Classic Autobot" ? theme.accent : theme.text};
- border-radius: ${theme.borderRadius};
- padding: 0;
- box-shadow: ${theme.boxShadow};
- z-index: 9998;
- font-family: ${theme.fontFamily};
- color: ${theme.text};
- animation: slideIn 0.4s ease-out;
- overflow-y: auto; /* Allow scrolling for main panel */
- overflow-x: hidden;
- ${theme.backdropFilter ? `backdrop-filter: ${theme.backdropFilter};` : ""}
- transition: all 0.3s ease;
- user-select: none;
- ${CONFIG.currentTheme === "Neon Retro" ? "image-rendering: pixelated;" : ""}
- }
-
- ${theme.animations.scanline
- ? `
- #wplace-image-bot-container::before {
- content: '';
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- height: 2px;
- background: linear-gradient(90deg, transparent, ${theme.neon}, transparent);
- animation: scanline 3s linear infinite;
- z-index: 1;
- pointer-events: none;
- }`
- : ""
- }
-
- ${CONFIG.currentTheme === "Neon Retro"
- ? `
- #wplace-image-bot-container::after {
- content: '';
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background:
- repeating-linear-gradient(
- 0deg,
- transparent,
- transparent 2px,
- rgba(0, 255, 65, 0.03) 2px,
- rgba(0, 255, 65, 0.03) 4px
- );
- pointer-events: none;
- z-index: 1;
- }`
- : ""
- }
-
- #wplace-image-bot-container.wplace-dragging {
- transition: none;
- box-shadow: 0 12px 40px rgba(0,0,0,0.8), 0 0 0 2px rgba(255,255,255,0.2);
- transform: scale(1.02);
- z-index: 9999;
- }
- #wplace-image-bot-container.wplace-minimized {
- width: 200px;
- height: auto;
- overflow: hidden;
- }
- #wplace-image-bot-container.wplace-compact {
- width: 240px;
- }
-
- /* Stats Container */
- #wplace-stats-container {
- position: fixed;
- top: 20px;
- left: 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
- };
- 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
- };
- color: ${theme.highlight};
- font-size: ${CONFIG.currentTheme === "Neon Retro" ? "11px" : "13px"};
- font-weight: ${CONFIG.currentTheme === "Neon Retro" ? "normal" : "700"};
- display: flex;
- justify-content: space-between;
- align-items: center;
- cursor: move;
- user-select: none;
- border-bottom: ${CONFIG.currentTheme === "Neon Retro" ? "2px" : "1px"} solid ${CONFIG.currentTheme === "Classic Autobot" ? "rgba(255,255,255,0.1)" : theme.text};
- ${CONFIG.currentTheme === "Classic Autobot" ? "text-shadow: 0 1px 2px rgba(0,0,0,0.5);" : "text-transform: uppercase; letter-spacing: 1px;"}
- transition: background 0.2s ease;
- position: relative;
- z-index: 2;
- ${theme.animations.glow ? "animation: neonGlow 2s ease-in-out infinite alternate;" : ""}
- }
-
- .wplace-header-title {
- display: flex;
- align-items: center;
- gap: ${CONFIG.currentTheme === "Neon Retro" ? "6px" : "6px"};
- }
-
- .wplace-header-controls {
- display: flex;
- gap: ${CONFIG.currentTheme === "Neon Retro" ? "6px" : "6px"};
- }
-
- .wplace-header-btn {
- background: ${CONFIG.currentTheme === "Classic Autobot" ? "rgba(255,255,255,0.1)" : theme.accent};
- border: ${CONFIG.currentTheme === "Neon Retro" ? `2px solid ${theme.text}` : "none"};
- color: ${theme.text};
- cursor: pointer;
- border-radius: ${CONFIG.currentTheme === "Classic Autobot" ? "4px" : "0"};
- width: ${CONFIG.currentTheme === "Classic Autobot" ? "18px" : "auto"};
- height: ${CONFIG.currentTheme === "Classic Autobot" ? "18px" : "auto"};
- padding: ${CONFIG.currentTheme === "Neon Retro" ? "4px 6px" : "0"};
- font-size: ${CONFIG.currentTheme === "Neon Retro" ? "8px" : "10px"};
- display: flex;
- align-items: center;
- justify-content: center;
- transition: all 0.2s;
- font-family: ${theme.fontFamily};
- ${CONFIG.currentTheme === "Neon Retro" ? "image-rendering: pixelated;" : ""}
- }
- .wplace-header-btn:hover {
- background: ${CONFIG.currentTheme === "Classic Autobot" ? theme.accent : theme.text};
- color: ${CONFIG.currentTheme === "Classic Autobot" ? theme.text : theme.primary};
- transform: ${CONFIG.currentTheme === "Classic Autobot" ? "scale(1.1)" : "none"};
- ${CONFIG.currentTheme === "Neon Retro" ? `box-shadow: 0 0 10px ${theme.text};` : ""}
- }
-
- .wplace-content {
- padding: ${CONFIG.currentTheme === "Neon Retro" ? "12px" : "12px"};
- display: block;
- position: relative;
- z-index: 2;
- }
- .wplace-content.wplace-hidden {
- display: none;
- }
-
- .wplace-status-section {
- margin-bottom: 12px;
- padding: 8px;
- background: rgba(255,255,255,0.03);
- border-radius: ${theme.borderRadius};
- border: 1px solid rgba(255,255,255,0.1);
- }
-
- .wplace-section {
- margin-bottom: ${CONFIG.currentTheme === "Neon Retro" ? "12px" : "12px"};
- padding: 12px;
- background: rgba(255,255,255,0.03);
- border-radius: ${theme.borderRadius};
- border: 1px solid rgba(255,255,255,0.1);
- }
-
- .wplace-section-title {
- font-size: 11px;
- font-weight: 600;
- margin-bottom: 8px;
- color: ${theme.highlight};
- display: flex;
- align-items: center;
- gap: 6px;
- text-transform: uppercase;
- letter-spacing: 0.5px;
- }
-
- .wplace-controls {
- display: flex;
- flex-direction: column;
- gap: 8px;
- }
- .wplace-row {
- display: grid;
- grid-template-columns: 1fr 1fr;
- gap: 8px;
- }
- .wplace-row.single {
- grid-template-columns: 1fr;
- }
-
- .wplace-btn {
- padding: ${CONFIG.currentTheme === "Neon Retro" ? "12px 8px" : "8px 12px"};
- border: ${CONFIG.currentTheme === "Neon Retro" ? "2px solid" : "none"};
- border-radius: ${theme.borderRadius};
- font-weight: ${CONFIG.currentTheme === "Neon Retro" ? "normal" : "500"};
- cursor: pointer;
- display: flex;
- align-items: center;
- justify-content: center;
- gap: ${CONFIG.currentTheme === "Neon Retro" ? "8px" : "6px"};
- font-size: ${CONFIG.currentTheme === "Neon Retro" ? "8px" : "11px"};
- transition: all 0.3s ease;
- position: relative;
- overflow: hidden;
- font-family: ${theme.fontFamily};
- ${CONFIG.currentTheme === "Neon Retro" ? "text-transform: uppercase; letter-spacing: 1px; image-rendering: pixelated;" : ""}
- background: ${CONFIG.currentTheme === "Classic Autobot"
- ? `linear-gradient(135deg, ${theme.accent} 0%, #4a4a4a 100%)`
- : theme.accent
- };
- ${CONFIG.currentTheme === "Classic Autobot" ? "border: 1px solid rgba(255,255,255,0.1);" : ""}
- }
-
- ${CONFIG.currentTheme === "Classic Autobot"
- ? `
- .wplace-btn::before {
- content: '';
- position: absolute;
- top: 0;
- left: -100%;
- width: 100%;
- height: 100%;
- background: linear-gradient(90deg, transparent, rgba(255,255,255,0.1), transparent);
- transition: left 0.5s ease;
- }
- .wplace-btn:hover:not(:disabled)::before {
- left: 100%;
- }`
- : `
- .wplace-btn::before {
- content: '';
- position: absolute;
- top: 0;
- left: -100%;
- width: 100%;
- height: 100%;
- background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
- transition: left 0.5s;
- }
- .wplace-btn:hover::before {
- left: 100%;
- }`
- }
-
- .wplace-btn:hover:not(:disabled) {
- transform: ${CONFIG.currentTheme === "Classic Autobot" ? "translateY(-1px)" : "none"};
- box-shadow: ${CONFIG.currentTheme === "Classic Autobot" ? "0 4px 12px rgba(0,0,0,0.4)" : "0 0 15px currentColor"
- };
- ${theme.animations.pixelBlink ? "animation: pixelBlink 0.5s infinite;" : ""}
- }
- .wplace-btn:active:not(:disabled) {
- transform: translateY(0);
- }
-
- .wplace-btn-primary {
- background: ${CONFIG.currentTheme === "Classic Autobot"
- ? `linear-gradient(135deg, ${theme.accent} 0%, #6a5acd 100%)`
- : theme.accent
- };
- color: ${theme.text};
- ${CONFIG.currentTheme === "Neon Retro" ? `border-color: ${theme.text};` : ""}
- }
- .wplace-btn-upload {
- background: ${CONFIG.currentTheme === "Classic Autobot"
- ? `linear-gradient(135deg, ${theme.secondary} 0%, #4a4a4a 100%)`
- : theme.purple
- };
- color: ${theme.text};
- ${CONFIG.currentTheme === "Classic Autobot"
- ? `border: 1px dashed ${theme.highlight};`
- : `border-color: ${theme.text}; border-style: dashed;`
- }
- }
- .wplace-btn-start {
- background: ${CONFIG.currentTheme === "Classic Autobot"
- ? `linear-gradient(135deg, ${theme.success} 0%, #228b22 100%)`
- : theme.success
- };
- color: ${CONFIG.currentTheme === "Classic Autobot" ? "white" : theme.primary};
- ${CONFIG.currentTheme === "Neon Retro" ? `border-color: ${theme.success};` : ""}
- }
- .wplace-btn-stop {
- background: ${CONFIG.currentTheme === "Classic Autobot"
- ? `linear-gradient(135deg, ${theme.error} 0%, #dc143c 100%)`
- : theme.error
- };
- color: ${CONFIG.currentTheme === "Classic Autobot" ? "white" : theme.text};
- ${CONFIG.currentTheme === "Neon Retro" ? `border-color: ${theme.error};` : ""}
- }
- .wplace-btn-select {
- background: ${CONFIG.currentTheme === "Classic Autobot"
- ? `linear-gradient(135deg, ${theme.highlight} 0%, #9370db 100%)`
- : theme.highlight
- };
- color: ${CONFIG.currentTheme === "Classic Autobot" ? "white" : theme.primary};
- ${CONFIG.currentTheme === "Neon Retro" ? `border-color: ${theme.highlight};` : ""}
- }
- .wplace-btn-file {
- background: ${CONFIG.currentTheme === "Classic Autobot"
- ? "linear-gradient(135deg, #ff8c00 0%, #ff7f50 100%)"
- : theme.warning
- };
- color: ${CONFIG.currentTheme === "Classic Autobot" ? "white" : theme.primary};
- ${CONFIG.currentTheme === "Neon Retro" ? `border-color: ${theme.warning};` : ""}
- }
- .wplace-btn:disabled {
- opacity: ${CONFIG.currentTheme === "Classic Autobot" ? "0.5" : "0.3"};
- cursor: not-allowed;
- transform: none !important;
- ${theme.animations.pixelBlink ? "animation: none !important;" : ""}
- box-shadow: none !important;
- }
- .wplace-btn:disabled::before {
- display: none;
- }
-
- .wplace-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};
- padding: ${CONFIG.currentTheme === "Neon Retro" ? "12px" : "8px"};
- border: ${CONFIG.currentTheme === "Neon Retro" ? `2px solid ${theme.text}` : "1px solid rgba(255,255,255,0.1)"};
- border-radius: ${theme.borderRadius};
- margin-bottom: ${CONFIG.currentTheme === "Neon Retro" ? "15px" : "8px"};
- ${CONFIG.currentTheme === "Neon Retro" ? "box-shadow: inset 0 0 10px rgba(0, 255, 65, 0.1);" : ""}
- }
-
- .wplace-stat-item {
- display: flex;
- justify-content: space-between;
- padding: ${CONFIG.currentTheme === "Neon Retro" ? "6px 0" : "4px 0"};
- font-size: ${CONFIG.currentTheme === "Neon Retro" ? "8px" : "11px"};
- border-bottom: 1px solid rgba(255,255,255,0.05);
- ${CONFIG.currentTheme === "Neon Retro" ? "text-transform: uppercase; letter-spacing: 1px;" : ""}
- }
- .wplace-stat-item:last-child {
- border-bottom: none;
- }
- .wplace-stat-label {
- display: flex;
- align-items: center;
- gap: 6px;
- opacity: 0.9;
- font-size: ${CONFIG.currentTheme === "Neon Retro" ? "8px" : "10px"};
- }
- .wplace-stat-value {
- font-weight: 600;
- color: ${theme.highlight};
- }
-
- .wplace-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;
- }`
- : ""
- }
-
- .wplace-progress-bar {
- height: ${CONFIG.currentTheme === "Neon Retro" ? "100%" : "6px"};
- background: ${CONFIG.currentTheme === "Classic Autobot"
- ? `linear-gradient(135deg, ${theme.highlight} 0%, #9370db 100%)`
- : `linear-gradient(90deg, ${theme.success}, ${theme.neon})`
- };
- transition: width ${CONFIG.currentTheme === "Neon Retro" ? "0.3s" : "0.5s"} ease;
- position: relative;
- ${CONFIG.currentTheme === "Neon Retro" ? `box-shadow: 0 0 10px ${theme.success};` : ""}
- }
-
- ${CONFIG.currentTheme === "Classic Autobot"
- ? `
- .wplace-progress-bar::after {
- content: '';
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background: linear-gradient(90deg, transparent, rgba(255,255,255,0.3), transparent);
- animation: shimmer 2s infinite;
- }`
- : `
- .wplace-progress-bar::after {
- content: '';
- position: absolute;
- top: 0;
- right: 0;
- width: 4px;
- height: 100%;
- background: ${theme.text};
- animation: pixelBlink 1s infinite;
- }`
- }
-
- .wplace-status {
- padding: ${CONFIG.currentTheme === "Neon Retro" ? "10px" : "6px"};
- border: ${CONFIG.currentTheme === "Neon Retro" ? "2px solid" : "1px solid"};
- border-radius: ${theme.borderRadius};
- text-align: center;
- font-size: ${CONFIG.currentTheme === "Neon Retro" ? "8px" : "11px"};
- ${CONFIG.currentTheme === "Neon Retro" ? "text-transform: uppercase; letter-spacing: 1px;" : ""}
- position: relative;
- overflow: hidden;
- }
-
- .status-default {
- background: ${CONFIG.currentTheme === "Classic Autobot" ? "rgba(255,255,255,0.1)" : theme.accent};
- border-color: ${theme.text};
- color: ${theme.text};
- }
- .status-success {
- background: ${CONFIG.currentTheme === "Classic Autobot" ? "rgba(0, 255, 0, 0.1)" : theme.success};
- border-color: ${theme.success};
- color: ${CONFIG.currentTheme === "Classic Autobot" ? theme.success : theme.primary};
- box-shadow: 0 0 15px ${theme.success};
- }
- .status-error {
- background: ${CONFIG.currentTheme === "Classic Autobot" ? "rgba(255, 0, 0, 0.1)" : theme.error};
- border-color: ${theme.error};
- color: ${CONFIG.currentTheme === "Classic Autobot" ? theme.error : theme.text};
- box-shadow: 0 0 15px ${theme.error};
- ${theme.animations.pixelBlink ? "animation: pixelBlink 0.5s infinite;" : ""}
- }
- .status-warning {
- background: ${CONFIG.currentTheme === "Classic Autobot" ? "rgba(255, 165, 0, 0.1)" : theme.warning};
- border-color: ${theme.warning};
- color: ${CONFIG.currentTheme === "Classic Autobot" ? "orange" : theme.primary};
- box-shadow: 0 0 15px ${theme.warning};
- }
-
- .resize-container {
- display: none;
- position: fixed;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- background: ${theme.primary};
- padding: 20px;
- border: ${theme.borderWidth} ${theme.borderStyle} ${theme.text};
- border-radius: ${theme.borderRadius};
- z-index: 10000;
- box-shadow: ${CONFIG.currentTheme === "Classic Autobot" ? "0 0 20px rgba(0,0,0,0.5)" : "0 0 30px rgba(0, 255, 65, 0.5)"
- };
- width: 90%;
- max-width: 700px;
- max-height: 90%;
- overflow: auto;
- font-family: ${theme.fontFamily};
- }
-
- .resize-preview-wrapper {
- display: flex;
- justify-content: center;
- align-items: center;
- border: 1px solid ${theme.accent};
- background: rgba(0,0,0,0.2);
- margin: 15px 0;
- height: 300px;
- overflow: 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;" : ""}
- color: ${theme.text};
- }
-
- .resize-slider {
- width: 100%;
- height: ${CONFIG.currentTheme === "Neon Retro" ? "8px" : "4px"};
- background: ${CONFIG.currentTheme === "Classic Autobot" ? "#ccc" : theme.secondary};
- border: ${CONFIG.currentTheme === "Neon Retro" ? `2px solid ${theme.text}` : "none"};
- border-radius: ${theme.borderRadius};
- outline: none;
- -webkit-appearance: none;
- }
-
- ${CONFIG.currentTheme === "Neon Retro"
- ? `
- .resize-slider::-webkit-slider-thumb {
- -webkit-appearance: none;
- width: 16px;
- height: 16px;
- background: ${theme.highlight};
- border: 2px solid ${theme.text};
- border-radius: 0;
- cursor: pointer;
- box-shadow: 0 0 5px ${theme.highlight};
- }
-
- .resize-slider::-moz-range-thumb {
- width: 16px;
- height: 16px;
- background: ${theme.highlight};
- border: 2px solid ${theme.text};
- border-radius: 0;
- cursor: pointer;
- box-shadow: 0 0 5px ${theme.highlight};
- }`
- : ""
- }
-
- .resize-zoom-controls {
- grid-column: 1 / -1;
- 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)
+ // Link external CSS files
+ const cssLink = document.createElement('link');
+ cssLink.rel = 'stylesheet';
+ cssLink.href = 'https://staninna.github.io/WPlace-AutoBOT/decoupled-css/auto-image-styles.css'; // TODO: Before merge change to https://raw.githubusercontent.com/Wplace-AutoBot/WPlace-AutoBOT/refs/heads/main/auto-image-styles.css
+ cssLink.setAttribute('data-wplace-theme', 'true');
+ document.head.appendChild(cssLink);
const container = document.createElement("div")
container.id = "wplace-image-bot-container"
@@ -3804,7 +2677,7 @@
- ${state.cooldownChargeThreshold}
+ ${state.cooldownChargeThreshold}
@@ -3872,25 +2745,19 @@
// 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%)`
-
- settingsContainer.style.cssText = `
- position: fixed;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- background: ${themeBackground};
- border: ${theme.borderWidth || '1px'} ${theme.borderStyle || 'solid'} ${theme.accent || 'rgba(255,255,255,0.1)'};
- border-radius: ${theme.borderRadius || '16px'};
- padding: 0;
- z-index: 10002;
- display: none;
+ const themeBackground = theme.primary ?
+ `linear-gradient(135deg, ${theme.primary} 0%, ${theme.secondary || theme.primary} 100%)` :
+ `linear-gradient(135deg, #667eea 0%, #764ba2 100%)`
+
+ settingsContainer.className = 'wplace-settings-container-base';
+ // Apply theme-specific background
+ settingsContainer.style.background = themeBackground;
+ settingsContainer.style.cssText += `
min-width: 420px;
max-width: 480px;
+ z-index: 99999;
color: ${theme.text || 'white'};
font-family: ${theme.fontFamily || "'Segoe UI', Tahoma, Geneva, Verdana, sans-serif"};
box-shadow: ${theme.boxShadow || '0 20px 40px rgba(0,0,0,0.3), 0 0 0 1px rgba(255,255,255,0.1)'};
@@ -3901,171 +2768,85 @@
box-shadow: ${theme.boxShadow || '0 20px 40px rgba(0,0,0,0.3)'},
0 0 30px ${theme.highlight || theme.neon || '#00ffff'};
` : ''}
- `
+ `;
settingsContainer.innerHTML = `
-