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 01/19] 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 { state.colorMatchingAlgorithm = e.target.value; saveBotSettings(); _updateResizePreview(); }); if (chromaToggle) chromaToggle.addEventListener('change', e => { state.enableChromaPenalty = e.target.checked; saveBotSettings(); _updateResizePreview(); }); if (chromaSlider && chromaValue) chromaSlider.addEventListener('input', e => { state.chromaPenaltyWeight = parseFloat(e.target.value)||0.15; chromaValue.textContent = state.chromaPenaltyWeight.toFixed(2); saveBotSettings(); _updateResizePreview(); }); if (transInput) transInput.addEventListener('change', e => { const v=parseInt(e.target.value,10); if(!isNaN(v)&&v>=0&&v<=255){ state.customTransparencyThreshold=v; CONFIG.TRANSPARENCY_THRESHOLD=v; saveBotSettings(); _updateResizePreview(); }}); if (whiteInput) whiteInput.addEventListener('change', e => { const v=parseInt(e.target.value,10); if(!isNaN(v)&&v>=200&&v<=255){ state.customWhiteThreshold=v; CONFIG.WHITE_THRESHOLD=v; saveBotSettings(); _updateResizePreview(); }}); - if (ditherToggle) ditherToggle.addEventListener('change', e => { state.ditheringEnabled = e.target.checked; saveBotSettings(); _updateResizePreview(); }); + if (ditherToggle) ditherToggle.addEventListener('change', e => { state.ditheringEnabled = e.target.checked; saveBotSettings(); _updateResizePreview(); }); if (resetBtn) resetBtn.addEventListener('click', () => { state.colorMatchingAlgorithm='lab'; state.enableChromaPenalty=true; state.chromaPenaltyWeight=0.15; state.customTransparencyThreshold=CONFIG.TRANSPARENCY_THRESHOLD=100; state.customWhiteThreshold=CONFIG.WHITE_THRESHOLD=250; saveBotSettings(); const a=document.getElementById('colorAlgorithmSelect'); if(a) a.value='lab'; const ct=document.getElementById('enableChromaPenaltyToggle'); if(ct) ct.checked=true; if(chromaSlider) chromaSlider.value=0.15; if(chromaValue) chromaValue.textContent='0.15'; if(transInput) transInput.value=100; if(whiteInput) whiteInput.value=250; _updateResizePreview(); Utils.showAlert('Advanced color settings reset.', 'success'); }); }; // Delay to ensure resize UI built setTimeout(advancedInit, 500); - + // Add cleanup on page unload window.addEventListener('beforeunload', () => { Utils.cleanupTurnstile(); }); }) -})() +})() \ No newline at end of file From 42ad959e63c8828e4d1d3dc4848b8a8cfafa5f8c Mon Sep 17 00:00:00 2001 From: Staninna <26458805+Staninna@users.noreply.github.com> Date: Mon, 25 Aug 2025 20:12:27 +0200 Subject: [PATCH 02/19] this one was also gone --- auto-image-styles.css | 749 ------------------------------------------ 1 file changed, 749 deletions(-) delete mode 100644 auto-image-styles.css diff --git a/auto-image-styles.css b/auto-image-styles.css deleted file mode 100644 index 5ac4a82..0000000 --- a/auto-image-styles.css +++ /dev/null @@ -1,749 +0,0 @@ -/* WPlace Auto-Image Bot Styles */ - -/* Animations */ -@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; - } -} - -@keyframes pixelBlink { - 0%, 50% { opacity: 1; } - 51%, 100% { opacity: 0.7; } -} - -@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%); } -} - -@keyframes slideDown { - from { transform: translateX(-50%) translateY(-20px); opacity: 0; } - to { transform: translateX(-50%) translateY(0); opacity: 1; } -} - -/* Main containers - will be dynamically styled based on theme */ -#wplace-image-bot-container { - position: fixed; - top: 20px; - left: 20px; - width: 280px; - max-height: calc(100vh - 40px); - padding: 0; - z-index: 9998; - animation: slideIn 0.4s ease-out; - overflow-y: auto; - overflow-x: hidden; - transition: all 0.3s ease; - user-select: none; -} - -#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: 280px; - max-height: calc(100vh - 40px); - padding: 0; - z-index: 9997; - animation: slideIn 0.4s ease-out; - overflow-y: auto; - transition: all 0.3s ease; - user-select: none; -} - -#wplace-stats-container.wplace-dragging { - transition: none; -} - -/* Header styles */ -.wplace-header { - display: flex; - justify-content: space-between; - align-items: center; - cursor: move; - user-select: none; - transition: background 0.2s ease; - position: relative; - z-index: 2; -} - -.wplace-header-title { - display: flex; - align-items: center; -} - -.wplace-header-controls { - display: flex; -} - -.wplace-header-btn { - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - transition: all 0.2s; -} - -.wplace-header-btn:hover { - transform: scale(1.1); -} - -/* Content areas */ -.wplace-content { - 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: 1px solid rgba(255,255,255,0.1); -} - -.wplace-section { - margin-bottom: 12px; - padding: 12px; - background: rgba(255,255,255,0.03); - border: 1px solid rgba(255,255,255,0.1); -} - -.wplace-section-title { - font-size: 11px; - font-weight: 600; - margin-bottom: 8px; - display: flex; - align-items: center; - gap: 6px; - text-transform: uppercase; - letter-spacing: 0.5px; -} - -/* Controls and buttons */ -.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 { - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - transition: all 0.3s ease; - position: relative; - overflow: hidden; -} - -.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) { - box-shadow: 0 0 15px currentColor; -} - -.wplace-btn:active:not(:disabled) { - transform: translateY(0); -} - -.wplace-btn:disabled { - opacity: 0.3; - cursor: not-allowed; - transform: 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; -} - -/* Stats display */ -.wplace-stats { - margin-bottom: 8px; -} - -.wplace-stat-item { - display: flex; - justify-content: space-between; - border-bottom: 1px solid rgba(255,255,255,0.05); -} - -.wplace-stat-item:last-child { - border-bottom: none; -} - -.wplace-stat-label { - display: flex; - align-items: center; - gap: 6px; - opacity: 0.9; -} - -.wplace-stat-value { - font-weight: 600; -} - -.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; - 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); -} - -/* Progress bars */ -.wplace-progress { - width: 100%; - overflow: hidden; - position: relative; -} - -.wplace-progress-bar { - position: relative; - transition: width 0.5s ease; -} - -/* Status indicators */ -.wplace-status { - text-align: center; - position: relative; - overflow: hidden; -} - -.status-default { - background: rgba(255,255,255,0.1); -} - -.status-success { - background: rgba(0, 255, 0, 0.1); - box-shadow: 0 0 15px currentColor; -} - -.status-error { - background: rgba(255, 0, 0, 0.1); - box-shadow: 0 0 15px currentColor; -} - -.status-warning { - background: rgba(255, 165, 0, 0.1); - box-shadow: 0 0 15px currentColor; -} - -/* Resize container */ -.resize-container { - display: none; - position: fixed; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - z-index: 10000; - width: 90%; - max-width: 700px; - max-height: 90%; - overflow: auto; -} - -.resize-preview-wrapper { - display: flex; - justify-content: center; - align-items: center; - 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"] { - box-shadow: 0 0 8px rgba(0,0,0,0.25) inset, 0 0 6px rgba(0,0,0,0.2) !important; -} - -.mask-mode-group .wplace-btn.active, -.mask-mode-group .wplace-btn[aria-pressed="true"] { - 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-slider { - width: 100%; - outline: none; - -webkit-appearance: none; -} - -.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; -} - -/* Color grid */ -.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; -} - -/* Cooldown controls */ -.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; - border-radius: 50%; - cursor: pointer; -} - -/* Settings container */ -#wplace-settings-container { - position: fixed; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - z-index: 10001; - min-width: 400px; - max-width: 500px; -} - -.wplace-settings { - padding: 16px; - max-height: 400px; - overflow-y: auto; -} - -.wplace-setting-section { - margin-bottom: 20px; - padding: 12px; -} - -.wplace-setting-title { - display: flex; - align-items: center; - margin-bottom: 12px; - font-size: 14px; - font-weight: 600; -} - -.wplace-setting-title i { - margin-right: 8px; -} - -.wplace-setting-content { - /* Content styling will be theme-dependent */ -} - -.wplace-speed-control { - margin-top: 12px; - padding: 12px; -} - -.wplace-speed-label { - display: flex; - align-items: center; - margin-bottom: 8px; - font-size: 13px; - font-weight: 600; -} - -.wplace-speed-label i { - margin-right: 6px; -} - -.wplace-speed-slider-container { - display: flex; - align-items: center; - gap: 12px; -} - -.wplace-speed-slider { - flex: 1; - height: 6px; - border-radius: 3px; - 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%; - cursor: pointer; - border: 2px solid; -} - -.wplace-speed-slider::-moz-range-thumb { - width: 18px; - height: 18px; - border-radius: 50%; - cursor: pointer; - border: 2px solid; -} - -.wplace-speed-display { - display: flex; - align-items: center; - gap: 4px; - min-width: 90px; - justify-content: flex-end; -} - -#speedValue { - font-weight: 600; - font-size: 14px; -} - -.wplace-speed-unit { - font-size: 11px; - opacity: 0.8; -} - -.wplace-speed-container { - display: flex; - align-items: center; - gap: 15px; - margin-bottom: 10px; -} - -.wplace-speed-display { - padding: 5px 10px; - border-radius: 4px; - font-weight: 600; - min-width: 80px; - text-align: center; -} - -.wplace-select { - width: 100%; - padding: 8px 12px; - font-size: 14px; - margin-bottom: 10px; -} - -.wplace-select:focus { - outline: none; -} - -.wplace-description { - font-size: 12px; - opacity: 0.8; - line-height: 1.4; -} - -.wplace-theme-custom { - margin-top: 15px; - padding: 15px; -} - -.wplace-custom-group { - margin-bottom: 15px; -} - -.wplace-custom-label { - display: flex; - align-items: center; - margin-bottom: 8px; - font-size: 13px; - font-weight: 600; -} - -.wplace-custom-label i { - margin-right: 8px; - 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; - border-radius: 4px; - 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; - font-size: 12px; - cursor: pointer; -} - -.wplace-custom-actions { - display: flex; - gap: 10px; - margin-top: 20px; - border-top: 1px solid; - padding-top: 15px; -} - -/* Theme-specific overrides will be applied dynamically via JavaScript */ \ No newline at end of file From 58db3cb9820ebc02002b8bf0af76e68e314eff02 Mon Sep 17 00:00:00 2001 From: Staninna <26458805+Staninna@users.noreply.github.com> Date: Wed, 27 Aug 2025 13:28:25 +0200 Subject: [PATCH 03/19] css --- themes/classic-light.css | 54 ++++++++++++++++++++++++++++++++++++++++ themes/classic.css | 4 +-- themes/neon.css | 26 ++++++++++++++++++- 3 files changed, 81 insertions(+), 3 deletions(-) diff --git a/themes/classic-light.css b/themes/classic-light.css index 3ad47b7..4f36c93 100644 --- a/themes/classic-light.css +++ b/themes/classic-light.css @@ -106,6 +106,18 @@ color: var(--wplace-text) !important; } +/* Data management section buttons */ +.wplace-theme--classic-light .wplace-data-management .wplace-btn { + background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%) !important; + border: 1px solid rgba(0, 0, 0, 0.2) !important; + color: var(--wplace-text) !important; +} + +.wplace-theme--classic-light .wplace-data-management .wplace-btn:hover:not(:disabled) { + background: linear-gradient(135deg, #e9ecef 0%, #dee2e6 100%) !important; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15) !important; +} + /* Settings dialog */ .wplace-theme--classic-light #wplace-settings-container { background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%) !important; @@ -295,6 +307,17 @@ box-shadow: 0 0 0 2px rgba(111, 66, 193, 0.3) !important; } +/* Dropdown menu options */ +.wplace-theme--classic-light .wplace-settings-select option { + background: #ffffff !important; + color: var(--wplace-text) !important; +} + +.wplace-theme--classic-light .wplace-settings-option { + background: #ffffff !important; + color: var(--wplace-text) !important; +} + /* Sliders with classic gradient */ .wplace-theme--classic-light .wplace-speed-slider { background: linear-gradient(to right, #4facfe 0%, #00f2fe 100%) !important; @@ -306,6 +329,11 @@ border-radius: 4px !important; } +.wplace-theme--classic-light .wplace-slider { + background: linear-gradient(to right, #4facfe 0%, #00f2fe 100%) !important; + border-radius: 4px !important; +} + .wplace-theme--classic-light .wplace-speed-value { background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%) !important; border-radius: 8px !important; @@ -342,6 +370,32 @@ } } +/* Turnstile/CF checkbox overlay - CRITICAL FIX */ +.wplace-theme--classic-light .wplace-turnstile-overlay { + background: rgba(255, 255, 255, 0.98) !important; + border-radius: 12px !important; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3) !important; + backdrop-filter: blur(10px) !important; + border: 1px solid rgba(0, 0, 0, 0.2) !important; + color: var(--wplace-text) !important; + font-family: 'Segoe UI', Roboto, sans-serif !important; +} + +.wplace-theme--classic-light .wplace-turnstile-title { + color: var(--wplace-text) !important; +} + +.wplace-theme--classic-light .wplace-turnstile-hide-btn { + color: var(--wplace-text) !important; + border: 1px solid rgba(0, 0, 0, 0.2) !important; + border-radius: 6px !important; + background: rgba(0, 0, 0, 0.05) !important; +} + +.wplace-theme--classic-light .wplace-turnstile-hide-btn:hover { + background: rgba(0, 0, 0, 0.1) !important; +} + /* Color swatches with light styling */ .wplace-theme--classic-light .wplace-color-swatch { border: 1px solid rgba(0, 0, 0, 0.2) !important; diff --git a/themes/classic.css b/themes/classic.css index cd1a45b..9d19a9e 100644 --- a/themes/classic.css +++ b/themes/classic.css @@ -492,8 +492,8 @@ /* Form controls and sliders for classic theme */ :root .wplace-slider, .wplace-theme--classic .wplace-slider { - background: #444; - border-radius: 2px; + background: linear-gradient(to right, #4facfe 0%, #00f2fe 100%); + border-radius: 4px; } :root .wplace-slider::-webkit-slider-thumb, diff --git a/themes/neon.css b/themes/neon.css index 2676c14..ee67493 100644 --- a/themes/neon.css +++ b/themes/neon.css @@ -285,6 +285,16 @@ text-shadow: 0 0 5px var(--wplace-text) !important; } +/* Neon slider styling */ +.wplace-theme--neon .wplace-slider, +.wplace-theme--neon .wplace-speed-slider, +.wplace-theme--neon .wplace-overlay-opacity-slider { + background: var(--wplace-secondary) !important; + border: 1px solid var(--wplace-text) !important; + border-radius: 0 !important; + box-shadow: 0 0 10px rgba(0, 255, 65, 0.3) !important; +} + /* Scanline animation for neon theme */ .wplace-theme--neon #wplace-image-bot-container::before { content: ''; @@ -300,6 +310,11 @@ opacity: 0.7; } +.wplace-theme--neon #wplace-stats-container { + position: relative; + overflow: hidden !important; +} + .wplace-theme--neon #wplace-stats-container::before { content: ''; position: absolute; @@ -310,7 +325,7 @@ background: linear-gradient(90deg, transparent, var(--wplace-text), transparent); z-index: 1; pointer-events: none; - animation: scanline 4s linear infinite; + animation: scanlineStats 4s linear infinite; opacity: 0.7; } @@ -347,6 +362,15 @@ } } +@keyframes scanlineStats { + 0% { + transform: translateY(-100%); + } + 100% { + transform: translateY(300px); + } +} + /* Resize dialog styling for neon theme */ .wplace-theme--neon .resize-container { background: #1a1a2e !important; From d230e515022d7e3ad480008c8104110d7c127e14 Mon Sep 17 00:00:00 2001 From: Staninna <26458805+Staninna@users.noreply.github.com> Date: Wed, 27 Aug 2025 13:58:40 +0200 Subject: [PATCH 04/19] css --- themes/classic-light.css | 15 ++++++++++++++- themes/classic.css | 7 +++++++ themes/neon.css | 24 +++++++++++++++++++++++- 3 files changed, 44 insertions(+), 2 deletions(-) diff --git a/themes/classic-light.css b/themes/classic-light.css index 4f36c93..2e58279 100644 --- a/themes/classic-light.css +++ b/themes/classic-light.css @@ -110,7 +110,8 @@ .wplace-theme--classic-light .wplace-data-management .wplace-btn { background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%) !important; border: 1px solid rgba(0, 0, 0, 0.2) !important; - color: var(--wplace-text) !important; + color: #212529 !important; + font-weight: 500 !important; } .wplace-theme--classic-light .wplace-data-management .wplace-btn:hover:not(:disabled) { @@ -334,6 +335,18 @@ border-radius: 4px !important; } +.wplace-theme--classic-light .wplace-slider::-webkit-slider-thumb { + -webkit-appearance: none; + width: 16px; + height: 16px; + border-radius: 50%; + background: var(--wplace-slider-thumb-bg); + border: 1px solid rgba(0, 0, 0, 0.2); + cursor: pointer; + transition: all 0.2s ease; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); +} + .wplace-theme--classic-light .wplace-speed-value { background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%) !important; border-radius: 8px !important; diff --git a/themes/classic.css b/themes/classic.css index 9d19a9e..564d2cd 100644 --- a/themes/classic.css +++ b/themes/classic.css @@ -498,7 +498,14 @@ :root .wplace-slider::-webkit-slider-thumb, .wplace-theme--classic .wplace-slider::-webkit-slider-thumb { + -webkit-appearance: none; + width: 16px; + height: 16px; border-radius: 50%; + background: var(--wplace-slider-thumb-bg); + border: none; + cursor: pointer; + transition: all 0.2s ease; } :root .wplace-speed-slider, diff --git a/themes/neon.css b/themes/neon.css index ee67493..af3a0d8 100644 --- a/themes/neon.css +++ b/themes/neon.css @@ -295,6 +295,27 @@ box-shadow: 0 0 10px rgba(0, 255, 65, 0.3) !important; } +.wplace-theme--neon .wplace-slider::-webkit-slider-thumb, +.wplace-theme--neon .wplace-speed-slider::-webkit-slider-thumb, +.wplace-theme--neon .wplace-overlay-opacity-slider::-webkit-slider-thumb { + -webkit-appearance: none; + width: 16px; + height: 16px; + border-radius: 0 !important; + background: var(--wplace-slider-thumb-bg) !important; + border: 2px solid var(--wplace-text) !important; + cursor: pointer; + transition: all 0.2s ease; + box-shadow: 0 0 8px rgba(0, 255, 65, 0.5) !important; +} + +.wplace-theme--neon .wplace-slider::-webkit-slider-thumb:hover, +.wplace-theme--neon .wplace-speed-slider::-webkit-slider-thumb:hover, +.wplace-theme--neon .wplace-overlay-opacity-slider::-webkit-slider-thumb:hover { + transform: scale(1.1); + box-shadow: 0 0 15px var(--wplace-text) !important; +} + /* Scanline animation for neon theme */ .wplace-theme--neon #wplace-image-bot-container::before { content: ''; @@ -311,8 +332,9 @@ } .wplace-theme--neon #wplace-stats-container { - position: relative; + position: fixed !important; overflow: hidden !important; + z-index: 9998 !important; } .wplace-theme--neon #wplace-stats-container::before { From a657bd6b2b37f67d157e5ca4125f725a178767ef Mon Sep 17 00:00:00 2001 From: Staninna <26458805+Staninna@users.noreply.github.com> Date: Wed, 27 Aug 2025 14:15:50 +0200 Subject: [PATCH 05/19] css --- themes/classic-light.css | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/themes/classic-light.css b/themes/classic-light.css index 2e58279..01db6ad 100644 --- a/themes/classic-light.css +++ b/themes/classic-light.css @@ -92,10 +92,7 @@ color: var(--wplace-text) !important; } -/* Comprehensive text and element styling for light theme */ -.wplace-theme--classic-light * { - color: var(--wplace-text) !important; -} +/* Comprehensive text and element styling for light theme - scoped to bot containers only */ .wplace-theme--classic-light .wplace-status, .wplace-theme--classic-light .wplace-stats, @@ -268,8 +265,13 @@ text-shadow: none !important; } +.wplace-theme--classic-light .wplace-stat-label { + color: white !important; + text-shadow: none !important; +} + .wplace-theme--classic-light .wplace-stat-value { - color: var(--wplace-highlight) !important; + color: white !important; text-shadow: none !important; font-weight: 600 !important; } From c1d6f15da4f129977613f61d61714fa56d3df7ae Mon Sep 17 00:00:00 2001 From: Stan <26458805+Staninna@users.noreply.github.com> Date: Wed, 27 Aug 2025 15:21:36 +0200 Subject: [PATCH 06/19] Create cd --- .github/workflows/cd | 48 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 .github/workflows/cd diff --git a/.github/workflows/cd b/.github/workflows/cd new file mode 100644 index 0000000..a4ac8e8 --- /dev/null +++ b/.github/workflows/cd @@ -0,0 +1,48 @@ +name: Pages โ€“ per-branch static + +on: + push: + branches-ignore: + - gh-pages + workflow_dispatch: + +permissions: + contents: write + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Sanitize branch name + id: br + shell: bash + run: | + SAFE="$(echo "${GITHUB_REF_NAME}" \ + | tr '/:@ ' '----' \ + | sed 's/[^A-Za-z0-9._-]/-/g')" + echo "safe=$SAFE" >> "$GITHUB_OUTPUT" + + - name: Stage only static files + run: | + mkdir -p _site + rsync -a --prune-empty-dirs \ + --include='*/' \ + --include='*.html' \ + --include='*.css' \ + --include='*.js' \ + --include='*.json' \ + --exclude='*' \ + ./ _site/ + + - name: Deploy to gh-pages/ + uses: peaceiris/actions-gh-pages@v4 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_branch: gh-pages + publish_dir: _site + destination_dir: ${{ steps.br.outputs.safe }} + keep_files: true + commit_message: Deploy ${{ github.ref_name }} From a8cfe7f4a5a29d4f91d6806f9231b410f08876d4 Mon Sep 17 00:00:00 2001 From: Stan <26458805+Staninna@users.noreply.github.com> Date: Wed, 27 Aug 2025 15:23:20 +0200 Subject: [PATCH 07/19] Rename cd to cd.yml --- .github/workflows/{cd => cd.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/workflows/{cd => cd.yml} (100%) diff --git a/.github/workflows/cd b/.github/workflows/cd.yml similarity index 100% rename from .github/workflows/cd rename to .github/workflows/cd.yml From 47137af1a17e19b570af6ca43706fef95bdfb082 Mon Sep 17 00:00:00 2001 From: Stan <26458805+Staninna@users.noreply.github.com> Date: Wed, 27 Aug 2025 15:41:37 +0200 Subject: [PATCH 08/19] Update cd.yml --- .github/workflows/cd.yml | 90 ++++++++++++++++++++++++++-------------- 1 file changed, 59 insertions(+), 31 deletions(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index a4ac8e8..c77e2e1 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -1,48 +1,76 @@ -name: Pages โ€“ per-branch static +name: Pages โ€“ aggregate all branches on: push: - branches-ignore: - - gh-pages + branches: ["**"] workflow_dispatch: permissions: - contents: write + contents: read + pages: write + id-token: write + +concurrency: + group: pages + cancel-in-progress: true jobs: - deploy: + aggregate_deploy: runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deploy.outputs.page_url }} steps: - - name: Checkout + - name: Checkout (shallow) uses: actions/checkout@v4 + with: + fetch-depth: 1 - - name: Sanitize branch name - id: br + - name: Configure Pages + uses: actions/configure-pages@v5 + + - name: Aggregate files from all branches into ./public shell: bash run: | - SAFE="$(echo "${GITHUB_REF_NAME}" \ - | tr '/:@ ' '----' \ - | sed 's/[^A-Za-z0-9._-]/-/g')" - echo "safe=$SAFE" >> "$GITHUB_OUTPUT" + set -euo pipefail + mkdir -p public - - name: Stage only static files - run: | - mkdir -p _site - rsync -a --prune-empty-dirs \ - --include='*/' \ - --include='*.html' \ - --include='*.css' \ - --include='*.js' \ - --include='*.json' \ - --exclude='*' \ - ./ _site/ + # List remote branches (exclude HEAD pointer) + mapfile -t BRANCHES < <(git ls-remote --heads origin \ + | awk '{print $2}' | sed 's@refs/heads/@@') - - name: Deploy to gh-pages/ - uses: peaceiris/actions-gh-pages@v4 + # Minimal index page + cat > public/index.html <<'HTML' + + Branches

