diff --git a/Auto-Image.js b/Auto-Image.js
index 41db943..4200f49 100644
--- a/Auto-Image.js
+++ b/Auto-Image.js
@@ -43,12 +43,11 @@
"209,128,81": "Dark Beige", "255,197,165": "Light Beige", "109,100,63": "Dark Stone", "148,140,107": "Stone",
"205,197,158": "Light Stone", "51,57,65": "Dark Slate", "109,117,141": "Slate", "179,185,209": "Light Slate",
},
- // --- CORRECTED PAID COLORS ---
- // Corresponds to IDs 32-61 from the main palette
PAID_COLORS: new Set([
- "125,199,255", "77,49,184", "107,80,246", "153,177,251", "74,66,132", "122,113,196", "181,174,241", "170,56,185", "224,159,249", "203,0,122",
- "236,31,128", "243,141,169", "155,82,73", "209,128,120", "250,182,164", "104,70,52", "149,104,42", "219,164,99", "123,99,82", "156,132,107",
- "214,181,148", "209,128,81", "248,178,119", "255,197,165", "109,100,63", "148,140,107", "205,197,158", "51,57,65", "109,117,141", "179,185,209"
+ "170,170,170", "165,14,30", "250,128,114", "228,92,26", "156,132,49", "197,173,49", "232,212,95", "74,107,58",
+ "90,148,74", "132,197,115", "15,121,159", "187,250,242", "125,199,255", "77,49,184", "74,66,132", "122,113,196",
+ "181,174,241", "155,82,73", "209,128,120", "250,182,164", "219,164,99", "123,99,82", "156,132,107", "214,181,148",
+ "209,128,81", "255,197,165", "109,100,63", "148,140,107", "205,197,158", "51,57,65", "109,117,141", "179,185,209",
]),
// --- END: Color data ---
// Optimized CSS Classes for reuse
@@ -177,6 +176,7 @@
const TEXT = {
en: {
title: "WPlace Auto-Image",
+ toggleOverlay: "Toggle Overlay",
scanColors: "Scan Colors",
uploadImage: "Upload Image",
resizeImage: "Resize Image",
@@ -552,57 +552,256 @@
language: "en",
paintingSpeed: CONFIG.PAINTING_SPEED.DEFAULT, // pixels per second
cooldownChargeThreshold: CONFIG.COOLDOWN_CHARGE_THRESHOLD,
- // --- NEW: Overlay state ---
- overlayCanvas: null,
- overlayCtx: null,
- gameCanvas: null,
}
// Placeholder for the resize preview update function
let _updateResizePreview = () => {};
- // Turnstile token handling (promise-based) inspired by external logic
- let turnstileToken = null
- let _resolveToken = null
- let tokenPromise = new Promise((resolve) => { _resolveToken = resolve })
-
- function setTurnstileToken(t) {
- if (_resolveToken) {
- _resolveToken(t)
- _resolveToken = null
+ // --- OVERLAY UPDATE: New OverlayManager class to handle all overlay logic ---
+ class OverlayManager {
+ constructor() {
+ this.isEnabled = false;
+ this.startCoords = null; // { region: {x, y}, pixel: {x, y} }
+ this.imageBitmap = null;
+ this.chunkedTiles = new Map(); // Map<"tileX,tileY", ImageBitmap>
+ this.tileSize = 1000;
}
- turnstileToken = t
- }
- async function ensureToken() {
- if (!turnstileToken) {
- updateUI("captchaNeeded", "error")
- Utils.showAlert(Utils.t("captchaNeeded"), "error")
- try { await tokenPromise } catch (_) {}
+ toggle() {
+ this.isEnabled = !this.isEnabled;
+ console.log(`Overlay ${this.isEnabled ? 'enabled' : 'disabled'}.`);
+ return this.isEnabled;
}
- return turnstileToken
- }
- // Intercept fetch to capture Turnstile token from pixel placement requests
- const originalFetch = window.fetch
- window.fetch = async (url, options) => {
- if (typeof url === "string" && url.includes("https://backend.wplace.live/s0/pixel/")) {
- try {
- const payload = JSON.parse(options.body)
- if (payload.t) {
- console.log("✅ Turnstile Token Captured:", payload.t)
- setTurnstileToken(payload.t)
- if (document.querySelector("#statusText")?.textContent.includes("CAPTCHA")) {
- Utils.showAlert("Token captured successfully! You can start the bot now.", "success")
- updateUI("colorsFound", "success", { count: state.availableColors.length })
- }
+ enable() { this.isEnabled = true; }
+ disable() { this.isEnabled = false; }
+ clear() {
+ this.disable();
+ this.imageBitmap = null;
+ this.chunkedTiles.clear();
+ }
+
+ async setImage(imageBitmap) {
+ this.imageBitmap = imageBitmap;
+ if (this.imageBitmap && this.startCoords) {
+ await this.processImageIntoChunks();
}
- } catch (_) { /* ignore */ }
}
- return originalFetch(url, options)
- }
- // LANGUAGE DETECTION
+ async setPosition(startPosition, region) {
+ if (!startPosition || !region) {
+ this.startCoords = null;
+ this.chunkedTiles.clear();
+ return;
+ }
+ this.startCoords = { region, pixel: startPosition };
+ if (this.imageBitmap) {
+ await this.processImageIntoChunks();
+ }
+ }
+
+ // --- OVERLAY UPDATE: Simplified chunking logic for solid, semi-transparent overlay ---
+ async processImageIntoChunks() {
+ if (!this.imageBitmap || !this.startCoords) return;
+
+ 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;
+
+ const endPixelX = startPixelX + imageWidth;
+ const endPixelY = startPixelY + imageHeight;
+
+ const startTileX = startRegionX + Math.floor(startPixelX / this.tileSize);
+ const startTileY = startRegionY + Math.floor(startPixelY / this.tileSize);
+ const endTileX = startRegionX + Math.floor(endPixelX / this.tileSize);
+ const endTileY = startRegionY + Math.floor(endPixelY / this.tileSize);
+
+ for (let ty = startTileY; ty <= endTileY; ty++) {
+ for (let tx = startTileX; tx <= endTileX; tx++) {
+ const tileKey = `${tx},${ty}`;
+
+ // Calculate the portion of the image that overlaps with this tile
+ const imgStartX = (tx - startRegionX) * this.tileSize - startPixelX;
+ const imgStartY = (ty - startRegionY) * this.tileSize - startPixelY;
+
+ // Crop coordinates within the source image
+ const sX = Math.max(0, imgStartX);
+ const sY = Math.max(0, imgStartY);
+ const sW = Math.min(imageWidth - sX, this.tileSize - (sX - imgStartX));
+ const sH = Math.min(imageHeight - sY, this.tileSize - (sY - imgStartY));
+
+ if (sW <= 0 || sH <= 0) continue;
+
+ // Destination coordinates on the new chunk canvas
+ const dX = Math.max(0, -imgStartX);
+ const dY = Math.max(0, -imgStartY);
+
+ const chunkCanvas = new OffscreenCanvas(this.tileSize, this.tileSize);
+ const chunkCtx = chunkCanvas.getContext('2d');
+ chunkCtx.imageSmoothingEnabled = false;
+
+ chunkCtx.drawImage(this.imageBitmap, sX, sY, sW, sH, dX, dY, sW, sH);
+
+ const chunkBitmap = await chunkCanvas.transferToImageBitmap();
+ this.chunkedTiles.set(tileKey, chunkBitmap);
+ }
+ }
+
+ console.log(`Overlay processed into ${this.chunkedTiles.size} chunks.`);
+ }
+
+ // --- OVERLAY UPDATE: Simplified compositing logic for solid, semi-transparent overlay ---
+ async processAndRespondToTileRequest(eventData) {
+ const { endpoint, blobID, blobData } = eventData;
+
+ let finalBlob = blobData;
+
+ if (this.isEnabled && this.chunkedTiles.size > 0) {
+ const tileMatch = endpoint.match(/(\d+)\/(\d+)\.png/);
+ if (tileMatch) {
+ const tileX = parseInt(tileMatch[1], 10);
+ const tileY = parseInt(tileMatch[2], 10);
+ const tileKey = `${tileX},${tileY}`;
+
+ const chunkBitmap = this.chunkedTiles.get(tileKey);
+ if (chunkBitmap) {
+ try {
+ const originalTileBitmap = await createImageBitmap(blobData);
+ const canvas = new OffscreenCanvas(originalTileBitmap.width, originalTileBitmap.height);
+ const ctx = canvas.getContext('2d');
+ ctx.imageSmoothingEnabled = false;
+
+ // Draw original tile first
+ ctx.drawImage(originalTileBitmap, 0, 0);
+
+ // Set opacity and draw our solid overlay chunk on top
+ ctx.globalAlpha = 0.6;
+ ctx.drawImage(chunkBitmap, 0, 0);
+
+ finalBlob = await canvas.convertToBlob({ type: 'image/png' });
+ } catch (e) {
+ console.error("Error compositing overlay:", e);
+ }
+ }
+ }
+ }
+
+ // Send the (possibly modified) blob back to the injected script
+ window.postMessage({
+ source: 'auto-image-overlay',
+ blobID: blobID,
+ blobData: finalBlob
+ }, '*');
+ }
+}
+
+const overlayManager = new OverlayManager();
+
+// Turnstile token handling (promise-based) inspired by external logic
+let turnstileToken = null
+let _resolveToken = null
+let tokenPromise = new Promise((resolve) => { _resolveToken = resolve })
+
+function setTurnstileToken(t) {
+ if (_resolveToken) {
+ _resolveToken(t)
+ _resolveToken = null
+ }
+ turnstileToken = t
+}
+
+async function ensureToken() {
+ if (!turnstileToken) {
+ updateUI("captchaNeeded", "error")
+ Utils.showAlert(Utils.t("captchaNeeded"), "error")
+ try { await tokenPromise } catch (_) {}
+ }
+ return turnstileToken
+}
+
+function inject(callback) {
+ const script = document.createElement('script');
+ script.textContent = `(${callback})();`;
+ document.documentElement?.appendChild(script);
+ script.remove();
+}
+
+inject(() => {
+ const fetchedBlobQueue = new Map();
+
+ window.addEventListener('message', (event) => {
+ const { source, blobID, blobData } = event.data;
+ if (source === 'auto-image-overlay' && blobID && blobData) {
+ const callback = fetchedBlobQueue.get(blobID);
+ if (typeof callback === 'function') {
+ callback(blobData);
+ }
+ fetchedBlobQueue.delete(blobID);
+ }
+ });
+
+ const originalFetch = window.fetch;
+ window.fetch = async function(...args) {
+ const response = await originalFetch.apply(this, args);
+ const url = (args[0] instanceof Request) ? args[0].url : args[0];
+
+ if (typeof url === "string") {
+ if (url.includes("https://backend.wplace.live/s0/pixel/")) {
+ try {
+ const payload = JSON.parse(args[1].body);
+ if (payload.t) {
+ console.log("✅ Turnstile Token Captured:", payload.t);
+ window.postMessage({ source: 'turnstile-capture', token: payload.t }, '*');
+ }
+ } catch (_) { /* ignore */ }
+ }
+
+ const contentType = response.headers.get('content-type') || '';
+ if (contentType.includes('image/png') && url.includes('.png')) {
+ const cloned = response.clone();
+ return new Promise(async (resolve) => {
+ const blobUUID = crypto.randomUUID();
+ const originalBlob = await cloned.blob();
+
+ fetchedBlobQueue.set(blobUUID, (processedBlob) => {
+ resolve(new Response(processedBlob, {
+ headers: cloned.headers,
+ status: cloned.status,
+ statusText: cloned.statusText
+ }));
+ });
+
+ window.postMessage({
+ source: 'auto-image-tile',
+ endpoint: url,
+ blobID: blobUUID,
+ blobData: originalBlob,
+ }, '*');
+ });
+ }
+ }
+
+ return response;
+ };
+});
+
+window.addEventListener('message', (event) => {
+ const { source, endpoint, blobID, blobData, token } = event.data;
+
+ if (source === 'auto-image-tile' && endpoint && blobID && blobData) {
+ overlayManager.processAndRespondToTileRequest(event.data);
+ }
+
+ if (source === 'turnstile-capture' && token) {
+ setTurnstileToken(token);
+ if (document.querySelector("#statusText")?.textContent.includes("CAPTCHA")) {
+ Utils.showAlert("Token captured successfully! You can start the bot now.", "success");
+ updateUI("colorsFound", "success", { count: state.availableColors.length });
+ }
+ }
+});
+
async function detectLanguage() {
try {
const response = await fetch("https://backend.wplace.live/me", {
@@ -629,11 +828,9 @@
return null;
},
- // Optimized DOM creation helpers
createElement: (tag, props = {}, children = []) => {
const element = document.createElement(tag)
- // Set properties efficiently
Object.entries(props).forEach(([key, value]) => {
if (key === 'style' && typeof value === 'object') {
Object.assign(element.style, value)
@@ -646,7 +843,6 @@
}
})
- // Append children efficiently
if (typeof children === 'string') {
element.textContent = children
} else if (Array.isArray(children)) {
@@ -662,7 +858,6 @@
return element
},
- // Create button with common styling
createButton: (id, text, icon, onClick, style = CONFIG.CSS_CLASSES.BUTTON_PRIMARY) => {
const button = Utils.createElement('button', {
id: id,
@@ -732,7 +927,6 @@
colorDistance: (a, b) => Math.sqrt(Math.pow(a[0] - b[0], 2) + Math.pow(a[1] - b[1], 2) + Math.pow(a[2] - b[2], 2)),
- // The color metric from colour-converter.js for higher accuracy
findClosestPaletteColor: (r, g, b, palette) => {
let menorDist = Infinity;
let cor = [0, 0, 0];
@@ -841,19 +1035,15 @@
calculateEstimatedTime: (remainingPixels, charges, cooldown) => {
if (remainingPixels <= 0) return 0
- // Calculate time based on painting speed (pixels per second)
const paintingSpeedDelay = state.paintingSpeed > 0 ? (1000 / state.paintingSpeed) : 1000
- const timeFromSpeed = remainingPixels * paintingSpeedDelay // ms
+ const timeFromSpeed = remainingPixels * paintingSpeedDelay
- // Calculate time based on charges and cooldown
const cyclesNeeded = Math.ceil(remainingPixels / Math.max(charges, 1))
- const timeFromCharges = cyclesNeeded * cooldown // ms
+ const timeFromCharges = cyclesNeeded * cooldown
- // Return the maximum of both calculations (the limiting factor)
return Math.max(timeFromSpeed, timeFromCharges)
},
- // Save/Load Progress Functions
saveProgress: () => {
try {
const progressData = {
@@ -909,10 +1099,8 @@
restoreProgress: (savedData) => {
try {
- // Restore state
Object.assign(state, savedData.state)
- // Restore image data
if (savedData.imageData) {
state.imageData = {
...savedData.imageData,
@@ -920,7 +1108,6 @@
}
}
- // Restore painted map
if (savedData.paintedMap) {
state.paintedMap = savedData.paintedMap.map((row) => Array.from(row))
}
@@ -1106,10 +1293,8 @@
const colorCache = new Map()
function findClosestColor(targetRgb, availableColors) {
- // Create cache key from RGB values
const cacheKey = `${targetRgb[0]},${targetRgb[1]},${targetRgb[2]}`
- // Check cache first
if (colorCache.has(cacheKey)) {
return colorCache.get(cacheKey)
}
@@ -1126,23 +1311,18 @@
let minDistance = Number.POSITIVE_INFINITY
let closestColorId = availableColors[0]?.id || 1
- // Use optimized loop for better performance
for (let i = 0; i < availableColors.length; i++) {
const color = availableColors[i]
const distance = Utils.colorDistance(targetRgb, color.rgb)
if (distance < minDistance) {
minDistance = distance
closestColorId = color.id
-
- // If perfect match, break early
if (distance === 0) break
}
}
- // Cache the result for future use
colorCache.set(cacheKey, closestColorId)
- // Limit cache size to prevent memory leaks
if (colorCache.size > 10000) {
const firstKey = colorCache.keys().next().value
colorCache.delete(firstKey)
@@ -1156,7 +1336,6 @@
let updateStats = () => {}
let updateDataButtons = () => {}
- // --- START: Color Palette Functions ---
function updateActiveColorPalette() {
state.activeColorPalette = [];
const activeSwatches = document.querySelectorAll('.wplace-color-swatch.active');
@@ -1166,7 +1345,6 @@
state.activeColorPalette.push(rgb);
});
}
- // If the resize dialog is open, update its preview
if (document.querySelector('.resize-container')?.style.display === 'block') {
_updateResizePreview();
}
@@ -1191,7 +1369,6 @@
freeContainer.innerHTML = '';
paidContainer.innerHTML = '';
- // Create a unique set of colors to avoid duplicates
const uniqueColors = [...new Set(CONFIG.COLOR_PALETTE.map(JSON.stringify))].map(JSON.parse);
uniqueColors.forEach(rgb => {
@@ -1209,7 +1386,6 @@
const nameLabel = Utils.createElement('span', { className: 'wplace-color-item-name' }, name);
- // Default state: free are active, paid are not
if (!isPaid) {
swatch.classList.add('active');
}
@@ -1229,18 +1405,13 @@
}
});
- // Add event listeners for master buttons
container.querySelector('#selectAllFreeBtn')?.addEventListener('click', () => toggleAllColors(true, false));
container.querySelector('#unselectAllFreeBtn')?.addEventListener('click', () => toggleAllColors(false, false));
container.querySelector('#selectAllPaidBtn')?.addEventListener('click', () => toggleAllColors(true, true));
container.querySelector('#unselectAllPaidBtn')?.addEventListener('click', () => toggleAllColors(false, true));
- // Set the initial state
updateActiveColorPalette();
}
- // --- END: Color Palette Functions ---
-
- // --- START: Auto-CAPTCHA Solver ---
async function handleCaptcha() {
return new Promise(async (resolve, reject) => {
if (!CONFIG.AUTO_CAPTCHA_ENABLED) {
@@ -1248,23 +1419,19 @@
}
try {
- // Set a timeout for the entire operation
const timeoutPromise = Utils.sleep(20000).then(() => reject(new Error("Auto-CAPTCHA timed out.")));
const solvePromise = (async () => {
- // 1. Find and click the main "Paint" button on the screen
const mainPaintBtn = await Utils.waitForSelector('button.btn.btn-primary.btn-lg, button.btn-primary.sm\\:btn-xl', 200, 10000);
if (!mainPaintBtn) throw new Error("Could not find the main paint button.");
mainPaintBtn.click();
await Utils.sleep(500);
- // 2. Select the transparent color to avoid wasting a real color
const transBtn = await Utils.waitForSelector('button#color-0', 200, 5000);
if (!transBtn) throw new Error("Could not find the transparent color button.");
transBtn.click();
await Utils.sleep(500);
- // 3. Find the canvas and simulate a click
const canvas = await Utils.waitForSelector('canvas', 200, 5000);
if (!canvas) throw new Error("Could not find the canvas element.");
@@ -1280,7 +1447,6 @@
canvas.dispatchEvent(new KeyboardEvent('keyup', { key: ' ', code: 'Space', bubbles: true }));
await Utils.sleep(500);
- // 4. Find and click the confirmation button
let confirmBtn = await Utils.waitForSelector('button.btn.btn-primary.btn-lg, button.btn.btn-primary.sm\\:btn-xl');
if (!confirmBtn) {
const allPrimary = Array.from(document.querySelectorAll('button.btn-primary'));
@@ -1289,7 +1455,6 @@
if (!confirmBtn) throw new Error("Could not find the confirmation button.");
confirmBtn.click();
- // The fetch interceptor will capture the token. We wait for it.
await tokenPromise;
resolve();
})();
@@ -1302,56 +1467,11 @@
}
});
}
- // --- END: Auto-CAPTCHA Solver ---
-
- // --- START: Overlay Management ---
- async function initializeOverlay() {
- state.gameCanvas = await Utils.waitForSelector('canvas');
- if (!state.gameCanvas || document.getElementById('wplace-overlay-canvas')) {
- console.log("Overlay not initialized (game canvas not found or overlay exists).");
- return; // Already initialized or game canvas not found
- }
-
- state.overlayCanvas = document.createElement('canvas');
- state.overlayCanvas.id = 'wplace-overlay-canvas';
- state.overlayCtx = state.overlayCanvas.getContext('2d');
-
- // Style the overlay
- state.overlayCanvas.style.position = 'absolute';
- state.overlayCanvas.style.pointerEvents = 'none';
- state.overlayCanvas.style.zIndex = '1'; // Just above the game canvas
- state.overlayCanvas.style.display = 'none';
- state.overlayCanvas.style.imageRendering = 'pixelated'; // Keep pixels sharp
-
- // Insert it into the DOM, right after the game canvas
- state.gameCanvas.parentElement.insertBefore(state.overlayCanvas, state.gameCanvas.nextSibling);
-
- const syncOverlay = () => {
- if (!state.gameCanvas || !state.overlayCanvas) return;
- // The canvas element's width/height attributes are the resolution
- // The style width/height are the display size, but transform handles that.
- state.overlayCanvas.width = state.gameCanvas.width;
- state.overlayCanvas.height = state.gameCanvas.height;
- // Copy position and transform to align perfectly
- state.overlayCanvas.style.top = state.gameCanvas.offsetTop + 'px';
- state.overlayCanvas.style.left = state.gameCanvas.offsetLeft + 'px';
- state.overlayCanvas.style.transform = state.gameCanvas.style.transform;
- };
-
- // Use a MutationObserver to watch for style changes (pan/zoom)
- const observer = new MutationObserver(syncOverlay);
- observer.observe(state.gameCanvas, { attributes: true, attributeFilter: ['style', 'width', 'height'] });
-
- syncOverlay(); // Initial sync
- console.log("✅ Overlay Initialized.");
- }
- // --- END: Overlay Management ---
async function createUI() {
await detectLanguage()
- // Clean up existing UI elements to prevent duplicates
const existingContainer = document.getElementById("wplace-image-bot-container")
const existingStats = document.getElementById("wplace-stats-container")
const existingSettings = document.getElementById("wplace-settings-container")
@@ -1680,7 +1800,7 @@
? `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" ? "border: 1px solid rgba(255,255,255,0.1);" : ""}
}
${
@@ -1794,6 +1914,11 @@
.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};
@@ -1827,7 +1952,6 @@
color: ${theme.highlight};
}
- /* Styles for the new color display in stats */
.wplace-colors-section {
margin-top: 10px;
padding-top: 8px;
@@ -1977,7 +2101,7 @@
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; /* Increased width */
+ max-width: 700px;
max-height: 90%;
overflow: auto;
font-family: ${theme.fontFamily};
@@ -1995,7 +2119,7 @@
}
.resize-preview {
- max-width: none; /* Allow image to exceed wrapper for zoom */
+ max-width: none;
transition: transform 0.1s ease;
image-rendering: pixelated;
image-rendering: -moz-crisp-edges;
@@ -2052,7 +2176,7 @@
}
.resize-zoom-controls {
- grid-column: 1 / -1; /* Span full width */
+ grid-column: 1 / -1;
display: flex;
align-items: center;
gap: 10px;
@@ -2076,10 +2200,9 @@
z-index: 9999;
display: none;
}
- /* --- START: Color Palette Styles --- */
.wplace-color-grid {
display: grid;
- grid-template-columns: repeat(auto-fill, minmax(60px, 1fr)); /* Wider columns for name */
+ grid-template-columns: repeat(auto-fill, minmax(60px, 1fr));
gap: 10px;
padding-top: 8px;
}
@@ -2136,9 +2259,7 @@
background: rgba(255,255,255,0.1);
margin: 8px 0;
}
- /* --- END: Color Palette Styles --- */
- /* Cooldown slider styles */
.wplace-cooldown-control {
margin-top: 8px;
}
@@ -2174,7 +2295,6 @@
${
CONFIG.currentTheme === "Neon Retro"
? `
- /* Retro checkbox styling */
input[type="checkbox"] {
-webkit-appearance: none;
-moz-appearance: none;
@@ -2202,12 +2322,10 @@
font-weight: bold;
}
- /* Icon styling for retro feel */
.fas, .fa {
filter: drop-shadow(0 0 3px currentColor);
}
- /* Speed Control Styles */
.wplace-speed-control {
margin-top: 12px;
padding: 12px;
@@ -2290,7 +2408,6 @@
opacity: 0.8;
}
- /* Settings Window Styles */
#wplace-settings-container {
position: fixed;
top: 50%;
@@ -2602,6 +2719,12 @@
${Utils.t("stopPainting")}
+
+
+
@@ -2933,7 +3056,6 @@
background: #4a5568;
}
- /* Dragging state styles */
.wplace-dragging {
opacity: 0.9;
box-shadow: 0 30px 60px rgba(0,0,0,0.4), 0 0 0 1px rgba(255,255,255,0.2);
@@ -2956,11 +3078,11 @@
${Utils.t("resizeImage")}
-
Color Palette
@@ -3001,7 +3122,6 @@
-