diff --git a/Auto-Image.js b/Auto-Image.js
index 4200f49..41db943 100644
--- a/Auto-Image.js
+++ b/Auto-Image.js
@@ -43,11 +43,12 @@
"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([
- "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",
+ "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"
]),
// --- END: Color data ---
// Optimized CSS Classes for reuse
@@ -176,7 +177,6 @@
const TEXT = {
en: {
title: "WPlace Auto-Image",
- toggleOverlay: "Toggle Overlay",
scanColors: "Scan Colors",
uploadImage: "Upload Image",
resizeImage: "Resize Image",
@@ -552,256 +552,57 @@
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 = () => {};
- // --- 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;
+ // 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
}
-
- toggle() {
- this.isEnabled = !this.isEnabled;
- console.log(`Overlay ${this.isEnabled ? 'enabled' : 'disabled'}.`);
- return this.isEnabled;
- }
-
- 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();
- }
- }
-
- 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
}
- turnstileToken = t
-}
-async function ensureToken() {
- if (!turnstileToken) {
- updateUI("captchaNeeded", "error")
- Utils.showAlert(Utils.t("captchaNeeded"), "error")
- try { await tokenPromise } catch (_) {}
+ async function ensureToken() {
+ if (!turnstileToken) {
+ updateUI("captchaNeeded", "error")
+ Utils.showAlert(Utils.t("captchaNeeded"), "error")
+ try { await tokenPromise } catch (_) {}
+ }
+ return turnstileToken
}
- 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);
+ // 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 })
+ }
}
- });
-
- 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);
+ } catch (_) { /* ignore */ }
}
+ return originalFetch(url, options)
+ }
- 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 });
- }
- }
-});
-
+ // LANGUAGE DETECTION
async function detectLanguage() {
try {
const response = await fetch("https://backend.wplace.live/me", {
@@ -828,9 +629,11 @@ window.addEventListener('message', (event) => {
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)
@@ -843,6 +646,7 @@ window.addEventListener('message', (event) => {
}
})
+ // Append children efficiently
if (typeof children === 'string') {
element.textContent = children
} else if (Array.isArray(children)) {
@@ -858,6 +662,7 @@ window.addEventListener('message', (event) => {
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,
@@ -927,6 +732,7 @@ window.addEventListener('message', (event) => {
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];
@@ -1035,15 +841,19 @@ window.addEventListener('message', (event) => {
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
+ const timeFromSpeed = remainingPixels * paintingSpeedDelay // ms
+ // Calculate time based on charges and cooldown
const cyclesNeeded = Math.ceil(remainingPixels / Math.max(charges, 1))
- const timeFromCharges = cyclesNeeded * cooldown
+ const timeFromCharges = cyclesNeeded * cooldown // ms
+ // Return the maximum of both calculations (the limiting factor)
return Math.max(timeFromSpeed, timeFromCharges)
},
+ // Save/Load Progress Functions
saveProgress: () => {
try {
const progressData = {
@@ -1099,8 +909,10 @@ window.addEventListener('message', (event) => {
restoreProgress: (savedData) => {
try {
+ // Restore state
Object.assign(state, savedData.state)
+ // Restore image data
if (savedData.imageData) {
state.imageData = {
...savedData.imageData,
@@ -1108,6 +920,7 @@ window.addEventListener('message', (event) => {
}
}
+ // Restore painted map
if (savedData.paintedMap) {
state.paintedMap = savedData.paintedMap.map((row) => Array.from(row))
}
@@ -1293,8 +1106,10 @@ window.addEventListener('message', (event) => {
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)
}
@@ -1311,18 +1126,23 @@ window.addEventListener('message', (event) => {
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)
@@ -1336,6 +1156,7 @@ window.addEventListener('message', (event) => {
let updateStats = () => {}
let updateDataButtons = () => {}
+ // --- START: Color Palette Functions ---
function updateActiveColorPalette() {
state.activeColorPalette = [];
const activeSwatches = document.querySelectorAll('.wplace-color-swatch.active');
@@ -1345,6 +1166,7 @@ window.addEventListener('message', (event) => {
state.activeColorPalette.push(rgb);
});
}
+ // If the resize dialog is open, update its preview
if (document.querySelector('.resize-container')?.style.display === 'block') {
_updateResizePreview();
}
@@ -1369,6 +1191,7 @@ window.addEventListener('message', (event) => {
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 => {
@@ -1386,6 +1209,7 @@ window.addEventListener('message', (event) => {
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');
}
@@ -1405,13 +1229,18 @@ window.addEventListener('message', (event) => {
}
});
+ // 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) {
@@ -1419,19 +1248,23 @@ window.addEventListener('message', (event) => {
}
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.");
@@ -1447,6 +1280,7 @@ window.addEventListener('message', (event) => {
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'));
@@ -1455,6 +1289,7 @@ window.addEventListener('message', (event) => {
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();
})();
@@ -1467,11 +1302,56 @@ window.addEventListener('message', (event) => {
}
});
}
+ // --- 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")
@@ -1800,7 +1680,7 @@ window.addEventListener('message', (event) => {
? `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);` : ""}
}
${
@@ -1914,11 +1794,6 @@ window.addEventListener('message', (event) => {
.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};
@@ -1952,6 +1827,7 @@ window.addEventListener('message', (event) => {
color: ${theme.highlight};
}
+ /* Styles for the new color display in stats */
.wplace-colors-section {
margin-top: 10px;
padding-top: 8px;
@@ -2101,7 +1977,7 @@ window.addEventListener('message', (event) => {
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-width: 700px; /* Increased width */
max-height: 90%;
overflow: auto;
font-family: ${theme.fontFamily};
@@ -2119,7 +1995,7 @@ window.addEventListener('message', (event) => {
}
.resize-preview {
- max-width: none;
+ max-width: none; /* Allow image to exceed wrapper for zoom */
transition: transform 0.1s ease;
image-rendering: pixelated;
image-rendering: -moz-crisp-edges;
@@ -2176,7 +2052,7 @@ window.addEventListener('message', (event) => {
}
.resize-zoom-controls {
- grid-column: 1 / -1;
+ grid-column: 1 / -1; /* Span full width */
display: flex;
align-items: center;
gap: 10px;
@@ -2200,9 +2076,10 @@ window.addEventListener('message', (event) => {
z-index: 9999;
display: none;
}
+ /* --- START: Color Palette Styles --- */
.wplace-color-grid {
display: grid;
- grid-template-columns: repeat(auto-fill, minmax(60px, 1fr));
+ grid-template-columns: repeat(auto-fill, minmax(60px, 1fr)); /* Wider columns for name */
gap: 10px;
padding-top: 8px;
}
@@ -2259,7 +2136,9 @@ window.addEventListener('message', (event) => {
background: rgba(255,255,255,0.1);
margin: 8px 0;
}
+ /* --- END: Color Palette Styles --- */
+ /* Cooldown slider styles */
.wplace-cooldown-control {
margin-top: 8px;
}
@@ -2295,6 +2174,7 @@ window.addEventListener('message', (event) => {
${
CONFIG.currentTheme === "Neon Retro"
? `
+ /* Retro checkbox styling */
input[type="checkbox"] {
-webkit-appearance: none;
-moz-appearance: none;
@@ -2322,10 +2202,12 @@ window.addEventListener('message', (event) => {
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;
@@ -2408,6 +2290,7 @@ window.addEventListener('message', (event) => {
opacity: 0.8;
}
+ /* Settings Window Styles */
#wplace-settings-container {
position: fixed;
top: 50%;
@@ -2719,12 +2602,6 @@ window.addEventListener('message', (event) => {
${Utils.t("stopPainting")}
-
-
-
@@ -3056,6 +2933,7 @@ window.addEventListener('message', (event) => {
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);
@@ -3078,11 +2956,11 @@ window.addEventListener('message', (event) => {
${Utils.t("resizeImage")}
+
Color Palette
@@ -3122,6 +3001,7 @@ window.addEventListener('message', (event) => {
+