Branches

    + HTML + + for BR in "${BRANCHES[@]}"; do + SAFE=$(echo "$BR" | tr '/:@ ' '----' | sed 's/[^A-Za-z0-9._-]/-/g') + mkdir -p "public/$SAFE" + + # Fetch that branch tip and export without checkout + git fetch --depth=1 origin "$BR" + git archive --format=tar FETCH_HEAD | tar -x -C "public/$SAFE" + + # Keep only static assets; drop heavy/unneeded dirs + rm -rf "public/$SAFE/.git" "public/$SAFE/.github" "public/$SAFE/node_modules" + find "public/$SAFE" -type f ! \ + \( -name '*.html' -o -name '*.js' -o -name '*.css' -o -name '*.json' -o \ + -name '*.png' -o -name '*.jpg' -o -name '*.jpeg' -o -name '*.gif' -o -name '*.svg' \) \ + -delete + find "public/$SAFE" -type d -empty -delete + + echo "
  • $BR
  • " >> public/index.html + done + echo "
" >> public/index.html + echo "Built public/ for ${#BRANCHES[@]} branches." + + - name: Upload Pages artifact + uses: actions/upload-pages-artifact@v4 with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_branch: gh-pages - publish_dir: _site - destination_dir: ${{ steps.br.outputs.safe }} - keep_files: true - commit_message: Deploy ${{ github.ref_name }} + path: public + + - name: Deploy to GitHub Pages + id: deploy + uses: actions/deploy-pages@v4 From 5e13c567275475f410f6d306c2a90d368f72b287 Mon Sep 17 00:00:00 2001 From: Staninna <26458805+Staninna@users.noreply.github.com> Date: Wed, 27 Aug 2025 16:12:03 +0200 Subject: [PATCH 09/19] test commit to check if ci gets triggered when i push another branch --- themes/neon.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/themes/neon.css b/themes/neon.css index af3a0d8..3b69283 100644 --- a/themes/neon.css +++ b/themes/neon.css @@ -1,5 +1,5 @@ /* WPlace Auto-Image Bot - Neon Theme */ -/* Retro cyberpunk aesthetic with green neon glow effects */ +/* Retro cyberpunk aesthetic with green neon glow effects :) */ .wplace-theme--neon { /* Neon theme colors - matching upstream main neon theme */ From be340b799caed0c4c046f826765e8e6ab1d08d03 Mon Sep 17 00:00:00 2001 From: Staninna <26458805+Staninna@users.noreply.github.com> Date: Wed, 27 Aug 2025 16:27:25 +0200 Subject: [PATCH 10/19] cd should run --- themes/neon.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/themes/neon.css b/themes/neon.css index 3b69283..af3a0d8 100644 --- a/themes/neon.css +++ b/themes/neon.css @@ -1,5 +1,5 @@ /* WPlace Auto-Image Bot - Neon Theme */ -/* Retro cyberpunk aesthetic with green neon glow effects :) */ +/* Retro cyberpunk aesthetic with green neon glow effects */ .wplace-theme--neon { /* Neon theme colors - matching upstream main neon theme */ From a5e82fe408931a7dfbc36c5ac2c5c3f7f2209d78 Mon Sep 17 00:00:00 2001 From: Staninna <26458805+Staninna@users.noreply.github.com> Date: Wed, 27 Aug 2025 16:37:45 +0200 Subject: [PATCH 11/19] update cd.yml to be better??? test run --- .github/workflows/cd.yml | 122 ++++++++++++++++++++++++++++++++++----- 1 file changed, 106 insertions(+), 16 deletions(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index c77e2e1..ebc3c81 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -29,42 +29,132 @@ jobs: - name: Configure Pages uses: actions/configure-pages@v5 - - name: Aggregate files from all branches into ./public + - name: Aggregate and build recursive directory pages shell: bash run: | set -euo pipefail + + OWNER="${GITHUB_REPOSITORY%/*}" + REPO="${GITHUB_REPOSITORY#*/}" + SITE_ROOT="/${REPO}/" + mkdir -p public + : > public/.nojekyll - # List remote branches (exclude HEAD pointer) - mapfile -t BRANCHES < <(git ls-remote --heads origin \ - | awk '{print $2}' | sed 's@refs/heads/@@') + # List remote branches + mapfile -t BRANCHES < <( + git ls-remote --heads origin | awk '{print $2}' \ + | sed 's@refs/heads/@@' + ) - # Minimal index page + # Top-level landing page cat > public/index.html <<'HTML' - - Branches

Branches

    + + + + Branches + +

    Branches

    + +
    + HTML + # Helper to create an index.html for every directory + gen_index_dir() { + local DIR="$1" ROOT="$2" BR="$3" SAFE="$4" + local rel="${DIR#$ROOT}" # "" or "/sub/dir" + local title="Branch: ${BR}${rel:-/}" + + # Collect entries + mapfile -t SUBDIRS < <(find "$DIR" -mindepth 1 -maxdepth 1 -type d \ + -printf '%f\n' | sort) + mapfile -t FILES < <(find "$DIR" -mindepth 1 -maxdepth 1 -type f \ + ! -name 'index.html' -printf '%f\n' | sort) + + { + echo "" + echo "" + echo "${title}" + echo "" + echo "

    ${title}

    " + if [[ "$DIR" != "$ROOT" ]]; then + echo "

    โฌ† Up โ€ข All branches

    " + else + echo "

    All branches

    " + fi + echo "" + for d in "${SUBDIRS[@]}"; do + echo "" + done + for f in "${FILES[@]}"; do + local size=$(stat -c %s "$DIR/$f" 2>/dev/null || echo 0) + local size_h=$(numfmt --to=iec --suffix=B "$size" 2>/dev/null || echo "$size") + local mod=$(date -r "$DIR/$f" "+%Y-%m-%d %H:%M" 2>/dev/null || echo "") + echo "" + done + echo "
    NameSizeModified
    ๐Ÿ“ $d/โ€”โ€”
    ๐Ÿ“„ $f$size_h$mod
    " + echo "

    Generated $(date -u '+%Y-%m-%d %H:%M UTC')

    " + } > "$DIR/index.html" + } + + # Process each branch for BR in "${BRANCHES[@]}"; do - SAFE=$(echo "$BR" | tr '/:@ ' '----' | sed 's/[^A-Za-z0-9._-]/-/g') + SAFE="$(echo "$BR" | tr '/:@ ' '----' | sed 's/[^A-Za-z0-9._-]/-/g')" mkdir -p "public/$SAFE" - # Fetch that branch tip and export without checkout + # Fetch and extract content without checkout git fetch --depth=1 origin "$BR" git archive --format=tar FETCH_HEAD | tar -x -C "public/$SAFE" - # Keep only static assets; drop heavy/unneeded dirs + # Keep only static assets, then remove empty dirs rm -rf "public/$SAFE/.git" "public/$SAFE/.github" "public/$SAFE/node_modules" find "public/$SAFE" -type f ! \ \( -name '*.html' -o -name '*.js' -o -name '*.css' -o -name '*.json' -o \ - -name '*.png' -o -name '*.jpg' -o -name '*.jpeg' -o -name '*.gif' -o -name '*.svg' \) \ - -delete + -name '*.png' -o -name '*.jpg' -o -name '*.jpeg' -o -name '*.gif' -o \ + -name '*.svg' \) -delete find "public/$SAFE" -type d -empty -delete - echo "
  • $BR
  • " >> public/index.html + # Generate index.html for every directory (recursive) + ROOT_DIR="public/$SAFE" + while IFS= read -r -d '' d; do + gen_index_dir "$d" "$ROOT_DIR" "$BR" "$SAFE" + done < <(find "$ROOT_DIR" -type d -print0) + + # Add branch to landing page list + echo "" >> public/index.html done - echo "
" >> public/index.html - echo "Built public/ for ${#BRANCHES[@]} branches." + + # Finish landing page with JS to render branch cards + cat >> public/index.html <<'HTML' + + HTML - name: Upload Pages artifact uses: actions/upload-pages-artifact@v4 @@ -73,4 +163,4 @@ jobs: - name: Deploy to GitHub Pages id: deploy - uses: actions/deploy-pages@v4 + uses: actions/deploy-pages@v4 \ No newline at end of file From e20d2818bed3a5b81c02a601abc648d808f6e25b Mon Sep 17 00:00:00 2001 From: Staninna <26458805+Staninna@users.noreply.github.com> Date: Wed, 27 Aug 2025 16:55:03 +0200 Subject: [PATCH 12/19] update cd.yml to be better??? test run --- .github/workflows/cd.yml | 36 +++++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index ebc3c81..1c26237 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -20,6 +20,7 @@ jobs: environment: name: github-pages url: ${{ steps.deploy.outputs.page_url }} + steps: - name: Checkout (shallow) uses: actions/checkout@v4 @@ -34,6 +35,12 @@ jobs: run: | set -euo pipefail + # Ensure jq is available (usually preinstalled) + if ! command -v jq >/dev/null 2>&1; then + sudo apt-get update -y + sudo apt-get install -y jq + fi + OWNER="${GITHUB_REPOSITORY%/*}" REPO="${GITHUB_REPOSITORY#*/}" SITE_ROOT="/${REPO}/" @@ -41,13 +48,14 @@ jobs: mkdir -p public : > public/.nojekyll - # List remote branches + # List remote branches (exclude symbolic HEAD) mapfile -t BRANCHES < <( - git ls-remote --heads origin | awk '{print $2}' \ + git ls-remote --heads origin \ + | awk '{print $2}' \ | sed 's@refs/heads/@@' ) - # Top-level landing page + # Landing page shell cat > public/index.html <<'HTML' @@ -66,17 +74,16 @@ jobs:
HTML - # Helper to create an index.html for every directory + # Function to emit an index.html for a directory gen_index_dir() { local DIR="$1" ROOT="$2" BR="$3" SAFE="$4" local rel="${DIR#$ROOT}" # "" or "/sub/dir" local title="Branch: ${BR}${rel:-/}" - # Collect entries mapfile -t SUBDIRS < <(find "$DIR" -mindepth 1 -maxdepth 1 -type d \ -printf '%f\n' | sort) mapfile -t FILES < <(find "$DIR" -mindepth 1 -maxdepth 1 -type f \ @@ -108,16 +115,16 @@ jobs: } > "$DIR/index.html" } - # Process each branch + # Build trees and index entries per branch for BR in "${BRANCHES[@]}"; do SAFE="$(echo "$BR" | tr '/:@ ' '----' | sed 's/[^A-Za-z0-9._-]/-/g')" mkdir -p "public/$SAFE" - # Fetch and extract content without checkout + # Export branch contents without checkout git fetch --depth=1 origin "$BR" git archive --format=tar FETCH_HEAD | tar -x -C "public/$SAFE" - # Keep only static assets, then remove empty dirs + # Keep only static assets, then prune empties rm -rf "public/$SAFE/.git" "public/$SAFE/.github" "public/$SAFE/node_modules" find "public/$SAFE" -type f ! \ \( -name '*.html' -o -name '*.js' -o -name '*.css' -o -name '*.json' -o \ @@ -125,17 +132,20 @@ jobs: -name '*.svg' \) -delete find "public/$SAFE" -type d -empty -delete - # Generate index.html for every directory (recursive) + # Generate index.html for every directory in this branch ROOT_DIR="public/$SAFE" while IFS= read -r -d '' d; do gen_index_dir "$d" "$ROOT_DIR" "$BR" "$SAFE" done < <(find "$ROOT_DIR" -type d -print0) - # Add branch to landing page list - echo "" >> public/index.html + # Append to landing page using JSON-escaped values + SAFE_JSON=$(printf '%s' "$SAFE" | jq -Rr @json) + BR_JSON=$(printf '%s' "$BR" | jq -Rr @json) + echo "" \ + >> public/index.html done - # Finish landing page with JS to render branch cards + # Finish landing page cat >> public/index.html <<'HTML' + HTML - # Function to emit an index.html for a directory + # Emit index.html for a directory gen_index_dir() { - local DIR="$1" ROOT="$2" BR="$3" SAFE="$4" - local rel="${DIR#$ROOT}" # "" or "/sub/dir" + local DIR="$1" ROOT="$2" BR="$3" + local rel="${DIR#$ROOT}" local title="Branch: ${BR}${rel:-/}" - mapfile -t SUBDIRS < <(find "$DIR" -mindepth 1 -maxdepth 1 -type d \ - -printf '%f\n' | sort) - mapfile -t FILES < <(find "$DIR" -mindepth 1 -maxdepth 1 -type f \ - ! -name 'index.html' -printf '%f\n' | sort) - { echo "" echo "" + echo "" echo "${title}" echo "" echo "

${title}

" @@ -101,68 +98,82 @@ jobs: echo "

All branches

" fi echo "" - for d in "${SUBDIRS[@]}"; do - echo "" - done - for f in "${FILES[@]}"; do - local size=$(stat -c %s "$DIR/$f" 2>/dev/null || echo 0) - local size_h=$(numfmt --to=iec --suffix=B "$size" 2>/dev/null || echo "$size") - local mod=$(date -r "$DIR/$f" "+%Y-%m-%d %H:%M" 2>/dev/null || echo "") - echo "" - done + + # Subdirectories + while IFS= read -r d; do + [[ -n "$d" ]] || continue + printf "\n" "$d" "$d" + done < <(find "$DIR" -mindepth 1 -maxdepth 1 -type d -printf '%f\n' | sort) + + # Files + while IFS= read -r f; do + [[ -n "$f" ]] || continue + size=$(stat -c %s "$DIR/$f" 2>/dev/null || echo 0) + size_h=$(numfmt --to=iec --suffix=B "$size" 2>/dev/null || echo "$size") + mod=$(date -r "$DIR/$f" "+%Y-%m-%d %H:%M" 2>/dev/null || echo "") + printf "\n" "$f" "$f" "$size_h" "$mod" + done < <(find "$DIR" -mindepth 1 -maxdepth 1 -type f ! -name 'index.html' -printf '%f\n' | sort) + echo "
NameSizeModified
๐Ÿ“ $d/โ€”โ€”
๐Ÿ“„ $f$size_h$mod
๐Ÿ“ %s/โ€”โ€”
๐Ÿ“„ %s%s%s
" echo "

Generated $(date -u '+%Y-%m-%d %H:%M UTC')

" } > "$DIR/index.html" } - # Build trees and index entries per branch + # Build per-branch trees and indexes for BR in "${BRANCHES[@]}"; do SAFE="$(echo "$BR" | tr '/:@ ' '----' | sed 's/[^A-Za-z0-9._-]/-/g')" mkdir -p "public/$SAFE" - # Export branch contents without checkout git fetch --depth=1 origin "$BR" git archive --format=tar FETCH_HEAD | tar -x -C "public/$SAFE" - # Keep only static assets, then prune empties - rm -rf "public/$SAFE/.git" "public/$SAFE/.github" "public/$SAFE/node_modules" + # Keep only static assets; prune empty dirs + rm -rf "public/$SAFE/.git" "public/$SAFE/.github" \ + "public/$SAFE/node_modules" find "public/$SAFE" -type f ! \ - \( -name '*.html' -o -name '*.js' -o -name '*.css' -o -name '*.json' -o \ - -name '*.png' -o -name '*.jpg' -o -name '*.jpeg' -o -name '*.gif' -o \ - -name '*.svg' \) -delete + \( -name '*.html' -o -name '*.js' -o -name '*.css' -o \ + -name '*.json' -o -name '*.png' -o -name '*.jpg' -o \ + -name '*.jpeg' -o -name '*.gif' -o -name '*.svg' \) \ + -delete find "public/$SAFE" -type d -empty -delete - # Generate index.html for every directory in this branch ROOT_DIR="public/$SAFE" while IFS= read -r -d '' d; do - gen_index_dir "$d" "$ROOT_DIR" "$BR" "$SAFE" + gen_index_dir "$d" "$ROOT_DIR" "$BR" done < <(find "$ROOT_DIR" -type d -print0) - # Append to landing page using JSON-escaped values + # Append to landing page (JSON-escaped) SAFE_JSON=$(printf '%s' "$SAFE" | jq -Rr @json) BR_JSON=$(printf '%s' "$BR" | jq -Rr @json) - echo "" \ + echo "" \ >> public/index.html done # Finish landing page cat >> public/index.html <<'HTML' HTML From 9165c5b7509f89c527d5362ec81acecb29c69452 Mon Sep 17 00:00:00 2001 From: Staninna <26458805+Staninna@users.noreply.github.com> Date: Wed, 27 Aug 2025 19:24:59 +0200 Subject: [PATCH 14/19] Are we revived? Restored all the mysteriously disappeared code --- Auto-Image.js | 2801 +++++++++++------------------------------ auto-image-styles.css | 1966 +++++++++++++++++++++++++++++ 2 files changed, 2715 insertions(+), 2052 deletions(-) create mode 100644 auto-image-styles.css diff --git a/Auto-Image.js b/Auto-Image.js index c967534..546cb66 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 { @@ -212,19 +269,13 @@ } const loadLanguagePreference = () => { - const savedLanguage = localStorage.getItem("wplace_language") - - const browserLocale = navigator.language - const browserLanguage = browserLocale.split("-")[0] - - if (TEXT[savedLanguage]) { // trying to load saved language - state.language = savedLanguage - } else if (TEXT[browserLocale]) { // trying to load full locale match (e.g. "zh-CN", "zh-TW" etc) - state.language = browserLocale - localStorage.setItem("wplace_language", browserLocale) - } else if (TEXT[browserLanguage]) { // trying to load base language match (e.g. "en" for "en-US" or "en-GB" etc) - state.language = browserLanguage - localStorage.setItem("wplace_language", browserLanguage) + try { + const saved = localStorage.getItem("wplace_language") + if (saved && TEXT[saved]) { + state.language = saved + } + } catch (e) { + console.warn("Could not load language preference:", e) } } @@ -740,7 +791,7 @@ automation: "Otomasyon", noChargesThreshold: "โŒ› Haklarฤฑn {threshold} seviyesine ulaลŸmasฤฑ bekleniyor. ลžu anda {current}. Sonraki {time} iรงinde...", }, - "zh-CN": { + zh: { title: "WPlace ่‡ชๅŠจๅ›พๅƒ", toggleOverlay: "ๅˆ‡ๆข่ฆ†็›–ๅฑ‚", scanColors: "ๆ‰ซๆ้ขœ่‰ฒ", @@ -816,7 +867,7 @@ automation: "่‡ชๅŠจๅŒ–", noChargesThreshold: "โŒ› ็ญ‰ๅพ…ๆฌกๆ•ฐ่พพๅˆฐ {threshold}ใ€‚ๅฝ“ๅ‰ {current}ใ€‚ไธ‹ๆฌกๅœจ {time}...", }, - "zh-TW": { + "zh-tw": { title: "WPlace ่‡ชๅ‹•ๅœ–ๅƒ", toggleOverlay: "ๅˆ‡ๆ›่ฆ†่“‹ๅฑค", scanColors: "ๆŽƒๆ้ก่‰ฒ", @@ -1152,28 +1203,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 = () => { }; @@ -1206,8 +1257,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; @@ -1274,7 +1325,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; @@ -1303,7 +1354,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); @@ -1351,18 +1402,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); } @@ -1491,7 +1542,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; @@ -1504,7 +1555,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 }); @@ -1561,7 +1612,7 @@ } tokenGenerationInProgress = true; - + try { console.log("๐Ÿ”„ Token expired or missing, generating new one..."); const token = await handleCaptchaWithRetry(); @@ -1700,6 +1751,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)), @@ -1727,7 +1790,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"]')) { @@ -1741,7 +1804,7 @@ }; return checkReady(); } - + const script = document.createElement('script'); script.src = 'https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit'; script.async = true; @@ -1766,20 +1829,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); @@ -1795,35 +1847,19 @@ 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 = 'Cloudflare Turnstile โ€” please complete the check if shown'; - title.style.cssText = 'font: 600 12px/1.3 "Segoe UI",sans-serif; margin-bottom: 8px; opacity: 0.9;'; + title.className = 'wplace-turnstile-title'; 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 = 'Hide'; - 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); @@ -1870,24 +1906,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, @@ -1902,10 +1938,10 @@ 'error-callback': () => resolve(null), 'timeout-callback': () => resolve(null) }); - + this._turnstileWidgetId = widgetId; this._lastSitekey = sitekey; - + if (!widgetId) { return resolve(null); } @@ -1925,31 +1961,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, @@ -1957,9 +1995,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 { @@ -1969,17 +2008,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); @@ -2006,15 +2047,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; @@ -2092,11 +2133,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; @@ -2152,40 +2193,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) @@ -2194,7 +2202,6 @@ alertDiv.style.animation = "slideDown 0.3s ease-out reverse" setTimeout(() => { document.body.removeChild(alertDiv) - document.head.removeChild(style) }, 300) }, 4000) }, @@ -2236,7 +2243,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') { @@ -2290,17 +2297,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" }) @@ -2315,30 +2322,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-"]') @@ -2431,8 +2438,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; } @@ -2441,8 +2448,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]; } @@ -2454,28 +2461,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; }, @@ -2529,7 +2536,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 }; @@ -2574,7 +2581,7 @@ } }, - saveProgress: () => { + saveProgress: () => { try { // Pack painted map if available let paintedMapPacked = null; @@ -2603,13 +2610,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, } @@ -2679,7 +2686,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; @@ -2703,7 +2710,7 @@ } }, - saveProgressToFile: () => { + saveProgressToFile: () => { try { // Pack painted map if available let paintedMapPacked = null; @@ -2732,13 +2739,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, } @@ -2751,7 +2758,7 @@ } }, - loadProgressFromFile: async () => { + loadProgressFromFile: async () => { try { const data = await Utils.createFileUploader() if (!data || !data.state) { @@ -2765,7 +2772,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) @@ -2782,9 +2789,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); @@ -2933,96 +2940,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 @@ -3127,7 +3134,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 = '
Upload an image first to capture available colors
'; + colorsContainer.innerHTML = '
Upload an image first to capture available colors
'; return; } @@ -3146,7 +3153,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 @@ -3211,29 +3218,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`); @@ -3244,7 +3251,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..."); @@ -3329,6 +3336,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") @@ -3345,6 +3354,7 @@ loadLanguagePreference() const theme = getCurrentTheme() + applyTheme() // <- new: set CSS vars and theme class before building UI const fontAwesome = document.createElement("link") fontAwesome.rel = "stylesheet" @@ -3358,1155 +3368,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/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" @@ -4595,7 +3462,7 @@
- ${state.cooldownChargeThreshold} + ${state.cooldownChargeThreshold}
@@ -4663,25 +3530,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)'}; @@ -4692,171 +3553,85 @@ box-shadow: ${theme.boxShadow || '0 20px 40px rgba(0,0,0,0.3)'}, 0 0 30px ${theme.highlight || theme.neon || '#00ffff'}; ` : ''} - ` + `; settingsContainer.innerHTML = ` -
- ${theme.animations?.scanline ? ` -
- ` : ''} -
-

- +
+
+

+ ${Utils.t("settings")}

- +
-
+
-
-