diff --git a/Extensions/Script-manager.js b/Extensions/Script-manager.js
deleted file mode 100644
index 3c3c391..0000000
--- a/Extensions/Script-manager.js
+++ /dev/null
@@ -1,642 +0,0 @@
-// ==UserScript==
-// @name WPlace AutoBOT Script Manager
-// @namespace http://tampermonkey.net/
-// @version 2025-09-08.1
-// @description Script manager and launcher for WPlace AutoBOT
-// @author TH3C0D3R
-// @match https://wplace.live/*
-// @grant none
-// @icon
-// ==/UserScript==l
-
-; (async () => {
- console.log('%cš WPlace AutoBOT Script Manager Loading...', 'color: #00ff41; font-weight: bold; font-size: 16px;');
-
- // Available scripts configuration
- const AVAILABLE_SCRIPTS = [
- {
- name: 'Auto-Farm.js',
- displayName: 'š¾ Auto Farm',
- description: 'Automated farming and pixel painting',
- icon: 'š¾',
- category: 'automation'
- },
- {
- name: 'Auto-Image.js',
- displayName: 'š¼ļø Auto Image',
- description: 'Automated image processing and placement',
- icon: 'š¼ļø',
- category: 'automation'
- },
- {
- name: 'Auto-Repair.js',
- displayName: 'š§ Auto Repair',
- description: 'Automated repair and maintenance tasks',
- icon: 'š§',
- category: 'utility'
- },
- {
- name: 'Art-Extractor.js',
- displayName: 'šØ Art Extractor',
- description: 'Extract artwork areas to JSON for auto-repair',
- icon: 'šØ',
- category: 'utility'
- }
- ]; // Neon theme styling
- const NEON_STYLES = `
- @import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');
-
- .script-manager-container {
- position: fixed !important;
- top: 50% !important;
- left: 50% !important;
- transform: translate(-50%, -50%) !important;
- background: #1a1a2e;
- border: 3px solid #00ff41;
- border-radius: 0;
- box-shadow:
- 0 0 30px rgb(0 255 65 / 50%),
- inset 0 0 30px rgb(0 255 65 / 10%),
- 0 0 0 1px #00ff41;
- font-family: 'Press Start 2P', monospace, 'Courier New';
- z-index: 10001 !important;
- min-width: 600px;
- max-width: 800px;
- max-height: 80vh;
- overflow: hidden;
- color: #00ff41;
- animation: neon-pulse 2s ease-in-out infinite alternate;
- }
-
- .script-manager-container::before {
- content: '';
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- height: 2px;
- background: linear-gradient(90deg, transparent, #00ff41, transparent);
- z-index: 1;
- pointer-events: none;
- animation: scanline 3s linear infinite;
- opacity: 0.7;
- }
-
- .script-manager-header {
- background: #16213e;
- border-bottom: 2px solid #00ff41;
- padding: 15px 20px;
- position: relative;
- display: flex;
- justify-content: space-between;
- align-items: center;
- }
-
- .header-content {
- display: flex;
- align-items: center;
- gap: 15px;
- }
-
- .header-icon {
- width: 32px;
- height: 32px;
- border-radius: 6px;
- box-shadow: 0 0 15px rgba(0, 255, 65, 0.4);
- transition: all 0.3s ease;
- animation: pixel-blink 3s infinite;
- }
-
- .header-icon:hover {
- transform: scale(1.1);
- box-shadow: 0 0 25px rgba(0, 255, 65, 0.6);
- }
-
- .script-manager-title {
- color: #00ff41;
- font-size: 14px;
- text-shadow: 0 0 15px #00ff41;
- margin: 0;
- text-transform: uppercase;
- letter-spacing: 2px;
- animation: text-glow 2s ease-in-out infinite alternate;
- }
-
- .script-manager-close {
- background: #16213e;
- border: 2px solid #ff073a;
- border-radius: 0;
- color: #ff073a;
- width: 30px;
- height: 30px;
- cursor: pointer;
- font-family: 'Press Start 2P', monospace;
- font-size: 12px;
- display: flex;
- align-items: center;
- justify-content: center;
- transition: all 0.3s ease;
- text-shadow: 0 0 10px #ff073a;
- }
-
- .script-manager-close:hover {
- background: #ff073a;
- color: #1a1a2e;
- box-shadow: 0 0 20px #ff073a;
- animation: pixel-blink 0.5s infinite;
- }
-
- .script-manager-content {
- padding: 20px;
- max-height: 60vh;
- overflow-y: auto;
- background: linear-gradient(45deg, rgba(0,255,65,0.03) 0%, rgba(22,33,62,0.05) 100%);
- }
-
- .script-grid {
- display: grid;
- grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
- gap: 15px;
- margin-bottom: 20px;
- }
-
- .script-card {
- background: #16213e;
- border: 2px solid #00ff41;
- border-radius: 0;
- padding: 15px;
- cursor: pointer;
- transition: all 0.3s ease;
- position: relative;
- overflow: hidden;
- }
-
- .script-card::before {
- content: '';
- position: absolute;
- top: -50%;
- left: -50%;
- width: 200%;
- height: 200%;
- background: linear-gradient(45deg, transparent, rgba(0,255,65,0.1), transparent);
- transform: rotate(45deg);
- transition: all 0.5s ease;
- opacity: 0;
- }
-
- .script-card:hover::before {
- animation: neon-sweep 1s ease-in-out;
- }
-
- .script-card:hover {
- background: rgba(0,255,65,0.1);
- box-shadow:
- 0 0 25px rgb(0 255 65 / 60%),
- inset 0 0 25px rgb(0 255 65 / 15%);
- transform: translateY(-3px);
- animation: card-glow 0.5s ease-in-out infinite alternate;
- }
-
- .script-card-header {
- display: flex;
- align-items: center;
- margin-bottom: 10px;
- }
-
- .script-icon {
- font-size: 24px;
- margin-right: 10px;
- filter: drop-shadow(0 0 10px #00ff41);
- }
-
- .script-title {
- color: #00ff41;
- font-size: 11px;
- text-shadow: 0 0 10px #00ff41;
- text-transform: uppercase;
- letter-spacing: 1px;
- margin: 0;
- }
-
- .script-description {
- color: #00ff41dd;
- font-size: 8px;
- line-height: 1.4;
- text-shadow: 0 0 5px #00ff41;
- margin-bottom: 15px;
- }
-
- .script-category {
- background: rgba(255, 107, 53, 0.2);
- border: 1px solid #ff6b35;
- color: #ff6b35;
- padding: 3px 8px;
- font-size: 7px;
- text-transform: uppercase;
- letter-spacing: 1px;
- text-shadow: 0 0 5px #ff6b35;
- display: inline-block;
- }
-
- .script-manager-footer {
- background: #16213e;
- border-top: 2px solid #00ff41;
- padding: 15px 20px;
- display: flex;
- justify-content: space-between;
- align-items: center;
- }
-
- .status-text {
- color: #00ff41dd;
- font-size: 8px;
- text-shadow: 0 0 5px #00ff41;
- }
-
- .action-buttons {
- display: flex;
- gap: 10px;
- }
-
- .neon-btn {
- background: #16213e;
- border: 2px solid #00ff41;
- border-radius: 0;
- color: #00ff41;
- padding: 8px 15px;
- font-family: 'Press Start 2P', monospace;
- font-size: 8px;
- text-transform: uppercase;
- cursor: pointer;
- transition: all 0.3s ease;
- text-shadow: 0 0 8px #00ff41;
- letter-spacing: 1px;
- }
-
- .neon-btn:hover {
- background: rgba(0,255,65,0.1);
- box-shadow: 0 0 20px rgb(0 255 65 / 60%);
- animation: pixel-blink 0.5s infinite;
- }
-
- .neon-btn.secondary {
- border-color: #ff6b35;
- color: #ff6b35;
- text-shadow: 0 0 8px #ff6b35;
- }
-
- .neon-btn.secondary:hover {
- background: rgba(255, 107, 53, 0.1);
- box-shadow: 0 0 20px rgb(255 107 53 / 60%);
- }
-
- .script-manager-backdrop {
- position: fixed !important;
- top: 0 !important;
- left: 0 !important;
- width: 100% !important;
- height: 100% !important;
- background: rgba(0, 0, 0, 0.8);
- z-index: 10000 !important;
- backdrop-filter: blur(5px);
- animation: backdrop-fade-in 0.3s ease-out;
- }
-
- /* Loading animation */
- .loading-container {
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- height: 200px;
- }
-
- .loading-spinner {
- width: 40px;
- height: 40px;
- border: 3px solid #16213e;
- border-top: 3px solid #00ff41;
- border-radius: 0;
- animation: neon-spin 1s linear infinite;
- margin-bottom: 15px;
- box-shadow: 0 0 20px rgb(0 255 65 / 50%);
- }
-
- .loading-text {
- color: #00ff41;
- font-size: 8px;
- text-shadow: 0 0 10px #00ff41;
- text-transform: uppercase;
- letter-spacing: 2px;
- animation: text-pulse 1.5s ease-in-out infinite;
- }
-
- /* Custom scrollbar */
- .script-manager-content::-webkit-scrollbar {
- width: 12px;
- }
-
- .script-manager-content::-webkit-scrollbar-track {
- background: #16213e;
- border: 1px solid #00ff41;
- }
-
- .script-manager-content::-webkit-scrollbar-thumb {
- background: #00ff41;
- border-radius: 0;
- box-shadow: 0 0 10px #00ff41;
- }
-
- .script-manager-content::-webkit-scrollbar-thumb:hover {
- background: #39ff14;
- box-shadow: 0 0 15px #39ff14;
- }
-
- /* Animations */
- @keyframes neon-pulse {
- 0% { box-shadow: 0 0 30px rgb(0 255 65 / 50%), inset 0 0 30px rgb(0 255 65 / 10%), 0 0 0 1px #00ff41; }
- 100% { box-shadow: 0 0 40px rgb(0 255 65 / 70%), inset 0 0 40px rgb(0 255 65 / 15%), 0 0 0 1px #00ff41; }
- }
-
- @keyframes text-glow {
- 0% { text-shadow: 0 0 15px #00ff41; }
- 100% { text-shadow: 0 0 25px #00ff41, 0 0 35px #00ff41; }
- }
-
- @keyframes pixel-blink {
- 0%, 50% { opacity: 1; }
- 51%, 100% { opacity: 0.7; }
- }
-
- @keyframes scanline {
- 0% { transform: translateY(-100%); }
- 100% { transform: translateY(400px); }
- }
-
- @keyframes neon-sweep {
- 0% { opacity: 0; transform: translateX(-100%) translateY(-100%) rotate(45deg); }
- 50% { opacity: 1; }
- 100% { opacity: 0; transform: translateX(100%) translateY(100%) rotate(45deg); }
- }
-
- @keyframes card-glow {
- 0% { box-shadow: 0 0 25px rgb(0 255 65 / 60%), inset 0 0 25px rgb(0 255 65 / 15%); }
- 100% { box-shadow: 0 0 35px rgb(0 255 65 / 80%), inset 0 0 35px rgb(0 255 65 / 20%); }
- }
-
- @keyframes neon-spin {
- 0% { transform: rotate(0deg); }
- 100% { transform: rotate(360deg); }
- }
-
- @keyframes text-pulse {
- 0%, 100% { opacity: 1; }
- 50% { opacity: 0.6; }
- }
-
- @keyframes backdrop-fade-in {
- 0% { opacity: 0; }
- 100% { opacity: 1; }
- }
-
- /* Responsive design */
- @media (max-width: 768px) {
- .script-manager-container {
- min-width: 90vw;
- max-width: 95vw;
- }
-
- .script-grid {
- grid-template-columns: 1fr;
- }
-
- .script-manager-title {
- font-size: 10px;
- }
- }
- `;
-
- // Execute script function - Fixed to work like extension popup
- async function executeScript(scriptName) {
- console.group(`%cš Executing ${scriptName}`, 'color: #00ff41; font-weight: bold;');
-
- try {
- // Show loading in the UI
- showLoading(`Launching ${scriptName}...`);
-
- // The Script Manager runs in MAIN world context and doesn't have direct Chrome API access
- // Instead, we need to communicate back to the content script which can use Chrome APIs
- console.log('%cš Script Manager context - delegating to content script', 'color: #ff6b35;');
-
- // Create a custom event to communicate with the content script
- const executeEvent = new CustomEvent('autobot-execute-script', {
- detail: { scriptName: scriptName }
- });
-
- // Dispatch the event to the content script
- window.dispatchEvent(executeEvent);
-
- // Show success immediately since we're delegating
- console.log(`%cā
${scriptName} execution delegated to content script`, 'color: #39ff14; font-weight: bold;');
- showSuccess(`${scriptName} execution started!`);
-
- // Auto-close after success
- setTimeout(() => {
- closeScriptManager();
- }, 1500);
-
- } catch (error) {
- console.error(`%cā Failed to execute ${scriptName}:`, 'color: #ff073a; font-weight: bold;', error);
- showError(`Failed to launch ${scriptName}: ${error.message}`);
- } finally {
- console.groupEnd();
- }
- }
-
- // UI Management functions
- function showLoading(message) {
- const container = document.getElementById('script-manager-content');
- if (!container) return;
-
- container.innerHTML = `
-
- `;
- }
-
- function showSuccess(message) {
- const statusText = document.querySelector('.status-text');
- if (statusText) {
- statusText.textContent = `ā
${message}`;
- statusText.style.color = '#39ff14';
- statusText.style.textShadow = '0 0 10px #39ff14';
- }
- }
-
- function showError(message) {
- const statusText = document.querySelector('.status-text');
- if (statusText) {
- statusText.textContent = `ā ${message}`;
- statusText.style.color = '#ff073a';
- statusText.style.textShadow = '0 0 10px #ff073a';
- }
-
- // Reset the content to show scripts again
- setTimeout(() => {
- renderScripts();
- }, 3000);
- }
-
- function renderScripts() {
- const container = document.getElementById('script-manager-content');
- if (!container) return;
-
- const scriptGrid = AVAILABLE_SCRIPTS.map(script => `
-
-
-
${script.description}
-
${script.category}
-
- `).join('');
-
- container.innerHTML = `
-
- ${scriptGrid}
-
- `;
- }
-
- // Close script manager
- function closeScriptManager() {
- const container = document.getElementById('script-manager-container');
- const backdrop = document.getElementById('script-manager-backdrop');
-
- if (container) {
- container.style.animation = 'neon-fade-out 0.3s ease-in forwards';
- setTimeout(() => {
- container.remove();
- }, 300);
- }
-
- if (backdrop) {
- backdrop.style.animation = 'backdrop-fade-out 0.3s ease-in forwards';
- setTimeout(() => {
- backdrop.remove();
- }, 300);
- }
-
- // Remove ESC key listener
- document.removeEventListener('keydown', handleEscKey);
-
- console.log('%cš Script Manager closed', 'color: #ff6b35;');
- }
-
- // ESC key handler
- function handleEscKey(event) {
- if (event.key === 'Escape') {
- closeScriptManager();
- }
- }
-
- // Main function to show script manager
- function showScriptManager() {
- // Remove any existing manager
- const existing = document.getElementById('script-manager-container');
- if (existing) existing.remove();
-
- const existingBackdrop = document.getElementById('script-manager-backdrop');
- if (existingBackdrop) existingBackdrop.remove();
-
- console.log('%cš® Opening Script Manager with Neon Theme', 'color: #00ff41; font-weight: bold;');
-
- // Get icon URL for display
- let iconUrl = '';
- try {
- if (chrome && chrome.runtime && chrome.runtime.getURL) {
- iconUrl = chrome.runtime.getURL('icons/icon32.png');
- console.log('š· Icon URL:', iconUrl);
- }
- } catch (e) {
- console.log('Extension context not available for icon');
- }
-
- // Inject styles
- if (!document.getElementById('script-manager-styles')) {
- const styleElement = document.createElement('style');
- styleElement.id = 'script-manager-styles';
- styleElement.textContent = NEON_STYLES;
- document.head.appendChild(styleElement);
- }
-
- // Create backdrop
- const backdrop = document.createElement('div');
- backdrop.id = 'script-manager-backdrop';
- backdrop.className = 'script-manager-backdrop';
- backdrop.addEventListener('click', closeScriptManager);
-
- // Create container
- const container = document.createElement('div');
- container.id = 'script-manager-container';
- container.className = 'script-manager-container';
-
- container.innerHTML = `
-
-
-
-
-
- `;
-
- // Add to page
- document.body.appendChild(backdrop);
- document.body.appendChild(container);
-
- // Debug: Check positioning
- console.log('%cš Script Manager Positioning Debug:', 'color: #ff6b35; font-weight: bold;');
- console.log(` - Container position: ${getComputedStyle(container).position}`);
- console.log(` - Container top: ${getComputedStyle(container).top}`);
- console.log(` - Container left: ${getComputedStyle(container).left}`);
- console.log(` - Container transform: ${getComputedStyle(container).transform}`);
- console.log(` - Container z-index: ${getComputedStyle(container).zIndex}`);
- console.log(` - Backdrop z-index: ${getComputedStyle(backdrop).zIndex}`);
-
- // Render scripts
- renderScripts();
-
- // Add ESC key listener
- document.addEventListener('keydown', handleEscKey);
-
- // Focus container for accessibility
- container.focus();
-
- console.log('%cā
Script Manager opened successfully', 'color: #39ff14; font-weight: bold;');
- }
-
- // Make functions globally available
- window.executeScript = executeScript;
- window.closeScriptManager = closeScriptManager;
- window.showScriptManager = showScriptManager;
-
- // Auto-start the script manager
- console.log('%cšÆ Auto-launching Script Manager...', 'color: #00ff41; font-weight: bold;');
- showScriptManager();
-
- console.log('%cš WPlace AutoBOT Script Manager Ready!', 'color: #39ff14; font-weight: bold; font-size: 16px;');
-})();
diff --git a/Extensions/auto-image-styles.css b/Extensions/auto-image-styles.css
deleted file mode 100644
index dff366b..0000000
--- a/Extensions/auto-image-styles.css
+++ /dev/null
@@ -1,2194 +0,0 @@
-/* WPlace Auto-Image Bot - Unified CSS Styles (decoupled from JS)
- Why: bring external CSS in-sync with the UI that Auto-Image.js renders,
- fix layout (positions, widths, z-index), and ensure class names match JS
- (e.g., .wplace-dragging) so buttons and panels behave correctly. */
-
-/* ========================= */
-/* Theme tokens (CSS vars) */
-/* ========================= */
-
-/* NOTE: Themes are now loaded dynamically from extension local resources
- instead of using @import from CDN. The extension will inject theme CSS
- directly when needed using window.applyTheme() function.
-
- Previous CDN imports (now removed):
- - https://wplace-autobot.github.io/WPlace-AutoBOT/main/themes/classic.css
- - https://wplace-autobot.github.io/WPlace-AutoBOT/main/themes/classic-light.css
- - https://wplace-autobot.github.io/WPlace-AutoBOT/main/themes/acrylic.css
- - https://wplace-autobot.github.io/WPlace-AutoBOT/main/themes/neon.css
- - https://wplace-autobot.github.io/WPlace-AutoBOT/main/themes/neon-cyan.css
- - https://wplace-autobot.github.io/WPlace-AutoBOT/main/themes/neon-light.css
-
- These are now loaded from local extension files for better performance
- and offline functionality. */
-
-/* Default :root CSS variables for 100% classic theme compliance */
-/* These ensure the bot works perfectly even if theme files fail to load */
-:root {
- /* Classic theme colors - exact upstream main values */
- --wplace-primary: linear-gradient(135deg, #000 0%, #1a1a1a 100%);
- --wplace-secondary: linear-gradient(135deg, #111 0%, #2a2a2a 100%);
- --wplace-accent: #222;
- --wplace-text: #fff;
- --wplace-highlight: #775ce3;
- --wplace-highlight-secondary: #d3a4ff;
- --wplace-success: #0f0;
- --wplace-error: #f00;
- --wplace-warning: #fa0;
-
- /* UI properties */
- --wplace-radius: 12px;
- --wplace-btn-radius: 16px;
- --wplace-border-style: solid;
- --wplace-border-width: 1px;
- --wplace-border-color: #222;
- --wplace-shadow: 0 8px 32px rgb(0 0 0 / 60%), 0 0 0 1px rgb(255 255 255 / 10%);
- --wplace-backdrop: blur(10px);
- --wplace-font: 'Segoe UI', roboto, sans-serif;
-
- /* Z-index layers */
- --wplace-z-overlay: 10000;
- --wplace-z-alert: 10002;
- --wplace-z-settings: 10002;
-
- /* Feature toggles */
- --wplace-scanline: 0;
- --wplace-pixel-blink: 0;
-
- /* Icon colors */
- --wplace-icon-primary: #4facfe;
- --wplace-icon-secondary: #00f2fe;
- --wplace-icon-palette: #f093fb;
-
- /* Additional UI colors */
- --wplace-danger: #ff6a6a;
- --wplace-danger-dark: #ff4757;
- --wplace-muted-text: #fffb;
-
- /* Text variants */
- --wplace-text-secondary: rgb(255 255 255 / 90%);
- --wplace-text-muted: rgb(255 255 255 / 70%);
- --wplace-text-dim: rgb(255 255 255 / 60%);
- --wplace-text-faded: rgb(255 255 255 / 80%);
-
- /* Background variants */
- --wplace-bg-input: rgb(255 255 255 / 15%);
- --wplace-bg-subtle: rgb(255 255 255 / 10%);
- --wplace-bg-faint: rgb(255 255 255 / 8%);
- --wplace-bg-ghost: rgb(255 255 255 / 6%);
- --wplace-bg-whisper: rgb(255 255 255 / 5%);
-
- /* Border variants */
- --wplace-border-subtle: rgb(255 255 255 / 20%);
- --wplace-border-faint: rgb(255 255 255 / 15%);
- --wplace-border-ghost: rgb(255 255 255 / 10%);
- --wplace-border-ultra-faint: rgb(255 255 255 / 5%);
-
- /* Shadow variants */
- --wplace-shadow-drag: 0 12px 40px rgb(0 0 0 / 80%), 0 0 0 2px rgb(255 255 255 / 20%);
- --wplace-shadow-notification: 0 4px 12px rgb(0 0 0 / 30%);
- --wplace-shadow-slider-thumb: 0 3px 6px rgb(0 0 0 / 30%), 0 0 0 2px var(--wplace-icon-primary);
- --wplace-shadow-slider-hover: 0 4px 8px rgb(0 0 0 / 40%), 0 0 0 3px var(--wplace-icon-primary);
-
- /* Animation colors */
- --wplace-pulse-start: rgb(0 255 0 / 70%);
- --wplace-pulse-mid: rgb(0 255 0 / 0%);
- --wplace-pulse-end: rgb(0 255 0 / 0%);
-
- /* Slider colors - defaults for classic compatibility */
- --wplace-slider-thumb-bg: white;
- --wplace-slider-track-bg: linear-gradient(to right,
- var(--wplace-icon-primary) 0%,
- var(--wplace-icon-secondary) 100%);
-}
-
-/* Theme classes are now defined in separate files */
-/* Classic theme: ./themes/classic.css */
-/* Neon theme: ./themes/neon.css */
-
-/* ========================= */
-/* Core animations (shared) */
-/* ========================= */
-@keyframes neon-glow {
-
- 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 pixel-blink {
-
- 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 var(--wplace-pulse-start);
- }
-
- 70% {
- box-shadow: 0 0 0 10px var(--wplace-pulse-mid);
- }
-
- 100% {
- box-shadow: 0 0 0 0 var(--wplace-pulse-end);
- }
-}
-
-@keyframes slide-in {
- from {
- transform: translateY(-10px);
- opacity: 0;
- }
-
- to {
- transform: translateY(0);
- opacity: 1;
- }
-}
-
-@keyframes slide-down {
- from {
- transform: translateX(-50%) translateY(-20px);
- opacity: 0;
- }
-
- to {
- transform: translateX(-50%) translateY(0);
- opacity: 1;
- }
-}
-
-@keyframes shimmer {
- 0% {
- transform: translateX(-100%);
- }
-
- 100% {
- transform: translateX(100%);
- }
-}
-
-/* ========================= */
-/* Main containers (fixed) */
-/* Align with JS structure */
-/* ========================= */
-#wplace-image-bot-container {
- position: fixed;
- top: 20px;
- left: 20px;
- width: 340px;
- max-height: calc(100vh - 40px);
- padding: 0;
- z-index: 9998;
- animation: slide-in 0.4s ease-out;
- overflow: hidden;
- transition: all 0.3s ease;
- user-select: none;
-
- /* Default classic theme styling for 100% compliance */
- background: var(--wplace-primary);
- color: var(--wplace-text);
- border-radius: var(--wplace-radius);
- box-shadow: var(--wplace-shadow);
- font-family: var(--wplace-font);
- backdrop-filter: var(--wplace-backdrop);
- border: var(--wplace-border-width) var(--wplace-border-style) var(--wplace-border-color);
-}
-
-#wplace-image-bot-container.wplace-dragging {
- transition: none;
- box-shadow: var(--wplace-shadow-drag);
- transform: scale(1.02);
- z-index: 9999;
-}
-
-#wplace-image-bot-container.wplace-compact {
- width: 300px;
-}
-
-#wplace-image-bot-container.wplace-minimized {
- width: 260px;
- height: auto;
- overflow: hidden;
-}
-
-/* Stats container sits to the right of main (340 + 30 = 370) */
-#wplace-stats-container {
- position: fixed;
- top: 20px;
- left: 370px;
- width: 350px;
- max-height: calc(100vh - 40px);
- padding: 0;
- z-index: 9997;
- animation: slide-in 0.4s ease-out;
- overflow-y: auto;
- transition: all 0.3s ease;
- user-select: none;
- display: none;
-
- /* Default classic theme styling for 100% compliance */
- background: var(--wplace-primary);
- color: var(--wplace-text);
- border-radius: var(--wplace-radius);
- box-shadow: var(--wplace-shadow);
- font-family: var(--wplace-font);
- backdrop-filter: var(--wplace-backdrop);
- border: var(--wplace-border-width) var(--wplace-border-style) var(--wplace-border-color);
-}
-
-#wplace-stats-container.wplace-dragging {
- transition: none;
-}
-
-/* Back-compat for legacy class (some earlier CSS used this) */
-.wplace-drag-active {
- transition: none !important;
- box-shadow: var(--wplace-shadow-drag) !important;
- transform: scale(1.02) !important;
- z-index: 9999 !important;
-}
-
-/* ========================= */
-/* Header and content blocks */
-/* ========================= */
-.wplace-header {
- padding: 8px 12px;
- font-size: 13px;
- font-weight: 700;
- display: flex;
- justify-content: space-between;
- align-items: center;
- cursor: move;
- user-select: none;
- border-bottom: 1px solid var(--wplace-border-ghost);
- transition: background 0.2s ease;
- position: relative;
- z-index: 2;
-
- /* Default styling for 100% classic compliance */
- background: var(--wplace-secondary);
- color: var(--wplace-highlight);
- text-shadow: 0 1px 2px rgb(0 0 0 / 50%);
-}
-
-.wplace-header-title {
- display: flex;
- align-items: center;
- gap: 6px;
-}
-
-.wplace-header-controls {
- display: flex;
- gap: 6px;
-}
-
-.wplace-header-btn {
- border: none;
- cursor: pointer;
- width: 18px;
- height: 18px;
- padding: 0;
- font-size: 10px;
- display: flex;
- align-items: center;
- justify-content: center;
- transition: all 0.2s;
-
- /* Default styling for 100% classic compliance */
- background: rgb(255 255 255 / 10%);
- color: var(--wplace-text);
- border-radius: 4px;
- font-family: var(--wplace-font);
-}
-
-.wplace-header-btn:hover {
- transform: scale(1.1);
-
- /* Default styling for 100% classic compliance */
- background: var(--wplace-accent);
- color: var(--wplace-text);
-}
-
-.wplace-content {
- display: block;
- position: relative;
- z-index: 2;
- padding: 12px;
-}
-
-.wplace-content.wplace-hidden {
- display: none;
-}
-
-/* Sections */
-.wplace-status-section {
- margin-bottom: 6px;
- padding: 8px;
-
- /* Theme-specific styling applied via theme files */
-}
-
-.wplace-section {
- margin-bottom: 6px;
- padding: 8px;
-
- /* Theme-specific styling applied via theme files */
-}
-
-.wplace-section-title {
- font-size: 11px;
- font-weight: 600;
- margin-bottom: 8px;
- display: flex;
- justify-content: space-between;
- align-items: center;
- cursor: pointer;
- gap: 6px;
- text-transform: uppercase;
- letter-spacing: 1px;
-
- /* Theme-specific styling applied via theme files */
-}
-
-.wplace-section-title i.arrow {
- transition: transform 0.3s ease;
-}
-
-.wplace-section.collapsed .wplace-section-title i.arrow {
- transform: rotate(-90deg);
-}
-
-/* ========================= */
-/* Controls and buttons */
-/* ========================= */
-.wplace-controls {
- display: flex;
- flex-direction: column;
- gap: 8px;
-}
-
-.wplace-cooldown-control {
- margin-top: 8px;
-}
-
-.wplace-section.collapsed .wplace-controls,
-.wplace-section.collapsed .wplace-cooldown-control {
- max-height: 0;
- opacity: 0;
- pointer-events: none;
-}
-
-.wplace-row {
- display: grid;
- grid-template-columns: 1fr 1fr;
- gap: 8px;
-}
-
-.wplace-row.single {
- grid-template-columns: 1fr;
-}
-
-.wplace-btn {
- padding: 8px 12px;
- border: none;
- cursor: pointer;
- display: flex;
- align-items: center;
- justify-content: center;
- gap: 6px;
- font-size: 11px;
- transition: all 0.3s ease;
- position: relative;
- overflow: hidden;
- font-weight: 500;
-
- /* Theme-specific styling applied via theme files */
-}
-
-.wplace-btn:disabled {
- opacity: 0.5;
- cursor: not-allowed;
- transform: none !important;
-
- /* Theme-specific styling applied via theme files */
-}
-
-.wplace-btn::before {
- content: '';
- position: absolute;
- top: 0;
- left: -100%;
- width: 100%;
- height: 100%;
- transition: left 0.5s ease;
-
- /* Theme-specific styling applied via theme files */
-}
-
-.wplace-btn:disabled::before {
- display: none;
-}
-
-.wplace-btn:hover:not(:disabled)::before {
- left: 100%;
-}
-
-.wplace-btn:hover:not(:disabled) {
- transform: translateY(-1px);
-
- /* Theme-specific styling applied via theme files */
-}
-
-.wplace-btn:active:not(:disabled) {
- transform: translateY(0);
-}
-
-/* Button variants moved to theme files */
-
-/* ========================= */
-/* Stats and progress */
-/* ========================= */
-.wplace-stats {
- margin-bottom: 8px;
- padding: 8px;
-
- /* Theme-specific styling applied via theme files */
-}
-
-.wplace-stat-item {
- display: flex;
- justify-content: space-between;
- padding: 4px 0;
- font-size: 11px;
- border-bottom: 1px solid var(--wplace-border-ultra-faint);
-}
-
-.wplace-stat-item:last-child {
- border-bottom: none;
-}
-
-.wplace-stat-label {
- display: flex;
- align-items: center;
- gap: 6px;
- opacity: 0.9;
- font-size: 10px;
-}
-
-.wplace-stat-value {
- font-weight: 600;
-
- /* Theme-specific styling applied via theme files */
-}
-
-.wplace-colors-section {
- margin-top: 10px;
- padding-top: 8px;
- border-top: 1px solid var(--wplace-border-ultra-faint);
-}
-
-.wplace-stat-colors-grid {
- display: grid;
- grid-template-columns: repeat(auto-fill, minmax(16px, 1fr));
- gap: 4px;
- margin-top: 8px;
- padding: 4px;
- max-height: 80px;
- overflow-y: auto;
-
- /* Theme-specific styling applied via theme files */
-}
-
-.wplace-colors-placeholder {
- text-align: center;
- color: #888;
- padding: 20px;
- font-style: italic;
-}
-
-.wplace-cooldown-value {
- font-weight: bold;
- min-width: 20px;
- text-align: center;
- display: inline-block;
-}
-
-.wplace-stat-color-swatch {
- width: 16px;
- height: 16px;
-
- /* Theme-specific styling applied via theme files */
-}
-
-/* Progress */
-.wplace-progress {
- width: 100%;
- margin: 8px 0;
- overflow: hidden;
- height: 6px;
- position: relative;
-
- /* Theme-specific styling applied via theme files */
-}
-
-.wplace-progress-bar {
- height: 6px;
- transition: width 0.5s ease;
- position: relative;
-
- /* Theme-specific styling applied via theme files */
-}
-
-.wplace-progress-bar::after {
- content: '';
- position: absolute;
- inset: 0;
- animation: shimmer 2s infinite;
-
- /* Theme-specific styling applied via theme files */
-}
-
-/* ========================= */
-/* Status blocks */
-/* ========================= */
-.wplace-status {
- text-align: center;
- position: relative;
- overflow: hidden;
- padding: 6px;
- border: 1px solid;
- font-size: 11px;
-
- /* Theme-specific styling applied via theme files */
-}
-
-/* Status styling moved to theme files */
-
-/* ========================= */
-/* Resize dialog */
-/* ========================= */
-.resize-container {
- display: none;
- position: fixed;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- padding: 20px;
- z-index: 10000;
- width: 90%;
- max-width: 700px;
- max-height: 90%;
- overflow: auto;
-
- /* Theme-specific styling applied via theme files */
-}
-
-.resize-preview-wrapper {
- display: flex;
- justify-content: center;
- align-items: center;
- margin: 15px 0;
- height: 300px;
- overflow: hidden;
-
- /* Theme-specific styling applied via theme files */
-}
-
-.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: -moz-crisp-edges;
- image-rendering: pixelated;
-}
-
-.resize-mask-canvas {
- pointer-events: auto;
-}
-
-.resize-tools {
- display: flex;
- gap: 8px;
- align-items: center;
- margin-top: 8px;
- font-size: 12px;
-}
-
-/* Missing button hover styles */
-.resize-tools button {
- padding: 6px 10px;
- border-radius: 6px;
- border: 1px solid var(--wplace-border-subtle);
- background: var(--wplace-bg-ghost);
- color: var(--wplace-text);
- cursor: pointer;
-
- /* Theme-specific styling applied via theme files */
-}
-
-/* Button active states moved to theme files */
-
-.resize-controls {
- display: grid;
- grid-template-columns: 1fr 1fr;
- gap: 15px;
- align-items: center;
-}
-
-.resize-slider {
- width: 100%;
- height: 4px;
- border: none;
- outline: none;
- -webkit-appearance: none;
-
- /* Theme-specific styling applied via theme files */
-}
-
-.resize-zoom-controls {
- grid-column: 1 / -1;
- display: flex;
- align-items: center;
- gap: 8px;
- margin-top: 15px;
- flex-wrap: wrap;
-}
-
-.resize-buttons {
- display: flex;
- gap: 10px;
- justify-content: center;
- margin-top: 20px;
-}
-
-
-/* ========================= */
-/* 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;
- text-align: center;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- width: 100%;
-
- /* Theme-specific styling applied via theme files */
-}
-
-.wplace-color-swatch {
- width: 22px;
- height: 22px;
- cursor: pointer;
- transition:
- transform 0.1s ease,
- box-shadow 0.2s ease;
- position: relative;
- margin: 0 auto;
-
- /* Theme-specific styling applied via theme files */
-}
-
-.wplace-color-swatch.unavailable {
- border-style: dashed;
- cursor: not-allowed;
-
- /* Theme-specific styling applied via theme files */
-}
-
-.wplace-color-swatch:hover {
- transform: scale(1.1);
- z-index: 1;
-}
-
-.wplace-color-swatch:not(.active) {
- opacity: 0.3;
-
- /* Theme-specific styling applied via theme files */
-}
-
-.wplace-color-swatch.unavailable:not(.active) {
- opacity: 0.2;
-
- /* Theme-specific styling applied via theme files */
-}
-
-.wplace-color-swatch.active::after {
- content: 'ā';
- position: absolute;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- color: var(--wplace-text);
- font-size: 12px;
- font-weight: bold;
-
- /* Theme-specific styling applied via theme files */
-}
-
-.wplace-color-divider {
- border: none;
- height: 1px;
- margin: 8px 0;
-
- /* Theme-specific styling applied via theme files */
-}
-
-/* ========================= */
-/* Cooldown controls */
-/* ========================= */
-.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;
- outline: none;
-
- /* Theme-specific styling applied via theme files */
-}
-
-.wplace-slider::-webkit-slider-thumb {
- -webkit-appearance: none;
- width: 14px;
- height: 14px;
- cursor: pointer;
-
- /* Theme-specific styling applied via theme files */
-}
-
-/* ========================= */
-/* Settings container (base) */
-/* ========================= */
-#wplace-settings-container {
- position: fixed;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- padding: 0;
- z-index: 10002;
- display: none;
- min-width: 420px;
- max-width: 480px;
- overflow: hidden;
-
- /* Default classic theme styling for 100% compliance */
- background: var(--wplace-primary);
- color: var(--wplace-text);
- border-radius: var(--wplace-radius);
- box-shadow: var(--wplace-shadow);
- font-family: var(--wplace-font);
- backdrop-filter: var(--wplace-backdrop);
- border: var(--wplace-border-width) var(--wplace-border-style) var(--wplace-border-color);
-}
-
-#wplace-settings-container.show {
- display: block;
- animation: settings-slide-in 0.4s ease-out;
-}
-
-@keyframes settings-slide-in {
- from {
- opacity: 0;
- transform: translate(-50%, -50%) scale(0.9);
- }
-
- to {
- opacity: 1;
- transform: translate(-50%, -50%) scale(1);
- }
-}
-
-@keyframes settings-fade-out {
- from {
- opacity: 1;
- transform: translate(-50%, -50%) scale(1);
- }
-
- to {
- opacity: 0;
- transform: translate(-50%, -50%) scale(0.9);
- }
-}
-
-.wplace-settings {
- padding: 16px;
- max-height: 400px;
- overflow-y: auto;
-}
-
-.wplace-setting-section {
- margin-bottom: 20px;
- padding: 12px;
-}
-
-/* ========================= */
-/* Form controls */
-/* ========================= */
-.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;
-}
-
-/* Speed controls */
-.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 {
- flex: 1;
- height: 8px;
- outline: none;
- -webkit-appearance: none;
- appearance: none;
- cursor: pointer;
- background: var(--wplace-slider-track-bg);
- border-radius: 4px;
-}
-
-
-.wplace-speed-slider::-webkit-slider-thumb {
- -webkit-appearance: none;
- appearance: none;
- width: 18px;
- height: 18px;
- cursor: pointer;
-
- /* Theme-specific styling applied via theme files */
-}
-
-.wplace-speed-slider::-moz-range-thumb {
- width: 18px;
- height: 18px;
- cursor: pointer;
-
- /* Theme-specific styling applied via theme files */
-}
-
-.wplace-speed-display {
- display: flex;
- align-items: center;
- gap: 4px;
- min-width: 90px;
- justify-content: flex-end;
-}
-
-/* ========================= */
-/* Turnstile overlay */
-/* ========================= */
-/* Hidden invisible widget host (token generator) */
-.wplace-turnstile-hidden {
- 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;
-}
-
-/* Visible overlay (interactive fallback) */
-.wplace-turnstile-overlay {
- position: fixed !important;
- bottom: 20px !important;
- right: 20px !important;
- z-index: 99999 !important;
- padding: 20px !important;
- min-width: 300px !important;
- max-width: 400px !important;
-
- /* Theme-specific styling applied via theme files */
-}
-
-.wplace-turnstile-title {
- font:
- 600 12px/1.3 'Segoe UI',
- sans-serif !important;
- margin-bottom: 8px !important;
- opacity: 0.9 !important;
-
- /* Theme-specific styling applied via theme files */
-}
-
-.wplace-turnstile-host {
- width: 100% !important;
- min-height: 70px !important;
-}
-
-.wplace-turnstile-hide-btn {
- position: absolute !important;
- top: 6px !important;
- right: 6px !important;
- font-size: 11px !important;
- background: transparent !important;
- padding: 2px 6px !important;
- cursor: pointer !important;
- transition: background 0.2s ease !important;
-
- /* Theme-specific styling applied via theme files */
-}
-
-.wplace-turnstile-hide-btn:hover {
- /* Theme-specific styling applied via theme files */
-}
-
-/* ========================= */
-/* Alert system (used by JS) */
-/* ========================= */
-.wplace-alert-base {
- position: fixed;
- top: 20px;
- left: 50%;
- transform: translateX(-50%);
- padding: 12px 20px;
- color: var(--wplace-text);
- font-weight: 600;
- z-index: 10002;
- max-width: 400px;
- text-align: center;
- animation: slide-down 0.3s ease-out;
- box-shadow: var(--wplace-shadow-notification);
-
- /* Theme-specific styling applied via theme files */
-}
-
-/* Alert styling moved to theme files */
-
-/* ========================= */
-/* Modal overlay helpers */
-/* ========================= */
-.wplace-overlay {
- position: fixed;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- z-index: 100002;
- display: none;
-
- /* Theme-specific styling applied via theme files */
-}
-
-.wplace-overlay.show {
- display: block;
-}
-
-/* Overlay visibility helpers */
-.wplace-overlay-hidden {
- display: none !important;
-}
-
-.wplace-overlay-visible {
- display: block !important;
-}
-
-/* ========================= */
-/* Responsive tweaks */
-/* ========================= */
-@media (width <=768px) {
- #wplace-image-bot-container {
- left: 10px;
- width: calc(100vw - 20px);
- max-height: calc(100vh - 20px);
- }
-
- #wplace-stats-container {
- display: none !important;
- /* hide secondary panel on small screens */
- }
-
- .wplace-alert-base {
- max-width: 90vw;
- margin: 0 5vw;
- }
-
- .wplace-turnstile-overlay {
- bottom: 10px !important;
- right: 10px !important;
- left: 10px !important;
- min-width: auto !important;
- }
-}
-
-/* Auto light/dark support moved to theme files */
-
-/* ===================================== */
-/* Settings container variants (optional) */
-/* ===================================== */
-.wplace-settings-container-base {
- position: fixed;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- z-index: 100001;
- display: none;
- min-width: 400px;
- max-width: 500px;
- max-height: 100vh;
- overflow-y: auto;
- animation: slide-in 0.3s ease-out;
- border: 1px solid var(--wplace-border-ghost);
- padding: 0;
-
- /* Theme-specific styling applied via theme files */
-}
-
-.wplace-settings-container-base.show {
- display: block;
-}
-
-/* ===================================== */
-/* Utility styles */
-/* ===================================== */
-.wplace-paint-effect {
- animation: pulse 0.5s ease-out;
-}
-
-.wplace-settings-error {
- /* Theme-specific styling applied via theme files */
-}
-
-.wplace-stats-container.hidden {
- display: none;
-}
-
-/* Theme-specific effects are now in separate theme files */
-
-/* ===================================== */
-/* Settings Dialog Styles */
-/* ===================================== */
-
-/* Settings content container */
-.wplace-settings-content {
- padding: 25px 25px 0;
- max-height: 67vh;
- overflow-y: auto;
-}
-
-/* Settings section containers */
-.wplace-settings-section {
- margin-bottom: 25px;
-}
-
-/* Section labels */
-.wplace-settings-section-label {
- margin-bottom: 12px;
- color: var(--wplace-text);
- font-weight: 500;
- font-size: 16px;
- display: flex;
- align-items: center;
- gap: 8px;
-}
-
-/* Icon colors */
-.wplace-icon-key {
- font-size: 16px;
-}
-
-.wplace-icon-robot {
- font-size: 16px;
-}
-
-.wplace-icon-speed {
- font-size: 16px;
-}
-
-.wplace-icon-bell {
- font-size: 16px;
-}
-
-.wplace-icon-palette {
- font-size: 16px;
-}
-
-.wplace-icon-globe {
- font-size: 16px;
-}
-
-.wplace-icon-paint {
- font-size: 16px;
-}
-
-/* Section wrapper styling */
-.wplace-settings-section-wrapper {
- padding: 18px;
-
- /* Theme-specific styling applied via theme files */
-}
-
-/* Select dropdowns */
-.wplace-settings-select {
- width: 100%;
- padding: 12px 16px;
- font-size: 14px;
- outline: none;
- cursor: pointer;
- transition: all 0.3s ease;
- font-family: inherit;
-
- /* Theme-specific styling applied via theme files */
-}
-
-.wplace-settings-option {
- padding: 10px;
-
- /* Theme-specific styling applied via theme files */
-}
-
-/* Settings description text */
-.wplace-settings-description {
- font-size: 12px;
- color: var(--wplace-text-muted);
- margin: 8px 0 0;
-}
-
-/* Batch Controls */
-.wplace-batch-controls {
- padding: 18px;
- margin-bottom: 15px;
-
- /* Theme-specific styling applied via theme files */
-}
-
-.wplace-random-batch-controls {
- display: none;
-}
-
-/* Speed Slider Container */
-.wplace-speed-slider-container {
- display: flex;
- align-items: center;
- gap: 15px;
- margin-bottom: 10px;
-}
-
-
-.wplace-speed-value {
- min-width: 100px;
- text-align: center;
- padding: 8px 12px;
- font-weight: bold;
- font-size: 13px;
-
- /* Theme-specific styling applied via theme files */
-}
-
-.wplace-speed-labels {
- display: flex;
- justify-content: space-between;
- color: var(--wplace-text-muted);
- font-size: 11px;
- margin-top: 8px;
-}
-
-/* Random Batch Controls */
-.wplace-random-batch-grid {
- display: grid;
- grid-template-columns: 1fr 1fr;
- gap: 15px;
-}
-
-.wplace-random-batch-label {
- display: block;
- color: var(--wplace-text-faded);
- font-size: 12px;
- margin-bottom: 8px;
-}
-
-.wplace-icon-min {
- color: var(--wplace-icon-primary);
- margin-right: 4px;
-}
-
-.wplace-icon-max {
- color: var(--wplace-icon-secondary);
- margin-right: 4px;
-}
-
-.wplace-settings-number-input {
- width: 100%;
- padding: 10px 12px;
- background: var(--wplace-bg-input);
- color: var(--wplace-text);
- border: 1px solid var(--wplace-border-subtle);
- border-radius: 8px;
- font-size: 13px;
- outline: none;
-}
-
-.wplace-random-batch-description {
- font-size: 11px;
- color: var(--wplace-text-dim);
- margin: 8px 0 0;
- text-align: center;
-}
-
-/* Speed Control Toggle */
-.wplace-speed-control-toggle {
- display: flex;
- align-items: center;
- gap: 8px;
- color: var(--wplace-text);
-}
-
-.wplace-speed-checkbox {
- cursor: pointer;
-}
-
-/* Overlay Settings */
-.wplace-overlay-opacity-control {
- margin-bottom: 15px;
-}
-
-.wplace-overlay-opacity-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 8px;
-}
-
-.wplace-overlay-opacity-label {
- font-weight: 500;
- font-size: 13px;
-}
-
-.wplace-overlay-opacity-value {
- min-width: 40px;
- text-align: center;
- background: var(--wplace-accent);
- color: var(--wplace-text);
- padding: 4px 8px;
- border-radius: 6px;
- font-size: 12px;
- border: var(--wplace-border-width) var(--wplace-border-style) var(--wplace-border-color);
-}
-
-.wplace-overlay-opacity-slider {
- width: 100%;
- -webkit-appearance: none;
- height: 8px;
- outline: none;
- cursor: pointer;
-}
-
-.wplace-settings-toggle {
- display: flex;
- align-items: center;
- justify-content: space-between;
- cursor: pointer;
-}
-
-.wplace-settings-toggle-title {
- font-weight: 500;
-}
-
-.wplace-settings-toggle-description {
- font-size: 12px;
- margin: 4px 0 0;
- color: var(--wplace-muted-text);
-}
-
-.wplace-settings-checkbox {
- cursor: pointer;
- width: 20px;
- height: 20px;
- flex-shrink: 0;
- accent-color: var(--wplace-highlight);
-}
-
-/* Notifications */
-.wplace-notifications-wrapper {
- display: flex;
- flex-direction: column;
- gap: 10px;
-}
-
-.wplace-notification-toggle {
- display: flex;
- align-items: center;
- justify-content: space-between;
-}
-
-.wplace-notification-checkbox {
- width: 18px;
- height: 18px;
- cursor: pointer;
-}
-
-.wplace-notification-interval {
- display: flex;
- align-items: center;
- gap: 10px;
-}
-
-.wplace-notification-interval-input {
- width: 70px;
- padding: 6px 8px;
- border-radius: 6px;
- border: 1px solid var(--wplace-border-subtle);
- background: var(--wplace-bg-faint);
- color: var(--wplace-text);
-}
-
-.wplace-notification-buttons {
- display: flex;
- gap: 10px;
-}
-
-.wplace-notification-perm-btn,
-.wplace-notification-test-btn {
- flex: 1;
-}
-
-/* Settings Footer */
-.wplace-settings-footer {
- border-top: 1px solid var(--wplace-border-ghost);
- padding: 20px;
- position: sticky;
- bottom: 0;
- background: var(--wplace-secondary);
-}
-
-/* Settings select option styling moved to theme files */
-/* Settings description styling moved to theme files */
-
-.wplace-settings-apply-btn {
- width: 100%;
- border: none;
- padding: 10px 16px;
- cursor: pointer;
- font-weight: 500;
- transition: all 0.3s ease;
- display: flex;
- align-items: center;
- gap: 8px;
- justify-content: center;
-
- /* Theme-specific styling applied via theme files */
-}
-
-/* ===================================== */
-/* Resize Dialog Styles */
-/* ===================================== */
-
-/* Resize dialog title */
-.resize-dialog-title {
- margin-top: 0;
- color: var(--wplace-text);
-}
-
-/* Resize control labels */
-.resize-control-label {
- display: block;
- margin-bottom: 8px;
- font-size: 14px;
-}
-
-.resize-checkbox-label {
- display: flex;
- align-items: center;
- margin-bottom: 8px;
- font-size: 14px;
- gap: 8px;
-}
-
-/* Zoom controls */
-
-.resize-zoom-btn {
- padding: 4px 8px;
-}
-
-.resize-zoom-slider {
- max-width: 220px;
-}
-
-.resize-zoom-value {
- margin-left: 6px;
- min-width: 48px;
- text-align: right;
- opacity: 0.85;
- font-size: 12px;
-}
-
-.resize-camera-help {
- font-size: 11px;
- opacity: 0.75;
- margin-left: auto;
-}
-
-/* Canvas positioning */
-.resize-pan-stage {
- position: relative;
- width: 100%;
- height: 100%;
- overflow: hidden;
-}
-
-.resize-canvas-positioned {
- position: absolute;
- left: 0;
- top: 0;
- transform-origin: top left;
-}
-
-/* Resize tools */
-.resize-tools-container {
- display: flex;
- gap: 10px;
- flex-wrap: wrap;
- align-items: center;
-}
-
-.resize-brush-controls {
- display: flex;
- flex-direction: column;
- gap: 8px;
-}
-
-.resize-brush-control {
- display: flex;
- align-items: center;
- gap: 6px;
- justify-content: space-between;
-}
-
-.resize-tool-label {
- font-size: 12px;
- opacity: 0.85;
-}
-
-.resize-tool-input-group {
- display: flex;
- align-items: center;
- gap: 6px;
-}
-
-.resize-tool-slider {
- width: 120px;
-}
-
-.resize-tool-value {
- font-size: 12px;
- opacity: 0.85;
- min-width: 18px;
- text-align: center;
-}
-
-.resize-mode-controls {
- display: flex;
- align-items: center;
- gap: 6px;
-}
-
-.resize-mode-group {
- display: flex;
- gap: 6px;
-}
-
-.resize-mode-btn,
-.resize-clear-btn,
-.resize-invert-btn {
- padding: 4px 8px;
- font-size: 12px;
-}
-
-.resize-shortcut-help {
- opacity: 0.8;
- font-size: 12px;
-}
-
-/* Color palette section */
-.resize-color-palette-section {
- margin-top: 15px;
-}
-
-.resize-color-toggle-label {
- display: flex;
- align-items: center;
- gap: 8px;
- font-size: 12px;
-}
-
-.resize-color-checkbox {
- cursor: pointer;
-}
-
-/* Advanced color section */
-.resize-advanced-color-section {
- margin-top: 15px;
-}
-
-.resize-advanced-controls {
- display: flex;
- flex-direction: column;
- gap: 10px;
-}
-
-.resize-advanced-label {
- display: flex;
- flex-direction: column;
- gap: 4px;
- font-size: 12px;
-}
-
-.resize-advanced-label-text {
- font-weight: 600;
-}
-
-.resize-advanced-select {
- padding: 6px 8px;
- border-radius: 6px;
- border: 1px solid var(--wplace-border-faint);
- background: var(--wplace-bg-whisper);
- color: var(--wplace-text);
-}
-
-.resize-advanced-toggle {
- display: flex;
- align-items: center;
- justify-content: space-between;
- font-size: 12px;
-}
-
-.resize-advanced-toggle-content {
- flex: 1;
-}
-
-.resize-advanced-description {
- margin-top: 2px;
- opacity: 0.65;
-}
-
-.resize-advanced-checkbox {
- width: 18px;
- height: 18px;
- cursor: pointer;
-}
-
-.resize-chroma-weight-control {
- display: flex;
- flex-direction: column;
- gap: 4px;
-}
-
-.resize-chroma-weight-header {
- display: flex;
- justify-content: space-between;
- font-size: 11px;
- margin-bottom: 4px;
-}
-
-.resize-chroma-weight-value {
- background: var(--wplace-bg-faint);
- padding: 2px 6px;
- border-radius: 4px;
-}
-
-.resize-chroma-weight-slider {
- width: 100%;
-}
-
-.resize-threshold-controls {
- display: grid;
- grid-template-columns: 1fr 1fr;
- gap: 10px;
-}
-
-.resize-threshold-label {
- display: flex;
- flex-direction: column;
- gap: 4px;
- font-size: 12px;
-}
-
-.resize-threshold-input {
- padding: 6px 8px;
- border-radius: 6px;
- border: 1px solid var(--wplace-border-faint);
- background: var(--wplace-bg-whisper);
- color: var(--wplace-text);
-}
-
-.resize-reset-advanced-btn {
- background: linear-gradient(135deg, var(--wplace-danger), var(--wplace-danger-dark));
- font-size: 11px;
-}
-
-/* ===================================== */
-/* Additional styles for exact upstream match */
-/* ===================================== */
-
-/* Overlay styles exact match */
-.resize-overlay {
- position: fixed;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- z-index: 9999;
- display: none;
-
- /* Theme-specific styling applied via theme files */
-}
-
-/* Settings animations - removed duplicate, handled above */
-
-/* Settings Header Styling */
-.wplace-settings-header {
- background: var(--wplace-accent);
- padding: 20px;
- border-bottom: 1px solid var(--wplace-border-color);
- cursor: move;
-}
-
-.wplace-settings-title-wrapper {
- display: flex;
- justify-content: space-between;
- align-items: center;
-}
-
-.wplace-settings-title {
- margin: 0;
- color: var(--wplace-text);
- font-size: 20px;
- font-weight: 300;
- display: flex;
- align-items: center;
- gap: 10px;
-}
-
-.wplace-settings-icon {
- font-size: 18px;
- color: var(--wplace-highlight);
- animation: spin 2s linear infinite;
-}
-
-.wplace-settings-close-btn {
- background: rgb(34 34 34 / 40%);
- color: var(--wplace-text);
- border: 1px solid var(--wplace-border-color);
- border-radius: 50%;
- width: 32px;
- height: 32px;
- cursor: pointer;
- display: flex;
- align-items: center;
- justify-content: center;
- transition: all 0.3s ease;
- font-size: 14px;
- font-weight: 300;
-}
-
-.wplace-settings-close-btn:hover {
- background: rgb(255 0 0 / 40%);
- transform: scale(1.1);
-}
-
-@keyframes spin {
- from {
- transform: rotate(0deg);
- }
-
- to {
- transform: rotate(360deg);
- }
-}
-
-/* Font styling corrections */
-.resize-controls label {
- font-size: 12px;
- color: var(--wplace-text);
-}
-
-
-/* Additional precision fixes for exact upstream main matching */
-/* Status default styling moved to theme files */
-
-/* Duplicate settings section removed - all settings styling moved to theme files */
-
-/* Settings header styling moved to theme files */
-/* All remaining settings styling moved to theme files */
-
-
-/* Icon styling moved to theme files */
-.wplace-icon-eye {
- font-size: 16px;
-}
-
-/* Overlay Settings Controls */
-.wplace-overlay-opacity-slider::-webkit-slider-thumb {
- -webkit-appearance: none;
- appearance: none;
- width: 18px;
- height: 18px;
- border-radius: 50%;
- background: var(--wplace-slider-thumb-bg);
- box-shadow: var(--wplace-shadow-slider-thumb);
- cursor: pointer;
- transition: all 0.2s ease;
-}
-
-.wplace-overlay-opacity-slider::-webkit-slider-thumb:hover {
- transform: scale(1.2);
- box-shadow: var(--wplace-shadow-slider-hover);
-}
-
-/* Coordinate Generation Controls */
-.wplace-icon-route {
- color: var(--wplace-icon-primary);
- font-size: 16px;
-}
-
-.wplace-icon-table {
- color: var(--wplace-icon-palette);
- margin-right: 6px;
-}
-
-.wplace-icon-compass {
- color: var(--wplace-icon-secondary);
- margin-right: 6px;
-}
-
-/* Pixel Filter Controls */
-.wplace-pixel-filter-controls {
- padding: 18px;
- margin-bottom: 15px;
- display: flex;
- flex-direction: column;
- gap: 12px;
-
- /* Theme-specific styling applied via theme files */
-}
-
-/* Snake Pattern Controls */
-.wplace-snake-pattern-controls {
- padding: 18px;
- margin-bottom: 15px;
-
- /* Theme-specific styling applied via theme files */
-}
-
-/* Block Size Controls */
-.wplace-block-size-controls {
- padding: 18px;
- margin-bottom: 15px;
-
- /* Theme-specific styling applied via theme files */
-}
-
-.wplace-block-size-grid {
- display: grid;
- grid-template-columns: 1fr 1fr;
- gap: 15px;
-}
-
-.wplace-block-size-label {
- display: block;
- color: var(--wplace-text-faded);
- font-size: 12px;
- margin-bottom: 8px;
-}
-
-.wplace-icon-width {
- color: var(--wplace-icon-primary);
- margin-right: 4px;
-}
-
-.wplace-icon-height {
- color: var(--wplace-icon-secondary);
- margin-right: 4px;
-}
-
-.wplace-block-size-description {
- font-size: 11px;
- color: var(--wplace-text-dim);
- margin: 8px 0 0;
- text-align: center;
-}
-
-/* Random Block Controls */
-.wplace-shuffle-block-size-controls {
- display: none;
-}
-
-/* Batch Mode Controls */
-.wplace-mode-selection {
- margin-bottom: 15px;
-}
-
-.wplace-mode-label {
- display: block;
- margin-bottom: 8px;
- color: var(--wplace-text-secondary);
- font-weight: 500;
- font-size: 14px;
-}
-
-.wplace-icon-dice {
- color: var(--wplace-icon-palette);
- margin-right: 6px;
-}
-
-/* Account Management Styles */
-.wplace-account-info {
- display: flex;
- flex-direction: column;
- gap: 12px;
- margin-bottom: 12px;
- padding: 12px;
- background: var(--wplace-secondary);
- border: 1px solid var(--wplace-border-color);
- border-radius: var(--wplace-radius);
-}
-
-.wplace-account-current {
- display: flex;
- align-items: center;
- gap: 8px;
- font-size: 14px;
- color: var(--wplace-text);
-}
-
-.wplace-account-current i {
- color: var(--wplace-highlight);
- width: 16px;
-}
-
-.wplace-account-current strong {
- color: var(--wplace-success);
-}
-
-.wplace-account-switch {
- display: flex;
- align-items: center;
- gap: 8px;
-}
-
-.wplace-toggle-switch {
- display: flex;
- align-items: center;
- gap: 10px;
- cursor: pointer;
- user-select: none;
-}
-
-.wplace-toggle-switch input[type="checkbox"] {
- display: none;
-}
-
-.wplace-toggle-slider {
- position: relative;
- width: 44px;
- height: 24px;
- background: var(--wplace-accent);
- border: 1px solid var(--wplace-border-color);
- border-radius: 12px;
- transition: all 0.3s ease;
-}
-
-.wplace-toggle-slider:before {
- content: '';
- position: absolute;
- top: 2px;
- left: 2px;
- width: 18px;
- height: 18px;
- background: var(--wplace-text);
- border-radius: 50%;
- transition: all 0.3s ease;
-}
-
-.wplace-toggle-switch input:checked+.wplace-toggle-slider {
- background: var(--wplace-success);
- border-color: var(--wplace-success);
-}
-
-.wplace-toggle-switch input:checked+.wplace-toggle-slider:before {
- transform: translateX(20px);
- background: white;
-}
-
-.wplace-toggle-label {
- font-size: 14px;
- color: var(--wplace-text);
- font-weight: 500;
-}
-
-.wplace-account-list {
- max-height: 200px;
- overflow-y: auto;
- border: 1px solid var(--wplace-border-color);
- border-radius: var(--wplace-radius);
- background: var(--wplace-secondary);
-}
-
-.wplace-account-item {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 10px 12px;
- border-bottom: 1px solid var(--wplace-border-color);
- transition: all 0.3s ease, box-shadow 0.5s ease;
-}
-
-.wplace-account-item:last-child {
- border-bottom: none;
-}
-
-.wplace-account-item:hover {
- background: var(--wplace-accent);
-}
-
-.wplace-account-item.current {
- background: var(--wplace-success)20;
- border-left: 3px solid var(--wplace-success);
- box-shadow: 0 0 15px rgba(46, 204, 113, 0.6),
- 0 0 25px rgba(46, 204, 113, 0.4),
- 0 0 35px rgba(46, 204, 113, 0.2);
- animation: currentAccountGlow 2s ease-in-out infinite alternate;
-}
-
-@keyframes currentAccountGlow {
- from {
- box-shadow: 0 0 15px rgba(46, 204, 113, 0.6),
- 0 0 25px rgba(46, 204, 113, 0.4),
- 0 0 35px rgba(46, 204, 113, 0.2);
- }
- to {
- box-shadow: 0 0 20px rgba(46, 204, 113, 0.8),
- 0 0 30px rgba(46, 204, 113, 0.6),
- 0 0 40px rgba(46, 204, 113, 0.4);
- }
-}
-
-@keyframes currentAccountNumberPulse {
- from {
- box-shadow: 0 0 10px rgba(46, 204, 113, 0.8);
- transform: scale(1);
- }
- to {
- box-shadow: 0 0 15px rgba(46, 204, 113, 1.0);
- transform: scale(1.05);
- }
-}
-
-.wplace-account-item.next-in-sequence {
- background: var(--wplace-warning)15;
- border-left: 3px solid var(--wplace-warning);
-}
-
-.wplace-account-info-item {
- display: flex;
- align-items: center;
- gap: 8px;
-}
-
-.wplace-account-number {
- display: flex;
- align-items: center;
- justify-content: center;
- width: 24px;
- height: 24px;
- background: var(--wplace-highlight);
- color: white;
- border-radius: 50%;
- font-size: 12px;
- font-weight: bold;
-}
-
-.wplace-account-name {
- font-size: 14px;
- color: var(--wplace-text);
- font-weight: 500;
-}
-
-.wplace-account-status {
- font-size: 12px;
- color: var(--wplace-text-secondary);
-}
-
-.wplace-account-placeholder {
- display: flex;
- align-items: center;
- justify-content: center;
- gap: 8px;
- padding: 20px;
- color: var(--wplace-text-secondary);
- font-style: italic;
-}
-
-.wplace-account-placeholder i {
- color: var(--wplace-warning);
-}
-
-/* ===================================== */
-/* Hide browser default spinner buttons */
-/* ===================================== */
-
-/* Hide spinner buttons for all number inputs in the extension */
-input[type="number"]::-webkit-outer-spin-button,
-input[type="number"]::-webkit-inner-spin-button {
- -webkit-appearance: none;
- margin: 0;
-}
-
-/* Firefox support */
-input[type="number"] {
- -moz-appearance: textfield;
- appearance: textfield;
-}
-
-/* Specifically target extension number inputs */
-.wplace-settings-number-input::-webkit-outer-spin-button,
-.wplace-settings-number-input::-webkit-inner-spin-button {
- -webkit-appearance: none;
- margin: 0;
-}
-
-.wplace-settings-number-input {
- -moz-appearance: textfield;
- appearance: textfield;
-}
-
-/* Hide spinners on batch control inputs */
-.wplace-random-batch-grid input[type="number"]::-webkit-outer-spin-button,
-.wplace-random-batch-grid input[type="number"]::-webkit-inner-spin-button {
- -webkit-appearance: none;
- margin: 0;
-}
-
-.wplace-random-batch-grid input[type="number"] {
- -moz-appearance: textfield;
- appearance: textfield;
-}
-
-/* Hide spinners on block size inputs */
-.wplace-block-size-grid input[type="number"]::-webkit-outer-spin-button,
-.wplace-block-size-grid input[type="number"]::-webkit-inner-spin-button {
- -webkit-appearance: none;
- margin: 0;
-}
-
-.wplace-block-size-grid input[type="number"] {
- -moz-appearance: textfield;
- appearance: textfield;
-}
-
-/* Hide spinners on threshold inputs */
-.resize-threshold-input::-webkit-outer-spin-button,
-.resize-threshold-input::-webkit-inner-spin-button {
- -webkit-appearance: none;
- margin: 0;
-}
-
-.resize-threshold-input {
- -moz-appearance: textfield;
- appearance: textfield;
-}
-
-/* Hide spinners on notification interval inputs */
-.wplace-notification-interval-input::-webkit-outer-spin-button,
-.wplace-notification-interval-input::-webkit-inner-spin-button {
- -webkit-appearance: none;
- margin: 0;
-}
-
-.wplace-notification-interval-input {
- -moz-appearance: textfield;
- appearance: textfield;
-}
\ No newline at end of file
diff --git a/Extensions/background.js b/Extensions/background.js
deleted file mode 100644
index fa41233..0000000
--- a/Extensions/background.js
+++ /dev/null
@@ -1,921 +0,0 @@
-// Background service worker for WPlace AutoBOT Extension
-
-// Test resource loading on extension starto
-chrome.runtime.onStartup.addListener(async () => {
- console.log('š WPlace AutoBOT Extension started');
- const resources = await loadExtensionResources();
- console.log('š¦ Initial resource test:', resources);
-});
-
-chrome.runtime.onInstalled.addListener(async () => {
- console.log('š§ WPlace AutoBOT Extension installed');
- const resources = await loadExtensionResources();
- console.log('š¦ Installation resource test:', resources);
-});
-
-chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
- if (request.action === 'executeScript') {
- // Get tabId from sender or requests
- const tabId = request.tabId || sender.tab?.id;
-
- if (!tabId) {
- sendResponse({ success: false, error: 'Could not determine target tab' });
- return;
- }
-
- // Use IIFE for async handlings
- (async () => {
- try {
- await executeLocalScript(request.scriptName, tabId);
- sendResponse({ success: true });
- } catch (error) {
- sendResponse({ success: false, error: error.message });
- }
- })();
-
- return true; // Important: indicates async response
- }
-});
-
-async function executeLocalScript(scriptName, tabId) {
- try {
- console.log(`Loading script: ${scriptName}`);
-
- // Check if we need to inject dependencies first for Auto-Image.js or Art-Extractor.js
- if (scriptName === 'Auto-Image.js' || scriptName === 'Art-Extractor.js') {
- console.log(`š ${scriptName} detected, ensuring dependencies are loaded first...`);
-
- try {
- // First inject token-manager.js
- const tokenManagerUrl = chrome.runtime.getURL('scripts/token-manager.js');
- const tokenManagerResponse = await fetch(tokenManagerUrl);
-
- if (tokenManagerResponse.ok) {
- const tokenManagerCode = await tokenManagerResponse.text();
- console.log('š Token manager loaded, injecting first...');
-
- // Execute token manager first
- await chrome.scripting.executeScript({
- target: { tabId: tabId },
- world: "MAIN",
- func: (code) => {
- console.log('%cš Loading WPlace Token Manager...', 'color: #4ade80; font-weight: bold;');
- const script = document.createElement('script');
- script.textContent = code;
- document.head.appendChild(script);
- script.remove();
- console.log('%cā
Token Manager loaded successfully', 'color: #4ade80; font-weight: bold;');
- },
- args: [tokenManagerCode]
- });
-
- console.log('ā
Token manager injected successfully');
- } else {
- console.warn('ā ļø Could not load token-manager.js, proceeding without it');
- }
-
- // Then inject image-processor.js
- const imageProcessorUrl = chrome.runtime.getURL('scripts/image-processor.js');
- const imageProcessorResponse = await fetch(imageProcessorUrl);
-
- if (imageProcessorResponse.ok) {
- const imageProcessorCode = await imageProcessorResponse.text();
- console.log('š¼ļø Image processor loaded, injecting second...');
-
- // Execute image processor second
- await chrome.scripting.executeScript({
- target: { tabId: tabId },
- world: "MAIN",
- func: (code) => {
- console.log('%cš¼ļø Loading WPlace Image Processor...', 'color: #3b82f6; font-weight: bold;');
- const script = document.createElement('script');
- script.textContent = code;
- document.head.appendChild(script);
- script.remove();
- console.log('%cā
Image Processor loaded successfully', 'color: #3b82f6; font-weight: bold;');
- },
- args: [imageProcessorCode]
- });
-
- console.log('ā
Image processor injected successfully');
- } else {
- console.warn('ā ļø Could not load image-processor.js, proceeding without it');
- }
-
- // Then inject overlay-manager.js
- const overlayManagerUrl = chrome.runtime.getURL('scripts/overlay-manager.js');
- const overlayManagerResponse = await fetch(overlayManagerUrl);
-
- if (overlayManagerResponse.ok) {
- const overlayManagerCode = await overlayManagerResponse.text();
- console.log('šØ Overlay manager loaded, injecting third...');
-
- // Execute overlay manager third
- await chrome.scripting.executeScript({
- target: { tabId: tabId },
- world: "MAIN",
- func: (code) => {
- console.log('%cšØ Loading WPlace Overlay Manager...', 'color: #8b5cf6; font-weight: bold;');
- const script = document.createElement('script');
- script.textContent = code;
- document.head.appendChild(script);
- script.remove();
- console.log('%cā
Overlay Manager loaded successfully', 'color: #8b5cf6; font-weight: bold;');
- },
- args: [overlayManagerCode]
- });
-
- console.log('ā
Overlay manager injected successfully');
- } else {
- console.warn('ā ļø Could not load overlay-manager.js, proceeding without it');
- }
-
- // Finally inject utils-manager.js
- const utilsManagerUrl = chrome.runtime.getURL('scripts/utils-manager.js');
- const utilsManagerResponse = await fetch(utilsManagerUrl);
-
- if (utilsManagerResponse.ok) {
- const utilsManagerCode = await utilsManagerResponse.text();
- console.log('š ļø Utils manager loaded, injecting fourth...');
-
- // Execute utils manager fourth
- await chrome.scripting.executeScript({
- target: { tabId: tabId },
- world: "MAIN",
- func: (code) => {
- console.log('%cš ļø Loading WPlace Utils Manager...', 'color: #f59e0b; font-weight: bold;');
- const script = document.createElement('script');
- script.textContent = code;
- document.head.appendChild(script);
- script.remove();
- console.log('%cā
Utils Manager loaded successfully', 'color: #f59e0b; font-weight: bold;');
- },
- args: [utilsManagerCode]
- });
-
- console.log('ā
Utils manager injected successfully');
- } else {
- console.warn('ā ļø Could not load utils-manager.js, proceeding without it');
- }
-
- // Small delay to ensure dependencies are fully initialized
- await new Promise(resolve => setTimeout(resolve, 200));
- } catch (error) {
- console.warn('ā ļø Error loading dependencies:', error);
- }
- }
-
- // Determine script path - Script-manager files are in root, others in scripts/
- let scriptUrl;
- if (scriptName === 'Script-manager.js' || scriptName === 'Script-manager-fixed.js') {
- scriptUrl = chrome.runtime.getURL(scriptName);
- } else {
- scriptUrl = chrome.runtime.getURL(`scripts/${scriptName}`);
- }
-
- const response = await fetch(scriptUrl);
-
- if (!response.ok) {
- throw new Error(`Failed to load script: ${response.status} ${response.statusText}`);
- }
-
- const scriptCode = await response.text();
- console.log(`Script loaded: ${scriptCode.length} characters`);
-
- // Load theme and language resources
- const resources = await loadExtensionResources();
-
- // Execute in MAIN world context (bypasses CSP)
- await chrome.scripting.executeScript({
- target: { tabId: tabId },
- world: "MAIN", // Key: executes in page context, not extension context
- func: (code, name, themeCSS, languages) => {
- console.log(`%cš Executing ${name}...`, 'color: #4ade80; font-weight: bold; font-size: 14px;');
-
- // Create detailed resource report
- console.group(`%cš WPlace AutoBOT Resource Report for ${name}`, 'color: #3b82f6; font-weight: bold; font-size: 16px;');
-
- // Debug: Log what resources we received
- console.log(`%cš¦ Raw Resources Received:`, 'color: #8b5cf6; font-weight: bold;');
- console.log(` - Themes object:`, themeCSS);
- console.log(` - Languages object:`, languages);
- console.log(` - Theme count: ${Object.keys(themeCSS || {}).length}`);
- console.log(` - Language count: ${Object.keys(languages || {}).length}`);
-
- // Inject CSS themes if available
- if (themeCSS && Object.keys(themeCSS).length > 0) {
- console.group(`%cšØ Theme Processing`, 'color: #8b5cf6; font-weight: bold;');
- console.log(`%cš Loading ${Object.keys(themeCSS).length} theme files from extension local storage...`, 'color: #8b5cf6;');
-
- // Create a global themes object
- window.AUTOBOT_THEMES = themeCSS;
-
- // Log detailed theme information
- Object.entries(themeCSS).forEach(([filename, content]) => {
- console.log(`%cš Theme File: ${filename}`, 'color: #8b5cf6; font-weight: bold;');
- console.log(` š Size: ${content.length.toLocaleString()} characters`);
- console.log(` š Source: Extension local file (chrome-extension://)`);
- console.log(` š Full path: themes/${filename}`);
-
- // Show first few lines as preview
- const preview = content.substring(0, 200).split('\\n').slice(0, 3).join('\\n');
- console.log(` š Preview: ${preview}${content.length > 200 ? '...' : ''}`);
- console.log(` ā
Status: Loaded successfully`);
- });
-
- // Inject auto-image-styles.css if available
- if (themeCSS['auto-image-styles.css']) {
- const autoImageContent = themeCSS['auto-image-styles.css'];
- const styleElement = document.createElement('style');
- styleElement.id = 'autobot-auto-image-styles';
- styleElement.textContent = autoImageContent;
- document.head.appendChild(styleElement);
-
- console.log(`%c⨠AUTO-INJECTED: auto-image-styles.css`, 'color: #10b981; font-weight: bold;');
- console.log(` š Injected size: ${autoImageContent.length.toLocaleString()} characters`);
- console.log(` š Source: Extension local file`);
- console.log(` šÆ Target: as
-
- `;
-
- // Add event listeners
- container.querySelector('#art-extractor-close').addEventListener('click', closeArtExtractor);
- // Add event listeners
- container.querySelector('#art-extractor-close').addEventListener('click', closeArtExtractor);
- container.querySelector('#export-autofix').addEventListener('click', () => exportToJSON('autofix'));
- container.querySelector('#export-autoimage').addEventListener('click', () => exportToJSON('autoimage'));
- container.querySelector('#start-selecting').addEventListener('click', startPixelCapture);
- container.querySelector('#clear-selection').addEventListener('click', clearSelection);
-
- // Set up filename input with .json enforcement
- const filenameInput = container.querySelector('#filename-input');
- if (filenameInput) {
- // Initialize with default value
- filenameInput.value = '.json';
-
- // Add event listeners for .json enforcement
- filenameInput.addEventListener('input', handleFilenameInput);
- filenameInput.addEventListener('keydown', preventJsonDeletion);
- filenameInput.addEventListener('focus', function() {
- // Position cursor before .json on focus
- const nameLength = this.value.length - 5;
- if (nameLength >= 0) {
- this.setSelectionRange(nameLength, nameLength);
- }
- });
-
- // Set initial cursor position
- setTimeout(() => {
- filenameInput.setSelectionRange(0, 0);
- }, 100);
- }
-
- // Add to page
- document.body.appendChild(container);
- state.ui = container;
-
- console.log('ā
Art Extractor UI created');
-
- // Auto-start pixel capture immediately
- console.log('šÆ Auto-starting pixel capture...');
- setTimeout(() => {
- startPixelCapture();
- }, 500);
- updateUI();
-
- } catch (error) {
- console.error('ā Failed to create UI:', error);
- throw error;
- }
- }
-
- // UI update function
- function updateUI() {
- if (!state.ui) return;
-
- const statusElement = state.ui.querySelector('#current-status');
- const areaElement = state.ui.querySelector('#area-info');
- const pixelElement = state.ui.querySelector('#pixel-count');
- const exportAutofixBtn = state.ui.querySelector('#export-autofix');
- const exportAutoimageBtn = state.ui.querySelector('#export-autoimage');
-
- // Update status with Neon Cyan styling
- if (state.isCapturing) {
- statusElement.textContent = `CAPTURING (${state.capturedPixels.length}/${state.requiredPixels})`;
- statusElement.style.color = '#EA9C00';
- statusElement.style.textShadow = '0 0 8px #EA9C00';
- } else if (state.isScanning) {
- statusElement.textContent = 'SCANNING...';
- statusElement.style.color = '#39ff14';
- statusElement.style.textShadow = '0 0 8px #39ff14';
- } else if (state.selectionArea) {
- statusElement.textContent = 'READY TO EXPORT';
- statusElement.style.color = '#39ff14';
- statusElement.style.textShadow = '0 0 8px #39ff14';
- } else {
- statusElement.textContent = 'READY';
- statusElement.style.color = '#81DCF7';
- statusElement.style.textShadow = '0 0 5px #81DCF7';
- }
-
- // Update area info with uppercase styling
- if (state.selectionArea) {
- areaElement.textContent = `${state.selectionArea.width}Ć${state.selectionArea.height} PX`;
- } else if (state.capturedPixels.length > 0) {
- areaElement.textContent = `${state.capturedPixels.length}/${state.requiredPixels} CAPTURED`;
- } else {
- areaElement.textContent = 'NONE';
- }
-
- // Update pixel count with progress info
- if (state.isScanning && state.totalPixels > 0) {
- const progress = Math.round((state.scannedPixels.length / state.totalPixels) * 100);
- pixelElement.textContent = `${state.scannedPixels.length}/${state.totalPixels} (${progress}%)`;
- } else {
- pixelElement.textContent = state.scannedPixels.length.toString();
- }
-
- // Update export button states
- const hasScannedData = state.scannedPixels && state.scannedPixels.length > 0;
-
- if (exportAutofixBtn) {
- exportAutofixBtn.disabled = !hasScannedData;
- if (!exportAutofixBtn.disabled) {
- exportAutofixBtn.style.opacity = '1';
- exportAutofixBtn.style.cursor = 'pointer';
- } else {
- exportAutofixBtn.style.opacity = '0.5';
- exportAutofixBtn.style.cursor = 'not-allowed';
- }
- }
-
- if (exportAutoimageBtn) {
- exportAutoimageBtn.disabled = !hasScannedData;
- if (!exportAutoimageBtn.disabled) {
- exportAutoimageBtn.style.opacity = '1';
- exportAutoimageBtn.style.cursor = 'pointer';
- } else {
- exportAutoimageBtn.style.opacity = '0.5';
- exportAutoimageBtn.style.cursor = 'not-allowed';
- }
- }
- }
-
- // Control functions
- function startPixelCapture() {
- console.log('šÆ Starting Auto-Guard style pixel capture mode...');
-
- state.isCapturing = true;
- state.capturedPixels = [];
- state.selectionArea = null;
- state.scannedPixels = [];
-
- // Clear any existing overlay
- if (state.overlayManager) {
- state.overlayManager.disable();
- }
-
- // Set up Auto-Guard style pixel paint monitoring with fetch interception
- const setupSuccess = setupPixelCapture();
-
- if (setupSuccess) {
- updateUI();
- showAlert('šÆ Paint a pixel at the UPPER-LEFT corner of the area you want to extract', 'info');
-
- // Set timeout for capture process
- setTimeout(() => {
- if (state.isCapturing && state.capturedPixels.length === 0) {
- showAlert('ā° No pixels captured yet. Try painting a pixel or use "Enter Coordinates Manually"', 'warning');
- }
- }, 15000);
- } else {
- showAlert('ā ļø Could not set up automatic capture. Use "Enter Coordinates Manually"', 'warning');
- updateUI();
- }
- }
-
- function clearSelection() {
- console.log('š§¹ Clearing pixel capture...');
-
- state.isCapturing = false;
- state.capturedPixels = [];
- state.selectionArea = null;
- state.scannedPixels = [];
-
- // Restore original fetch function
- restoreFetch();
-
- // Disable overlay
- if (state.overlayManager) {
- state.overlayManager.disable();
- }
-
- updateUI();
- showAlert('Pixel capture cleared', 'info');
- }
-
- function closeArtExtractor() {
- console.log('š Closing Art Extractor...');
-
- // Stop capturing
- state.isCapturing = false;
-
- // Restore original fetch function
- restoreFetch();
-
- // Clean up overlay
- if (state.overlayManager) {
- state.overlayManager.disable();
- }
-
- // Remove UI
- if (state.ui) {
- state.ui.remove();
- }
-
- // Reset state
- Object.assign(state, {
- isCapturing: false,
- capturedPixels: [],
- selectionArea: null,
- ui: null,
- overlayManager: null,
- isScanning: false,
- scannedPixels: [],
- paintEventListener: null,
- lastPaintEvent: null
- });
-
- console.log('ā
Art Extractor closed');
- }
-
- // Main initialization
- async function initialize() {
- console.log('š Initializing Art Extractor...');
-
- try {
- // Create UI immediately - don't wait for dependencies
- console.log('šØ Creating UI first...');
- await createUI();
-
- // Then try to set up dependencies in background
- setTimeout(async () => {
- try {
- // Wait for Auto-Image overlay manager with timeout
- if (!window.autoImageOverlayManager) {
- console.log('ā³ Waiting for Auto-Image overlay manager...');
- await new Promise((resolve, reject) => {
- let attempts = 0;
- const maxAttempts = 50; // 5 seconds timeout
- const check = () => {
- attempts++;
- if (window.autoImageOverlayManager) {
- console.log('ā
Auto-Image overlay manager found');
- resolve();
- } else if (attempts >= maxAttempts) {
- console.warn('ā ļø Auto-Image overlay manager not found, proceeding without it');
- resolve(); // Continue anyway
- } else {
- setTimeout(check, 100);
- }
- };
- check();
- });
- }
-
- // Set up pixel capture monitoring
- try {
- const captureReady = setupPixelCapture();
- if (captureReady) {
- console.log('ā
Pixel capture monitoring ready');
- } else {
- console.warn('ā ļø Pixel capture monitoring setup failed');
- }
- } catch (error) {
- console.warn('ā ļø Pixel capture error:', error);
- }
-
- } catch (error) {
- console.warn('ā ļø Background initialization error:', error);
- }
- }, 1000);
-
- console.log('ā
Art Extractor initialized successfully');
- showAlert('Art Extractor ready! UI loaded - dependencies loading in background.', 'success');
-
- } catch (error) {
- console.error('ā Failed to initialize Art Extractor:', error);
-
- // Try to create UI anyway as fallback
- try {
- await createUI();
- showAlert(`Art Extractor loaded with limited functionality: ${error.message}`, 'warning');
- } catch (uiError) {
- console.error('ā Failed to create UI:', uiError);
- showAlert(`Critical initialization failure: ${error.message}`, 'error');
- }
- }
- }
-
- // Start initialization with additional error handling
- try {
- await initialize();
- } catch (error) {
- console.error('ā Critical error during Art Extractor initialization:', error);
-
- // Final fallback - try to show minimal UI
- try {
- const container = document.createElement('div');
- container.innerHTML = `
-
- ā Art Extractor failed to load: ${error.message}
- ā
-
- `;
- document.body.appendChild(container);
- } catch (e) {
- console.error('ā Even fallback UI failed:', e);
- }
- }
-
- // Expose globally for extension integration
- window.WPlaceArtExtractor = {
- state,
- initialize,
- closeArtExtractor,
- exportToJSON,
- scanSelectedArea
- };
-
- console.log('šØ WPlace Art Extractor loaded successfully!');
-})();
\ No newline at end of file
diff --git a/Extensions/scripts/Auto-Farm.js b/Extensions/scripts/Auto-Farm.js
deleted file mode 100644
index ee1e5db..0000000
--- a/Extensions/scripts/Auto-Farm.js
+++ /dev/null
@@ -1,650 +0,0 @@
-// eslint-disable-next-line prettier/prettier
-; (async () => {
- const CONFIG = {
- START_X: 742,
- START_Y: 1148,
- PIXELS_PER_LINE: 100,
- DELAY: 1000,
- THEME: {
- primary: '#000000',
- secondary: '#111111',
- accent: '#222222',
- text: '#ffffff',
- highlight: '#775ce3',
- success: '#00ff00',
- error: '#ff0000',
- },
- };
-
- const state = {
- running: false,
- paintedCount: 0,
- charges: { count: 0, max: 80, cooldownMs: 30000 },
- userInfo: null,
- lastPixel: null,
- minimized: false,
- menuOpen: false,
- language: 'en',
- autoRefresh: true,
- pausedForManual: false,
- };
-
- const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
- const waitForSelector = async (selector, interval = 200, timeout = 5000) => {
- const start = Date.now();
- while (Date.now() - start < timeout) {
- const el = document.querySelector(selector);
- if (el) return el;
- await sleep(interval);
- }
- return null;
- };
-
- const originalFetch = window.fetch;
- let capturedCaptchaToken = null;
- 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('ā
CAPTCHA Token Captured:', payload.t);
- capturedCaptchaToken = payload.t;
- if (state.pausedForManual) {
- state.pausedForManual = false;
- state.running = true;
- updateUI(
- state.language === 'pt' ? 'š Pintura reiniciada!' : 'š Farm resumed!',
- 'success'
- );
- paintLoop();
- }
- }
- } catch (e) {}
- }
- return originalFetch(url, options);
- };
-
- const fetchAPI = async (url, options = {}) => {
- try {
- const res = await fetch(url, {
- credentials: 'include',
- ...options,
- });
- return await res.json();
- } catch (e) {
- return null;
- }
- };
-
- const getRandomPosition = () => ({
- x: Math.floor(Math.random() * CONFIG.PIXELS_PER_LINE),
- y: Math.floor(Math.random() * CONFIG.PIXELS_PER_LINE),
- });
-
- const paintPixel = async (x, y) => {
- const randomColor = Math.floor(Math.random() * 31) + 1;
- const url = `https://backend.wplace.live/s0/pixel/${CONFIG.START_X}/${CONFIG.START_Y}`;
- const payload = JSON.stringify({
- coords: [x, y],
- colors: [randomColor],
- t: capturedCaptchaToken,
- });
- try {
- const res = await originalFetch(url, {
- method: 'POST',
- headers: { 'Content-Type': 'text/plain;charset=UTF-8' },
- credentials: 'include',
- body: payload,
- });
- if (res.status === 403) {
- console.error('ā 403 Forbidden. CAPTCHA token might be invalid or expired.');
- capturedCaptchaToken = null;
- stoppedForToken = true;
- return 'token_error';
- }
- const data = await res.json();
- return data;
- } catch (e) {
- return null;
- }
- };
-
- const getCharge = async () => {
- const data = await fetchAPI('https://backend.wplace.live/me');
- if (data) {
- state.userInfo = data;
- state.charges = {
- count: Math.floor(data.charges.count),
- max: Math.floor(data.charges.max),
- cooldownMs: data.charges.cooldownMs,
- };
- if (state.userInfo.level) {
- state.userInfo.level = Math.floor(state.userInfo.level);
- }
- }
- return state.charges;
- };
-
- const detectUserLocation = async () => {
- try {
- const response = await fetch('https://ipapi.co/json/');
- const data = await response.json();
- if (data.country === 'BR') {
- state.language = 'pt';
- } else if (data.country === 'US') {
- state.language = 'en';
- } else {
- state.language = 'en';
- }
- } catch {
- state.language = 'en';
- }
- };
-
- const paintLoop = async () => {
- while (state.running) {
- const { count, cooldownMs } = state.charges;
-
- if (count < 1) {
- updateUI(
- state.language === 'pt'
- ? `ā Sem cargas. Esperando ${Math.ceil(cooldownMs / 1000)}s...`
- : `ā No charges. Waiting ${Math.ceil(cooldownMs / 1000)}s...`,
- 'status'
- );
- await sleep(cooldownMs);
- await getCharge();
- continue;
- }
-
- const randomPos = getRandomPosition();
- const paintResult = await paintPixel(randomPos.x, randomPos.y);
- if (paintResult === 'token_error') {
- if (state.autoRefresh) {
- await getCharge();
- if (state.charges.count < 2) {
- if (!state.pausedForManual) {
- updateUI(
- state.language === 'pt'
- ? 'ā” Aguardando pelo menos 2 cargas para auto-refresh...'
- : 'Waiting for at least 2 charges for auto-refresh...',
- 'status'
- );
- state.pausedForManual = true;
- }
- while (state.charges.count < 2) {
- await sleep(60000);
- await getCharge();
- updateStats();
- }
- state.pausedForManual = false;
- }
- updateUI(
- state.language === 'pt'
- ? 'ā Token expirado. Aguardando elemento Paint...'
- : 'ā CAPTCHA token expired. Waiting for Paint button...',
- 'error'
- );
- const mainPaintBtn = await waitForSelector(
- 'button.btn.btn-primary.btn-lg, button.btn-primary.sm\\:btn-xl'
- );
- if (mainPaintBtn) mainPaintBtn.click();
- await sleep(500);
- updateUI(
- state.language === 'pt' ? 'Selecionando transparente...' : 'Selecting transparent...',
- 'status'
- );
- const transBtn = await waitForSelector('button#color-0');
- if (transBtn) transBtn.click();
- await sleep(500);
- const canvas = await waitForSelector('canvas');
- if (canvas) {
- canvas.setAttribute('tabindex', '0');
- canvas.focus();
- const rect = canvas.getBoundingClientRect();
- const centerX = Math.round(rect.left + rect.width / 2);
- const centerY = Math.round(rect.top + rect.height / 2);
- const moveEvt = new MouseEvent('mousemove', {
- clientX: centerX,
- clientY: centerY,
- bubbles: true,
- });
- canvas.dispatchEvent(moveEvt);
- const keyDown = new KeyboardEvent('keydown', {
- key: ' ',
- code: 'Space',
- bubbles: true,
- });
- const keyUp = new KeyboardEvent('keyup', { key: ' ', code: 'Space', bubbles: true });
- canvas.dispatchEvent(keyDown);
- canvas.dispatchEvent(keyUp);
- }
- await sleep(500);
- updateUI(
- state.language === 'pt' ? 'Confirmando pintura...' : 'Confirming paint...',
- 'status'
- );
- let confirmBtn = await 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'));
- confirmBtn = allPrimary.length ? allPrimary[allPrimary.length - 1] : null;
- }
- confirmBtn?.click();
- } else {
- // insufficient charges or auto-refresh disabled
- if (state.autoRefresh && state.charges.count < 2) {
- updateUI(
- state.language === 'pt'
- ? 'ā” Cargas insuficientes para auto-refresh. Por favor, clique manualmente.'
- : 'Insufficient charges for auto-refresh. Please click manually.',
- 'error'
- );
- }
- if (!state.pausedForManual) {
- updateUI(
- state.language === 'pt'
- ? 'Auto-refresh desativado. Por favor, clique no botão pintura manualmente.'
- : 'Auto-refresh disabled. Please click the Paint button manually.',
- 'status'
- );
- state.pausedForManual = true;
- }
- state.running = false;
- return;
- }
- await sleep(1000);
- continue;
- }
-
- if (paintResult?.painted === 1) {
- state.paintedCount++;
- state.lastPixel = {
- x: CONFIG.START_X + randomPos.x,
- y: CONFIG.START_Y + randomPos.y,
- time: new Date(),
- };
- state.charges.count--;
-
- document.getElementById('paintEffect').style.animation = 'pulse 0.5s';
- setTimeout(() => {
- document.getElementById('paintEffect').style.animation = '';
- }, 500);
-
- updateUI(state.language === 'pt' ? 'ā
Pixel pintado!' : 'ā
Pixel painted!', 'success');
- } else {
- updateUI(state.language === 'pt' ? 'ā Falha ao pintar' : 'ā Failed to paint', 'error');
- }
-
- await sleep(CONFIG.DELAY);
- updateStats();
- }
- };
-
- const createUI = () => {
- if (state.menuOpen) return;
- state.menuOpen = true;
-
- const fontAwesome = document.createElement('link');
- fontAwesome.rel = 'stylesheet';
- fontAwesome.href = 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css';
- document.head.appendChild(fontAwesome);
-
- const style = document.createElement('style');
- style.textContent = `
- @keyframes pulse {
- 0% { box-shadow: 0 0 0 0 rgba(0, 255, 0, 0.7); }n
- 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 slide-in {
- from { transform: translateY(20px); opacity: 0; }
- to { transform: translateY(0); opacity: 1; }
- }
- .wplace-bot-panel {
- position: fixed;
- top: 20px;
- right: 20px;
- width: 250px;
- background: ${CONFIG.THEME.primary};
- border: 1px solid ${CONFIG.THEME.accent};
- border-radius: 8px;
- padding: 0;
- box-shadow: 0 5px 15px rgba(0,0,0,0.5);
- z-index: 9999;
- font-family: 'Segoe UI', Roboto, sans-serif;
- color: ${CONFIG.THEME.text};
- animation: slide-in 0.4s ease-out;
- overflow: hidden;
- }
- .wplace-header {
- padding: 12px 15px;
- background: ${CONFIG.THEME.secondary};
- color: ${CONFIG.THEME.highlight};
- font-size: 16px;
- font-weight: 600;
- display: flex;
- justify-content: space-between;
- align-items: center;
- cursor: move;
- user-select: none;
- }
- .wplace-header-title {
- display: flex;
- align-items: center;
- gap: 8px;
- }
- .wplace-header-controls {
- display: flex;
- gap: 10px;
- }
- .wplace-header-btn {
- background: none;
- border: none;
- color: ${CONFIG.THEME.text};
- cursor: pointer;
- opacity: 0.7;
- transition: opacity 0.2s;
- }
- .wplace-header-btn:hover {
- opacity: 1;
- }
- .wplace-content {
- padding: 15px;
- display: ${state.minimized ? 'none' : 'block'};
- }
- .wplace-controls {
- display: flex;
- gap: 10px;
- margin-bottom: 15px;
- }
- .wplace-btn {
- flex: 1;
- padding: 10px;
- border: none;
- border-radius: 6px;
- font-weight: 600;
- cursor: pointer;
- display: flex;
- align-items: center;
- justify-content: center;
- gap: 8px;
- transition: all 0.2s;
- }
- .wplace-btn:hover {
- transform: translateY(-2px);
- }
- .wplace-btn-primary {
- background: ${CONFIG.THEME.accent};
- color: white;
- }
- .wplace-btn-stop {
- background: ${CONFIG.THEME.error};
- color: white;
- }
- .wplace-stats {
- background: ${CONFIG.THEME.secondary};
- padding: 12px;
- border-radius: 6px;
- margin-bottom: 15px;
- }
- .wplace-stat-item {
- display: flex;
- justify-content: space-between;
- padding: 6px 0;
- font-size: 14px;
- }
- .wplace-stat-label {
- display: flex;
- align-items: center;
- gap: 6px;
- opacity: 0.8;
- }
- .wplace-status {
- padding: 8px;
- border-radius: 4px;
- text-align: center;
- font-size: 13px;
- }
- .status-default {
- background: rgba(255,255,255,0.1);
- }
- .status-success {
- background: rgba(0, 255, 0, 0.1);
- color: ${CONFIG.THEME.success};
- }
- .status-error {
- background: rgba(255, 0, 0, 0.1);
- color: ${CONFIG.THEME.error};
- }
- #paintEffect {
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- pointer-events: none;
- border-radius: 8px;
- }
- `;
- document.head.appendChild(style);
-
- const translations = {
- pt: {
- title: 'WPlace Auto-Farm',
- start: 'Iniciar',
- stop: 'Parar',
- ready: 'Pronto para comeƧar',
- user: 'UsuƔrio',
- pixels: 'Pixels',
- charges: 'Cargas',
- level: 'Level',
- },
- en: {
- title: 'WPlace Auto-Farm',
- start: 'Start',
- stop: 'Stop',
- ready: 'Ready to start',
- user: 'User',
- pixels: 'Pixels',
- charges: 'Charges',
- level: 'Level',
- },
- };
-
- const t = translations[state.language] || translations.en;
-
- const panel = document.createElement('div');
- panel.className = 'wplace-bot-panel';
- panel.innerHTML = `
-
-
-
-
-
-
- ${t.start}
-
-
-
- Auto Refresh
-
-
-
-
-
-
-
${state.language === 'pt' ? 'Carregando...' : 'Loading...'}
-
-
-
-
-
- ${t.ready}
-
-
- `;
-
- document.body.appendChild(panel);
-
- const header = panel.querySelector('.wplace-header');
- let pos1 = 0,
- pos2 = 0,
- pos3 = 0,
- pos4 = 0;
-
- header.onmousedown = dragMouseDown;
-
- function dragMouseDown(e) {
- if (e.target.closest('.wplace-header-btn')) return;
-
- e = e || window.event;
- e.preventDefault();
- pos3 = e.clientX;
- pos4 = e.clientY;
- document.onmouseup = closeDragElement;
- document.onmousemove = elementDrag;
- }
-
- function elementDrag(e) {
- e = e || window.event;
- e.preventDefault();
- pos1 = pos3 - e.clientX;
- pos2 = pos4 - e.clientY;
- pos3 = e.clientX;
- pos4 = e.clientY;
- panel.style.top = panel.offsetTop - pos2 + 'px';
- panel.style.left = panel.offsetLeft - pos1 + 'px';
- }
-
- function closeDragElement() {
- document.onmouseup = null;
- document.onmousemove = null;
- }
-
- const toggleBtn = panel.querySelector('#toggleBtn');
- const minimizeBtn = panel.querySelector('#minimizeBtn');
- const statusText = panel.querySelector('#statusText');
- const content = panel.querySelector('.wplace-content');
- const statsArea = panel.querySelector('#statsArea');
-
- toggleBtn.addEventListener('click', () => {
- state.running = !state.running;
-
- if (state.running && !capturedCaptchaToken) {
- updateUI(
- state.language === 'pt'
- ? 'ā Token nĆ£o capturado. Clique em qualquer pixel primeiro.'
- : 'ā CAPTCHA token not captured. Please click any pixel manually first.',
- 'error'
- );
- state.running = false;
- return;
- }
-
- if (state.running) {
- toggleBtn.innerHTML = ` ${t.stop} `;
- toggleBtn.classList.remove('wplace-btn-primary');
- toggleBtn.classList.add('wplace-btn-stop');
- updateUI(
- state.language === 'pt' ? 'š Pintura iniciada!' : 'š Painting started!',
- 'success'
- );
- paintLoop();
- } else {
- toggleBtn.innerHTML = ` ${t.start} `;
- toggleBtn.classList.add('wplace-btn-primary');
- toggleBtn.classList.remove('wplace-btn-stop');
- statsArea.innerHTML = '';
- updateUI(state.language === 'pt' ? 'ā¹ļø Parado' : 'ā¹ļø Stopped', 'default');
- }
- });
-
- minimizeBtn.addEventListener('click', () => {
- state.minimized = !state.minimized;
- content.style.display = state.minimized ? 'none' : 'block';
- minimizeBtn.innerHTML = ` `;
- });
-
- const autoRefreshCheckbox = panel.querySelector('#autoRefreshCheckbox');
- autoRefreshCheckbox.addEventListener('change', () => {
- state.autoRefresh = autoRefreshCheckbox.checked;
- });
-
- window.addEventListener('beforeunload', () => {
- state.menuOpen = false;
- });
- };
-
- window.updateUI = (message, type = 'default') => {
- const statusText = document.querySelector('#statusText');
- if (statusText) {
- statusText.textContent = message;
- statusText.className = `wplace-status status-${type}`;
- statusText.style.animation = 'none';
- void statusText.offsetWidth;
- statusText.style.animation = 'slide-in 0.3s ease-out';
- }
- };
-
- window.updateStats = async () => {
- await getCharge();
- const statsArea = document.querySelector('#statsArea');
- if (statsArea) {
- const t = {
- pt: {
- user: 'UsuƔrio',
- pixels: 'Pixels',
- charges: 'Cargas',
- level: 'Level',
- },
- en: {
- user: 'User',
- pixels: 'Pixels',
- charges: 'Charges',
- level: 'Level',
- },
- }[state.language] || {
- user: 'User',
- pixels: 'Pixels',
- charges: 'Charges',
- level: 'Level',
- };
-
- statsArea.innerHTML = `
-
-
${t.user}
-
${state.userInfo.name}
-
-
-
${t.pixels}
-
${state.paintedCount}
-
-
-
${t.charges}
-
${Math.floor(state.charges.count)}/${Math.floor(state.charges.max)}
-
-
-
${t.level}
-
${state.userInfo?.level || '0'}
-
- `;
- }
- };
-
- await detectUserLocation();
- createUI();
- await getCharge();
- updateStats();
-})();
diff --git a/Extensions/scripts/Auto-Image.js b/Extensions/scripts/Auto-Image.js
deleted file mode 100644
index 91e7b23..0000000
--- a/Extensions/scripts/Auto-Image.js
+++ /dev/null
@@ -1,8199 +0,0 @@
-// ==UserScript==
-// @name WPlaceBot
-// @namespace http://tampermonkey.net/
-// @version 2025-08-08.3
-// @description Bot
-// @author Wbot
-// @match https://wplace.live/*
-// @grant none
-// @icon
-// ==/UserScript==
-localStorage.removeItem("lp");
-
-// Fallback translation function for when utils manager isn't loaded
-function getText(key, params) {
- // Try to get translation from loadedTranslations
- try {
- if (window.loadedTranslations && window.loadedTranslations[key]) {
- let text = window.loadedTranslations[key];
- if (params) {
- Object.keys(params).forEach(paramKey => {
- text = text.replace(new RegExp(`{{${paramKey}}}`, 'g'), params[paramKey]);
- });
- }
- return text;
- }
-
- // Try with state.language if available
- if (window.state && window.state.language && window.loadedTranslations && window.loadedTranslations[window.state.language] && window.loadedTranslations[window.state.language][key]) {
- let text = window.loadedTranslations[window.state.language][key];
- if (params) {
- Object.keys(params).forEach(paramKey => {
- text = text.replace(new RegExp(`{{${paramKey}}}`, 'g'), params[paramKey]);
- });
- }
- return text;
- }
- } catch (error) {
- console.warn('Error in getText fallback:', error);
- }
-
- return key; // Fallback to key if no translation found
-}
-
-; (async () => {
- // Prevent multiple instances of this script from running
- if (window.WPLACE_AUTO_IMAGE_LOADED) {
- console.log('%cā ļø Auto-Image script already loaded, skipping duplicate execution', 'color: #ff9800; font-weight: bold;');
- return;
- }
- window.WPLACE_AUTO_IMAGE_LOADED = true;
-
- console.log('%cš WPlace AutoBot Auto-Image Starting...', 'color: #00ff41; font-weight: bold; font-size: 16px;');
-
- // CONFIGURATION CONSTANTS
- const CONFIG = {
- COOLDOWN_DEFAULT: 31000,
- TRANSPARENCY_THRESHOLD: 100,
- WHITE_THRESHOLD: 250,
- LOG_INTERVAL: 10,
- PAINTING_SPEED: {
- MIN: 1, // Minimum 1 pixel batch size
- MAX: 1000, // Maximum 1000 pixels batch size
- DEFAULT: 5, // Default 5 pixels batch size
- },
- BATCH_MODE: 'normal', // "normal" or "random" - default to normal
- RANDOM_BATCH_RANGE: {
- MIN: 3, // Random range minimum
- MAX: 20, // Random range maximum
- },
- PAINTING_SPEED_ENABLED: true, // On by default
- AUTO_CAPTCHA_ENABLED: true, // Turnstile generator enabled by default
- TOKEN_SOURCE: 'generator', // "generator", "manual", or "hybrid" - default to generator
- COOLDOWN_CHARGE_THRESHOLD: 1, // Default wait threshold
- // Desktop Notifications (defaults)
- NOTIFICATIONS: {
- ENABLED: false,
- ON_CHARGES_REACHED: true,
- ONLY_WHEN_UNFOCUSED: true,
- REPEAT_MINUTES: 5, // repeat reminder while threshold condition holds
- },
- OVERLAY: {
- OPACITY_DEFAULT: 0.6,
- BLUE_MARBLE_DEFAULT: false,
- ditheringEnabled: false,
- }, // --- START: Color data from colour-converter.js ---
- // New color structure with proper ID mapping
- COLOR_MAP: {
- 0: { id: 1, name: 'Black', rgb: { r: 0, g: 0, b: 0 } },
- 1: { id: 2, name: 'Dark Gray', rgb: { r: 60, g: 60, b: 60 } },
- 2: { id: 3, name: 'Gray', rgb: { r: 120, g: 120, b: 120 } },
- 3: { id: 4, name: 'Light Gray', rgb: { r: 210, g: 210, b: 210 } },
- 4: { id: 5, name: 'White', rgb: { r: 255, g: 255, b: 255 } },
- 5: { id: 6, name: 'Deep Red', rgb: { r: 96, g: 0, b: 24 } },
- 6: { id: 7, name: 'Red', rgb: { r: 237, g: 28, b: 36 } },
- 7: { id: 8, name: 'Orange', rgb: { r: 255, g: 127, b: 39 } },
- 8: { id: 9, name: 'Gold', rgb: { r: 246, g: 170, b: 9 } },
- 9: { id: 10, name: 'Yellow', rgb: { r: 249, g: 221, b: 59 } },
- 10: { id: 11, name: 'Light Yellow', rgb: { r: 255, g: 250, b: 188 } },
- 11: { id: 12, name: 'Dark Green', rgb: { r: 14, g: 185, b: 104 } },
- 12: { id: 13, name: 'Green', rgb: { r: 19, g: 230, b: 123 } },
- 13: { id: 14, name: 'Light Green', rgb: { r: 135, g: 255, b: 94 } },
- 14: { id: 15, name: 'Dark Teal', rgb: { r: 12, g: 129, b: 110 } },
- 15: { id: 16, name: 'Teal', rgb: { r: 16, g: 174, b: 166 } },
- 16: { id: 17, name: 'Light Teal', rgb: { r: 19, g: 225, b: 190 } },
- 17: { id: 20, name: 'Cyan', rgb: { r: 96, g: 247, b: 242 } },
- 18: { id: 44, name: 'Light Cyan', rgb: { r: 187, g: 250, b: 242 } },
- 19: { id: 18, name: 'Dark Blue', rgb: { r: 40, g: 80, b: 158 } },
- 20: { id: 19, name: 'Blue', rgb: { r: 64, g: 147, b: 228 } },
- 21: { id: 21, name: 'Indigo', rgb: { r: 107, g: 80, b: 246 } },
- 22: { id: 22, name: 'Light Indigo', rgb: { r: 153, g: 177, b: 251 } },
- 23: { id: 23, name: 'Dark Purple', rgb: { r: 120, g: 12, b: 153 } },
- 24: { id: 24, name: 'Purple', rgb: { r: 170, g: 56, b: 185 } },
- 25: { id: 25, name: 'Light Purple', rgb: { r: 224, g: 159, b: 249 } },
- 26: { id: 26, name: 'Dark Pink', rgb: { r: 203, g: 0, b: 122 } },
- 27: { id: 27, name: 'Pink', rgb: { r: 236, g: 31, b: 128 } },
- 28: { id: 28, name: 'Light Pink', rgb: { r: 243, g: 141, b: 169 } },
- 29: { id: 29, name: 'Dark Brown', rgb: { r: 104, g: 70, b: 52 } },
- 30: { id: 30, name: 'Brown', rgb: { r: 149, g: 104, b: 42 } },
- 31: { id: 31, name: 'Beige', rgb: { r: 248, g: 178, b: 119 } },
- 32: { id: 52, name: 'Light Beige', rgb: { r: 255, g: 197, b: 165 } },
- 33: { id: 32, name: 'Medium Gray', rgb: { r: 170, g: 170, b: 170 } },
- 34: { id: 33, name: 'Dark Red', rgb: { r: 165, g: 14, b: 30 } },
- 35: { id: 34, name: 'Light Red', rgb: { r: 250, g: 128, b: 114 } },
- 36: { id: 35, name: 'Dark Orange', rgb: { r: 228, g: 92, b: 26 } },
- 37: { id: 37, name: 'Dark Goldenrod', rgb: { r: 156, g: 132, b: 49 } },
- 38: { id: 38, name: 'Goldenrod', rgb: { r: 197, g: 173, b: 49 } },
- 39: { id: 39, name: 'Light Goldenrod', rgb: { r: 232, g: 212, b: 95 } },
- 40: { id: 40, name: 'Dark Olive', rgb: { r: 74, g: 107, b: 58 } },
- 41: { id: 41, name: 'Olive', rgb: { r: 90, g: 148, b: 74 } },
- 42: { id: 42, name: 'Light Olive', rgb: { r: 132, g: 197, b: 115 } },
- 43: { id: 43, name: 'Dark Cyan', rgb: { r: 15, g: 121, b: 159 } },
- 44: { id: 45, name: 'Light Blue', rgb: { r: 125, g: 199, b: 255 } },
- 45: { id: 46, name: 'Dark Indigo', rgb: { r: 77, g: 49, b: 184 } },
- 46: { id: 47, name: 'Dark Slate Blue', rgb: { r: 74, g: 66, b: 132 } },
- 47: { id: 48, name: 'Slate Blue', rgb: { r: 122, g: 113, b: 196 } },
- 48: { id: 49, name: 'Light Slate Blue', rgb: { r: 181, g: 174, b: 241 } },
- 49: { id: 53, name: 'Dark Peach', rgb: { r: 155, g: 82, b: 73 } },
- 50: { id: 54, name: 'Peach', rgb: { r: 209, g: 128, b: 120 } },
- 51: { id: 55, name: 'Light Peach', rgb: { r: 250, g: 182, b: 164 } },
- 52: { id: 50, name: 'Light Brown', rgb: { r: 219, g: 164, b: 99 } },
- 53: { id: 56, name: 'Dark Tan', rgb: { r: 123, g: 99, b: 82 } },
- 54: { id: 57, name: 'Tan', rgb: { r: 156, g: 132, b: 107 } },
- 55: { id: 36, name: 'Light Tan', rgb: { r: 214, g: 181, b: 148 } },
- 56: { id: 51, name: 'Dark Beige', rgb: { r: 209, g: 128, b: 81 } },
- 57: { id: 61, name: 'Dark Stone', rgb: { r: 109, g: 100, b: 63 } },
- 58: { id: 62, name: 'Stone', rgb: { r: 148, g: 140, b: 107 } },
- 59: { id: 63, name: 'Light Stone', rgb: { r: 205, g: 197, b: 158 } },
- 60: { id: 58, name: 'Dark Slate', rgb: { r: 51, g: 57, b: 65 } },
- 61: { id: 59, name: 'Slate', rgb: { r: 109, g: 117, b: 141 } },
- 62: { id: 60, name: 'Light Slate', rgb: { r: 179, g: 185, b: 209 } },
- 63: { id: 0, name: 'Transparent', rgb: null },
- }, // --- END: Color data ---
- // Optimized CSS Classes for reuse
- CSS_CLASSES: {
- BUTTON_PRIMARY: `
- background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
- color: white; border: none; border-radius: 8px; padding: 10px 16px;
- cursor: pointer; font-weight: 500; transition: all 0.3s ease;
- display: flex; align-items: center; gap: 8px;
- `,
- BUTTON_SECONDARY: `
- background: rgba(255,255,255,0.1); color: white;
- border: 1px solid rgba(255,255,255,0.2); border-radius: 8px;
- padding: 8px 12px; cursor: pointer; transition: all 0.3s ease;
- `,
- MODERN_CARD: `
- background: rgba(255,255,255,0.1); border-radius: 12px;
- padding: 18px; border: 1px solid rgba(255,255,255,0.1);
- backdrop-filter: blur(5px);
- `,
- GRADIENT_TEXT: `
- background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
- -webkit-background-clip: text; -webkit-text-fill-color: transparent;
- background-clip: text; font-weight: bold;
- `,
- },
- THEMES: {
- 'Classic Autobot': {
- primary: '#000000',
- secondary: '#111111',
- accent: '#222222',
- text: '#ffffff',
- highlight: '#775ce3',
- success: '#00ff00',
- error: '#ff0000',
- warning: '#ffaa00',
- fontFamily: "'Segoe UI', Roboto, sans-serif",
- borderRadius: '12px',
- borderStyle: 'solid',
- borderWidth: '1px',
- boxShadow: '0 8px 32px rgba(0,0,0,0.6), 0 0 0 1px rgba(255,255,255,0.1)',
- backdropFilter: 'blur(10px)',
- animations: {
- glow: false,
- scanline: false,
- 'pixel-blink': false,
- },
- },
- 'Classic Light': {
- primary: '#E0E0E1',
- secondary: '#FBFBFB',
- accent: '#F3F3F3',
- text: '#203C5D',
- highlight: '#203C5D',
- 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,
- 'pixel-blink': false,
- },
- },
- 'Neon Retro': {
- primary: '#1a1a2e',
- secondary: '#16213e',
- accent: '#0f3460',
- text: '#00ff41',
- highlight: '#ff6b35',
- success: '#39ff14',
- error: '#ff073a',
- warning: '#ffff00',
- neon: '#00ffff',
- purple: '#bf00ff',
- pink: '#ff1493',
- fontFamily: "'Press Start 2P', monospace",
- borderRadius: '0',
- borderStyle: 'solid',
- borderWidth: '3px',
- boxShadow: '0 0 20px rgba(0, 255, 65, 0.3), inset 0 0 20px rgba(0, 255, 65, 0.1)',
- backdropFilter: 'none',
- animations: {
- glow: true,
- scanline: true,
- 'pixel-blink': true,
- },
- },
- 'Neon Retro Cyan': {
- primary: '#1959A1',
- secondary: '#3C74AF',
- accent: '#538CC0',
- text: '#81DCF7',
- highlight: '#EA9C00',
- success: '#39ff14',
- error: '#ff073a',
- warning: '#ffff00',
- neon: '00ffff',
- purple: '#bf00ff',
- pink: '#ff1493',
- fontFamily: "'Press Start 2P', monospace",
- borderRadius: '0',
- borderStyle: 'solid',
- borderWidth: '3px',
- boxShadow: '0 0 20px rgba(234 156 0, 0.3), inset 0 0 20px rgba(234 156 0, 0.1)',
- backdropFilter: 'none',
- animations: {
- glow: true,
- scanline: true,
- 'pixel-blink': true,
- },
- },
- 'Neon Retro Light': {
- primary: '#E0E0E1',
- secondary: '#FBFBFB',
- accent: '#F3F3F3',
- text: '#203C5D',
- highlight: '#203C5D',
- success: '#39ff14',
- error: '#ff073a',
- warning: '#ffff00',
- neon: '#203C5D',
- purple: '#bf00ff',
- pink: '#ff1493',
- fontFamily: "'Press Start 2P', monospace",
- borderRadius: '0',
- borderStyle: 'solid',
- borderWidth: '3px',
- boxShadow: '0 0 20px rgba(234 156 0, 0.3), inset 0 0 20px rgba(234 156 0, 0.1)',
- backdropFilter: 'none',
- animations: {
- glow: true,
- scanline: true,
- 'pixel-blink': true,
- },
- },
- 'Acrylic': {
- primary: '#00000080',
- secondary: '#00000040',
- accent: 'rgba(0,0,0,0.75)',
- text: '#ffffff',
- highlight: '#ffffff',
- success: '#00e500',
- error: '#e50000',
- warning: '#e5e500',
- fontFamily: "'Inter', 'Apple Color Emoji'",
- borderRadius: '10px',
- borderStyle: 'solid',
- borderWidth: '0px',
- boxShadow: 'none',
- backdropFilter: 'blur(20px)',
- animations: {
- glow: false,
- scanline: false,
- 'pixel-blink': false,
- },
- },
- },
- currentTheme: 'Classic Autobot',
- PAINT_UNAVAILABLE: true,
- COORDINATE_MODE: 'rows',
- COORDINATE_DIRECTION: 'top-left',
- COORDINATE_SNAKE: true,
- COORDINATE_BLOCK_WIDTH: 6,
- COORDINATE_BLOCK_HEIGHT: 2,
- autoSwap: true,
- };
-
- // Expose CONFIG globally for the utils manager and other modules
- window.CONFIG = CONFIG;
-
- const getCurrentTheme = () => CONFIG.THEMES[CONFIG.currentTheme];
-
- const switchTheme = (themeName) => {
- if (CONFIG.THEMES[themeName]) {
- CONFIG.currentTheme = themeName;
- saveThemePreference();
-
- // APPLY THEME VARS/CLASS (new)
- applyTheme();
-
- // Recreate UI (kept for now)
- createUI();
- }
- };
-
- // Add this helper (place it after getCurrentTheme/switchTheme definitions)
- function applyTheme() {
- const theme = getCurrentTheme();
- console.group('%cšØ Applying Theme in Auto-Image Script', 'color: #8b5cf6; font-weight: bold;');
- console.log(`%cšÆ Target theme: ${CONFIG.currentTheme}`, 'color: #8b5cf6;');
-
- // 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-acrylic',
- 'wplace-theme-neon',
- 'wplace-theme-neon-cyan',
- 'wplace-theme-neon-light'
- );
-
- let themeClass = 'wplace-theme-classic'; // default
- let themeFileName = 'classic'; // corresponding file name
-
- if (CONFIG.currentTheme === 'Neon Retro') {
- themeClass = 'wplace-theme-neon';
- themeFileName = 'neon';
- } else if (CONFIG.currentTheme === 'Classic Light') {
- themeClass = 'wplace-theme-classic-light';
- themeFileName = 'classic-light';
- } else if (CONFIG.currentTheme === 'Neon Retro Cyan') {
- themeClass = 'wplace-theme-neon-cyan';
- themeFileName = 'neon-cyan';
- } else if (CONFIG.currentTheme === 'Neon Retro Light') {
- themeClass = 'wplace-theme-neon-light';
- themeFileName = 'neon-light';
- } else if (CONFIG.currentTheme === 'Acrylic') {
- themeClass = 'wplace-theme-acrylic';
- themeFileName = 'acrylic';
- }
-
- document.documentElement.classList.add(themeClass);
- console.log(`%cā
Applied theme class: ${themeClass}`, 'color: #8b5cf6;');
-
- // Use extension's applyTheme helper if available (loads from local extension resources)
- if (typeof window.applyTheme === 'function') {
- console.log(`%cš§ Using extension's applyTheme() helper for: ${themeFileName}`, 'color: #10b981; font-weight: bold;');
- const success = window.applyTheme(themeFileName);
- if (success) {
- console.log(`%cā
Theme CSS loaded from extension local resources`, 'color: #10b981; font-weight: bold;');
- console.log(` š Source: Extension local file (themes/${themeFileName}.css)`);
- console.log(` š Performance: Instant load (no network request)`);
- } else {
- console.warn(`%cā ļø Extension theme loading failed, fallback to CSS classes only`, 'color: #f59e0b;');
- console.log(` šØ Using CSS class: ${themeClass}`);
- console.log(` š Note: Theme variables may be limited without CSS file`);
- }
- } else {
- console.log(`%cš Extension applyTheme() not available, using CSS class only`, 'color: #f59e0b;');
- console.log(` šØ Using CSS class: ${themeClass}`);
- console.log(` ā ļø Note: Full theme styling requires extension helper function`);
- }
-
- // 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)');
-
- console.log(`%cšØ Theme application complete`, 'color: #8b5cf6; font-weight: bold;');
- console.groupEnd();
- }
-
- const saveThemePreference = () => {
- try {
- localStorage.setItem('wplace-theme', CONFIG.currentTheme);
- } catch (e) {
- console.warn('Could not save theme preference:', e);
- }
- };
-
- const loadThemePreference = () => {
- try {
- const saved = localStorage.getItem('wplace-theme');
- if (saved && CONFIG.THEMES[saved]) {
- CONFIG.currentTheme = saved;
- }
- } catch (e) {
- console.warn('Could not load theme preference:', e);
- }
- };
-
- // Dynamically loaded translations
- window.loadedTranslations = {};
-
- // Available languages
- const AVAILABLE_LANGUAGES = [
- 'en',
- 'ru',
- 'pt',
- 'vi',
- 'fr',
- 'id',
- 'tr',
- 'zh-CN',
- 'zh-TW',
- 'ja',
- 'ko',
- 'uk',
- ];
-
- // Function to load translations from JSON file with retry mechanism
- const loadTranslations = async (language, retryCount = 0) => {
- if (window.loadedTranslations[language]) {
- return window.loadedTranslations[language];
- }
-
- console.group(`%cš Loading ${language.toUpperCase()} translations`, 'color: #06b6d4; font-weight: bold;');
-
- // First try: Check if extension has loaded local resources
- if (window.AUTOBOT_LANGUAGES && Object.keys(window.AUTOBOT_LANGUAGES).length > 0) {
- console.log(`%cš Checking extension local resources...`, 'color: #06b6d4;');
-
- const langFile = language + '.json';
- if (window.AUTOBOT_LANGUAGES[langFile]) {
- const translations = window.AUTOBOT_LANGUAGES[langFile];
-
- // Validate that translations is an object with keys
- if (
- typeof translations === 'object' &&
- translations !== null &&
- Object.keys(translations).length > 0
- ) {
- window.loadedTranslations[language] = translations;
- console.log(`%cā
Loaded ${language} translations from EXTENSION LOCAL FILES`, 'color: #10b981; font-weight: bold;');
- console.log(` š Source: Extension local storage (chrome-extension://)`);
- console.log(` š Keys count: ${Object.keys(translations).length}`);
- console.log(` š Performance: Instant load (no network request)`);
- console.groupEnd();
- return translations;
- }
- } else {
- console.log(`%cš ${langFile} not found in extension resources`, 'color: #f59e0b;');
- console.log(` š Available in extension: ${Object.keys(window.AUTOBOT_LANGUAGES).join(', ')}`);
- }
- } else {
- console.log(`%cš No extension local resources available`, 'color: #f59e0b;');
- console.log(` š window.AUTOBOT_LANGUAGES: ${typeof window.AUTOBOT_LANGUAGES}`);
- }
-
- // Second try: Use helper function if available
- if (typeof window.getLanguage === 'function') {
- console.log(`%cš§ Trying extension getLanguage() helper...`, 'color: #06b6d4;');
- try {
- const translations = window.getLanguage(language);
- if (
- typeof translations === 'object' &&
- translations !== null &&
- Object.keys(translations).length > 0
- ) {
- window.loadedTranslations[language] = translations;
- console.log(`%cā
Loaded ${language} translations via extension helper`, 'color: #10b981; font-weight: bold;');
- console.log(` š Source: Extension getLanguage() function`);
- console.log(` š Keys count: ${Object.keys(translations).length}`);
- console.groupEnd();
- return translations;
- }
- } catch (error) {
- console.warn(`%cā ļø Extension helper failed:`, 'color: #f59e0b;', error);
- }
- }
-
- // Fallback: Load from CDN (original behavior)
- console.log(`%cš Falling back to CDN loading...`, 'color: #8b5cf6;');
- const url = `https://wplace-autobot.github.io/WPlace-AutoBOT/main/lang/${language}.json`;
- const maxRetries = 3;
- const baseDelay = 1000; // 1 second
-
- try {
- if (retryCount === 0) {
- console.log(`š Loading ${language} translations from CDN...`);
- } else {
- console.log(
- `š Retrying ${language} translations (attempt ${retryCount + 1}/${maxRetries + 1})...`
- );
- }
-
- const response = await fetch(url);
- if (response.ok) {
- const translations = await response.json();
-
- // Validate that translations is an object with keys
- if (
- typeof translations === 'object' &&
- translations !== null &&
- Object.keys(translations).length > 0
- ) {
- window.loadedTranslations[language] = translations;
- console.log(
- `%cš Loaded ${language} translations from CDN (${Object.keys(translations).length} keys)`, 'color: #f59e0b; font-weight: bold;'
- );
- console.log(` š Source: CDN (wplace-autobot.github.io)`);
- console.log(` š URL: ${url}`);
- console.log(` ā ļø Performance: Network request required`);
- console.groupEnd();
- return translations;
- } else {
- console.warn(`ā Invalid translation format for ${language}`);
- throw new Error('Invalid translation format');
- }
- } else {
- console.warn(
- `ā CDN returned HTTP ${response.status}: ${response.statusText} for ${language} translations`
- );
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
- }
- } catch (error) {
- console.error(
- `ā Failed to load ${language} translations from CDN (attempt ${retryCount + 1}):`,
- error
- );
-
- // Retry with exponential backoff
- if (retryCount < maxRetries) {
- const delay = baseDelay * Math.pow(2, retryCount);
- console.log(`ā³ Retrying in ${delay}ms...`);
- await new Promise((resolve) => setTimeout(resolve, delay));
- console.groupEnd();
- return loadTranslations(language, retryCount + 1);
- }
- }
-
- console.error(`%cš„ All translation loading methods failed for ${language}`, 'color: #ef4444; font-weight: bold;');
- console.groupEnd();
- return null;
- };
-
- const loadLanguagePreference = async () => {
- const savedLanguage = localStorage.getItem('wplace_language');
- const browserLocale = navigator.language;
- const browserLanguage = browserLocale.split('-')[0];
-
- let selectedLanguage = 'en'; // Default fallback
-
- try {
- // Check if we have the saved language available
- if (savedLanguage && AVAILABLE_LANGUAGES.includes(savedLanguage)) {
- selectedLanguage = savedLanguage;
- console.log(`š Using saved language preference: ${selectedLanguage}`);
- }
- // Try full locale match (e.g. "zh-CN", "zh-TW" etc)
- else if (AVAILABLE_LANGUAGES.includes(browserLocale)) {
- selectedLanguage = browserLocale;
- localStorage.setItem('wplace_language', browserLocale);
- console.log(`š Using browser locale: ${selectedLanguage}`);
- }
- // Try base language match (e.g. "en" for "en-US" or "en-GB" etc)
- else if (AVAILABLE_LANGUAGES.includes(browserLanguage)) {
- selectedLanguage = browserLanguage;
- localStorage.setItem('wplace_language', browserLanguage);
- console.log(`š Using browser language: ${selectedLanguage}`);
- }
- // Use English as fallback
- else {
- console.log(`š No matching language found, using English fallback`);
- }
-
- // Set the language in state first
- state.language = selectedLanguage;
-
- // Only load translations if not already loaded and not English (which should already be loaded)
- if (selectedLanguage !== 'en' && !window.loadedTranslations[selectedLanguage]) {
- const loaded = await loadTranslations(selectedLanguage);
- if (!loaded) {
- console.warn(
- `ā ļø Failed to load ${selectedLanguage} translations, falling back to English`
- );
- state.language = 'en';
- localStorage.setItem('wplace_language', 'en');
- }
- }
- } catch (error) {
- console.error(`ā Error in loadLanguagePreference:`, error);
- state.language = 'en'; // Always ensure we have a valid language
- }
- };
-
- // Simple user notification function for critical issues
- const showTranslationWarning = (message) => {
- try {
- // Create a simple temporary notification banner
- const warning = document.createElement('div');
- warning.style.cssText = `
- position: fixed; top: 10px; right: 10px; z-index: 10001;
- background: rgba(255, 193, 7, 0.95); color: #212529; padding: 12px 16px;
- border-radius: 8px; font-size: 14px; font-weight: 500;
- box-shadow: 0 4px 12px rgba(0,0,0,0.3); border: 1px solid rgba(255, 193, 7, 0.8);
- max-width: 300px; word-wrap: break-word;
- `;
- warning.textContent = message;
- document.body.appendChild(warning);
-
- // Auto-remove after 8 seconds
- setTimeout(() => {
- if (warning.parentNode) {
- warning.remove();
- }
- }, 8000);
- } catch (e) {
- // If DOM manipulation fails, just log
- console.warn('Failed to show translation warning UI:', e);
- }
- };
-
- // Initialize translations function
- const initializeTranslations = async () => {
- try {
- console.log('š Initializing translation system...');
-
- // Always ensure English is loaded as fallback first
- if (!window.loadedTranslations['en']) {
- const englishLoaded = await loadTranslations('en');
- if (!englishLoaded) {
- console.warn('ā ļø Failed to load English translations from CDN, using fallback');
- showTranslationWarning('ā ļø Translation loading failed, using basic fallbacks');
- }
- }
-
- // Then load user's language preference
- await loadLanguagePreference();
-
- console.log(`ā
Translation system initialized. Active language: ${state.language}`);
- } catch (error) {
- console.error('ā Translation initialization failed:', error);
- // Ensure state has a valid language even if loading fails
- if (!state.language) {
- state.language = 'en';
- }
- console.warn('ā ļø Using fallback translations due to initialization failure');
- showTranslationWarning('ā ļø Translation system error, using basic English');
- }
- };
-
- // Emergency fallback TEXT (minimal)
- const FALLBACK_TEXT = {
- en: {
- title: 'WPlace Auto-Image',
- toggleOverlay: 'Toggle Overlay',
- scanColors: 'Scan Colors',
- uploadImage: 'Upload Image',
- resizeImage: 'Resize Image',
- selectPosition: 'Select Position',
- startPainting: 'Start Painting',
- stopPainting: 'Stop Painting',
- progress: 'Progress',
- pixels: 'Pixels',
- charges: 'Charges',
- batchSize: 'Batch Size',
- cooldownSettings: 'Cooldown Settings',
- waitCharges: 'Wait for Charges',
- settings: 'Settings',
- showStats: 'Show Statistics',
- compactMode: 'Compact Mode',
- minimize: 'Minimize',
- tokenCapturedSuccess: 'Token captured successfully',
- turnstileInstructions: 'Complete the verification',
- hideTurnstileBtn: 'Hide',
- notificationsNotSupported: 'Notifications not supported',
- chargesReadyMessage: 'Charges are ready',
- chargesReadyNotification: 'WPlace AutoBot',
- initMessage: "Click 'Upload Image' to begin",
- },
- };
-
- // Safe translation function with multiple fallback levels
- const getText = (key, replacements = {}) => {
- // Try current language first
- let text = window.loadedTranslations[state.language]?.[key];
-
- // Fallback to English translations
- if (!text && state.language !== 'en') {
- text = window.loadedTranslations['en']?.[key];
- }
-
- // Fallback to hardcoded English
- if (!text) {
- text = FALLBACK_TEXT['en']?.[key];
- }
-
- // Last resort - return the key itself
- if (!text) {
- console.warn(`ā ļø Missing translation for key: ${key}`);
- return key;
- }
-
- // Handle string replacements like {count}, {time}, etc.
- return Object.entries(replacements).reduce((result, [placeholder, value]) => {
- return result.replace(new RegExp(`\\{${placeholder}\\}`, 'g'), value);
- }, text);
- };
-
- // GLOBAL STATE
- const state = {
- running: false,
- imageLoaded: false,
- processing: false,
- totalPixels: 0,
- paintedPixels: 0,
- preFilteringDone: false, // Track if pre-filtering detection has been done this session
- availableColors: [],
- activeColorPalette: [], // User-selected colors for conversion
- paintWhitePixels: true, // Default to ON
- fullChargeData: null,
- fullChargeInterval: null,
- paintTransparentPixels: false, // Default to OFF
- displayCharges: 0,
- preciseCurrentCharges: 0,
- maxCharges: 1, // Default max charges
- cooldown: CONFIG.COOLDOWN_DEFAULT,
- imageData: null,
- stopFlag: false,
- colorsChecked: false,
- startPosition: null,
- selectingPosition: false,
- region: null,
- minimized: false,
- lastPosition: { x: 0, y: 0 },
- estimatedTime: 0,
- language: 'en',
- paintingSpeed: CONFIG.PAINTING_SPEED.DEFAULT, // pixels batch size
- batchMode: CONFIG.BATCH_MODE, // "normal" or "random"
- randomBatchMin: CONFIG.RANDOM_BATCH_RANGE.MIN, // Random range minimum
- randomBatchMax: CONFIG.RANDOM_BATCH_RANGE.MAX, // Random range maximum
- cooldownChargeThreshold: CONFIG.COOLDOWN_CHARGE_THRESHOLD,
- chargesThresholdInterval: null,
- tokenSource: CONFIG.TOKEN_SOURCE, // "generator" or "manual"
- 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,
- paintUnavailablePixels: CONFIG.PAINT_UNAVAILABLE,
- // Coordinate generation settings
- coordinateMode: CONFIG.COORDINATE_MODE,
- coordinateDirection: CONFIG.COORDINATE_DIRECTION,
- coordinateSnake: CONFIG.COORDINATE_SNAKE,
- blockWidth: CONFIG.COORDINATE_BLOCK_WIDTH,
- blockHeight: CONFIG.COORDINATE_BLOCK_HEIGHT,
- 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,
- accountIndex: 0,
- originalAccountOrder: [], // Store the original order of accounts
- currentActiveIndex: 0, // Track current position in the original order
- // Account switching state
- allAccountsInfo: [],
- isFetchingAllAccounts: false,
- };
-
- // Expose state globally for the utils manager and other modules
- window.state = state;
-
- let _updateResizePreview = () => { };
- let _resizeDialogCleanup = null;
-
- // --- OVERLAY UPDATE: Optimized OverlayManager class with performance improvements ---
- // OverlayManager class extracted to overlay-manager.js module
-
- // Overlay management is now handled by the OverlayManager module
- // Check for overlay manager availability
- if (!window.WPlaceOverlayManager) {
- console.error('ā WPlaceOverlayManager not available - please ensure overlay-manager.js is loaded first');
- throw new Error('OverlayManager dependency not found');
- }
-
- // Ensure we only have ONE overlay manager instance globally
- let overlayManager;
- if (!window.autoImageOverlayManager) {
- overlayManager = new window.WPlaceOverlayManager();
- window.autoImageOverlayManager = overlayManager;
- console.log('šÆ Created NEW overlay manager instance');
- } else {
- overlayManager = window.autoImageOverlayManager;
- console.log('šÆ Using EXISTING overlay manager instance');
- }
-
- // Token management is now handled by the TokenManager module
- // Global references for backward compatibility
- if (!window.WPlaceTokenManager) {
- console.error('ā WPlaceTokenManager not available - please ensure token-manager.js is loaded first');
- throw new Error('TokenManager dependency not found');
- }
-
- const tokenManager = window.WPlaceTokenManager;
- const setTurnstileToken = (token) => tokenManager.setTurnstileToken(token);
- const isTokenValid = () => tokenManager.isTokenValid();
- const invalidateToken = () => tokenManager.invalidateToken();
- const ensureToken = (forceRefresh = false) => tokenManager.ensureToken(forceRefresh);
-
- // Getter for the current token value
- const getTurnstileToken = () => tokenManager.turnstileToken;
-
- // Token promise management
- const createTokenPromise = () => {
- tokenManager.tokenPromise = new Promise((resolve) => {
- tokenManager._resolveToken = resolve;
- });
- };
-
- // Create global image processor instance for utility functions
- if (!window.WPlaceImageProcessor) {
- console.error('ā WPlaceImageProcessor not available - please ensure image-processor.js is loaded first');
- throw new Error('ImageProcessor dependency not found');
- }
-
- const globalImageProcessor = new window.WPlaceImageProcessor();
-
- // Keep these constants for compatibility with other parts of the script
- let retryCount = 0;
- const MAX_RETRIES = 10;
- const MAX_BATCH_RETRIES = 10; // Maximum attempts for batch sending
-
- 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;
- // Setup fetch interception
-
- window.fetch = async function (...args) {
- const response = await originalFetch.apply(this, args);
- const url = args[0] instanceof Request ? args[0].url : args[0];
-
- // TILE REQUEST logging removed to reduce console spam
-
- 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 - Type: ${typeof payload.t}, Value: ${payload.t
- ? typeof payload.t === 'string'
- ? payload.t.length > 50
- ? payload.t.substring(0, 50) + '...'
- : payload.t
- : JSON.stringify(payload.t)
- : 'null/undefined'
- }, Length: ${payload.t?.length || 0}`
- );
- 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 } = event.data;
-
- if (source === 'auto-image-tile' && endpoint && blobID && blobData) {
- overlayManager.processAndRespondToTileRequest(event.data);
- }
-
- // Token capture is now handled by TokenManager module
- // No need to handle 'turnstile-capture' events here
- });
-
- 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 WRAPPER - Uses WPlaceUtilsManager for modular functionality
- const Utils = {
- // Basic utilities
- sleep: (ms) => window.globalUtilsManager ? window.globalUtilsManager.sleep(ms) : new Promise(r => setTimeout(r, ms)),
- dynamicSleep: (tickAndGetRemainingMs) => window.globalUtilsManager ? window.globalUtilsManager.dynamicSleep(tickAndGetRemainingMs) : Promise.resolve(),
- waitForSelector: (selector, interval, timeout) => window.globalUtilsManager ? window.globalUtilsManager.waitForSelector(selector, interval, timeout) : Promise.resolve(null),
- msToTimeText: (ms) => window.globalUtilsManager ? window.globalUtilsManager.msToTimeText(ms) : `${Math.ceil(ms / 1000)}s`,
- createScrollToAdjust: (element, updateCallback, min, max, step) => window.globalUtilsManager ? window.globalUtilsManager.createScrollToAdjust(element, updateCallback, min, max, step) : () => { },
-
- // Tile calculation
- calculateTileRange: (startRegionX, startRegionY, startPixelX, startPixelY, width, height, tileSize) =>
- window.globalUtilsManager ? window.globalUtilsManager.calculateTileRange(startRegionX, startRegionY, startPixelX, startPixelY, width, height, tileSize) : {},
-
- // Token management
- loadTurnstile: () => window.globalUtilsManager ? window.globalUtilsManager.loadTurnstile() : Promise.resolve(),
- ensureTurnstileContainer: () => window.globalUtilsManager ? window.globalUtilsManager.ensureTurnstileContainer() : Promise.resolve(),
- ensureTurnstileOverlayContainer: () => window.globalUtilsManager ? window.globalUtilsManager.ensureTurnstileOverlayContainer() : Promise.resolve(),
- executeTurnstile: (sitekey, action) => window.globalUtilsManager ? window.globalUtilsManager.executeTurnstile(sitekey, action) : Promise.resolve(null),
- createTurnstileWidget: (sitekey, action) => window.globalUtilsManager ? window.globalUtilsManager.createTurnstileWidget(sitekey, action) : Promise.resolve(),
- createTurnstileWidgetInteractive: (sitekey, action) => window.globalUtilsManager ? window.globalUtilsManager.createTurnstileWidgetInteractive(sitekey, action) : Promise.resolve(),
- cleanupTurnstile: () => window.globalUtilsManager ? window.globalUtilsManager.cleanupTurnstile() : Promise.resolve(),
- obtainSitekeyAndToken: (fallback) => window.globalUtilsManager ? window.globalUtilsManager.obtainSitekeyAndToken(fallback) : Promise.resolve(fallback),
-
- // DOM utilities
- createElement: (tag, props, children) => window.globalUtilsManager ? window.globalUtilsManager.createElement(tag, props, children) : document.createElement(tag),
- createButton: (id, text, icon, onClick, style) => {
- if (window.globalUtilsManager) {
- return window.globalUtilsManager.createButton(id, text, icon, onClick, style);
- }
- const btn = document.createElement('button');
- btn.id = id;
- btn.innerHTML = `${icon ? ` ` : ''}${text} `;
- if (onClick) btn.addEventListener('click', onClick);
- return btn;
- },
-
- // Translation with fallback
- t: (key, params) => {
- if (window.globalUtilsManager) {
- return window.globalUtilsManager.t(key, params);
- }
- return getText(key, params);
- },
- showAlert: (message, type) => {
- if (window.globalUtilsManager) {
- window.globalUtilsManager.showAlert(message, type);
- } else {
- console.log(`Alert [${type}]: ${message}`);
- }
- },
-
- hideAlert: () => {
- // Remove existing alerts immediately with safety checks
- const alerts = document.querySelectorAll('.wplace-alert-base');
- alerts.forEach(alert => {
- try {
- if (alert && alert.parentNode) {
- alert.parentNode.removeChild(alert);
- }
- } catch (error) {
- // Ignore removeChild errors if element was already removed
- console.debug('Alert already removed:', error);
- }
- });
- },
-
- // Color utilities
- colorDistance: (a, b) => window.globalUtilsManager ? window.globalUtilsManager.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)),
- findClosestPaletteColor: (r, g, b, palette) => window.globalUtilsManager ? window.globalUtilsManager.findClosestPaletteColor(r, g, b, palette) : [r, g, b],
- isWhitePixel: (r, g, b) => window.globalUtilsManager ? window.globalUtilsManager.isWhitePixel(r, g, b) : (r > 240 && g > 240 && b > 240),
- resolveColor: (targetRgb, availableColors, exactMatch) => window.globalUtilsManager ? window.globalUtilsManager.resolveColor(targetRgb, availableColors, exactMatch) : targetRgb,
-
- // CRITICAL FUNCTIONS - Direct implementations to ensure they work
- createImageUploader: () => {
- return new Promise((resolve, reject) => {
- const input = document.createElement('input');
- input.type = 'file';
- input.accept = 'image/*';
- input.onchange = (e) => {
- const file = e.target.files[0];
- if (file) {
- const reader = new FileReader();
- reader.onload = () => resolve(reader.result);
- reader.onerror = () => reject(new Error('File reading error'));
- reader.readAsDataURL(file);
- } else {
- reject(new Error('No file selected'));
- }
- };
- input.click();
- });
- },
-
- createFileUploader: () => {
- return 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'));
- }
- };
- input.click();
- });
- },
-
- createFileDownloader: (data, filename) => {
- const blob = new Blob([data], { type: 'application/json' });
- const url = URL.createObjectURL(blob);
- const a = document.createElement('a');
- a.href = url;
- a.download = filename;
- document.body.appendChild(a);
- a.click();
- document.body.removeChild(a);
- URL.revokeObjectURL(url);
- },
-
- loadProgress: () => {
- try {
- const saved = localStorage.getItem('wplace-bot-progress');
- if (!saved) return null;
- return JSON.parse(saved);
- } catch (error) {
- console.error('Error loading progress:', error);
- return null;
- }
- },
-
- saveProgress: () => {
- try {
- // Use utils manager to build proper save structure like old version
- const data = window.globalUtilsManager ?
- window.globalUtilsManager.buildProgressData() :
- {
- timestamp: Date.now(),
- version: '2.2',
- state: {
- totalPixels: window.state.totalPixels,
- paintedPixels: window.state.paintedPixels,
- lastPosition: window.state.lastPosition,
- startPosition: window.state.startPosition,
- region: window.state.region,
- imageLoaded: window.state.imageLoaded,
- colorsChecked: window.state.colorsChecked,
- coordinateMode: window.state.coordinateMode,
- coordinateDirection: window.state.coordinateDirection,
- coordinateSnake: window.state.coordinateSnake,
- blockWidth: window.state.blockWidth,
- blockHeight: window.state.blockHeight,
- availableColors: window.state.availableColors,
- },
- imageData: window.state.imageData ? {
- width: window.state.imageData.width,
- height: window.state.imageData.height,
- pixels: Array.from(window.state.imageData.pixels),
- totalPixels: window.state.imageData.totalPixels,
- } : null,
- paintedMapPacked: window.globalUtilsManager ?
- window.globalUtilsManager.buildPaintedMapPacked() : null
- };
-
- localStorage.setItem('wplace-bot-progress', JSON.stringify(data));
- return true;
- } catch (error) {
- console.error('Error saving progress:', error);
- return false;
- }
- },
-
- clearProgress: () => {
- try {
- localStorage.removeItem('wplace-bot-progress');
- if (window.state) {
- window.state.paintedMap = null;
- window.state._lastSavePixelCount = 0;
- window.state._lastSaveTime = 0;
- }
- return true;
- } catch (error) {
- console.error('Error clearing progress:', error);
- return false;
- }
- },
-
- restoreProgress: (savedData) => {
- try {
- console.log('š [DEBUG] RestoreProgress: Starting restoration...');
- console.log('š [DEBUG] SavedData exists:', !!savedData);
- console.log('š [DEBUG] SavedData.state exists:', !!savedData?.state);
-
- if (!savedData || !savedData.state) {
- console.error('ā [DEBUG] RestoreProgress: Invalid savedData or missing state');
- return false;
- }
-
- console.log('š [DEBUG] Current window.state keys before restore:', Object.keys(window.state || {}));
- console.log('š [DEBUG] SavedData.state keys:', Object.keys(savedData.state));
-
- console.log('š [DEBUG] Performing Object.assign...');
- Object.assign(window.state, savedData.state);
- console.log('ā
[DEBUG] Object.assign completed successfully');
-
- // Restore available colors from old save files (backward compatibility)
- if (savedData.state.availableColors && Array.isArray(savedData.state.availableColors)) {
- console.log('š [DEBUG] Restoring availableColors, count:', savedData.state.availableColors.length);
- window.state.availableColors = savedData.state.availableColors;
- window.state.colorsChecked = true; // Mark colors as checked
- console.log('ā
[DEBUG] AvailableColors restored successfully');
- } else {
- console.log('ā ļø [DEBUG] No availableColors found in savedData or not an array');
- }
-
- if (savedData.imageData) {
- console.log('š [DEBUG] Restoring imageData...');
- console.log('š [DEBUG] ImageData type check - pixels is array:', Array.isArray(savedData.imageData.pixels));
- console.log('š [DEBUG] ImageData pixels length:', savedData.imageData.pixels?.length);
-
- window.state.imageData = {
- ...savedData.imageData,
- pixels: new Uint8ClampedArray(savedData.imageData.pixels),
- };
- console.log('ā
[DEBUG] ImageData restored successfully');
- console.log('š [DEBUG] Converted pixels to Uint8ClampedArray, length:', window.state.imageData.pixels.length);
- } else {
- console.log('ā ļø [DEBUG] No imageData found in savedData');
- }
-
- if (savedData.paintedMap) {
- console.log('š [DEBUG] Restoring paintedMap...');
- window.state.paintedMap = savedData.paintedMap.map((row) => Array.from(row));
- console.log('ā
[DEBUG] PaintedMap restored successfully');
- } else {
- console.log('š [DEBUG] No paintedMap found in savedData (this is normal for extracted data)');
- }
-
- console.log('ā
[DEBUG] RestoreProgress completed successfully');
- console.log('š [DEBUG] Final window.state.imageLoaded:', window.state.imageLoaded);
- console.log('š [DEBUG] Final window.state.totalPixels:', window.state.totalPixels);
-
- return true;
- } catch (error) {
- console.error('ā [DEBUG] Error in restoreProgress:', error);
- console.error('ā [DEBUG] Error stack:', error.stack);
- console.error('ā [DEBUG] SavedData structure at error:', JSON.stringify(savedData, null, 2));
- return false;
- }
- },
-
- saveProgressToFile: () => {
- try {
- // Use utils manager to build proper save structure like old version
- const progressData = window.globalUtilsManager ?
- window.globalUtilsManager.buildProgressData() :
- {
- timestamp: Date.now(),
- version: '2.2',
- state: {
- totalPixels: window.state.totalPixels,
- paintedPixels: window.state.paintedPixels,
- lastPosition: window.state.lastPosition,
- startPosition: window.state.startPosition,
- region: window.state.region,
- imageLoaded: window.state.imageLoaded,
- colorsChecked: window.state.colorsChecked,
- coordinateMode: window.state.coordinateMode,
- coordinateDirection: window.state.coordinateDirection,
- coordinateSnake: window.state.coordinateSnake,
- blockWidth: window.state.blockWidth,
- blockHeight: window.state.blockHeight,
- availableColors: window.state.availableColors,
- },
- imageData: window.state.imageData ? {
- width: window.state.imageData.width,
- height: window.state.imageData.height,
- pixels: Array.from(window.state.imageData.pixels),
- totalPixels: window.state.imageData.totalPixels,
- } : null,
- paintedMapPacked: window.globalUtilsManager ?
- window.globalUtilsManager.buildPaintedMapPacked() : null
- };
-
- const filename = `wplace-bot-progress-${new Date()
- .toISOString()
- .slice(0, 19)
- .replace(/:/g, '-')}.json`;
- Utils.createFileDownloader(JSON.stringify(progressData, null, 2), filename);
- return true;
- } catch (error) {
- console.error('Error saving to file:', error);
- return false;
- }
- },
-
- loadProgressFromFile: async () => {
- try {
- console.log('š [DEBUG] Starting file upload process...');
- const data = await Utils.createFileUploader();
-
- console.log('š [DEBUG] File upload completed. Data type:', typeof data);
- console.log('š [DEBUG] Data exists:', !!data);
-
- if (!data) {
- console.error('ā [DEBUG] No data received from file uploader');
- throw new Error('No data received from file');
- }
-
- console.log('š [DEBUG] Data keys:', Object.keys(data));
- console.log('š [DEBUG] Data.state exists:', !!data.state);
- console.log('š [DEBUG] Data.imageData exists:', !!data.imageData);
- console.log('š [DEBUG] Data.version:', data.version);
- console.log('š [DEBUG] Data.timestamp:', data.timestamp);
-
- if (!data.state) {
- console.error('ā [DEBUG] Missing state object in loaded data');
- console.log('š [DEBUG] Available top-level keys:', Object.keys(data));
- throw new Error('Invalid file format - missing state object');
- }
-
- console.log('š [DEBUG] State object keys:', Object.keys(data.state));
- console.log('š [DEBUG] State.imageLoaded:', data.state.imageLoaded);
- console.log('š [DEBUG] State.totalPixels:', data.state.totalPixels);
- console.log('š [DEBUG] State.startPosition:', data.state.startPosition);
- console.log('š [DEBUG] State.region:', data.state.region);
-
- if (data.imageData) {
- console.log('š [DEBUG] ImageData width:', data.imageData.width);
- console.log('š [DEBUG] ImageData height:', data.imageData.height);
- console.log('š [DEBUG] ImageData pixels length:', data.imageData.pixels?.length);
- console.log('š [DEBUG] ImageData totalPixels:', data.imageData.totalPixels);
- }
-
- console.log('š [DEBUG] Calling restoreProgress...');
- const success = Utils.restoreProgress(data);
- console.log('š [DEBUG] RestoreProgress result:', success);
-
- return success;
- } catch (error) {
- console.error('ā [DEBUG] Error in loadProgressFromFile:', error);
- console.error('ā [DEBUG] Error stack:', error.stack);
- return false;
- }
- },
-
- loadExtractedFileData: async () => {
- try {
- console.log('š [DEBUG] Starting Art-Extractor file upload process...');
- const data = await Utils.createFileUploader();
-
- console.log('š [DEBUG] File upload completed. Data type:', typeof data);
- console.log('š [DEBUG] Data exists:', !!data);
- console.log('š Raw loaded data:', data);
-
- if (!data) {
- console.error('ā [DEBUG] No data received from file uploader');
- return null;
- }
-
- console.log('š [DEBUG] Data keys:', Object.keys(data));
- console.log('š [DEBUG] Data.state exists:', !!data.state);
- console.log('š [DEBUG] Data.imageData exists:', !!data.imageData);
- console.log('š [DEBUG] Data.version:', data.version);
- console.log('š [DEBUG] Data.timestamp:', data.timestamp);
-
- console.log('š [DEBUG] Returning raw data object for Load Extracted feature');
- return data;
- } catch (error) {
- console.error('ā [DEBUG] Error in loadExtractedFileData:', error);
- return null;
- }
- },
-
- // Other utilities - delegate to globalUtilsManager or provide simple fallbacks
- extractAvailableColors: () => {
- if (window.globalUtilsManager) {
- return window.globalUtilsManager.extractAvailableColors();
- } else if (window.globalImageProcessor) {
- return window.globalImageProcessor.extractAvailableColors(window.CONFIG.COLOR_MAP);
- } else {
- return [];
- }
- },
- formatTime: (ms) => window.globalUtilsManager ? window.globalUtilsManager.formatTime(ms) : `${Math.floor(ms / 1000)}s`,
- calculateEstimatedTime: (remainingPixels, charges, cooldown) => window.globalUtilsManager ? window.globalUtilsManager.calculateEstimatedTime(remainingPixels, charges, cooldown) : 0,
- initializePaintedMap: (width, height) => window.globalUtilsManager ? window.globalUtilsManager.initializePaintedMap(width, height) : console.log('Painted map not available'),
- markPixelPainted: (x, y, regionX, regionY) => window.globalUtilsManager ? window.globalUtilsManager.markPixelPainted(x, y, regionX, regionY) : false,
- isPixelPainted: (x, y, regionX, regionY) => window.globalUtilsManager ? window.globalUtilsManager.isPixelPainted(x, y, regionX, regionY) : false,
- shouldAutoSave: () => window.globalUtilsManager ? window.globalUtilsManager.shouldAutoSave() : false,
- performSmartSave: () => window.globalUtilsManager ? window.globalUtilsManager.performSmartSave() : false,
- packPaintedMapToBase64: (paintedMap, width, height) => window.globalUtilsManager ? window.globalUtilsManager.packPaintedMapToBase64(paintedMap, width, height) : null,
- unpackPaintedMapFromBase64: (base64, width, height) => window.globalUtilsManager ? window.globalUtilsManager.unpackPaintedMapFromBase64(base64, width, height) : null,
- migrateProgressToV2: (saved) => window.globalUtilsManager ? window.globalUtilsManager.migrateProgressToV2(saved) : saved,
- migrateProgressToV21: (saved) => window.globalUtilsManager ? window.globalUtilsManager.migrateProgressToV21(saved) : saved,
- migrateProgressToV22: (data) => window.globalUtilsManager ? window.globalUtilsManager.migrateProgressToV22(data) : data,
- buildPaintedMapPacked: () => window.globalUtilsManager ? window.globalUtilsManager.buildPaintedMapPacked() : null,
- buildProgressData: () => window.globalUtilsManager ? window.globalUtilsManager.buildProgressData() : {},
- migrateProgress: (saved) => window.globalUtilsManager ? window.globalUtilsManager.migrateProgress(saved) : saved,
- restoreOverlayFromData: () => window.globalUtilsManager ? window.globalUtilsManager.restoreOverlayFromData() : Promise.resolve(false),
- updateCoordinateUI: (config) => window.globalUtilsManager ? window.globalUtilsManager.updateCoordinateUI(config) : console.log('Coordinate UI update not available'),
- };
-
- // IMAGE PROCESSOR WRAPPER - Uses WPlaceImageProcessor for modular functionality
- class ImageProcessor {
- constructor(imageSrc) {
- // Create instance of the modular ImageProcessor
- this._processor = new window.WPlaceImageProcessor(imageSrc);
-
- // Legacy compatibility properties
- this.imageSrc = imageSrc;
- this.img = null;
- this.canvas = null;
- this.ctx = null;
- }
-
- async load() {
- await this._processor.load();
-
- // Update legacy properties for compatibility
- this.img = this._processor.img;
- this.canvas = this._processor.canvas;
- this.ctx = this._processor.ctx;
- }
-
- getDimensions() {
- return this._processor.getDimensions();
- }
-
- getPixelData() {
- return this._processor.getPixelData();
- }
-
- resize(newWidth, newHeight) {
- const result = this._processor.resize(newWidth, newHeight);
-
- // Update legacy properties
- this.img = this._processor.img;
- this.canvas = this._processor.canvas;
- this.ctx = this._processor.ctx;
-
- return result;
- }
-
- generatePreview(width, height) {
- return this._processor.generatePreview(width, height);
- }
-
- // Cleanup method
- cleanup() {
- if (this._processor) {
- this._processor.cleanup();
- }
- }
- }
-
- // WPLACE API SERVICE
- const WPlaceService = {
- async paintPixelInRegion(regionX, regionY, pixelX, pixelY, color) {
- try {
- await ensureToken();
- if (!getTurnstileToken()) return 'token_error';
- const payload = {
- coords: [pixelX, pixelY],
- colors: [color],
- t: getTurnstileToken(),
- fp: fpStr32,
- };
- var token = await createWasmToken(regionX, regionY, payload);
- const res = await fetch(`https://backend.wplace.live/s0/pixel/${regionX}/${regionY}`, {
- method: 'POST',
- headers: { 'Content-Type': 'text/plain;charset=UTF-8', "x-pawtect-token": token },
- credentials: 'include',
- body: JSON.stringify(payload),
- });
- if (res.status === 403) {
- console.error('ā 403 Forbidden. Turnstile token might be invalid or expired.');
- setTurnstileToken(null);
- createTokenPromise();
- return 'token_error';
- }
- const data = await res.json();
- return data?.painted === 1;
- } catch (e) {
- console.error('Paint request failed:', e);
- return false;
- }
- },
-
- async getCharges() {
- try {
- const res = await fetch("https://backend.wplace.live/me", {
- credentials: "include",
- })
- const data = await res.json()
- return {
- id: data.id,
- charges: data.charges?.count || 0,
- max: data.charges?.max || 1,
- cooldown: data.charges?.next || CONFIG.COOLDOWN_DEFAULT,
- droplets: data.droplets || 0,
- }
- } catch (e) {
- console.error("Failed to get charges:", e)
- return {
- id: null,
- charges: 0,
- max: 1,
- cooldown: CONFIG.COOLDOWN_DEFAULT,
- droplets: 0,
- }
- }
- },
-
- async fetchCheck() {
- try {
- const res = await fetch("https://backend.wplace.live/me", {
- credentials: "include",
- })
- const data = await res.json()
- return {
- ID: data.id,
- Username: data.username || data.name || null,
- Charges: data.charges.count,
- Max: data.charges.max,
- Droplets: data.droplets
- }
- } catch (e) {
- console.error("Failed to get ID:", e)
- return {
-
- }
- }
- }
- };
-
- // 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(Utils.t('notificationsNotSupported'), '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.displayCharges < state.cooldownChargeThreshold;
- state._lastChargesNotifyAt = 0;
- },
- maybeNotifyChargesReached(force = false) {
- if (!state.notificationsEnabled || !state.notifyOnChargesReached) return;
- const reached = state.displayCharges >= 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 = Utils.t('chargesReadyMessage', {
- current: state.displayCharges,
- max: state.maxCharges,
- threshold: state.cooldownChargeThreshold,
- });
- this.notify(Utils.t('chargesReadyNotification'), 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.displayCharges = 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
- const colorCache = new Map();
-
- // UI UPDATE FUNCTIONS (declared early to avoid reference errors)
- let updateUI = () => { };
- let updateStats = async () => { };
- let updateDataButtons = () => { };
-
- function updateActiveColorPalette() {
- state.activeColorPalette = [];
- const activeSwatches = document.querySelectorAll('.wplace-color-swatch.active');
- if (activeSwatches) {
- activeSwatches.forEach((swatch) => {
- const rgbStr = swatch.getAttribute('data-rgb');
- if (rgbStr) {
- const rgb = rgbStr.split(',').map(Number);
- state.activeColorPalette.push(rgb);
- }
- });
- }
- if (document.querySelector('.resize-container')?.style.display === 'block') {
- _updateResizePreview();
- }
- }
-
- function toggleAllColors(select, showingUnavailable = false) {
- const swatches = document.querySelectorAll('.wplace-color-swatch');
- if (swatches) {
- swatches.forEach((swatch) => {
- // Only toggle colors that are available or if we're showing unavailable colors
- const isUnavailable = swatch.classList.contains('unavailable');
- if (!isUnavailable || showingUnavailable) {
- // Don't try to select unavailable colors
- if (!isUnavailable) {
- swatch.classList.toggle('active', select);
- }
- }
- });
- }
- updateActiveColorPalette();
- }
-
- function unselectAllPaidColors() {
- const swatches = document.querySelectorAll('.wplace-color-swatch');
- if (swatches) {
- swatches.forEach((swatch) => {
- const colorId = parseInt(swatch.getAttribute('data-color-id'), 10);
- if (!isNaN(colorId) && colorId >= 32) {
- swatch.classList.toggle('active', false);
- }
- });
- }
- updateActiveColorPalette();
- }
-
- function initializeColorPalette(container) {
- const colorsContainer = container.querySelector('#colors-container');
- const showAllToggle = container.querySelector('#showAllColorsToggle');
- if (!colorsContainer) return;
-
- // Use already captured colors from state (captured during upload)
- // Don't re-fetch colors here, use what was captured when user clicked upload
- if (!state.availableColors || state.availableColors.length === 0) {
- // If no colors have been captured yet, show message
- colorsContainer.innerHTML = `${Utils.t(
- 'uploadImageFirst'
- )}
`;
- return;
- }
-
- function populateColors(showUnavailable = false) {
- colorsContainer.innerHTML = '';
- let availableCount = 0;
- let totalCount = 0;
-
- // Convert COLOR_MAP to array and filter out transparent
- const allColors = Object.values(CONFIG.COLOR_MAP).filter((color) => color.rgb !== null);
-
- allColors.forEach((colorData) => {
- const { id, name, rgb } = colorData;
- const rgbKey = `${rgb.r},${rgb.g},${rgb.b}`;
- totalCount++;
-
- // 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
- );
-
- // If not showing all colors and this color is not available, skip it
- if (!showUnavailable && !isAvailable) {
- return;
- }
-
- if (isAvailable) availableCount++;
-
- const colorItem = Utils.createElement('div', {
- className: 'wplace-color-item',
- });
- const swatch = Utils.createElement('button', {
- className: `wplace-color-swatch ${!isAvailable ? 'unavailable' : ''}`,
- title: `${name} (ID: ${id})${!isAvailable ? ' (Unavailable)' : ''}`,
- 'data-rgb': rgbKey,
- 'data-color-id': id,
- });
- swatch.style.backgroundColor = `rgb(${rgb.r}, ${rgb.g}, ${rgb.b})`;
-
- // Make unavailable colors visually distinct
- if (!isAvailable) {
- swatch.style.opacity = '0.4';
- swatch.style.filter = 'grayscale(50%)';
- swatch.disabled = true;
- } else {
- // Select available colors by default
- swatch.classList.add('active');
- }
-
- const nameLabel = Utils.createElement(
- 'span',
- {
- className: 'wplace-color-item-name',
- style: !isAvailable ? 'color: #888; font-style: italic;' : '',
- },
- name + (!isAvailable ? ' (N/A)' : '')
- );
-
- // Only add click listener for available colors
- if (isAvailable) {
- swatch.addEventListener('click', () => {
- swatch.classList.toggle('active');
- updateActiveColorPalette();
- });
- }
-
- colorItem.appendChild(swatch);
- colorItem.appendChild(nameLabel);
- colorsContainer.appendChild(colorItem);
- });
-
- updateActiveColorPalette();
- }
-
- // Initialize with only available colors
- populateColors(false);
-
- // Add toggle functionality
- if (showAllToggle) {
- showAllToggle.addEventListener('change', (e) => {
- populateColors(e.target.checked);
- });
- }
-
- container
- .querySelector('#selectAllBtn')
- ?.addEventListener('click', () => toggleAllColors(true, showAllToggle?.checked));
- container
- .querySelector('#unselectAllBtn')
- ?.addEventListener('click', () => toggleAllColors(false, showAllToggle?.checked));
- container
- .querySelector('#unselectPaidBtn')
- ?.addEventListener('click', () => unselectAllPaidColors());
- }
-
- async function handleCaptcha(allowGeneration = true) {
- const startTime = performance.now();
-
- // Check user's token source preference
- if (state.tokenSource === 'manual') {
- console.log('šÆ Manual token source selected - using pixel placement automation');
- if (!allowGeneration) {
- console.log('ā Token generation disabled during processing');
- return null;
- }
- return await tokenManager.handleCaptchaFallback();
- }
-
- // Generator mode (pure) or Hybrid mode - try generator first
- try {
- // Use optimized token generation with automatic sitekey detection
- const { sitekey, token: preGeneratedToken } = await Utils.obtainSitekeyAndToken();
-
- if (!sitekey) {
- throw new Error('No valid sitekey found');
- }
-
- 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();
- }
-
- let token = null;
-
- // ā
Reuse pre-generated token if available and valid
- if (
- preGeneratedToken &&
- typeof preGeneratedToken === 'string' &&
- preGeneratedToken.length > 20
- ) {
- console.log('ā»ļø Reusing pre-generated token from sitekey detection phase');
- token = preGeneratedToken;
- }
- // ā
Or use globally cached token if still valid
- else if (isTokenValid()) {
- console.log('ā»ļø Using existing cached token (from previous operation)');
- token = getTurnstileToken();
- }
- // ā
Otherwise generate a new one (only if allowed)
- else {
- if (!allowGeneration) {
- console.log('ā Token expired/missing but generation disabled during processing');
- return null;
- }
- console.log('š No valid pre-generated or cached token, creating new one...');
- token = await Utils.executeTurnstile(sitekey, 'paint');
- if (token) {
- setTurnstileToken(token);
- }
- }
-
- 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}`
- );
-
- // ā
Final validation
- if (typeof token === 'string' && token.length > 20) {
- const duration = Math.round(performance.now() - startTime);
- console.log(`ā
Turnstile token generated successfully in ${duration}ms`);
- return token;
- } else {
- throw new Error(
- `Invalid or empty token received - Type: ${typeof token}, Value: ${JSON.stringify(
- token
- )}, Length: ${token?.length || 0}`
- );
- }
- } 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...'
- );
- const fbToken = await tokenManager.handleCaptchaFallback();
- return fbToken;
- } else {
- // Pure generator mode - don't fallback, just fail
- throw error;
- }
- }
- }
-
- 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');
- const existingResizeContainer = document.querySelector('.resize-container');
- const existingResizeOverlay = document.querySelector('.resize-overlay');
-
- if (existingContainer) existingContainer.remove();
- if (existingStats) existingStats.remove();
- if (existingSettings) existingSettings.remove();
- if (existingResizeContainer) existingResizeContainer.remove();
- if (existingResizeOverlay) existingResizeOverlay.remove();
-
- loadThemePreference();
- await initializeTranslations();
-
- const theme = getCurrentTheme();
- applyTheme(); // <- new: set CSS vars and theme class before building UI
-
- function appendLinkOnce(href, attributes = {}) {
- // Check if a link with the same href already exists in the document head
- const exists = Array.from(document.head.querySelectorAll('link')).some(
- (link) => link.href === href
- );
- if (exists) return;
-
- // Create a new link element
- const link = document.createElement('link');
- link.rel = 'stylesheet';
- link.href = href;
-
- // Add any additional attributes (e.g., data-* attributes)
- for (const [key, value] of Object.entries(attributes)) {
- link.setAttribute(key, value);
- }
-
- // Append the link element to the document head
- document.head.appendChild(link);
- }
-
- appendLinkOnce('https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css');
-
- if (theme.fontFamily.includes('Press Start 2P')) {
- appendLinkOnce('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');
- }
-
- // Load auto-image-styles.css - prioritize extension local resources
- console.group('%cšØ Loading Auto-Image Styles', 'color: #8b5cf6; font-weight: bold;');
-
- let stylesLoaded = false;
-
- // First try: Check if extension has loaded local CSS resources
- if (window.AUTOBOT_THEMES && window.AUTOBOT_THEMES['auto-image-styles.css']) {
- console.log('%cš Found auto-image-styles.css in extension local resources!', 'color: #10b981; font-weight: bold;');
-
- // Check if it's already injected by the extension
- const existingStyle = document.getElementById('autobot-auto-image-styles');
- if (existingStyle) {
- console.log('%cā
Styles already injected by extension - using local version', 'color: #10b981; font-weight: bold;');
- console.log(' š Source: Extension local file (already in DOM)');
- console.log(' š Element ID: autobot-auto-image-styles');
- stylesLoaded = true;
- } else {
- // Inject it ourselves
- const styleElement = document.createElement('style');
- styleElement.id = 'autobot-auto-image-styles-script';
- styleElement.setAttribute('data-wplace-theme', 'true');
- styleElement.textContent = window.AUTOBOT_THEMES['auto-image-styles.css'];
- document.head.appendChild(styleElement);
-
- console.log('%cā
Injected auto-image-styles.css from extension local resources', 'color: #10b981; font-weight: bold;');
- console.log(' š Source: Extension local file');
- console.log(' š Size: ' + window.AUTOBOT_THEMES['auto-image-styles.css'].length + ' characters');
- console.log(' š Element ID: autobot-auto-image-styles-script');
- stylesLoaded = true;
- }
- } else {
- console.log('%cš auto-image-styles.css not found in extension resources', 'color: #f59e0b;');
- console.log(' š window.AUTOBOT_THEMES:', typeof window.AUTOBOT_THEMES);
- if (window.AUTOBOT_THEMES) {
- console.log(' š Available themes:', Object.keys(window.AUTOBOT_THEMES));
- }
- }
-
- // Fallback: Load from CDN if local resources not available
- if (!stylesLoaded) {
- console.log('%cš Falling back to CDN loading...', 'color: #8b5cf6;');
- appendLinkOnce(
- 'https://wplace-autobot.github.io/WPlace-AutoBOT/main/auto-image-styles.css',
- { 'data-wplace-theme': 'true' }
- );
- console.log('%cš Loaded auto-image-styles.css from CDN (fallback)', 'color: #f59e0b; font-weight: bold;');
- console.log(' š Source: CDN (wplace-autobot.github.io)');
- console.log(' š URL: https://wplace-autobot.github.io/WPlace-AutoBOT/main/auto-image-styles.css');
- console.log(' ā ļø Performance: Network request required');
- }
-
- console.groupEnd();
-
- // Ensure default theme is loaded from extension resources
- if (window.applyTheme && typeof window.applyTheme === 'function') {
- console.group('%cšØ Loading Default Theme from Extension', 'color: #8b5cf6; font-weight: bold;');
-
- // Determine the current theme file name
- let defaultTheme = 'classic'; // fallback
- if (CONFIG.currentTheme === 'Neon Retro') {
- defaultTheme = 'neon';
- } else if (CONFIG.currentTheme === 'Neon Retro Cyan') {
- defaultTheme = 'neon-cyan';
- } else if (CONFIG.currentTheme === 'Neon Retro Light') {
- defaultTheme = 'neon-light';
- } else if (CONFIG.currentTheme === 'Classic Light') {
- defaultTheme = 'classic-light';
- } else if (CONFIG.currentTheme === 'Acrylic') {
- defaultTheme = 'acrylic';
- }
-
- console.log(`%cšÆ Loading theme: ${defaultTheme} (${CONFIG.currentTheme})`, 'color: #8b5cf6;');
- const success = window.applyTheme(defaultTheme);
-
- if (success) {
- console.log(`%cā
Default theme loaded from extension local resources`, 'color: #10b981; font-weight: bold;');
- console.log(` š Source: Extension local file (themes/${defaultTheme}.css)`);
- console.log(` š Performance: Instant load (no CDN request)`);
- console.log(` šØ Theme: ${CONFIG.currentTheme}`);
- } else {
- console.warn(`%cā ļø Failed to load theme ${defaultTheme} from extension`, 'color: #f59e0b;');
- console.log(` š Note: Theme CSS classes will still work`);
- }
-
- console.groupEnd();
- } else {
- console.warn(`%cā ļø Extension applyTheme() function not available`, 'color: #f59e0b; font-weight: bold;');
- console.log(` š Themes will be limited to CSS classes only`);
- }
-
- const container = document.createElement('div');
- container.id = 'wplace-image-bot-container';
- container.innerHTML = `
-
-
-
-
-
- ${Utils.t('initMessage')}
-
-
-
-
-
-
-
š¼ļø Image Management
-
-
-
-
- ${Utils.t('uploadImage')}
-
-
-
-
-
-
- ${Utils.t('resizeImage')}
-
-
-
- Move Artwork
-
-
-
-
-
- ${Utils.t('selectPosition')}
-
-
-
-
-
-
-
-
š® Painting Control
-
-
-
-
- ${Utils.t('startPainting')}
-
-
-
- ${Utils.t('stopPainting')}
-
-
-
-
-
- ${Utils.t('toggleOverlay')}
-
-
-
-
-
-
-
-
ā±ļø ${Utils.t('cooldownSettings')}
-
-
${Utils.t('waitCharges')}:
-
-
-
-
-
-
-
š¾ Data Management
-
-
-
-
- ${Utils.t('saveData')}
-
-
-
- ${Utils.t('loadData')}
-
-
-
-
-
- ${Utils.t('saveToFile')}
-
-
-
- ${Utils.t('loadFromFile')}
-
-
-
-
-
- `;
-
- // Stats Window - Separate UI
- const statsContainer = document.createElement('div');
- statsContainer.id = 'wplace-stats-container';
- statsContainer.style.display = 'none';
- statsContainer.innerHTML = `
-
-
-
-
-
-
${Utils.t(
- 'initMessage'
- )}
-
-
-
-
-
-
-
-
- Account Swapper
-
-
-
-
-
-
-
-
-
-
-
- All Accounts
-
-
-
-
Click the icon to load accounts.
-
-
-
- `;
-
- // Modern Settings Container with Theme Support
- // 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.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)'
- };
- backdrop-filter: ${theme.backdropFilter || 'blur(10px)'};
- overflow: hidden;
- animation: settings-slide-in 0.4s ease-out;
- ${theme.animations?.glow
- ? `
- box-shadow: ${theme.boxShadow || '0 20px 40px rgba(0,0,0,0.3)'},
- 0 0 30px ${theme.highlight || theme.neon || '#00ffff'};
- `
- : ''
- }
- `;
-
- // noinspection CssInvalidFunction
- settingsContainer.innerHTML = `
-
-
-
-
-
-
-
-
- Token Source
-
-
-
- š¤ Automatic Token Generator (Recommended)
- š Generator + Auto Fallback
- šÆ Manual Pixel Placement
-
-
- Generator mode creates tokens automatically. Hybrid mode falls back to manual when generator fails. Manual mode only uses pixel placement.
-
-
-
-
-
-
-
-
- ${Utils.t('automation')}
-
-
-
-
-
-
-
-
- Overlay Settings
-
-
-
-
-
-
-
-
-
-
-
Blue Marble Effect
-
Renders a dithered "shredded" overlay.
-
-
-
-
-
-
-
-
-
-
- ${Utils.t('paintOptions')}
-
-
-
-
-
-
-
- ${Utils.t('paintWhitePixels')}
-
-
- ${Utils.t('paintWhitePixelsDescription')}
-
-
-
-
-
-
-
-
-
- ${Utils.t('paintTransparentPixels')}
-
-
- ${Utils.t('paintTransparentPixelsDescription')}
-
-
-
-
-
-
-
${Utils.t('paintUnavailablePixels')}
-
${Utils.t('paintUnavailablePixelsDescription')}
-
-
-
-
-
-
-
-
-
-
- ${Utils.t('paintingSpeed')}
-
-
-
-
-
-
- Batch Mode
-
-
- š¦ Normal (Fixed Size)
- š² Random (Range)
-
-
-
-
-
-
-
-
- ${CONFIG.PAINTING_SPEED.MIN}
- ${CONFIG.PAINTING_SPEED.MAX}
-
-
-
-
-
-
-
- š² Random batch size between min and max values
-
-
-
-
-
-
- ${Utils.t('enablePaintingSpeedLimit')}
-
-
-
-
-
-
-
- Coordinate Generation
-
-
-
-
-
-
- Generation Mode
-
-
- š Rows (Horizontal Lines)
- š Columns (Vertical Lines)
- ā Circle Out (Center ā Edges)
- ā Circle In (Edges ā Center)
- š« Blocks (Ordered)
- š² Shuffle Blocks (Random)
-
-
-
-
-
-
-
- Starting Direction
-
-
- āļø Top-Left
- āļø Top-Right
- āļø Bottom-Left
- āļø Bottom-Right
-
-
-
-
-
-
-
-
Snake Pattern
-
Alternate direction for each row/column (zigzag pattern)
-
-
-
-
-
-
-
-
-
- š§± Block dimensions for block-based generation modes
-
-
-
-
-
-
-
-
- Desktop Notifications
-
-
-
-
-
-
-
-
- ${Utils.t('themeSettings')}
-
-
-
- ${Object.keys(CONFIG.THEMES)
- .map(
- (themeName) =>
- `${themeName} `
- )
- .join('')}
-
-
-
-
-
-
-
-
- ${Utils.t('language')}
-
-
-
- š»š³ Tiįŗæng Viį»t
- š®š© Bahasa Indonesia
- š·šŗ Š ŃŃŃŠŗŠøŠ¹
- šŗš¦ Š£ŠŗŃŠ°ŃнŃŃŠŗŠ°
- šŗšø English
- š§š· PortuguĆŖs
- š«š· FranƧais
- š¹š· TürkƧe
- šØš³ ē®ä½äøę
- š¹š¼ ē¹é«äøę
- šÆšµ ę„ę¬čŖ
- š°š· ķźµģ“
-
-
-
-
-
-
-
-
- `;
-
- const resizeContainer = document.createElement('div');
- resizeContainer.className = 'resize-container';
- resizeContainer.innerHTML = `
- ${Utils.t('resizeImage')}
-
-
-
-
-
-
-
- Color Palette
-
-
-
-
-
- ${Utils.t('showAllColorsIncluding')}
-
-
-
- Select All
- Unselect All
- Unselect Paid
-
-
-
-
-
-
-
- Advanced Color Matching
-
-
-
- Algorithm
-
- Perceptual (Lab)
- Legacy (RGB)
- HSV (Hue-Saturation-Value)
- HSL (Hue-Saturation-Lightness)
- XYZ (CIE Color Space)
- LUV (CIE L*u*v*)
- YUV (Luma-Chroma)
- Oklab (Perceptual Uniform)
- LCH (Lightness-Chroma-Hue)
-
-
-
-
-
Chroma Penalty
-
Preserve vivid colors (Lab only)
-
-
-
-
-
-
-
-
-
-
Enable Dithering
-
FloydāSteinberg error diffusion in preview and applied output
-
-
-
-
-
- Transparency
-
-
-
- White Thresh
-
-
-
-
Reset Advanced
-
-
-
-
-
-
- ${Utils.t('downloadPreview')}
-
-
-
- ${Utils.t('apply')}
-
-
-
- ${Utils.t('cancel')}
-
-
- `;
-
- const resizeOverlay = document.createElement('div');
- resizeOverlay.className = 'resize-overlay';
-
- document.body.appendChild(container);
- document.body.appendChild(resizeOverlay);
- document.body.appendChild(resizeContainer);
- document.body.appendChild(statsContainer);
- document.body.appendChild(settingsContainer);
-
- // Show the main container after all elements are appended
- container.style.display = 'block';
-
- const uploadBtn = container.querySelector('#uploadBtn');
- const resizeBtn = container.querySelector('#resizeBtn');
- const selectPosBtn = container.querySelector('#selectPosBtn');
- const startBtn = container.querySelector('#startBtn');
- const stopBtn = container.querySelector('#stopBtn');
- const saveBtn = container.querySelector('#saveBtn');
- const loadBtn = container.querySelector('#loadBtn');
- const saveToFileBtn = container.querySelector('#saveToFileBtn');
- const loadFromFileBtn = container.querySelector('#loadFromFileBtn');
-
- container.querySelectorAll('.wplace-section-title').forEach((title) => {
- // Add a right-side arrow if it doesn't exist
- if (!title.querySelector('i.arrow')) {
- const arrow = document.createElement('i');
- arrow.className = 'fas fa-chevron-down arrow'; // FontAwesome down arrow
- title.appendChild(arrow);
- }
-
- // Click event to toggle collapse/expand of the section
- title.addEventListener('click', () => {
- const section = title.parentElement;
- section.classList.toggle('collapsed');
- });
- });
-
- // Disable load/upload buttons until initial setup is complete (startup only)
- if (loadBtn) {
- loadBtn.disabled = !state.initialSetupComplete;
- loadBtn.title = state.initialSetupComplete
- ? ''
- : 'š Waiting for initial setup to complete...';
- }
- if (loadFromFileBtn) {
- loadFromFileBtn.disabled = !state.initialSetupComplete;
- loadFromFileBtn.title = state.initialSetupComplete
- ? ''
- : 'š Waiting for initial setup to complete...';
- }
- if (uploadBtn) {
- 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');
- const toggleOverlayBtn = container.querySelector('#toggleOverlayBtn');
- const statusText = container.querySelector('#statusText');
- const progressBar = container.querySelector('#progressBar');
- const statsArea = statsContainer.querySelector('#statsArea');
- const content = container.querySelector('.wplace-content');
- const closeStatsBtn = statsContainer.querySelector('#closeStatsBtn');
- const refreshChargesBtn = statsContainer.querySelector('#refreshChargesBtn');
- const cooldownSlider = container.querySelector('#cooldownSlider');
- const cooldownInput = container.querySelector('#cooldownInput');
- const cooldownDecrease = container.querySelector('#cooldownDecrease');
- const cooldownIncrease = container.querySelector('#cooldownIncrease');
- const cooldownValue = container.querySelector('#cooldownValue');
-
- if (!uploadBtn || !selectPosBtn || !startBtn || !stopBtn) {
- console.error('Some UI elements not found:', {
- uploadBtn: !!uploadBtn,
- selectPosBtn: !!selectPosBtn,
- startBtn: !!startBtn,
- stopBtn: !!stopBtn,
- });
- }
-
- if (!statsContainer || !statsArea || !closeStatsBtn) {
- // Note: base CSS now aligns with this layout: main panel at left:20px (width 280), stats at left:330px.
- }
-
- const header = container.querySelector('.wplace-header');
-
- makeDraggable(container);
-
- function makeDraggable(element) {
- let pos1 = 0,
- pos2 = 0,
- pos3 = 0,
- pos4 = 0;
- let isDragging = false;
- const header =
- element.querySelector('.wplace-header') || element.querySelector('.wplace-settings-header');
-
- if (!header) {
- console.warn('No draggable header found for element:', element);
- return;
- }
-
- header.onmousedown = dragMouseDown;
-
- function dragMouseDown(e) {
- if (e.target.closest('.wplace-header-btn') || e.target.closest('button')) return;
-
- e.preventDefault();
- isDragging = true;
-
- const rect = element.getBoundingClientRect();
-
- element.style.transform = 'none';
- element.style.top = rect.top + 'px';
- element.style.left = rect.left + 'px';
-
- pos3 = e.clientX;
- pos4 = e.clientY;
- element.classList.add('wplace-dragging');
- document.onmouseup = closeDragElement;
- document.onmousemove = elementDrag;
-
- document.body.style.userSelect = 'none';
- }
-
- function elementDrag(e) {
- if (!isDragging) return;
-
- e.preventDefault();
- pos1 = pos3 - e.clientX;
- pos2 = pos4 - e.clientY;
- pos3 = e.clientX;
- pos4 = e.clientY;
-
- let newTop = element.offsetTop - pos2;
- let newLeft = element.offsetLeft - pos1;
-
- const rect = element.getBoundingClientRect();
- const maxTop = window.innerHeight - rect.height;
- const maxLeft = window.innerWidth - rect.width;
-
- newTop = Math.max(0, Math.min(newTop, maxTop));
- newLeft = Math.max(0, Math.min(newLeft, maxLeft));
-
- element.style.top = newTop + 'px';
- element.style.left = newLeft + 'px';
- }
-
- function closeDragElement() {
- isDragging = false;
- element.classList.remove('wplace-dragging');
- document.onmouseup = null;
- document.onmousemove = null;
- document.body.style.userSelect = '';
- }
- }
-
- makeDraggable(statsContainer);
- makeDraggable(container);
-
- if (statsBtn && closeStatsBtn) {
- statsBtn.addEventListener('click', () => {
- const isVisible = statsContainer.style.display !== 'none';
- if (isVisible) {
- statsContainer.style.display = 'none';
- statsBtn.innerHTML = ' ';
- statsBtn.title = Utils.t('showStats');
- } else {
- statsContainer.style.display = 'block';
- statsBtn.innerHTML = ' ';
- statsBtn.title = Utils.t('hideStats');
- }
- });
-
- closeStatsBtn.addEventListener('click', () => {
- statsContainer.style.display = 'none';
- statsBtn.innerHTML = ' ';
- statsBtn.title = Utils.t('showStats');
- });
-
- if (refreshChargesBtn) {
- refreshChargesBtn.addEventListener('click', async () => {
- refreshChargesBtn.innerHTML = ' ';
- refreshChargesBtn.disabled = true;
-
- try {
- await updateStats();
- await updateCurrentAccountInList();
- } catch (error) {
- console.error('Error refreshing charges:', error);
- } finally {
- refreshChargesBtn.innerHTML = ' ';
- refreshChargesBtn.disabled = false;
- }
- });
- }
-
- // Account-related event handlers
- const autoSwapToggle = statsContainer.querySelector('#autoSwapToggle');
- const refreshAllAccountsBtn = statsContainer.querySelector('#refreshAllAccountsBtn');
-
- if (autoSwapToggle) {
- autoSwapToggle.checked = CONFIG.autoSwap;
- autoSwapToggle.addEventListener('change', (e) => {
- CONFIG.autoSwap = e.target.checked;
- console.log(`š Auto-swap ${CONFIG.autoSwap ? 'enabled' : 'disabled'}`);
- });
- }
-
- if (refreshAllAccountsBtn) {
- refreshAllAccountsBtn.addEventListener('click', fetchAllAccountDetails);
- }
- }
- if (statsContainer && statsBtn) {
- // Stats container starts visible by default - user clicks button to hide
- statsContainer.style.display = 'block';
- statsBtn.innerHTML = ' ';
- statsBtn.title = Utils.t('hideStats');
- }
-
- const settingsBtn = container.querySelector('#settingsBtn');
- const closeSettingsBtn = settingsContainer.querySelector('#closeSettingsBtn');
- const applySettingsBtn = settingsContainer.querySelector('#applySettingsBtn');
-
- if (settingsBtn && closeSettingsBtn && applySettingsBtn) {
- settingsBtn.addEventListener('click', () => {
- const isVisible = settingsContainer.classList.contains('show');
- if (isVisible) {
- settingsContainer.style.animation = 'settings-fade-out 0.3s ease-out forwards';
- settingsContainer.classList.remove('show');
- setTimeout(() => {
- settingsContainer.style.animation = '';
- }, 300);
- } else {
- settingsContainer.style.top = '50%';
- settingsContainer.style.left = '50%';
- settingsContainer.style.transform = 'translate(-50%, -50%)';
- settingsContainer.classList.add('show');
- settingsContainer.style.animation = 'settings-slide-in 0.4s ease-out';
- }
- });
-
- closeSettingsBtn.addEventListener('click', () => {
- settingsContainer.style.animation = 'settings-fade-out 0.3s ease-out forwards';
- settingsContainer.classList.remove('show');
- setTimeout(() => {
- settingsContainer.style.animation = '';
- settingsContainer.style.top = '50%';
- settingsContainer.style.left = '50%';
- settingsContainer.style.transform = 'translate(-50%, -50%)';
- }, 300);
- });
-
- applySettingsBtn.addEventListener('click', () => {
- // Sync advanced settings before save
- const colorAlgorithmSelect = document.getElementById('colorAlgorithmSelect');
- if (colorAlgorithmSelect) state.colorMatchingAlgorithm = colorAlgorithmSelect.value;
- const enableChromaPenaltyToggle = document.getElementById('enableChromaPenaltyToggle');
- if (enableChromaPenaltyToggle)
- state.enableChromaPenalty = enableChromaPenaltyToggle.checked;
- const chromaPenaltyWeightSlider = document.getElementById('chromaPenaltyWeightSlider');
- if (chromaPenaltyWeightSlider)
- state.chromaPenaltyWeight = parseFloat(chromaPenaltyWeightSlider.value) || 0.15;
- const transparencyThresholdInput = document.getElementById('transparencyThresholdInput');
- if (transparencyThresholdInput) {
- const v = parseInt(transparencyThresholdInput.value, 10);
- if (!isNaN(v) && v >= 0 && v <= 255) state.customTransparencyThreshold = v;
- }
- const whiteThresholdInput = document.getElementById('whiteThresholdInput');
- if (whiteThresholdInput) {
- const v = parseInt(whiteThresholdInput.value, 10);
- if (!isNaN(v) && v >= 200 && v <= 255) state.customWhiteThreshold = v;
- }
- // Update functional thresholds
- CONFIG.TRANSPARENCY_THRESHOLD = state.customTransparencyThreshold;
- CONFIG.WHITE_THRESHOLD = state.customWhiteThreshold;
- // Notifications
- const notifEnabledToggle = document.getElementById('notifEnabledToggle');
- const notifOnChargesToggle = document.getElementById('notifOnChargesToggle');
- const notifOnlyUnfocusedToggle = document.getElementById('notifOnlyUnfocusedToggle');
- const notifIntervalInput = document.getElementById('notifIntervalInput');
- if (notifEnabledToggle) state.notificationsEnabled = !!notifEnabledToggle.checked;
- 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;
- }
- saveBotSettings();
- Utils.showAlert(Utils.t('settingsSaved'), 'success');
- closeSettingsBtn.click();
- NotificationManager.syncFromState();
- });
-
- makeDraggable(settingsContainer);
-
- const tokenSourceSelect = settingsContainer.querySelector('#tokenSourceSelect');
- if (tokenSourceSelect) {
- tokenSourceSelect.addEventListener('change', (e) => {
- state.tokenSource = e.target.value;
- saveBotSettings();
- console.log(`š Token source changed to: ${state.tokenSource}`);
- const sourceNames = {
- generator: 'Automatic Generator',
- hybrid: 'Generator + Auto Fallback',
- manual: 'Manual Pixel Placement',
- };
- Utils.showAlert(
- Utils.t('tokenSourceSet', { source: sourceNames[state.tokenSource] }),
- 'success'
- );
- });
- }
-
- // Batch mode controls
- const batchModeSelect = settingsContainer.querySelector('#batchModeSelect');
- const normalBatchControls = settingsContainer.querySelector('#normalBatchControls');
- 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') {
- normalBatchControls.style.display = 'none';
- randomBatchControls.style.display = 'block';
- } else {
- normalBatchControls.style.display = 'block';
- randomBatchControls.style.display = 'none';
- }
- }
-
- saveBotSettings();
- console.log(`š¦ Batch mode changed to: ${state.batchMode}`);
- Utils.showAlert(
- Utils.t('batchModeSet', {
- mode:
- state.batchMode === 'random' ? Utils.t('randomRange') : Utils.t('normalFixedSize'),
- }),
- 'success'
- );
- });
- }
-
- if (randomBatchMin) {
- randomBatchMin.addEventListener('input', (e) => {
- const min = parseInt(e.target.value);
- if (min >= 1 && min <= 1000) {
- state.randomBatchMin = min;
- // Ensure min doesn't exceed max
- if (randomBatchMax && min > state.randomBatchMax) {
- state.randomBatchMax = min;
- randomBatchMax.value = min;
- }
- saveBotSettings();
- }
- });
- }
-
- if (randomBatchMax) {
- randomBatchMax.addEventListener('input', (e) => {
- const max = parseInt(e.target.value);
- if (max >= 1 && max <= 1000) {
- state.randomBatchMax = max;
- // Ensure max doesn't go below min
- if (randomBatchMin && max < state.randomBatchMin) {
- state.randomBatchMin = max;
- randomBatchMin.value = max;
- }
- saveBotSettings();
- }
- });
- }
-
- const languageSelect = settingsContainer.querySelector('#languageSelect');
- if (languageSelect) {
- languageSelect.addEventListener('change', async (e) => {
- const newLanguage = e.target.value;
- state.language = newLanguage;
- localStorage.setItem('wplace_language', newLanguage);
-
- // Load the new language translations
- await loadTranslations(newLanguage);
-
- setTimeout(() => {
- settingsContainer.style.display = 'none';
- createUI();
- }, 100);
- });
- }
-
- const themeSelect = settingsContainer.querySelector('#themeSelect');
- if (themeSelect) {
- themeSelect.addEventListener('change', (e) => {
- const newTheme = e.target.value;
- switchTheme(newTheme);
- });
- }
-
- const overlayOpacitySlider = settingsContainer.querySelector('#overlayOpacitySlider');
- const overlayOpacityValue = settingsContainer.querySelector('#overlayOpacityValue');
- const enableBlueMarbleToggle = settingsContainer.querySelector('#enableBlueMarbleToggle');
- const settingsPaintWhiteToggle = settingsContainer.querySelector('#settingsPaintWhiteToggle');
- const settingsPaintTransparentToggle = settingsContainer.querySelector(
- '#settingsPaintTransparentToggle'
- );
-
- if (overlayOpacitySlider && overlayOpacityValue) {
- const updateOpacity = (newValue) => {
- const opacity = parseFloat(newValue);
- state.overlayOpacity = opacity;
- overlayOpacitySlider.value = opacity;
- overlayOpacityValue.textContent = `${Math.round(opacity * 100)}%`;
- };
-
- overlayOpacitySlider.addEventListener('input', (e) => {
- updateOpacity(e.target.value);
- });
-
- // Add scroll-to-adjust for overlay opacity slider
- Utils.createScrollToAdjust(overlayOpacitySlider, updateOpacity, 0, 1, 0.05);
- }
-
- if (settingsPaintWhiteToggle) {
- settingsPaintWhiteToggle.checked = state.paintWhitePixels;
- settingsPaintWhiteToggle.addEventListener('change', (e) => {
- state.paintWhitePixels = e.target.checked;
- saveBotSettings();
- console.log(`šØ Paint white pixels: ${state.paintWhitePixels ? 'ON' : 'OFF'}`);
- const statusText = state.paintWhitePixels
- ? 'White pixels in the template will be painted'
- : 'White pixels will be skipped';
- Utils.showAlert(statusText, 'success');
- });
- }
-
- if (settingsPaintTransparentToggle) {
- settingsPaintTransparentToggle.checked = state.paintTransparentPixels;
- settingsPaintTransparentToggle.addEventListener('change', (e) => {
- state.paintTransparentPixels = e.target.checked;
- saveBotSettings();
- console.log(
- `šØ Paint transparent pixels: ${state.paintTransparentPixels ? 'ON' : 'OFF'}`
- );
- const statusText = state.paintTransparentPixels
- ? 'Transparent pixels in the template will be painted with the closest available color'
- : 'Transparent pixels will be skipped';
- Utils.showAlert(statusText, 'success');
- });
- }
-
- // Speed controls - both slider and input
- const speedSlider = settingsContainer.querySelector('#speedSlider');
- const speedInput = settingsContainer.querySelector('#speedInput');
- const speedDecrease = settingsContainer.querySelector('#speedDecrease');
- const speedIncrease = settingsContainer.querySelector('#speedIncrease');
- const speedValue = settingsContainer.querySelector('#speedValue');
-
- if (speedSlider && speedInput && speedValue && speedDecrease && speedIncrease) {
- const updateSpeed = (newValue) => {
- const speed = Math.max(CONFIG.PAINTING_SPEED.MIN, Math.min(CONFIG.PAINTING_SPEED.MAX, parseInt(newValue)));
- state.paintingSpeed = speed;
-
- // Update both controls (value shows in input, label shows unit only)
- speedSlider.value = speed;
- speedInput.value = speed;
- speedValue.textContent = `pixels`;
-
- saveBotSettings();
- };
-
- // Slider event listener
- speedSlider.addEventListener('input', (e) => {
- updateSpeed(e.target.value);
- });
-
- // Number input event listener
- speedInput.addEventListener('input', (e) => {
- updateSpeed(e.target.value);
- });
-
- // Decrease button
- speedDecrease.addEventListener('click', () => {
- updateSpeed(parseInt(speedInput.value) - 1);
- });
-
- // Increase button
- speedIncrease.addEventListener('click', () => {
- updateSpeed(parseInt(speedInput.value) + 1);
- });
-
- // Add scroll-to-adjust for speed slider
- Utils.createScrollToAdjust(speedSlider, updateSpeed, CONFIG.PAINTING_SPEED.MIN, CONFIG.PAINTING_SPEED.MAX, 1);
- }
-
- if (enableBlueMarbleToggle) {
- enableBlueMarbleToggle.addEventListener('click', async () => {
- state.blueMarbleEnabled = enableBlueMarbleToggle.checked;
- if (state.imageLoaded && overlayManager.imageBitmap) {
- Utils.showAlert(Utils.t('reprocessingOverlay'), 'info');
- await overlayManager.processImageIntoChunks();
- Utils.showAlert(Utils.t('overlayUpdated'), 'success');
- }
- });
- }
-
- // (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');
- const notifTestBtn = settingsContainer.querySelector('#notifTestBtn');
- if (notifPermBtn) {
- notifPermBtn.addEventListener('click', async () => {
- const perm = await NotificationManager.requestPermission();
- if (perm === 'granted') Utils.showAlert(Utils.t('notificationsEnabled'), 'success');
- else Utils.showAlert(Utils.t('notificationsPermissionDenied'), 'warning');
- });
- }
- if (notifTestBtn) {
- notifTestBtn.addEventListener('click', () => {
- NotificationManager.notify(
- Utils.t('testNotificationTitle'),
- Utils.t('testNotificationMessage'),
- 'wplace-notify-test',
- true
- );
- });
- }
- }
-
- const widthSlider = resizeContainer.querySelector('#widthSlider');
- const heightSlider = resizeContainer.querySelector('#heightSlider');
- const widthValue = resizeContainer.querySelector('#widthValue');
- const heightValue = resizeContainer.querySelector('#heightValue');
- const keepAspect = resizeContainer.querySelector('#keepAspect');
- const paintWhiteToggle = resizeContainer.querySelector('#paintWhiteToggle');
- const paintTransparentToggle = resizeContainer.querySelector('#paintTransparentToggle');
- 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');
-
- // Coordinate generation controls with smart visibility
- const coordinateModeSelect = settingsContainer.querySelector('#coordinateModeSelect');
- const coordinateDirectionSelect = settingsContainer.querySelector('#coordinateDirectionSelect');
- const coordinateSnakeToggle = settingsContainer.querySelector('#coordinateSnakeToggle');
- const directionControls = settingsContainer.querySelector('#directionControls');
- const snakeControls = settingsContainer.querySelector('#snakeControls');
- const blockControls = settingsContainer.querySelector('#blockControls');
- const blockWidthInput = settingsContainer.querySelector('#blockWidthInput');
- const blockHeightInput = settingsContainer.querySelector('#blockHeightInput');
- const paintUnavailablePixelsToggle = settingsContainer.querySelector(
- '#paintUnavailablePixelsToggle'
- );
-
- if (paintUnavailablePixelsToggle) {
- paintUnavailablePixelsToggle.checked = state.paintUnavailablePixels;
- paintUnavailablePixelsToggle.addEventListener('change', (e) => {
- state.paintUnavailablePixels = e.target.checked;
- saveBotSettings();
- console.log(`šØ Paint unavailable colors: ${state.paintUnavailablePixels ? 'ON' : 'OFF'}`);
- const statusText = state.paintUnavailablePixels
- ? 'Unavailable template colors will be painted with the closest available color'
- : 'Unavailable template colors will be skipped';
- Utils.showAlert(statusText, 'success');
- });
- }
- if (coordinateModeSelect) {
- coordinateModeSelect.value = state.coordinateMode;
- coordinateModeSelect.addEventListener('change', (e) => {
- state.coordinateMode = e.target.value;
- Utils.updateCoordinateUI({
- mode: state.coordinateMode,
- directionControls,
- snakeControls,
- blockControls,
- });
- saveBotSettings();
- console.log(`š Coordinate mode changed to: ${state.coordinateMode}`);
- Utils.showAlert(`Coordinate mode set to: ${state.coordinateMode}`, 'success');
- });
- }
-
- if (coordinateDirectionSelect) {
- coordinateDirectionSelect.value = state.coordinateDirection;
- coordinateDirectionSelect.addEventListener('change', (e) => {
- state.coordinateDirection = e.target.value;
- saveBotSettings();
- console.log(`š§ Coordinate direction changed to: ${state.coordinateDirection}`);
- Utils.showAlert(`Coordinate direction set to: ${state.coordinateDirection}`, 'success');
- });
- }
-
- if (coordinateSnakeToggle) {
- coordinateSnakeToggle.checked = state.coordinateSnake;
- coordinateSnakeToggle.addEventListener('change', (e) => {
- state.coordinateSnake = e.target.checked;
- saveBotSettings();
- console.log(`š Snake pattern ${state.coordinateSnake ? 'enabled' : 'disabled'}`);
- Utils.showAlert(
- `Snake pattern ${state.coordinateSnake ? 'enabled' : 'disabled'}`,
- 'success'
- );
- });
- }
-
- if (blockWidthInput) {
- blockWidthInput.value = state.blockWidth;
- blockWidthInput.addEventListener('input', (e) => {
- const width = parseInt(e.target.value);
- if (width >= 1 && width <= 50) {
- state.blockWidth = width;
- saveBotSettings();
- }
- });
- }
-
- if (blockHeightInput) {
- blockHeightInput.value = state.blockHeight;
- blockHeightInput.addEventListener('change', (e) => {
- const height = parseInt(e.target.value);
- if (height >= 1 && height <= 50) {
- state.blockHeight = height;
- saveBotSettings();
- }
- });
- }
-
- if (compactBtn) {
- compactBtn.addEventListener('click', () => {
- container.classList.toggle('wplace-compact');
- const isCompact = container.classList.contains('wplace-compact');
-
- if (isCompact) {
- compactBtn.innerHTML = ' ';
- compactBtn.title = Utils.t('expandMode');
- } else {
- compactBtn.innerHTML = ' ';
- compactBtn.title = Utils.t('compactMode');
- }
- });
- }
-
- if (minimizeBtn) {
- minimizeBtn.addEventListener('click', () => {
- state.minimized = !state.minimized;
- if (state.minimized) {
- container.classList.add('wplace-minimized');
- content.classList.add('wplace-hidden');
- minimizeBtn.innerHTML = ' ';
- minimizeBtn.title = Utils.t('restore');
- } else {
- container.classList.remove('wplace-minimized');
- content.classList.remove('wplace-hidden');
- minimizeBtn.innerHTML = ' ';
- minimizeBtn.title = Utils.t('minimize');
- }
- saveBotSettings();
- });
- }
-
- if (toggleOverlayBtn) {
- toggleOverlayBtn.addEventListener('click', () => {
- const isEnabled = overlayManager.toggle();
- toggleOverlayBtn.classList.toggle('active', isEnabled);
- toggleOverlayBtn.setAttribute('aria-pressed', isEnabled ? 'true' : 'false');
- Utils.showAlert(isEnabled ? Utils.t('overlayEnabled') : Utils.t('overlayDisabled'), 'info');
- });
- }
-
- if (state.minimized) {
- container.classList.add('wplace-minimized');
- content.classList.add('wplace-hidden');
- if (minimizeBtn) {
- minimizeBtn.innerHTML = ' ';
- minimizeBtn.title = Utils.t('restore');
- }
- } else {
- container.classList.remove('wplace-minimized');
- content.classList.remove('wplace-hidden');
- if (minimizeBtn) {
- minimizeBtn.innerHTML = ' ';
- minimizeBtn.title = Utils.t('minimize');
- }
- }
-
- if (saveBtn) {
- saveBtn.addEventListener('click', () => {
- if (!state.imageLoaded) {
- Utils.showAlert(Utils.t('missingRequirements'), 'error');
- return;
- }
-
- const success = Utils.saveProgress();
- if (success) {
- updateUI('autoSaved', 'success');
- Utils.showAlert(Utils.t('autoSaved'), 'success');
- } else {
- Utils.showAlert(Utils.t('errorSavingProgress'), 'error');
- }
- });
- }
-
- if (loadBtn) {
- loadBtn.addEventListener('click', async () => {
- const savedData = Utils.loadProgress();
- if (!savedData) {
- updateUI('noSavedData', 'warning');
- Utils.showAlert(Utils.t('noSavedData'), 'warning');
- return;
- }
-
- // CRITICAL FIX: If save file contains complete data, bypass initial setup check
- // This matches old version behavior where save files could be loaded immediately
- const hasCompleteData = savedData.state && savedData.imageData &&
- savedData.state.availableColors &&
- savedData.state.availableColors.length > 0;
-
- if (!state.initialSetupComplete && !hasCompleteData) {
- Utils.showAlert(Utils.t('pleaseWaitInitialSetup'), 'warning');
- return;
- }
-
- const confirmLoad = confirm(
- `${Utils.t('savedDataFound')}\n\n` +
- `Saved: ${new Date(savedData.timestamp).toLocaleString()}\n` +
- `Progress: ${savedData.state.paintedPixels}/${savedData.state.totalPixels} pixels`
- );
-
- if (confirmLoad) {
- const success = Utils.restoreProgress(savedData);
- if (success) {
- // CRITICAL FIX: Set initial setup complete if save had complete data
- if (hasCompleteData) {
- state.initialSetupComplete = true;
- }
-
- updateUI('dataLoaded', 'success');
- Utils.showAlert(Utils.t('dataLoaded'), 'success');
- updateDataButtons();
-
- // CRITICAL FIX: Force immediate stats update after save loading
- await updateStats();
-
- // Force stats display refresh by updating the stats container
- const statsContainer = document.getElementById('wplace-stats-container');
- if (statsContainer && typeof updateStats === 'function') {
- // Trigger a forced visual refresh of the stats
- setTimeout(async () => {
- await updateStats();
- }, 100);
- }
-
- // Restore overlay if image data was loaded from localStorage
- Utils.restoreOverlayFromData().catch((error) => {
- console.error('Failed to restore overlay from localStorage:', error);
- });
-
- if (!state.colorsChecked) {
- uploadBtn.disabled = false;
- const loadExtractedBtn = document.getElementById('loadExtractedBtn');
- if (loadExtractedBtn) loadExtractedBtn.disabled = false;
- } else {
- uploadBtn.disabled = false;
- const loadExtractedBtn = document.getElementById('loadExtractedBtn');
- if (loadExtractedBtn) loadExtractedBtn.disabled = false;
- selectPosBtn.disabled = false;
- }
-
- if (state.imageLoaded && state.startPosition && state.region && state.colorsChecked) {
- startBtn.disabled = false;
- }
- } else {
- Utils.showAlert(Utils.t('errorLoadingProgress'), 'error');
- }
- }
- });
- }
-
- if (saveToFileBtn) {
- saveToFileBtn.addEventListener('click', () => {
- const success = Utils.saveProgressToFile();
- if (success) {
- updateUI('fileSaved', 'success');
- Utils.showAlert(Utils.t('fileSaved'), 'success');
- } else {
- Utils.showAlert(Utils.t('fileError'), 'error');
- }
- });
- }
-
- if (loadFromFileBtn) {
- loadFromFileBtn.addEventListener('click', async () => {
- console.log('š [DEBUG] Load from file button clicked');
- console.log('š [DEBUG] Initial setup complete:', state.initialSetupComplete);
-
- try {
- // First check if we can load the file - this will tell us if it has complete data
- console.log('š [DEBUG] Starting file upload...');
- const fileData = await Utils.createFileUploader();
-
- console.log('š [DEBUG] File data received:', !!fileData);
- console.log('š [DEBUG] File data keys:', fileData ? Object.keys(fileData) : 'N/A');
-
- if (!fileData || !fileData.state) {
- console.error('ā [DEBUG] Invalid file data or missing state');
- console.log('š [DEBUG] FileData:', fileData);
- Utils.showAlert(Utils.t('invalidFileFormat'), 'error');
- return;
- }
-
- // CRITICAL FIX: If file contains complete data, bypass initial setup check
- const hasCompleteData = fileData.state && fileData.imageData &&
- fileData.state.availableColors &&
- fileData.state.availableColors.length > 0;
-
- console.log('š [DEBUG] Has complete data check:');
- console.log(' - fileData.state:', !!fileData.state);
- console.log(' - fileData.imageData:', !!fileData.imageData);
- console.log(' - fileData.state.availableColors:', !!fileData.state.availableColors);
- console.log(' - availableColors length:', fileData.state.availableColors?.length);
- console.log(' - hasCompleteData result:', hasCompleteData);
-
- if (!state.initialSetupComplete && !hasCompleteData) {
- console.log('ā ļø [DEBUG] Setup not complete and no complete data, showing warning');
- Utils.showAlert(Utils.t('pleaseWaitFileSetup'), 'warning');
- return;
- }
-
- console.log('š [DEBUG] Calling restoreProgress...');
- const success = Utils.restoreProgress(fileData);
- console.log('š [DEBUG] RestoreProgress result:', success);
-
- if (success) {
- console.log('ā
[DEBUG] File loaded successfully');
-
- // CRITICAL FIX: Set initial setup complete if file had complete data
- if (hasCompleteData) {
- console.log('š [DEBUG] Setting initialSetupComplete to true due to complete data');
- state.initialSetupComplete = true;
- }
-
- updateUI('fileLoaded', 'success');
- Utils.showAlert(Utils.t('fileLoaded'), 'success');
- updateDataButtons();
-
- console.log('š [DEBUG] Updating stats...');
- await updateStats();
-
- // Restore overlay if image data was loaded from file
- console.log('š [DEBUG] Attempting to restore overlay...');
- await Utils.restoreOverlayFromData().catch((error) => {
- console.error('ā [DEBUG] Failed to restore overlay from file:', error);
- });
-
- console.log('š [DEBUG] Updating button states after load...');
- console.log(' - state.colorsChecked:', state.colorsChecked);
- console.log(' - state.imageLoaded:', state.imageLoaded);
- console.log(' - state.startPosition:', !!state.startPosition);
- console.log(' - state.region:', !!state.region);
-
- if (state.colorsChecked) {
- console.log('š [DEBUG] Enabling buttons due to colorsChecked');
- uploadBtn.disabled = false;
- selectPosBtn.disabled = false;
- resizeBtn.disabled = false;
- } else {
- console.log('š [DEBUG] Only enabling upload button');
- uploadBtn.disabled = false;
- }
-
- if (state.imageLoaded && state.startPosition && state.region && state.colorsChecked) {
- console.log('š [DEBUG] All conditions met, enabling start button');
- startBtn.disabled = false;
- } else {
- console.log('ā ļø [DEBUG] Start button requirements not met');
- }
- } else {
- console.error('ā [DEBUG] RestoreProgress failed');
- Utils.showAlert('File loading failed - check console for details', 'error');
- }
- } catch (error) {
- console.error('ā [DEBUG] Error in file load process:', error);
- console.error('ā [DEBUG] Error stack:', error.stack);
-
- if (error.message === 'Invalid JSON file') {
- console.error('ā [DEBUG] Invalid JSON file detected');
- Utils.showAlert(Utils.t('invalidFileFormat'), 'error');
- } else {
- console.error('ā [DEBUG] General file error:', error.message);
- Utils.showAlert(`File error: ${error.message}`, 'error');
- }
- }
- });
- }
-
- updateUI = (messageKey, type = 'default', params = {}, silent = false) => {
- const message = Utils.t(messageKey, params);
- statusText.textContent = message;
- statusText.className = `wplace-status status-${type}`;
-
- if (!silent) {
- // Trigger animation only when silent = false
- statusText.style.animation = 'none';
- void statusText.offsetWidth; // trick to restart the animation
- statusText.style.animation = 'slide-in 0.3s ease-out';
- }
- };
-
- function updateChargeStatsDisplay(intervalMs) {
- const currentChargesEl = document.getElementById('wplace-stat-charges-value');
- const fullChargeEl = document.getElementById('wplace-stat-fullcharge-value');
- if (!fullChargeEl && !currentChargesEl) return;
- if (!state.fullChargeData) {
- fullChargeEl.textContent = '--:--:--';
- return;
- }
-
- const { current, max, cooldownMs, startTime, spentSinceShot } = state.fullChargeData;
- const elapsed = Date.now() - startTime;
-
- // total charges including elapsed time and spent during painting since snapshot
- const chargesGained = elapsed / cooldownMs;
- const rawCharges = current + chargesGained - spentSinceShot;
- const cappedCharges = Math.min(rawCharges, max);
-
- // rounding with 0.95 threshold
- let displayCharges;
- const fraction = cappedCharges - Math.floor(cappedCharges);
- if (fraction >= 0.95) {
- displayCharges = Math.ceil(cappedCharges);
- } else {
- displayCharges = Math.floor(cappedCharges);
- }
-
- state.displayCharges = Math.max(0, displayCharges);
- state.preciseCurrentCharges = cappedCharges;
-
- const remainingMs = getMsToTargetCharges(cappedCharges, max, state.cooldown, intervalMs);
- const timeText = Utils.msToTimeText(remainingMs);
-
- if (currentChargesEl) {
- currentChargesEl.innerHTML = `${state.displayCharges} / ${state.maxCharges}`;
- }
-
- if (
- state.displayCharges < state.cooldownChargeThreshold &&
- !state.stopFlag &&
- state.running
- ) {
- updateChargesThresholdUI(intervalMs);
- }
-
- if (fullChargeEl) {
- if (state.displayCharges >= max) {
- fullChargeEl.innerHTML = `FULL `;
- } else {
- fullChargeEl.innerHTML = `
- ${timeText}
- `;
- }
- }
- }
-
- updateStats = async (isManualRefresh = false) => {
- const isFirstCheck = !state.fullChargeData?.startTime;
-
- const minUpdateInterval = 60_000;
- const maxUpdateInterval = 90_000;
- const randomUpdateThreshold =
- minUpdateInterval + Math.random() * (maxUpdateInterval - minUpdateInterval);
- const timeSinceLastUpdate = Date.now() - (state.fullChargeData?.startTime || 0);
- const isTimeToUpdate = timeSinceLastUpdate >= randomUpdateThreshold;
-
- const shouldCallApi = isFirstCheck || isTimeToUpdate;
-
- const { charges, max, cooldown } = await WPlaceService.getCharges();
- state.displayCharges = Math.floor(charges);
- state.preciseCurrentCharges = charges;
- state.cooldown = cooldown;
- state.maxCharges = Math.floor(max) > 1 ? Math.floor(max) : state.maxCharges;
-
- state.fullChargeData = {
- current: charges,
- max: max,
- cooldownMs: cooldown,
- startTime: Date.now(),
- spentSinceShot: 0,
- };
- // Evaluate notifications every time we refresh server-side charges
- NotificationManager.maybeNotifyChargesReached();
-
- if (state.fullChargeInterval) {
- clearInterval(state.fullChargeInterval);
- state.fullChargeInterval = null;
- }
- const intervalMs = 1000;
- state.fullChargeInterval = setInterval(
- () => updateChargeStatsDisplay(intervalMs),
- intervalMs
- );
-
- if (cooldownSlider && cooldownSlider.max !== state.maxCharges) {
- cooldownSlider.max = state.maxCharges;
- }
- if (cooldownInput && cooldownInput.max !== state.maxCharges) {
- cooldownInput.max = state.maxCharges;
- }
-
- // // Update current account charges in the account list
- // await updateCurrentAccountInList();
-
- let imageStatsHTML = '';
- if (state.imageLoaded) {
- const progress =
- state.totalPixels > 0 ? Math.round((state.paintedPixels / state.totalPixels) * 100) : 0;
- const remainingPixels = state.totalPixels - state.paintedPixels;
-
- // Updated estimation calculation for new cooldown logic
- // Now we wait for cooldownChargeThreshold before processing pixels
-
- // Calculate batch size (with fallback if function not yet defined)
- let batchSize;
- try {
- batchSize = typeof calculateBatchSize === 'function' ? calculateBatchSize() : state.paintingSpeed || 5;
- } catch (e) {
- batchSize = state.paintingSpeed || 5;
- }
-
- // Ensure batchSize is valid and not zero
- batchSize = Math.max(1, Math.min(batchSize, remainingPixels));
- const batchesNeeded = Math.ceil(remainingPixels / batchSize);
-
- // Calculate time accounting for cooldown threshold behavior with safety checks
- let estimatedMs = 0;
- if (remainingPixels > 0 && Number.isFinite(state.cooldown) && state.cooldown > 0) {
- // Current charges available check
- const currentCharges = Math.max(0, state.displayCharges);
- const thresholdCharges = Math.max(1, state.cooldownChargeThreshold || 1);
-
- if (currentCharges < thresholdCharges) {
- // Need to wait for charges to reach threshold first
- const chargesNeeded = thresholdCharges - currentCharges;
- estimatedMs += chargesNeeded * state.cooldown;
- }
-
- // Calculate painting time more accurately
- // Each batch uses thresholdCharges, so calculate how many cycles we need
- const pixelsPerCycle = Math.min(batchSize, thresholdCharges);
- const cyclesNeeded = Math.ceil(remainingPixels / pixelsPerCycle);
-
- // Time between cycles (waiting for charges to regenerate)
- if (cyclesNeeded > 1) {
- const regenerationTime = (cyclesNeeded - 1) * thresholdCharges * state.cooldown;
- estimatedMs += regenerationTime;
- }
-
- // Add painting speed delay if enabled
- if (CONFIG.PAINTING_SPEED_ENABLED && state.paintingSpeed > 0) {
- const paintingDelay = remainingPixels * (1000 / state.paintingSpeed);
- estimatedMs += paintingDelay;
- }
- }
-
- // Safety check to prevent infinity or invalid values
- if (!Number.isFinite(estimatedMs) || estimatedMs < 0) {
- estimatedMs = 0;
- }
-
- state.estimatedTime = estimatedMs;
- progressBar.style.width = `${progress}%`;
-
- imageStatsHTML = `
-
-
${Utils.t('progress')}
-
${progress}%
-
-
-
${Utils.t(
- 'pixels'
- )}
-
${state.paintedPixels}/${state.totalPixels}
-
- `;
- }
-
- let colorSwatchesHTML = '';
- state.availableColors = state.availableColors.filter(
- (c) => c.name !== 'Unknown CoIor NaN' && c.id !== null
- );
-
- const availableColors = Utils.extractAvailableColors();
- const newCount = Array.isArray(availableColors) ? availableColors.length : 0;
-
- if (newCount === 0 && isManualRefresh) {
- Utils.showAlert(Utils.t('noColorsFound'), 'warning');
- } else if (newCount > 0 && state.availableColors.length < newCount) {
- const oldCount = state.availableColors.length;
-
- Utils.showAlert(
- Utils.t('colorsUpdated', {
- oldCount,
- newCount: newCount,
- diffCount: newCount - oldCount,
- }),
- 'success'
- );
- state.availableColors = availableColors;
- }
- if (state.colorsChecked) {
- colorSwatchesHTML = state.availableColors
- .map((color) => {
- const rgbString = `rgb(${color.rgb.join(',')})`;
- return `
`;
- })
- .join('');
- }
-
- // Calculate multi-account statistics
- let totalAllCharges = 0;
- let totalMaxCharges = 0;
- if (state.allAccountsInfo.length > 0) {
- totalAllCharges = state.allAccountsInfo.reduce((sum, acc) => sum + Math.floor(acc.Charges || 0), 0);
- totalMaxCharges = state.allAccountsInfo.reduce((sum, acc) => sum + Math.floor(acc.Max || 0), 0);
- }
-
- statsArea.innerHTML = `
- ${imageStatsHTML}
- ${state.allAccountsInfo.length > 0 ? `
-
-
- Total All Accounts Charges
-
-
- ${totalAllCharges}/${totalMaxCharges}
-
-
- ` : ''}
-
-
- ${Utils.t('charges')}
-
-
- ${state.displayCharges} / ${state.maxCharges}
-
-
-
-
- ${Utils.t('fullChargeIn')}
-
-
--:--:--
-
- ${state.colorsChecked
- ? `
-
-
${Utils.t(
- 'availableColors',
- { count: state.availableColors.length }
- )}
-
- ${colorSwatchesHTML}
-
-
- `
- : ''
- }
- `;
-
- // should be after statsArea.innerHTML = '...'. todo make full stats ui update partial
- updateChargeStatsDisplay(intervalMs);
- };
-
- updateDataButtons = () => {
- const hasImageData = state.imageLoaded && state.imageData;
- saveBtn.disabled = !hasImageData;
- saveToFileBtn.disabled = !hasImageData;
- };
-
- updateDataButtons();
-
- function showMoveArtworkPanel() {
- // Create move artwork control panel like status panels
- const movePanel = document.createElement('div');
- movePanel.id = 'moveArtworkPanel';
- movePanel.className = 'wplace-move-panel';
-
- movePanel.innerHTML = `
-
-
-
-
ā²
-
-
ā
-
1px
-
āŗ
-
-
ā¼
-
-
- `;
-
- // No overlay - append directly to body like other panels
- document.body.appendChild(movePanel);
-
- // Make the panel draggable
- makeDraggable(movePanel);
-
- // Add event listeners for movement
- let currentMessageTimeout = null;
-
- function moveArtwork(deltaX, deltaY) {
- if (state.startPosition && state.region) {
- const newX = state.startPosition.x + deltaX;
- const newY = state.startPosition.y + deltaY;
-
- console.log(`š Moving artwork from (${state.startPosition.x}, ${state.startPosition.y}) to (${newX}, ${newY})`);
-
- state.startPosition.x = newX;
- state.startPosition.y = newY;
-
- // Clear any existing message timeout and alerts immediately
- if (currentMessageTimeout) {
- clearTimeout(currentMessageTimeout);
- currentMessageTimeout = null;
- }
- Utils.hideAlert(); // Hide current alert immediately
-
- // Update overlay position if available
- if (overlayManager) {
- overlayManager.setPosition(state.startPosition, state.region)
- .then(() => {
- console.log('ā
Overlay position updated');
- Utils.showAlert(`Moved to position (${newX}, ${newY})`, 'success');
- // Set new timeout for this message
- currentMessageTimeout = setTimeout(() => {
- Utils.hideAlert();
- currentMessageTimeout = null;
- }, 1500); // Message disappears after 1.5 seconds
- })
- .catch(err => {
- console.error('ā Failed to update overlay:', err);
- Utils.showAlert('Failed to update overlay position', 'error');
- });
- } else {
- Utils.showAlert(`Position updated to (${newX}, ${newY})`, 'success');
- // Set new timeout for this message
- currentMessageTimeout = setTimeout(() => {
- Utils.hideAlert();
- currentMessageTimeout = null;
- }, 1500);
- }
- } else {
- Utils.showAlert('Please select a position first before moving artwork', 'warning');
- }
- }
-
- document.getElementById('moveUp').addEventListener('click', () => moveArtwork(0, -1));
- document.getElementById('moveDown').addEventListener('click', () => moveArtwork(0, 1));
- document.getElementById('moveLeft').addEventListener('click', () => moveArtwork(-1, 0));
- document.getElementById('moveRight').addEventListener('click', () => moveArtwork(1, 0));
-
- // Close panel
- function closeMovePanel() {
- if (currentMessageTimeout) {
- clearTimeout(currentMessageTimeout);
- currentMessageTimeout = null;
- }
- Utils.hideAlert();
- document.body.removeChild(movePanel);
- }
-
- document.getElementById('closeMovePanel').addEventListener('click', closeMovePanel);
- }
-
- function showResizeDialog(processor) {
- let baseProcessor = processor;
- let width, height;
- if (state.originalImage?.dataUrl) {
- baseProcessor = new ImageProcessor(state.originalImage.dataUrl);
- width = state.originalImage.width;
- height = state.originalImage.height;
- } else {
- const dims = processor.getDimensions();
- width = dims.width;
- height = dims.height;
- }
- 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%';
- paintWhiteToggle.checked = state.paintWhitePixels;
- paintTransparentToggle.checked = state.paintTransparentPixels;
-
- let _previewTimer = null;
- let _previewJobId = 0;
- let _isDraggingSize = false;
- let _zoomLevel = 1;
- let _ditherWorkBuf = null;
- let _ditherEligibleBuf = null;
- const ensureDitherBuffers = (n) => {
- if (!_ditherWorkBuf || _ditherWorkBuf.length !== n * 3)
- _ditherWorkBuf = new Float32Array(n * 3);
- if (!_ditherEligibleBuf || _ditherEligibleBuf.length !== n)
- _ditherEligibleBuf = new Uint8Array(n);
- return { work: _ditherWorkBuf, eligible: _ditherEligibleBuf };
- };
- let _maskImageData = null;
- let _maskData = null;
- let _dirty = null;
- const _resetDirty = () => {
- _dirty = { minX: Infinity, minY: Infinity, maxX: -1, maxY: -1 };
- };
- const _markDirty = (x, y) => {
- if (!_dirty) _resetDirty();
- if (x < _dirty.minX) _dirty.minX = x;
- if (y < _dirty.minY) _dirty.minY = y;
- if (x > _dirty.maxX) _dirty.maxX = x;
- if (y > _dirty.maxY) _dirty.maxY = y;
- };
- const _flushDirty = () => {
- if (!_dirty || _dirty.maxX < _dirty.minX || _dirty.maxY < _dirty.minY) return;
- const x = Math.max(0, _dirty.minX);
- const y = Math.max(0, _dirty.minY);
- const w = Math.min(maskCanvas.width - x, _dirty.maxX - x + 1);
- const h = Math.min(maskCanvas.height - y, _dirty.maxY - y + 1);
- if (w > 0 && h > 0) maskCtx.putImageData(_maskImageData, 0, 0, x, y, w, h);
- _resetDirty();
- };
- const _ensureMaskOverlayBuffers = (w, h, rebuildFromMask = false) => {
- if (!_maskImageData || _maskImageData.width !== w || _maskImageData.height !== h) {
- _maskImageData = maskCtx.createImageData(w, h);
- _maskData = _maskImageData.data;
- rebuildFromMask = true;
- }
- if (rebuildFromMask) {
- const m = state.resizeIgnoreMask;
- const md = _maskData;
- md.fill(0);
- if (m) {
- for (let i = 0; i < m.length; i++)
- if (m[i]) {
- const p = i * 4;
- md[p] = 255;
- md[p + 1] = 0;
- md[p + 2] = 0;
- md[p + 3] = 150;
- }
- }
- maskCtx.putImageData(_maskImageData, 0, 0);
- _resetDirty();
- }
- };
- const ensureMaskSize = (w, h) => {
- if (!state.resizeIgnoreMask || state.resizeIgnoreMask.length !== w * h) {
- state.resizeIgnoreMask = new Uint8Array(w * h);
- }
- baseCanvas.width = w;
- baseCanvas.height = h;
- maskCanvas.width = w;
- maskCanvas.height = h;
- maskCtx.clearRect(0, 0, maskCanvas.width, maskCanvas.height);
- // Ensure overlay buffers exist and rebuild from mask when dimensions change
- _ensureMaskOverlayBuffers(w, h, true);
- };
- _updateResizePreview = async () => {
- const jobId = ++_previewJobId;
- const newWidth = parseInt(widthSlider.value, 10);
- const newHeight = parseInt(heightSlider.value, 10);
- _zoomLevel = parseFloat(zoomSlider.value);
-
- widthValue.textContent = newWidth;
- heightValue.textContent = newHeight;
-
- 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)) {
- await baseProcessor.load();
- }
- baseCtx.clearRect(0, 0, newWidth, newHeight);
- baseCtx.drawImage(baseProcessor.img, 0, 0, newWidth, newHeight);
- // Draw existing mask overlay buffer
- maskCtx.clearRect(0, 0, maskCanvas.width, maskCanvas.height);
- if (_maskImageData) maskCtx.putImageData(_maskImageData, 0, 0);
- updateZoomLayout();
- return;
- }
- if (baseProcessor !== processor && (!baseProcessor.img || !baseProcessor.canvas)) {
- await baseProcessor.load();
- }
- baseCtx.clearRect(0, 0, newWidth, newHeight);
- baseCtx.drawImage(baseProcessor.img, 0, 0, newWidth, newHeight);
- const imgData = baseCtx.getImageData(0, 0, newWidth, newHeight);
- const data = imgData.data;
-
- const tThresh = state.customTransparencyThreshold || CONFIG.TRANSPARENCY_THRESHOLD;
-
- const applyFSDither = () => {
- const w = newWidth,
- h = newHeight;
- const n = w * h;
- const { work, eligible } = ensureDitherBuffers(n);
- for (let y = 0; y < h; y++) {
- for (let x = 0; x < w; x++) {
- const idx = y * w + x;
- const i4 = idx * 4;
- const r = data[i4],
- g = data[i4 + 1],
- b = data[i4 + 2],
- a = data[i4 + 3];
- const isEligible =
- (state.paintTransparentPixels || a >= tThresh) &&
- (state.paintWhitePixels || !Utils.isWhitePixel(r, g, b));
- eligible[idx] = isEligible ? 1 : 0;
- work[idx * 3] = r;
- work[idx * 3 + 1] = g;
- work[idx * 3 + 2] = b;
- if (!isEligible) {
- data[i4 + 3] = 0; // transparent in preview overlay
- }
- }
- }
-
- const diffuse = (nx, ny, er, eg, eb, factor) => {
- if (nx < 0 || nx >= w || ny < 0 || ny >= h) return;
- const nidx = ny * w + nx;
- if (!eligible[nidx]) return;
- const base = nidx * 3;
- work[base] = Math.min(255, Math.max(0, work[base] + er * factor));
- work[base + 1] = Math.min(255, Math.max(0, work[base + 1] + eg * factor));
- work[base + 2] = Math.min(255, Math.max(0, work[base + 2] + eb * factor));
- };
-
- for (let y = 0; y < h; y++) {
- for (let x = 0; x < w; x++) {
- const idx = y * w + x;
- if (!eligible[idx]) continue;
- const base = idx * 3;
- const r0 = work[base],
- g0 = work[base + 1],
- b0 = work[base + 2];
- const [nr, ng, nb] = Utils.findClosestPaletteColor(
- r0,
- g0,
- b0,
- state.activeColorPalette
- );
- const i4 = idx * 4;
- data[i4] = nr;
- data[i4 + 1] = ng;
- data[i4 + 2] = nb;
- data[i4 + 3] = 255;
-
- const er = r0 - nr;
- const eg = g0 - ng;
- const eb = b0 - nb;
-
- diffuse(x + 1, y, er, eg, eb, 7 / 16);
- diffuse(x - 1, y + 1, er, eg, eb, 3 / 16);
- diffuse(x, y + 1, er, eg, eb, 5 / 16);
- diffuse(x + 1, y + 1, er, eg, eb, 1 / 16);
- }
- }
- };
-
- // Skip expensive dithering while user is dragging sliders
- if (state.ditheringEnabled && !_isDraggingSize) {
- applyFSDither();
- } else {
- for (let i = 0; i < data.length; i += 4) {
- const r = data[i],
- g = data[i + 1],
- b = data[i + 2],
- a = data[i + 3];
- if (
- (!state.paintTransparentPixels && a < tThresh) ||
- (!state.paintWhitePixels && Utils.isWhitePixel(r, g, b))
- ) {
- data[i + 3] = 0;
- continue;
- }
- const [nr, ng, nb] = Utils.findClosestPaletteColor(r, g, b, state.activeColorPalette);
- data[i] = nr;
- data[i + 1] = ng;
- data[i + 2] = nb;
- data[i + 3] = 255;
- }
- }
-
- 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();
- };
-
- const onWidthInput = () => {
- if (keepAspect.checked) {
- 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();
- // Auto-fit after size changes
- const fit = typeof computeFitZoom === 'function' ? computeFitZoom() : 1;
- if (!isNaN(fit) && isFinite(fit)) applyZoom(fit);
- };
-
- const onHeightInput = () => {
- if (keepAspect.checked) {
- 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();
- // Auto-fit after size changes
- const fit = typeof computeFitZoom === 'function' ? computeFitZoom() : 1;
- if (!isNaN(fit) && isFinite(fit)) applyZoom(fit);
- };
-
- paintWhiteToggle.onchange = (e) => {
- state.paintWhitePixels = e.target.checked;
- _updateResizePreview();
- saveBotSettings();
- };
-
- paintTransparentToggle.onchange = (e) => {
- state.paintTransparentPixels = e.target.checked;
- _updateResizePreview();
- saveBotSettings();
- };
-
- let panX = 0,
- panY = 0;
- const clampPan = () => {
- const wrapRect = panStage?.getBoundingClientRect() || {
- width: 0,
- height: 0,
- };
- const w = (baseCanvas.width || 1) * _zoomLevel;
- const h = (baseCanvas.height || 1) * _zoomLevel;
- if (w <= wrapRect.width) {
- panX = Math.floor((wrapRect.width - w) / 2);
- } else {
- const minX = wrapRect.width - w;
- panX = Math.min(0, Math.max(minX, panX));
- }
- if (h <= wrapRect.height) {
- panY = Math.floor((wrapRect.height - h) / 2);
- } else {
- const minY = wrapRect.height - h;
- panY = Math.min(0, Math.max(minY, panY));
- }
- };
- let _panRaf = 0;
- const applyPan = () => {
- if (_panRaf) return;
- _panRaf = requestAnimationFrame(() => {
- clampPan();
- canvasStack.style.transform = `translate3d(${Math.round(
- panX
- )}px, ${Math.round(panY)}px, 0) scale(${_zoomLevel})`;
- _panRaf = 0;
- });
- };
-
- const updateZoomLayout = () => {
- const w = baseCanvas.width || 1,
- h = baseCanvas.height || 1;
- baseCanvas.style.width = w + 'px';
- baseCanvas.style.height = h + 'px';
- maskCanvas.style.width = w + 'px';
- maskCanvas.style.height = h + 'px';
- canvasStack.style.width = w + 'px';
- canvasStack.style.height = h + 'px';
- applyPan();
- };
- const applyZoom = (z) => {
- _zoomLevel = Math.max(0.05, Math.min(20, z || 1));
- zoomSlider.value = _zoomLevel;
- updateZoomLayout();
- if (zoomValue) zoomValue.textContent = `${Math.round(_zoomLevel * 100)}%`;
- };
- 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));
- const computeFitZoom = () => {
- const wrapRect = panStage?.getBoundingClientRect();
- if (!wrapRect) return 1;
- const w = baseCanvas.width || 1;
- const h = baseCanvas.height || 1;
- const margin = 10;
- const scaleX = (wrapRect.width - margin) / w;
- 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();
- });
-
- const centerInView = () => {
- if (!panStage) return;
- const rect = panStage.getBoundingClientRect();
- const w = (baseCanvas.width || 1) * _zoomLevel;
- const h = (baseCanvas.height || 1) * _zoomLevel;
- panX = Math.floor((rect.width - w) / 2);
- panY = Math.floor((rect.height - h) / 2);
- 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
- 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 updatePanModeBtn = () => {
- if (!panModeBtn) return;
- panModeBtn.classList.toggle('active', panMode);
- panModeBtn.setAttribute('aria-pressed', panMode ? 'true' : 'false');
- };
- if (panModeBtn) {
- updatePanModeBtn();
- panModeBtn.addEventListener('click', () => {
- panMode = !panMode;
- updatePanModeBtn();
- setCursor(panMode ? 'grab' : '');
- });
- }
- if (panStage) {
- panStage.addEventListener('contextmenu', (e) => {
- if (allowPan) e.preventDefault();
- });
- window.addEventListener('keydown', (e) => {
- if (e.code === 'Space') {
- allowPan = true;
- setCursor('grab');
- }
- });
- window.addEventListener('keyup', (e) => {
- if (e.code === 'Space') {
- allowPan = false;
- if (!isPanning) setCursor('');
- }
- });
- panStage.addEventListener('mousedown', (e) => {
- if (!isPanActive(e)) return;
- e.preventDefault();
- isPanning = true;
- startX = e.clientX;
- startY = e.clientY;
- startPanX = panX;
- startPanY = panY;
- setCursor('grabbing');
- });
- window.addEventListener('mousemove', (e) => {
- if (!isPanning) return;
- const dx = e.clientX - startX;
- const dy = e.clientY - startY;
- panX = startPanX + dx;
- panY = startPanY + dy;
- applyPan();
- });
- window.addEventListener('mouseup', () => {
- if (isPanning) {
- isPanning = false;
- setCursor(allowPan ? 'grab' : '');
- }
- });
- panStage.addEventListener(
- 'wheel',
- (e) => {
- if (!e.ctrlKey && !e.metaKey) return;
- e.preventDefault();
- const rect = panStage.getBoundingClientRect();
- const cx = e.clientX - rect.left - panX;
- const cy = e.clientY - rect.top - panY;
- const before = _zoomLevel;
- const step = Math.max(0.05, Math.min(0.5, Math.abs(e.deltaY) > 20 ? 0.2 : 0.1));
- const next = Math.max(0.05, Math.min(20, before + (e.deltaY > 0 ? -step : step)));
- if (next === before) return;
- const scale = next / before;
- panX = panX - cx * (scale - 1);
- panY = panY - cy * (scale - 1);
- applyZoom(next);
- },
- { passive: false }
- );
- let lastTouchDist = null;
- let touchStartTime = 0;
- let doubleTapTimer = null;
- panStage.addEventListener(
- 'touchstart',
- (e) => {
- if (e.touches.length === 1) {
- const t = e.touches[0];
- isPanning = true;
- startX = t.clientX;
- startY = t.clientY;
- startPanX = panX;
- startPanY = panY;
- setCursor('grabbing');
- const now = Date.now();
- if (now - touchStartTime < 300) {
- // double tap -> toggle 100%/fit
- const z = Math.abs(_zoomLevel - 1) < 0.01 ? computeFitZoom() : 1;
- applyZoom(z);
- centerInView();
- if (doubleTapTimer) clearTimeout(doubleTapTimer);
- } else {
- touchStartTime = now;
- doubleTapTimer = setTimeout(() => {
- doubleTapTimer = null;
- }, 320);
- }
- } else if (e.touches.length === 2) {
- // Pinch start
- const [a, b] = e.touches;
- lastTouchDist = Math.hypot(b.clientX - a.clientX, b.clientY - a.clientY);
- }
- },
- { passive: true }
- );
- panStage.addEventListener(
- 'touchmove',
- (e) => {
- if (e.touches.length === 1 && isPanning) {
- const t = e.touches[0];
- const dx = t.clientX - startX;
- const dy = t.clientY - startY;
- panX = startPanX + dx;
- panY = startPanY + dy;
- applyPan();
- } else if (e.touches.length === 2 && lastTouchDist != null) {
- e.preventDefault();
- const [a, b] = e.touches;
- const dist = Math.hypot(b.clientX - a.clientX, b.clientY - a.clientY);
- const rect = panStage.getBoundingClientRect();
- const centerX = (a.clientX + b.clientX) / 2 - rect.left - panX;
- const centerY = (a.clientY + b.clientY) / 2 - rect.top - panY;
- const before = _zoomLevel;
- const scale = dist / (lastTouchDist || dist);
- const next = Math.max(0.05, Math.min(20, before * scale));
- if (next !== before) {
- panX = panX - centerX * (next / before - 1);
- panY = panY - centerY * (next / before - 1);
- applyZoom(next);
- }
- lastTouchDist = dist;
- }
- },
- { passive: false }
- );
- panStage.addEventListener('touchend', () => {
- isPanning = false;
- lastTouchDist = null;
- setCursor(panMode || allowPan ? 'grab' : '');
- });
- }
- const schedulePreview = () => {
- if (_previewTimer) clearTimeout(_previewTimer);
- const run = () => {
- _previewTimer = null;
- _updateResizePreview();
- };
- if (window.requestIdleCallback) {
- _previewTimer = setTimeout(() => requestIdleCallback(run, { timeout: 150 }), 50);
- } else {
- _previewTimer = setTimeout(() => requestAnimationFrame(run), 50);
- }
- };
- // Track dragging to reduce work and skip dithering during drag
- const markDragStart = () => {
- _isDraggingSize = true;
- };
- const markDragEnd = () => {
- _isDraggingSize = false;
- schedulePreview();
- };
- widthSlider.addEventListener('pointerdown', markDragStart);
- heightSlider.addEventListener('pointerdown', markDragStart);
- widthSlider.addEventListener('pointerup', markDragEnd);
- heightSlider.addEventListener('pointerup', markDragEnd);
- 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;
- let lastPaintX = -1,
- lastPaintY = -1;
- let brushSize = 1;
- let rowColSize = 1;
- let maskMode = 'ignore'; // 'ignore' | 'unignore' | 'toggle'
- const brushEl = resizeContainer.querySelector('#maskBrushSize');
- const brushValEl = resizeContainer.querySelector('#maskBrushSizeValue');
- const btnIgnore = resizeContainer.querySelector('#maskModeIgnore');
- const btnUnignore = resizeContainer.querySelector('#maskModeUnignore');
- const btnToggle = resizeContainer.querySelector('#maskModeToggle');
- const clearIgnoredBtnEl = resizeContainer.querySelector('#clearIgnoredBtn');
- const invertMaskBtn = resizeContainer.querySelector('#invertMaskBtn');
- const rowColSizeEl = resizeContainer.querySelector('#rowColSize');
- const rowColSizeValEl = resizeContainer.querySelector('#rowColSizeValue');
-
- const updateModeButtons = () => {
- const map = [
- [btnIgnore, 'ignore'],
- [btnUnignore, 'unignore'],
- [btnToggle, 'toggle'],
- ];
- for (const [el, m] of map) {
- if (!el) continue;
- const active = maskMode === m;
- el.classList.toggle('active', active);
- el.setAttribute('aria-pressed', active ? 'true' : 'false');
- }
- };
- const setMode = (mode) => {
- maskMode = mode;
- updateModeButtons();
- };
- if (brushEl && brushValEl) {
- brushEl.addEventListener('input', () => {
- brushSize = parseInt(brushEl.value, 10) || 1;
- brushValEl.textContent = brushSize;
- });
- brushValEl.textContent = brushEl.value;
- brushSize = parseInt(brushEl.value, 10) || 1;
- }
- if (rowColSizeEl && rowColSizeValEl) {
- rowColSizeEl.addEventListener('input', () => {
- rowColSize = parseInt(rowColSizeEl.value, 10) || 1;
- rowColSizeValEl.textContent = rowColSize;
- });
- 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();
-
- const mapClientToPixel = (clientX, clientY) => {
- // Compute without rounding until final step to avoid drift at higher zoom
- const rect = baseCanvas.getBoundingClientRect();
- const scaleX = rect.width / baseCanvas.width;
- const scaleY = rect.height / baseCanvas.height;
- const dx = (clientX - rect.left) / scaleX;
- const dy = (clientY - rect.top) / scaleY;
- const x = Math.floor(dx);
- const y = Math.floor(dy);
- return { x, y };
- };
-
- const ensureMask = (w, h) => {
- if (!state.resizeIgnoreMask || state.resizeIgnoreMask.length !== w * h) {
- state.resizeIgnoreMask = new Uint8Array(w * h);
- }
- };
-
- const paintCircle = (cx, cy, radius, value) => {
- const w = baseCanvas.width,
- h = baseCanvas.height;
- ensureMask(w, h);
- const r2 = radius * radius;
- for (let yy = cy - radius; yy <= cy + radius; yy++) {
- if (yy < 0 || yy >= h) continue;
- for (let xx = cx - radius; xx <= cx + radius; xx++) {
- if (xx < 0 || xx >= w) continue;
- const dx = xx - cx,
- dy = yy - cy;
- if (dx * dx + dy * dy <= r2) {
- const idx = yy * w + xx;
- let val = state.resizeIgnoreMask[idx];
- if (maskMode === 'toggle') {
- val = val ? 0 : 1;
- } else if (maskMode === 'ignore') {
- val = 1;
- } else {
- val = 0;
- }
- state.resizeIgnoreMask[idx] = val;
- if (_maskData) {
- const p = idx * 4;
- if (val) {
- _maskData[p] = 255;
- _maskData[p + 1] = 0;
- _maskData[p + 2] = 0;
- _maskData[p + 3] = 150;
- } else {
- _maskData[p] = 0;
- _maskData[p + 1] = 0;
- _maskData[p + 2] = 0;
- _maskData[p + 3] = 0;
- }
- _markDirty(xx, yy);
- }
- }
- }
- }
- };
-
- const paintRow = (y, value) => {
- 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;
- let val = state.resizeIgnoreMask[idx];
- if (maskMode === 'toggle') {
- val = val ? 0 : 1;
- } else if (maskMode === 'ignore') {
- val = 1;
- } else {
- val = 0;
- }
- state.resizeIgnoreMask[idx] = val;
- if (_maskData) {
- const p = idx * 4;
- if (val) {
- _maskData[p] = 255;
- _maskData[p + 1] = 0;
- _maskData[p + 2] = 0;
- _maskData[p + 3] = 150;
- } else {
- _maskData[p] = 0;
- _maskData[p + 1] = 0;
- _maskData[p + 2] = 0;
- _maskData[p + 3] = 0;
- }
- }
- }
- if (_maskData) {
- _markDirty(0, rowY);
- _markDirty(w - 1, rowY);
- }
- }
- };
-
- const paintColumn = (x, value) => {
- 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;
- let val = state.resizeIgnoreMask[idx];
- if (maskMode === 'toggle') {
- val = val ? 0 : 1;
- } else if (maskMode === 'ignore') {
- val = 1;
- } else {
- val = 0;
- }
- state.resizeIgnoreMask[idx] = val;
- if (_maskData) {
- const p = idx * 4;
- if (val) {
- _maskData[p] = 255;
- _maskData[p + 1] = 0;
- _maskData[p + 2] = 0;
- _maskData[p + 3] = 150;
- } else {
- _maskData[p] = 0;
- _maskData[p + 1] = 0;
- _maskData[p + 2] = 0;
- _maskData[p + 3] = 0;
- }
- }
- }
- if (_maskData) {
- _markDirty(colX, 0);
- _markDirty(colX, h - 1);
- }
- }
- };
-
- const redrawMaskOverlay = () => {
- // Only flush the dirty region; full rebuild happens on size change
- _flushDirty();
- };
-
- const handlePaint = (e) => {
- // Suppress painting while panning
- if ((e.buttons & 4) === 4 || (e.buttons & 2) === 2 || allowPan) return;
- const { x, y } = mapClientToPixel(e.clientX, e.clientY);
- const w = baseCanvas.width,
- h = baseCanvas.height;
- if (x < 0 || y < 0 || x >= w || y >= h) return;
- const radius = Math.max(1, Math.floor(brushSize / 2));
- if (e.shiftKey) {
- paintRow(y);
- } else if (e.altKey) {
- paintColumn(x);
- } else {
- paintCircle(x, y, radius);
- }
- lastPaintX = x;
- lastPaintY = y;
- redrawMaskOverlay();
- };
-
- 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 }
- );
- window.addEventListener('mousemove', (e) => {
- if (draggingMask) handlePaint(e);
- });
- window.addEventListener('mouseup', () => {
- if (draggingMask) {
- draggingMask = false;
- saveBotSettings();
- }
- });
-
- if (clearIgnoredBtnEl)
- clearIgnoredBtnEl.addEventListener('click', () => {
- const w = baseCanvas.width,
- h = baseCanvas.height;
- if (state.resizeIgnoreMask) state.resizeIgnoreMask.fill(0);
- _ensureMaskOverlayBuffers(w, h, true);
- _updateResizePreview();
- saveBotSettings();
- });
-
- if (invertMaskBtn)
- invertMaskBtn.addEventListener('click', () => {
- if (!state.resizeIgnoreMask) return;
- for (let i = 0; i < state.resizeIgnoreMask.length; i++)
- state.resizeIgnoreMask[i] = state.resizeIgnoreMask[i] ? 0 : 1;
- const w = baseCanvas.width,
- h = baseCanvas.height;
- _ensureMaskOverlayBuffers(w, h, true);
- _updateResizePreview();
- saveBotSettings();
- });
-
- confirmResize.onclick = async () => {
- const newWidth = parseInt(widthSlider.value, 10);
- const newHeight = parseInt(heightSlider.value, 10);
-
- // Generate the final paletted image data
- const tempCanvas = document.createElement('canvas');
- const tempCtx = tempCanvas.getContext('2d');
- tempCanvas.width = newWidth;
- tempCanvas.height = newHeight;
- tempCtx.imageSmoothingEnabled = false;
- 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 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 applyFSDitherFinal = async () => {
- const w = newWidth,
- h = newHeight;
- const n = w * h;
- const { work, eligible } = ensureDitherBuffers(n);
- for (let y = 0; y < h; y++) {
- for (let x = 0; x < w; x++) {
- const idx = y * w + x;
- const i4 = idx * 4;
- const r = data[i4],
- g = data[i4 + 1],
- b = data[i4 + 2],
- a = data[i4 + 3];
- const masked = mask && mask[idx];
- const isEligible =
- !masked &&
- (state.paintTransparentPixels || a >= tThresh2) &&
- (state.paintWhitePixels || !Utils.isWhitePixel(r, g, b));
- eligible[idx] = isEligible ? 1 : 0;
- work[idx * 3] = r;
- work[idx * 3 + 1] = g;
- work[idx * 3 + 2] = b;
- if (!isEligible) {
- data[i4 + 3] = 0;
- }
- }
- // Yield to keep UI responsive
- if ((y & 15) === 0) await Promise.resolve();
- }
-
- const diffuse = (nx, ny, er, eg, eb, factor) => {
- if (nx < 0 || nx >= w || ny < 0 || ny >= h) return;
- const nidx = ny * w + nx;
- if (!eligible[nidx]) return;
- const base = nidx * 3;
- work[base] = Math.min(255, Math.max(0, work[base] + er * factor));
- work[base + 1] = Math.min(255, Math.max(0, work[base + 1] + eg * factor));
- work[base + 2] = Math.min(255, Math.max(0, work[base + 2] + eb * factor));
- };
-
- for (let y = 0; y < h; y++) {
- for (let x = 0; x < w; x++) {
- const idx = y * w + x;
- if (!eligible[idx]) continue;
- const base = idx * 3;
- const r0 = work[base],
- g0 = work[base + 1],
- b0 = work[base + 2];
- const [nr, ng, nb] = Utils.findClosestPaletteColor(
- r0,
- g0,
- b0,
- state.activeColorPalette
- );
- const i4 = idx * 4;
- data[i4] = nr;
- data[i4 + 1] = ng;
- data[i4 + 2] = nb;
- data[i4 + 3] = 255;
- totalValidPixels++;
-
- const er = r0 - nr;
- const eg = g0 - ng;
- const eb = b0 - nb;
-
- diffuse(x + 1, y, er, eg, eb, 7 / 16);
- diffuse(x - 1, y + 1, er, eg, eb, 3 / 16);
- diffuse(x, y + 1, er, eg, eb, 5 / 16);
- diffuse(x + 1, y + 1, er, eg, eb, 1 / 16);
- }
- // Yield every row to reduce jank
- await Promise.resolve();
- }
- };
-
- if (state.ditheringEnabled) {
- await applyFSDitherFinal();
- } else {
- for (let i = 0; i < data.length; i += 4) {
- const r = data[i],
- g = data[i + 1],
- b = data[i + 2],
- a = data[i + 3];
- const masked = mask && mask[i >> 2];
- const isTransparent = (!state.paintTransparentPixels && a < tThresh2) || masked;
- const isWhiteAndSkipped = !state.paintWhitePixels && Utils.isWhitePixel(r, g, b);
- if (isTransparent || isWhiteAndSkipped) {
- data[i + 3] = 0; // overlay transparency
- continue;
- }
- totalValidPixels++;
- const [nr, ng, nb] = Utils.findClosestPaletteColor(r, g, b, state.activeColorPalette);
- data[i] = nr;
- data[i + 1] = ng;
- data[i + 2] = nb;
- data[i + 3] = 255;
- }
- }
- tempCtx.putImageData(imgData, 0, 0);
-
- // Save the final pixel data for painting
- // Persist the paletted (and possibly dithered) pixels so painting uses the same output seen in overlay
- const palettedPixels = new Uint8ClampedArray(imgData.data);
- state.imageData.pixels = palettedPixels;
- state.imageData.width = newWidth;
- state.imageData.height = newHeight;
- state.imageData.totalPixels = totalValidPixels;
- state.totalPixels = totalValidPixels;
- state.paintedPixels = 0;
-
- 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');
-
- // Keep state.imageData.processor as the original-based source; painting uses paletted pixels already stored
-
- await updateStats();
- updateUI('resizeSuccess', 'success', {
- width: newWidth,
- height: newHeight,
- });
- closeResizeDialog();
- };
-
- downloadPreviewBtn.onclick = () => {
- try {
- const w = baseCanvas.width,
- h = baseCanvas.height;
- const out = document.createElement('canvas');
- out.width = w;
- out.height = h;
- const octx = out.getContext('2d');
- octx.imageSmoothingEnabled = false;
- octx.drawImage(baseCanvas, 0, 0);
- octx.drawImage(maskCanvas, 0, 0);
- const link = document.createElement('a');
- link.download = 'wplace-preview.png';
- link.href = out.toDataURL();
- link.click();
- } catch (e) {
- console.warn('Failed to download preview:', e);
- }
- };
-
- cancelResize.onclick = closeResizeDialog;
-
- resizeOverlay.style.display = 'block';
- resizeContainer.style.display = 'block';
-
- // Reinitialize color palette with current available colors
- initializeColorPalette(resizeContainer);
-
- _updateResizePreview();
- _resizeDialogCleanup = () => {
- try {
- zoomSlider.replaceWith(zoomSlider.cloneNode(true));
- } catch { }
- try {
- if (zoomInBtn) zoomInBtn.replaceWith(zoomInBtn.cloneNode(true));
- } catch { }
- try {
- if (zoomOutBtn) zoomOutBtn.replaceWith(zoomOutBtn.cloneNode(true));
- } catch { }
- };
- setTimeout(() => {
- if (typeof computeFitZoom === 'function') {
- const z = computeFitZoom();
- if (!isNaN(z) && isFinite(z)) {
- applyZoom(z);
- centerInView();
- }
- } else {
- centerInView();
- }
- }, 0);
- }
-
- function closeResizeDialog() {
- try {
- if (typeof _resizeDialogCleanup === 'function') {
- _resizeDialogCleanup();
- }
- } catch { }
- resizeOverlay.style.display = 'none';
- resizeContainer.style.display = 'none';
- _updateResizePreview = () => { };
- try {
- if (typeof cancelAnimationFrame === 'function' && _panRaf) {
- cancelAnimationFrame(_panRaf);
- }
- } catch { }
- try {
- if (_previewTimer) {
- clearTimeout(_previewTimer);
- _previewTimer = null;
- }
- } catch { }
- _maskImageData = null;
- _maskData = null;
- _dirty = null;
- _ditherWorkBuf = null;
- _ditherEligibleBuf = null;
- _resizeDialogCleanup = null;
- }
-
- if (uploadBtn) {
- uploadBtn.addEventListener('click', async () => {
- const availableColors = Utils.extractAvailableColors();
- if (availableColors === null || availableColors.length < 10) {
- updateUI('noColorsFound', 'error');
- Utils.showAlert(Utils.t('noColorsFound'), 'error');
- return;
- }
-
- if (!state.colorsChecked) {
- state.availableColors = availableColors;
- state.colorsChecked = true;
- updateUI('colorsFound', 'success', { count: availableColors.length });
- await updateStats();
- selectPosBtn.disabled = false;
- // Only enable resize button if image is also loaded
- if (state.imageLoaded) {
- resizeBtn.disabled = false;
- }
- }
-
- try {
- updateUI('loadingImage', 'default');
- const imageSrc = await Utils.createImageUploader();
- if (!imageSrc) {
- updateUI('colorsFound', 'success', {
- count: state.availableColors.length,
- });
- return;
- }
-
- const processor = new ImageProcessor(imageSrc);
- await processor.load();
-
- const { width, height } = processor.getDimensions();
- const pixels = processor.getPixelData();
-
- let totalValidPixels = 0;
- for (let i = 0; i < pixels.length; i += 4) {
- const isTransparent =
- !state.paintTransparentPixels &&
- pixels[i + 3] < (state.customTransparencyThreshold || CONFIG.TRANSPARENCY_THRESHOLD);
- const isWhiteAndSkipped =
- !state.paintWhitePixels &&
- Utils.isWhitePixel(pixels[i], pixels[i + 1], pixels[i + 2]);
- if (!isTransparent && !isWhiteAndSkipped) {
- totalValidPixels++;
- }
- }
-
- state.imageData = {
- width,
- height,
- pixels,
- totalPixels: totalValidPixels,
- processor,
- };
-
- state.totalPixels = totalValidPixels;
- state.paintedPixels = 0;
- state.imageLoaded = true;
- // Keep existing lastPosition to continue from where we left off
- // state.lastPosition = { x: 0, y: 0 }; // REMOVED: Don't reset position
-
- // Initialize painted map for tracking
- Utils.initializePaintedMap(width, height);
-
- // New image: clear previous resize settings
- state.resizeSettings = null;
- // Also clear any previous ignore mask
- state.resizeIgnoreMask = null;
- // Save original image for this browser (dataUrl + dims)
- state.originalImage = { dataUrl: imageSrc, width, height };
- saveBotSettings();
-
- // Use the original image for the overlay initially
- const imageBitmap = await createImageBitmap(processor.img);
- await overlayManager.setImage(imageBitmap);
- overlayManager.enable();
- toggleOverlayBtn.disabled = false;
- toggleOverlayBtn.classList.add('active');
- toggleOverlayBtn.setAttribute('aria-pressed', 'true');
-
- // Only enable resize button if colors have also been captured
- if (state.colorsChecked) {
- resizeBtn.disabled = false;
- }
- saveBtn.disabled = false;
-
- if (state.startPosition) {
- startBtn.disabled = false;
- }
-
- await updateStats();
- updateDataButtons();
- updateUI('imageLoaded', 'success', { count: totalValidPixels });
- } catch {
- updateUI('imageError', 'error');
- }
- });
- }
-
- // Load Extracted button event listener - for loading Art-Extractor JSON files
- const loadExtractedBtn = document.getElementById('loadExtractedBtn');
- if (loadExtractedBtn) {
- loadExtractedBtn.addEventListener('click', async () => {
- try {
- updateUI('loadingImage', 'default');
- const fileData = await Utils.loadExtractedFileData();
-
- if (!fileData) {
- updateUI('ready', 'default');
- return;
- }
-
- console.log('š Loading extracted artwork from Art-Extractor...');
- console.log('š [DEBUG] Loaded file data structure:', {
- hasState: !!fileData.state,
- hasImageData: !!fileData.imageData,
- topLevelKeys: Object.keys(fileData),
- stateKeys: fileData.state ? Object.keys(fileData.state) : 'N/A',
- imageDataKeys: fileData.imageData ? Object.keys(fileData.imageData) : 'N/A',
- fileDataType: typeof fileData,
- stateType: typeof fileData.state,
- imageDataType: typeof fileData.imageData
- });
-
- // Validate the data structure before restoring
- if (!fileData || typeof fileData !== 'object') {
- throw new Error('Invalid file format: File data is not a valid object');
- }
-
- if (!fileData.state || typeof fileData.state !== 'object') {
- console.error('ā State validation failed. FileData:', fileData);
- throw new Error('Invalid file format: Missing or invalid state object. Please ensure you exported from Art-Extractor correctly.');
- }
-
- if (!fileData.imageData || typeof fileData.imageData !== 'object') {
- console.error('ā ImageData validation failed. FileData:', fileData);
- throw new Error('Invalid file format: Missing or invalid imageData object. Please ensure you completed the area scan in Art-Extractor.');
- }
-
- if (!fileData.imageData.pixels || !Array.isArray(fileData.imageData.pixels) || fileData.imageData.pixels.length === 0) {
- console.error('ā Pixel data validation failed. ImageData:', fileData.imageData);
- throw new Error('Invalid file format: No pixel data found. Please scan an area in Art-Extractor before exporting.');
- }
-
- // Ensure critical fields are present for Art-Extractor compatibility
- if (!fileData.state.availableColors) {
- console.warn('ā ļø No availableColors in file, using defaults');
- fileData.state.availableColors = [];
- }
-
- // Use the existing restoreProgress function but with special handling
- const restoreSuccess = await Utils.restoreProgress(fileData);
-
- if (!restoreSuccess) {
- throw new Error('Failed to restore progress data');
- }
-
- // After loading, we need to enter position selection mode like when uploading images
- if (state.imageLoaded) {
- console.log('šÆ Extracted artwork loaded, entering position selection mode...');
-
- // Clear position data since Art-Extractor exports may have null positions for manual placement
- state.startPosition = null;
- state.region = null;
- state.selectingPosition = true;
-
- // For extracted files, force enable all necessary flags for full functionality
- state.colorsChecked = true;
- state.imageLoaded = true;
-
- // Ensure image processor is available for extracted files
- if (state.imageData && !state.imageData.processor) {
- try {
- // Create a temporary canvas to generate a data URL for the processor
- const canvas = document.createElement('canvas');
- canvas.width = state.imageData.width;
- canvas.height = state.imageData.height;
- const ctx = canvas.getContext('2d');
-
- // Create image data from pixels
- const imageData = new ImageData(
- new Uint8ClampedArray(state.imageData.pixels),
- state.imageData.width,
- state.imageData.height
- );
- ctx.putImageData(imageData, 0, 0);
-
- // Create processor with canvas data URL using the correct class
- const dataUrl = canvas.toDataURL();
- state.imageData.processor = new window.WPlaceImageProcessor(dataUrl);
-
- // CRITICAL: Load the processor before using it
- await state.imageData.processor.load();
- console.log('š§ Created and loaded WPlaceImageProcessor for extracted artwork');
- } catch (error) {
- console.error('ā Failed to create image processor for extracted artwork:', error);
- // Fallback: use global processor if available
- if (window.globalImageProcessor) {
- console.log('š Using global image processor as fallback');
- state.imageData.processor = window.globalImageProcessor;
- }
- }
- }
-
- console.log('šØ Force-enabled all flags for extracted JSON to access resize panel');
-
- // For extracted images, ensure overlay is enabled but don't set position yet
- try {
- if (overlayManager && state.imageData) {
- // Create image bitmap from the restored image data
- const canvas = new OffscreenCanvas(state.imageData.width, state.imageData.height);
- const ctx = canvas.getContext('2d');
- const imageData = new ImageData(
- new Uint8ClampedArray(state.imageData.pixels),
- state.imageData.width,
- state.imageData.height
- );
- ctx.putImageData(imageData, 0, 0);
- const imageBitmap = await canvas.transferToImageBitmap();
-
- await overlayManager.setImage(imageBitmap);
- overlayManager.enable();
- console.log('ā
Overlay enabled for extracted artwork');
- }
- } catch (overlayError) {
- console.warn('ā ļø Could not set overlay for extracted artwork:', overlayError);
- }
-
- // For extracted images, don't try to set overlay position until user selects one
- // The overlay should already be enabled from the restoreProgress function
-
- // Update UI with custom message for extracted artwork
- Utils.showAlert('šØ Extracted artwork loaded! Please click on the canvas to select where to place it.', 'info');
- updateUI('ready', 'default'); // Use 'ready' instead of 'waitingPosition' to avoid translation issues
-
- // Enable relevant buttons
- selectPosBtn.disabled = false;
- // Always enable resize button for extracted files since they have image processor
- resizeBtn.disabled = false;
- console.log('š§ Resize button enabled for extracted artwork');
-
- // Enable move artwork button (will be fully enabled after position is set)
- const moveArtworkBtn = document.getElementById('moveArtworkBtn');
- if (moveArtworkBtn) {
- moveArtworkBtn.disabled = false;
- }
-
- // Set up position selection like the regular selectPosBtn
- const tempFetch = async (url, options) => {
- if (
- typeof url === 'string' &&
- url.includes('/s0/pixel/') &&
- options &&
- options.method === 'POST'
- ) {
- let coords;
- try {
- const body = JSON.parse(options.body);
- coords = body.coords;
- } catch (e) {
- return window.originalFetch(url, options);
- }
-
- const tileMatches = url.match(/\/s0\/pixel\/(\-?\d+)\/(\-?\d+)/);
- if (tileMatches && coords && coords.length >= 2) {
- const tileX = parseInt(tileMatches[1]);
- const tileY = parseInt(tileMatches[2]);
- const pixelX = coords[0];
- const pixelY = coords[1];
-
- state.startPosition = { x: pixelX, y: pixelY };
- state.region = { x: tileX, y: tileY };
- state.selectingPosition = false;
-
- console.log('šÆ Position selected for extracted artwork:', {
- startPosition: state.startPosition,
- region: state.region
- });
-
- // Restore original fetch
- window.fetch = window.originalFetch;
-
- // Update overlay position with validation
- try {
- if (state.startPosition && state.region && overlayManager) {
- await overlayManager.setPosition(state.startPosition, state.region);
- console.log('ā
Overlay position updated successfully');
- } else {
- console.warn('ā ļø Cannot set overlay position: missing startPosition or region');
- }
- } catch (positionError) {
- console.error('ā Failed to set overlay position:', positionError);
- }
-
- startBtn.disabled = false;
- selectPosBtn.textContent = Utils.t('selectPosition');
-
- // Enable Move Artwork button when position is set
- const moveArtworkBtn = document.getElementById('moveArtworkBtn');
- if (moveArtworkBtn) {
- moveArtworkBtn.disabled = false;
- }
-
- updateUI('ready', 'success');
- Utils.showAlert('Position selected! Ready to start painting.', 'success');
- }
- }
- return window.originalFetch(url, options);
- };
-
- // Store original fetch and replace with position capture
- if (!window.originalFetch) {
- window.originalFetch = window.fetch;
- }
- window.fetch = tempFetch;
- }
-
- } catch (error) {
- console.error('ā Failed to load extracted artwork:', error);
- Utils.showAlert(`Failed to load extracted artwork: ${error.message}`, 'error');
- updateUI('ready', 'default');
- }
- });
- }
-
- if (resizeBtn) {
- resizeBtn.addEventListener('click', () => {
- console.log('š [DEBUG] Resize button clicked. State check:', {
- imageLoaded: state.imageLoaded,
- hasProcessor: !!(state.imageData && state.imageData.processor),
- colorsChecked: state.colorsChecked,
- hasAvailableColors: !!(state.availableColors && state.availableColors.length > 0)
- });
-
- if (state.imageLoaded && state.imageData && state.imageData.processor) {
- showResizeDialog(state.imageData.processor);
- } else {
- let message = 'Please upload an image and check colors first.';
- if (!state.imageLoaded) message = 'Please upload an image first.';
- else if (!state.imageData || !state.imageData.processor) message = 'Image processor not available. Please reload the image.';
- Utils.showAlert(message, 'warning');
- }
- });
- }
-
- // Move Artwork button event listener
- const moveArtworkBtn = document.getElementById('moveArtworkBtn');
- if (moveArtworkBtn) {
- moveArtworkBtn.addEventListener('click', () => {
- if (state.imageLoaded && (state.startPosition || state.selectingPosition)) {
- showMoveArtworkPanel();
- } else if (!state.imageLoaded) {
- Utils.showAlert('Please upload or load an image first', 'warning');
- } else {
- Utils.showAlert('Please select a position for the artwork first', 'warning');
- }
- });
- }
-
- if (selectPosBtn) {
- selectPosBtn.addEventListener('click', async () => {
- if (state.selectingPosition) return;
-
- state.selectingPosition = true;
- state.startPosition = null;
- state.region = null;
- startBtn.disabled = true;
-
- // Disable Move Artwork button when selecting new position
- const moveArtworkBtn = document.getElementById('moveArtworkBtn');
- if (moveArtworkBtn) {
- moveArtworkBtn.disabled = true;
- }
-
- Utils.showAlert(Utils.t('selectPositionAlert'), 'info');
- updateUI('waitingPosition', 'default');
-
- const tempFetch = async (url, options) => {
- if (
- typeof url === 'string' &&
- url.includes('https://backend.wplace.live/s0/pixel/') &&
- options?.method?.toUpperCase() === 'POST'
- ) {
- try {
- const response = await originalFetch(url, options);
- const clonedResponse = response.clone();
- const data = await clonedResponse.json();
-
- if (data?.painted === 1) {
- const regionMatch = url.match(/\/pixel\/(\d+)\/(\d+)/);
- if (regionMatch && regionMatch.length >= 3) {
- state.region = {
- x: Number.parseInt(regionMatch[1]),
- y: Number.parseInt(regionMatch[2]),
- };
- }
-
- const payload = JSON.parse(options.body);
- if (payload?.coords && Array.isArray(payload.coords)) {
- state.startPosition = {
- x: payload.coords[0],
- y: payload.coords[1],
- };
- // Keep existing lastPosition to continue from where we left off
- // state.lastPosition = { x: 0, y: 0 }; // REMOVED: Don't reset position
-
- // Update overlay position with validation
- try {
- if (state.startPosition && state.region && overlayManager) {
- await overlayManager.setPosition(state.startPosition, state.region);
- console.log('ā
Regular overlay position updated successfully');
- } else {
- console.warn('ā ļø Cannot set regular overlay position: missing startPosition or region');
- }
- } catch (positionError) {
- console.error('ā Failed to set regular overlay position:', positionError);
- }
-
- if (state.imageLoaded) {
- startBtn.disabled = false;
-
- // Enable Move Artwork button when position is set
- const moveArtworkBtn = document.getElementById('moveArtworkBtn');
- if (moveArtworkBtn) {
- moveArtworkBtn.disabled = false;
- }
- }
-
- window.fetch = originalFetch;
- state.selectingPosition = false;
- updateUI('positionSet', 'success');
- }
- }
-
- return response;
- } catch {
- return originalFetch(url, options);
- }
- }
- return originalFetch(url, options);
- };
-
- const originalFetch = window.fetch;
- window.fetch = tempFetch;
-
- setTimeout(() => {
- if (state.selectingPosition) {
- window.fetch = originalFetch;
- state.selectingPosition = false;
- updateUI('positionTimeout', 'error');
- Utils.showAlert(Utils.t('positionTimeout'), 'error');
- }
- }, 120000);
- });
- }
-
- async function startPainting() {
- if (!state.imageLoaded || !state.startPosition || !state.region) {
- updateUI('missingRequirements', 'error');
- return;
- }
- await ensureToken();
- if (!getTurnstileToken()) return;
-
- // Only reset painted pixels on first start of session (when pre-filtering hasn't been done)
- if (!state.preFilteringDone) {
- const savedPaintedPixels = state.paintedPixels; // Store original value
- state.paintedPixels = 0;
- console.log(`š First start this session - reset progress counter for accurate tracking (was: ${savedPaintedPixels})`);
- await updateStats(); // Update UI to show 0 progress
- } else {
- console.log('š Continuing session - pre-filtering already done, keeping current progress');
- }
-
- state.running = true;
- state.stopFlag = false;
- startBtn.disabled = true;
- stopBtn.disabled = false;
- uploadBtn.disabled = true;
- selectPosBtn.disabled = true;
- resizeBtn.disabled = true;
- saveBtn.disabled = true;
- toggleOverlayBtn.disabled = true;
-
- updateUI('startPaintingMsg', 'success');
-
- try {
- await getAccounts();
- await processImage();
- } catch (e) {
- console.error('Unexpected error:', e);
- updateUI('paintingError', 'error');
- } finally {
- state.running = false;
- stopBtn.disabled = true;
- saveBtn.disabled = false;
-
- if (state.stopFlag) {
- startBtn.disabled = false;
- } else {
- startBtn.disabled = true;
- uploadBtn.disabled = false;
- const loadExtractedBtn = document.getElementById('loadExtractedBtn');
- if (loadExtractedBtn) loadExtractedBtn.disabled = false;
- selectPosBtn.disabled = false;
- resizeBtn.disabled = false;
- }
- toggleOverlayBtn.disabled = false;
- }
- }
-
- if (startBtn) {
- startBtn.addEventListener('click', startPainting);
- }
-
- if (stopBtn) {
- stopBtn.addEventListener('click', () => {
- state.stopFlag = true;
- state.running = false;
- stopBtn.disabled = true;
- updateUI('paintingStoppedByUser', 'warning');
-
- if (state.imageLoaded && state.paintedPixels > 0) {
- Utils.saveProgress();
- Utils.showAlert(Utils.t('autoSaved'), 'success');
- }
- });
- }
-
- const checkSavedProgress = () => {
- const savedData = Utils.loadProgress();
- if (savedData && savedData.state.paintedPixels > 0) {
- const savedDate = new Date(savedData.timestamp).toLocaleString();
- 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'
- );
- }
- };
-
- setTimeout(checkSavedProgress, 1000);
-
- if (cooldownSlider && cooldownInput && cooldownValue && cooldownDecrease && cooldownIncrease) {
- const updateCooldown = (newValue) => {
- const threshold = Math.max(1, Math.min(state.maxCharges || 999, parseInt(newValue)));
- state.cooldownChargeThreshold = threshold;
-
- // Update both controls (value shows in input, label shows unit only)
- cooldownSlider.value = threshold;
- cooldownInput.value = threshold;
- cooldownValue.textContent = `${Utils.t('charges')}`;
-
- saveBotSettings();
- NotificationManager.resetEdgeTracking(); // prevent spurious notify after threshold change
- };
-
- // Slider event listener
- cooldownSlider.addEventListener('input', (e) => {
- updateCooldown(e.target.value);
- });
-
- // Number input event listener
- cooldownInput.addEventListener('input', (e) => {
- updateCooldown(e.target.value);
- });
-
- // Decrease button
- cooldownDecrease.addEventListener('click', () => {
- updateCooldown(parseInt(cooldownInput.value) - 1);
- });
-
- // Increase button
- cooldownIncrease.addEventListener('click', () => {
- updateCooldown(parseInt(cooldownInput.value) + 1);
- });
-
- // Add scroll-to-adjust for cooldown slider
- Utils.createScrollToAdjust(cooldownSlider, updateCooldown, 1, state.maxCharges, 1);
- }
-
- loadBotSettings();
- // Ensure notification poller reflects current settings
- NotificationManager.syncFromState();
- }
-
- function getMsToTargetCharges(current, target, cooldown, intervalMs = 0) {
- const remainingCharges = target - current;
- return Math.max(0, remainingCharges * cooldown - intervalMs);
- }
-
- function updateChargesThresholdUI(intervalMs) {
- if (state.stopFlag) return;
-
- const threshold = state.cooldownChargeThreshold;
- const remainingMs = getMsToTargetCharges(
- state.preciseCurrentCharges,
- threshold,
- state.cooldown,
- intervalMs
- );
- const timeText = Utils.msToTimeText(remainingMs);
-
- updateUI(
- 'noChargesThreshold',
- 'warning',
- {
- threshold,
- current: state.displayCharges,
- time: timeText,
- },
- true
- );
- }
-
- function generateCoordinates(width, height, mode, direction, snake, blockWidth, blockHeight, startFromX = 0, startFromY = 0) {
- const coords = [];
- console.log(
- 'Generating coordinates with \n mode:',
- mode,
- '\n direction:',
- direction,
- '\n snake:',
- snake,
- '\n blockWidth:',
- blockWidth,
- '\n blockHeight:',
- blockHeight,
- '\n startFromX:',
- startFromX,
- '\n startFromY:',
- startFromY
- );
- // --------- Standard 4 corners traversal ----------
- let xStart, xEnd, xStep;
- let yStart, yEnd, yStep;
- switch (direction) {
- case 'top-left':
- xStart = 0;
- xEnd = width;
- xStep = 1;
- yStart = 0;
- yEnd = height;
- yStep = 1;
- break;
- case 'top-right':
- xStart = width - 1;
- xEnd = -1;
- xStep = -1;
- yStart = 0;
- yEnd = height;
- yStep = 1;
- break;
- case 'bottom-left':
- xStart = 0;
- xEnd = width;
- xStep = 1;
- yStart = height - 1;
- yEnd = -1;
- yStep = -1;
- break;
- case 'bottom-right':
- xStart = width - 1;
- xEnd = -1;
- xStep = -1;
- yStart = height - 1;
- yEnd = -1;
- yStep = -1;
- break;
- default:
- throw new Error(`Unknown direction: ${direction}`);
- }
-
- // --------- Traversal modes ----------
- if (mode === 'rows') {
- for (let y = yStart; y !== yEnd; y += yStep) {
- if (snake && (y - yStart) % 2 !== 0) {
- for (let x = xEnd - xStep; x !== xStart - xStep; x -= xStep) {
- coords.push([x, y]);
- }
- } else {
- for (let x = xStart; x !== xEnd; x += xStep) {
- coords.push([x, y]);
- }
- }
- }
- } else if (mode === 'columns') {
- for (let x = xStart; x !== xEnd; x += xStep) {
- if (snake && (x - xStart) % 2 !== 0) {
- for (let y = yEnd - yStep; y !== yStart - yStep; y -= yStep) {
- coords.push([x, y]);
- }
- } else {
- for (let y = yStart; y !== yEnd; y += yStep) {
- coords.push([x, y]);
- }
- }
- }
- } else if (mode === 'circle-out') {
- const cx = Math.floor(width / 2);
- const cy = Math.floor(height / 2);
- const maxRadius = Math.ceil(Math.sqrt(cx * cx + cy * cy));
-
- for (let r = 0; r <= maxRadius; r++) {
- for (let y = cy - r; y <= cy + r; y++) {
- for (let x = cx - r; x <= cx + r; x++) {
- if (x >= 0 && x < width && y >= 0 && y < height) {
- const dist = Math.max(Math.abs(x - cx), Math.abs(y - cy));
- if (dist === r) coords.push([x, y]);
- }
- }
- }
- }
- } else if (mode === 'circle-in') {
- const cx = Math.floor(width / 2);
- const cy = Math.floor(height / 2);
- const maxRadius = Math.ceil(Math.sqrt(cx * cx + cy * cy));
-
- for (let r = maxRadius; r >= 0; r--) {
- for (let y = cy - r; y <= cy + r; y++) {
- for (let x = cx - r; x <= cx + r; x++) {
- if (x >= 0 && x < width && y >= 0 && y < height) {
- const dist = Math.max(Math.abs(x - cx), Math.abs(y - cy));
- if (dist === r) coords.push([x, y]);
- }
- }
- }
- }
- } else if (mode === 'blocks' || mode === 'shuffle-blocks') {
- const blocks = [];
- for (let by = 0; by < height; by += blockHeight) {
- for (let bx = 0; bx < width; bx += blockWidth) {
- const block = [];
- for (let y = by; y < Math.min(by + blockHeight, height); y++) {
- for (let x = bx; x < Math.min(bx + blockWidth, width); x++) {
- block.push([x, y]);
- }
- }
- blocks.push(block);
- }
- }
-
- if (mode === 'shuffle-blocks') {
- // Simple Fisher-Yates shuffle
- for (let i = blocks.length - 1; i > 0; i--) {
- const j = Math.floor(Math.random() * (i + 1));
- [blocks[i], blocks[j]] = [blocks[j], blocks[i]];
- }
- }
-
- // Concatenate all blocks
- for (const block of blocks) {
- coords.push(...block);
- }
- } else {
- throw new Error(`Unknown mode: ${mode}`);
- }
-
- // Filter coordinates to start from the specified position
- if (startFromX > 0 || startFromY > 0) {
- console.log(`š Filtering coordinates to resume from position (${startFromX}, ${startFromY})`);
- let startIndex = -1;
-
- // Find the starting position in the coordinate list
- for (let i = 0; i < coords.length; i++) {
- const [x, y] = coords[i];
- if (x === startFromX && y === startFromY) {
- startIndex = i;
- break;
- }
- }
-
- if (startIndex >= 0) {
- // Resume from the found position (skip all previous coordinates)
- const filteredCoords = coords.slice(startIndex);
- console.log(`āļø Resuming: skipped ${startIndex} coordinates, continuing with ${filteredCoords.length} remaining`);
- return filteredCoords;
- } else {
- console.warn(`ā ļø Resume position (${startFromX}, ${startFromY}) not found in coordinate list, starting from beginning`);
- }
- }
-
- return coords;
- }
-
- async function flushPixelBatch(pixelBatch) {
- if (!pixelBatch || pixelBatch.pixels.length === 0) return true;
-
- const batchSize = pixelBatch.pixels.length;
- console.log(
- `š¦ Sending batch with ${batchSize} pixels (region: ${pixelBatch.regionX},${pixelBatch.regionY})`
- );
- const success = await sendBatchWithRetry(
- pixelBatch.pixels,
- pixelBatch.regionX,
- pixelBatch.regionY
- );
- if (success) {
- pixelBatch.pixels.forEach((p) => {
- state.paintedPixels++;
- Utils.markPixelPainted(p.x, p.y, pixelBatch.regionX, pixelBatch.regionY);
- });
-
- // IMPORTANT: Decrement charges locally to match Acc-Switch.js behavior
- state.displayCharges = Math.max(0, state.displayCharges - batchSize);
- state.preciseCurrentCharges = Math.max(0, state.preciseCurrentCharges - batchSize);
-
- state.fullChargeData = {
- ...state.fullChargeData,
- spentSinceShot: state.fullChargeData.spentSinceShot + batchSize,
- };
- await updateStats();
- // Update account list with new charges
- await updateCurrentAccountInList();
- // Progress tracking removed from UI to reduce visual clutter
- Utils.performSmartSave();
-
- if (CONFIG.PAINTING_SPEED_ENABLED && state.paintingSpeed > 0 && batchSize > 0) {
- const delayPerPixel = 1000 / state.paintingSpeed;
- const totalDelay = Math.max(100, delayPerPixel * batchSize);
- await Utils.sleep(totalDelay);
- }
- } else {
- console.error(`ā Batch failed permanently after retries. Stopping painting.`);
- state.stopFlag = true;
- updateUI('paintingBatchFailed', 'error');
- }
-
- pixelBatch.pixels = [];
- return success;
- }
-
- async function processImage() {
- console.log('š Starting auto-swap enabled painting workflow');
-
- try {
- // Main painting cycle - repeats until image complete or stopped
- while (!state.stopFlag) {
- console.log('š Phase 1: Starting painting session');
- const paintingResult = await executePaintingSession();
-
- if (paintingResult === 'completed') {
- console.log('š Image painting completed!');
- break;
- }
-
- if (paintingResult === 'stopped') {
- console.log('ā¹ļø Painting stopped by user');
- break;
- }
-
- if (paintingResult === 'charges_depleted') {
- if (!CONFIG.autoSwap) {
- // Original workflow: cooldown period
- console.log('ā±ļø Phase 2: Entering cooldown period (auto-swap disabled)');
- const cooldownResult = await executeCooldownPeriod();
-
- if (cooldownResult === 'stopped') {
- console.log('ā¹ļø Cooldown stopped by user');
- break;
- }
-
- // Phase 3: Regenerate token for next painting session
- console.log('š Phase 3: Regenerating token for next session');
- const tokenResult = await regenerateTokenForNewSession();
-
- if (!tokenResult) {
- console.log('ā Failed to regenerate token, stopping');
- state.stopFlag = true;
- break;
- }
- } else {
- // Auto-swap workflow: simplified logic
- console.log('š Auto-swap enabled: checking account switching options');
-
- // Retrieve fresh accounts list each time to avoid stale data
- const accounts = JSON.parse(localStorage.getItem("accounts")) || [];
- console.log(`š Retrieved ${accounts.length} accounts from localStorage`);
-
- if (accounts.length <= 1) {
- console.log('š Only one account available, using standard cooldown');
- const cooldownResult = await executeCooldownPeriod();
- if (cooldownResult === 'stopped') break;
-
- const tokenResult = await regenerateTokenForNewSession();
- if (!tokenResult) {
- state.stopFlag = true;
- break;
- }
- } else {
- // Debug current state
- console.log(`š Account Status - Current ID: ${state.originalAccountOrder[state.currentActiveIndex]?.orderId}, Total IDs: ${state.originalAccountOrder.length}`);
-
- // Check if we're at the last ID in the sequence
- const isLastAccount = state.currentActiveIndex >= state.originalAccountOrder.length - 1;
- console.log(`š Is last ID? ${isLastAccount} (ID ${state.originalAccountOrder[state.currentActiveIndex]?.orderId} of ${state.originalAccountOrder.length})`);
-
- if (!isLastAccount) {
- // Switch to next ID immediately (no cooldown)
- const nextId = state.originalAccountOrder[state.currentActiveIndex + 1]?.orderId;
- console.log(`š Switching to next ID ${nextId} (${state.currentActiveIndex + 2}/${state.originalAccountOrder.length})`);
- const switchResult = await switchToNextAccount(accounts);
- if (!switchResult) {
- console.log('ā Account switch failed, stopping');
- state.stopFlag = true;
- break;
- }
- // Continue painting with new account immediately
- continue;
- } else {
- // Last account reached - use cooldown then switch to ID 1 (first account)
- console.log('ā±ļø Last account reached, entering cooldown period');
- console.log(`š Current ID: ${state.originalAccountOrder[state.currentActiveIndex]?.orderId}, Last ID: ${state.originalAccountOrder[state.originalAccountOrder.length - 1]?.orderId}`);
-
- const cooldownResult = await executeCooldownPeriod();
- if (cooldownResult === 'stopped') break;
-
- // After cooldown, switch to ID 1 (first account in order)
- console.log('š Cooldown complete, switching to ID 1 (first account)');
- console.log(`š Before switch - Current ID: ${state.originalAccountOrder[state.currentActiveIndex]?.orderId}, Target: ID 1`);
-
- const firstAccountInfo = state.originalAccountOrder[0]; // ID 1 is always at index 0
- const firstAccountToken = firstAccountInfo?.token;
-
- if (!firstAccountToken) {
- console.log('ā First account (ID 1) token not found, stopping');
- state.stopFlag = true;
- break;
- }
-
- // Reset to first position (ID 1)
- state.currentActiveIndex = 0;
-
- // Update accountIndex to match the position in accounts array
- const accountsIndex = accounts.findIndex(token => token === firstAccountToken);
- if (accountsIndex !== -1) {
- state.accountIndex = accountsIndex;
- }
-
- const switchResult = await switchToSpecificAccount(firstAccountToken, 0);
- if (!switchResult) {
- console.log('ā Switch to ID 1 failed, stopping');
- state.stopFlag = true;
- break;
- }
-
- console.log(`ā
Successfully switched to ID 1 (${firstAccountInfo.displayName}). Cycle restarted.`);
- // Continue painting with first account
- continue;
- }
- }
- }
- }
-
- console.log('š Cycle complete, starting next painting session');
- }
- } finally {
- await finalizePaintingProcess();
- }
- }
-
- // Phase 1: Execute a complete painting session using all available charges
- async function executePaintingSession() {
- console.log('šØ Starting painting session - using all charges until 0');
- const { width, height, pixels } = state.imageData;
- const { x: startX, y: startY } = state.startPosition;
- const { x: regionX, y: regionY } = state.region;
-
- // Check if we're working with restored data by looking for existing availableColors
- const isRestoredData = state.availableColors && state.availableColors.length > 0 && state.colorsChecked;
-
- if (!isRestoredData) {
- // Wait for original tiles to load if needed
- const tilesReady = await overlayManager.waitForTiles(
- regionX,
- regionY,
- width,
- height,
- startX,
- startY,
- 10000 // timeout 10s
- );
-
- if (!tilesReady) {
- updateUI('overlayTilesNotLoaded', 'error');
- state.stopFlag = true;
- return 'stopped';
- }
- }
-
- let pixelBatch = null;
- let skippedPixels = {
- transparent: 0,
- white: 0,
- alreadyPainted: 0,
- colorUnavailable: 0,
- };
-
- // IMPORTANT: Check charges once at start, then paint until depleted
- console.log('š Checking initial charges for painting session');
- const initialChargeCheck = await WPlaceService.getCharges();
- state.displayCharges = Math.floor(initialChargeCheck.charges);
- state.preciseCurrentCharges = initialChargeCheck.charges;
- state.cooldown = initialChargeCheck.cooldown;
- await updateStats();
- if (state.displayCharges <= 0) {
- console.log('ā” No charges available, skipping painting session');
- return 'charges_depleted';
- }
-
- console.log(`š Starting with ${state.displayCharges} charges - painting until depleted`);
-
- // Paint pixels until we run out of charges or complete the image
- try {
- const coords = generateCoordinates(
- width,
- height,
- state.coordinateMode,
- state.coordinateDirection,
- state.coordinateSnake,
- state.blockWidth,
- state.blockHeight,
- state.lastPosition.x,
- state.lastPosition.y
- );
-
- // OPTIMIZATION: Pre-filter already painted pixels (happens only once per session)
- let eligibleCoords = [];
- let alreadyPaintedCount = 0;
-
- // Log resume information if applicable
- if (state.lastPosition.x > 0 || state.lastPosition.y > 0) {
- console.log(`š Resuming painting from position (${state.lastPosition.x}, ${state.lastPosition.y})`);
- console.log(`š Current progress: ${state.paintedPixels} pixels painted`);
- }
-
- if (!state.preFilteringDone) {
- console.log('š Pre-filtering already painted pixels (one-time detection for this session)...');
-
- for (const [x, y] of coords) {
- const targetPixelInfo = checkPixelEligibility(x, y);
-
- if (!targetPixelInfo.eligible) {
- if (targetPixelInfo.reason !== 'alreadyPainted') {
- skippedPixels[targetPixelInfo.reason]++;
-
- }
- continue;
- }
-
- // Check if already painted (only once per session)
- let absX = startX + x;
- let absY = startY + y;
- let adderX = Math.floor(absX / 1000);
- let adderY = Math.floor(absY / 1000);
- let pixelX = absX % 1000;
- let pixelY = absY % 1000;
-
- try {
- const tilePixelRGBA = await overlayManager.getTilePixelColor(
- regionX + adderX,
- regionY + adderY,
- pixelX,
- pixelY
- );
-
- if (tilePixelRGBA && Array.isArray(tilePixelRGBA)) {
- const mappedCanvasColor = Utils.resolveColor(
- tilePixelRGBA.slice(0, 3),
- state.availableColors,
- !state.paintUnavailablePixels // Use same parameter as target pixel
- );
- const isMatch = mappedCanvasColor.id === targetPixelInfo.mappedColorId;
- if (isMatch) {
- alreadyPaintedCount++;
- // Add detected already-painted pixels to progress (after reset to 0)
- state.paintedPixels++;
- Utils.markPixelPainted(x, y, regionX + adderX, regionY + adderY);
- continue; // Skip already painted pixels
- }
- }
- } catch (e) {
- // If we can't check, include the pixel (better to attempt than skip)
- }
-
- // Add eligible unpainted pixel to list
- eligibleCoords.push([x, y, targetPixelInfo]);
- }
-
- // Mark pre-filtering as done for this session
- state.preFilteringDone = true;
-
- // Log pre-filtering results
- if (alreadyPaintedCount > 0) {
- console.log(`ā Pre-filter complete: ${alreadyPaintedCount} already painted pixels detected and added to progress`);
- console.log('ā¹ļø This detection will not happen again until a new image/save is loaded');
- // Update UI to reflect the new progress immediately
- await updateStats();
- }
- skippedPixels.alreadyPainted = alreadyPaintedCount;
- } else {
- // Pre-filtering already done this session, just filter for basic eligibility
- console.log('š Using existing pre-filter results (already done this session)');
- for (const [x, y] of coords) {
- const targetPixelInfo = checkPixelEligibility(x, y);
-
- if (!targetPixelInfo.eligible) {
- if (targetPixelInfo.reason !== 'alreadyPainted') {
- skippedPixels[targetPixelInfo.reason]++;
- }
- continue;
- }
-
- // Only include pixels that haven't been marked as painted yet
- let absX = startX + x;
- let absY = startY + y;
- let adderX = Math.floor(absX / 1000);
- let adderY = Math.floor(absY / 1000);
-
- if (!Utils.isPixelPainted(x, y, regionX + adderX, regionY + adderY)) {
- eligibleCoords.push([x, y, targetPixelInfo]);
- }
- }
- }
-
- // Paint eligible pixels (already pre-filtered, no duplicate checks)
- outerLoop: for (const [x, y, targetPixelInfo] of eligibleCoords) {
- if (state.stopFlag) {
- if (pixelBatch && pixelBatch.pixels.length > 0) {
- console.log(`šÆ Sending last batch before stop with ${pixelBatch.pixels.length} pixels`);
- await flushPixelBatch(pixelBatch);
- }
- state.lastPosition = { x, y };
- // Removed 'Paused at' message from main panel to reduce UI clutter
- return 'stopped';
- }
-
- // Check if we have charges left (local count, no API call)
- if (state.displayCharges <= 0) {
- // console.log("Try to buy paint charges");
- // await purchase("paint_charges");
- await updateStats();
- if (state.displayCharges <= 0) {
- console.log('ā” No charges left (local count), ending painting session');
- if (pixelBatch && pixelBatch.pixels.length > 0) {
- console.log(`šÆ Sending final batch with ${pixelBatch.pixels.length} pixels`);
- await flushPixelBatch(pixelBatch);
- }
- state.lastPosition = { x, y };
- return 'charges_depleted';
- }
- // else {
- // console.log(`š Charges after purchase: ${state.displayCharges}, continuing painting`);
- // }
- }
-
- let absX = startX + x;
- let absY = startY + y;
-
- let adderX = Math.floor(absX / 1000);
- let adderY = Math.floor(absY / 1000);
- let pixelX = absX % 1000;
- let pixelY = absY % 1000;
-
- const targetMappedColorId = targetPixelInfo.mappedColorId;
-
- // Set up pixel batch for new region if needed
- if (
- !pixelBatch ||
- 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`);
- const success = await flushPixelBatch(pixelBatch);
- if (!success) {
- console.error(`ā Batch failed permanently after retries. Stopping painting.`);
- state.stopFlag = true;
- return 'stopped';
- }
- await updateStats();
- }
-
- pixelBatch = {
- regionX: regionX + adderX,
- regionY: regionY + adderY,
- pixels: [],
- };
- }
-
- // Add pixel to batch (no need to check again - already pre-filtered)
- pixelBatch.pixels.push({
- x: pixelX,
- y: pixelY,
- color: targetMappedColorId,
- localX: x,
- localY: y,
- });
-
- // Send batch if it's full
- const maxBatchSize = calculateBatchSize();
- if (pixelBatch.pixels.length >= maxBatchSize) {
- console.log(`š¦ Sending batch with ${pixelBatch.pixels.length} pixels`);
- const success = await flushPixelBatch(pixelBatch);
- if (!success) {
- console.error(`ā Batch failed permanently after retries. Stopping painting.`);
- state.stopFlag = true;
- return 'stopped';
- }
- pixelBatch.pixels = [];
- await updateStats();
- }
- }
-
- // Send final batch if any pixels remain
- if (pixelBatch && pixelBatch.pixels.length > 0 && !state.stopFlag) {
- console.log(`š Sending final batch with ${pixelBatch.pixels.length} pixels`);
- const success = await flushPixelBatch(pixelBatch);
- if (!success) {
- console.warn(`ā ļø Final batch failed with ${pixelBatch.pixels.length} pixels`);
- }
- }
-
- // If we completed the entire coordinate loop, image is complete
- return state.stopFlag ? 'stopped' : 'completed';
-
- } finally {
- // Log skip statistics for this session
- console.log(`š Session Statistics:`);
- console.log(` New pixels painted: ${state.paintedPixels - (skippedPixels.alreadyPainted || 0)}`);
- console.log(` Already painted detected: ${skippedPixels.alreadyPainted}`);
- console.log(` Total progress: ${state.paintedPixels}`);
- console.log(` Pre-filtered - Transparent: ${skippedPixels.transparent}`);
- console.log(` Pre-filtered - White: ${skippedPixels.white}`);
- console.log(` Pre-filtered - Color Unavailable: ${skippedPixels.colorUnavailable}`);
- }
- }
-
- // Phase 2: Execute cooldown period - wait for target charges (NO token regeneration)
- async function executeCooldownPeriod() {
- console.log('ā±ļø Entering cooldown period - waiting for target charges');
- console.log('š« NO token regeneration during cooldown (even if expired/invalid)');
-
- // Check initial charges to calculate wait time
- let chargeCheckCount = 0;
- // const maxChargeChecks = 10; // REMOVED: No limit on API calls during cooldown
-
- while (!state.stopFlag) {
- chargeCheckCount++;
-
- const { charges, cooldown } = await WPlaceService.getCharges();
- state.displayCharges = Math.floor(charges);
- state.preciseCurrentCharges = charges;
- state.cooldown = cooldown;
-
- if (state.displayCharges >= state.cooldownChargeThreshold) {
- console.log(`ā
Cooldown target reached: ${state.displayCharges}/${state.cooldownChargeThreshold}`);
- NotificationManager.maybeNotifyChargesReached(true);
- await updateStats();
- return 'target_reached';
- }
-
- updateUI('noChargesThreshold', 'warning', {
- time: Utils.msToTimeText(state.cooldown),
- threshold: state.cooldownChargeThreshold,
- current: state.displayCharges,
- });
- await updateStats();
-
- // Smart delay calculation to reduce API calls
- const chargesNeeded = state.cooldownChargeThreshold - state.displayCharges;
- const estimatedWaitTime = chargesNeeded * state.cooldown;
-
- // Use longer delays during cooldown to prevent rate limiting
- let delayTime;
- if (chargeCheckCount < 3) {
- // First few checks - shorter delay
- delayTime = Math.max(3000, state.cooldown); // 3 seconds minimum
- } else if (estimatedWaitTime > 60000) {
- // Long wait expected - check every 15 seconds
- delayTime = 10000;
- } else if (estimatedWaitTime > 30000) {
- // Medium wait - check every 15 seconds
- delayTime = 15000;
- } else {
- // Close to target - check every 5 seconds
- delayTime = 5000;
- }
-
- console.log(`ā±ļø Cooldown check ${chargeCheckCount}: ${state.displayCharges}/${state.cooldownChargeThreshold} charges, waiting 10s before next check`);
- await Utils.sleep(10000);
-
- // REMOVED: No limit on charge checks - bot will wait infinitely until charges are available
- // if (chargeCheckCount >= maxChargeChecks) {
- // console.warn('ā ļø Max charge checks reached during cooldown, continuing anyway');
- // break;
- // }
- }
-
- return 'stopped';
- }
-
- // Phase 3: Regenerate token for new painting session
- async function regenerateTokenForNewSession() {
- console.log('š Regenerating token for new painting session');
-
- try {
- // Force regenerate token for new session
- await ensureToken(true); // forceRefresh = true
-
- if (!getTurnstileToken()) {
- console.error('ā Failed to generate token for new session');
- return false;
- }
-
- console.log('ā
Token regenerated successfully for new session');
- return true;
- } catch (error) {
- console.error('ā Token regeneration failed:', error);
- return false;
- }
- }
-
- // Finalize painting process cleanup
- async function finalizePaintingProcess() {
- console.log('š§¹ Finalizing painting process');
-
- if (window._chargesInterval) {
- clearInterval(window._chargesInterval);
- window._chargesInterval = null;
- }
-
- if (state.stopFlag) {
- // Save progress when stopped to preserve painted map
- Utils.saveProgress();
- } else {
- updateUI('paintingComplete', 'success', { count: state.paintedPixels });
- state.lastPosition = { x: 0, y: 0 }; // Only reset when truly complete
- Utils.saveProgress(); // Save final complete state
- overlayManager.clear();
- const toggleOverlayBtn = document.getElementById('toggleOverlayBtn');
- if (toggleOverlayBtn) {
- toggleOverlayBtn.classList.remove('active');
- toggleOverlayBtn.disabled = true;
- }
- }
- }
-
- // Helper function to check pixel eligibility (shared by painting functions)
- function checkPixelEligibility(x, y) {
- const { width, height, pixels } = state.imageData;
- const transparencyThreshold = state.customTransparencyThreshold || CONFIG.TRANSPARENCY_THRESHOLD;
-
- // CRITICAL FIX: Check module availability before processing
- if (!Utils || typeof Utils.isWhitePixel !== 'function') {
- console.error('ā Utils module not available for pixel eligibility check');
- return {
- eligible: false,
- reason: 'moduleUnavailable',
- };
- }
-
- const idx = (y * width + x) * 4;
- const r = pixels[idx],
- g = pixels[idx + 1],
- b = pixels[idx + 2],
- a = pixels[idx + 3];
-
- if (!state.paintTransparentPixels && a < transparencyThreshold)
- return {
- eligible: false,
- reason: 'transparent',
- };
- if (!state.paintWhitePixels && Utils.isWhitePixel(r, g, b))
- return {
- eligible: false,
- reason: 'white',
- };
-
- let targetRgb = Utils.isWhitePixel(r, g, b)
- ? [255, 255, 255]
- : Utils.findClosestPaletteColor(r, g, b, state.activeColorPalette);
-
- const mappedTargetColorId = Utils.resolveColor(
- targetRgb,
- state.availableColors,
- !state.paintUnavailablePixels
- );
-
- if (!state.paintUnavailablePixels && !mappedTargetColorId.id) {
- return {
- eligible: false,
- reason: 'colorUnavailable',
- r,
- g,
- b,
- a,
- mappedColorId: mappedTargetColorId.id,
- };
- }
- return { eligible: true, r, g, b, a, mappedColorId: mappedTargetColorId.id };
- }
-
- // Helper function to skip pixel and log the reason (minimized logging)
- function skipPixel(reason, id, rgb, x, y, skippedPixels) {
- // Minimize logging to prevent console flooding - only log non-routine skips
- if (reason !== 'transparent' && reason !== 'alreadyPainted') {
- console.log(`Skipped pixel for ${reason} (id: ${id}, (${rgb.join(', ')})) at (${x}, ${y})`);
- }
- skippedPixels[reason]++;
- }
- function calculateBatchSize() {
- let targetBatchSize;
-
- if (state.batchMode === 'random') {
- // Generate random batch size within the specified range
- const min = Math.max(1, state.randomBatchMin);
- const max = Math.max(min, state.randomBatchMax);
- targetBatchSize = Math.floor(Math.random() * (max - min + 1)) + min;
- console.log(`š² Random batch size generated: ${targetBatchSize} (range: ${min}-${max})`);
- } else {
- // Normal mode - use the fixed paintingSpeed value
- targetBatchSize = state.paintingSpeed;
- }
-
- // Always limit by available charges
- const maxAllowed = state.displayCharges;
- const finalBatchSize = Math.min(targetBatchSize, maxAllowed);
-
- return finalBatchSize;
- }
-
- // Helper function to retry batch until success with exponential backoff
- async function sendBatchWithRetry(pixels, regionX, regionY, maxRetries = MAX_BATCH_RETRIES) {
- let attempt = 0;
- 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;
- } else if (result === 'token_error') {
- console.log(`š Token error on attempt ${attempt} - no token available during processing`);
- console.log(`ā Stopping batch processing - tokens must be generated at startup/start button only`);
- updateUI('captchaFailed', 'error');
- return false; // Stop processing entirely - don't regenerate during processing
- } else if (result === 'token_regenerated') {
- console.log(`š Token regenerated on attempt ${attempt} after 403 error - retrying batch`);
- updateUI('paintingPaused', 'warning', { message: 'Token refreshed, resuming...' });
- // Don't count token regeneration as a failed attempt, retry immediately
- attempt--;
- await Utils.sleep(500); // Brief pause before retry
- continue;
- } else if (result === 'token_regeneration_failed') {
- console.log(`ā Token regeneration failed on attempt ${attempt} after 403 error`);
- updateUI('captchaFailed', 'error');
- return false; // Stop processing if we can't get a valid token
- } else if (result === 'invalid_token_error') {
- console.log(`š Invalid token detected on attempt ${attempt}, regenerating...`);
- updateUI('captchaSolving', 'warning');
- try {
- await handleCaptcha(true); // Allow generation for invalid token cases
- // Don't count token regeneration as a failed attempt
- attempt--;
- continue;
- } catch (e) {
- console.error(`ā Token regeneration failed after invalid token on attempt ${attempt}:`, e);
- updateUI('captchaFailed', 'error');
- // Wait longer before retrying after token failure
- await Utils.sleep(5000);
- }
- } else {
- console.warn(`ā ļø Batch failed on attempt ${attempt}, retrying...`);
- // Exponential backoff with jitter
- const baseDelay = Math.min(1000 * Math.pow(2, attempt - 1), 30000); // Max 30s
- const jitter = Math.random() * 1000; // Add up to 1s random delay
- 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 = getTurnstileToken();
-
- // Don't auto-generate tokens during processing - return error if no token available
- if (!token) {
- console.warn('ā ļø No token available and auto-generation disabled during processing');
- return 'token_error';
- }
-
- const coords = new Array(pixelBatch.length * 2);
- const colors = new Array(pixelBatch.length);
- for (let i = 0; i < pixelBatch.length; i++) {
- const pixel = pixelBatch[i];
- coords[i * 2] = pixel.x;
- coords[i * 2 + 1] = pixel.y;
- colors[i] = pixel.color;
- }
-
- try {
- const payload = { coords, colors, t: token, fp: fpStr32 };
- var wasmtoken = await createWasmToken(regionX, regionY, payload);
- const res = await fetch(`https://backend.wplace.live/s0/pixel/${regionX}/${regionY}`, {
- method: 'POST',
- headers: { 'Content-Type': 'text/plain;charset=UTF-8', "x-pawtect-token": wasmtoken },
- credentials: 'include',
- body: JSON.stringify(payload),
- });
-
- if (res.status === 403) {
- let data = null;
- try {
- data = await res.json();
- } catch (_) { }
- console.error('ā 403 Forbidden. Token invalid during painting - regeneration allowed.');
-
- // 403 errors during painting allow token regeneration per workflow requirements
- console.log('ļæ½ Token invalid (403) during painting - regenerating token as allowed by workflow');
- setTurnstileToken(null);
- createTokenPromise();
-
- // Attempt to regenerate token immediately
- const newToken = await ensureToken(true);
- if (newToken) {
- console.log('ā
Token regenerated after 403 error, returning regenerate signal');
- return 'token_regenerated';
- } else {
- console.error('ā Failed to regenerate token after 403 error');
- return 'token_regeneration_failed';
- }
- }
-
- const data = await res.json();
- return data?.painted === pixelBatch.length;
- } catch (e) {
- console.error('Batch paint request failed:', e);
- return false;
- }
- }
-
- function saveBotSettings() {
- try {
- const settings = {
- paintingSpeed: state.paintingSpeed,
- paintingSpeedEnabled: document.getElementById('enableSpeedToggle')?.checked,
- batchMode: state.batchMode, // "normal" or "random"
- randomBatchMin: state.randomBatchMin,
- randomBatchMax: state.randomBatchMax,
- cooldownChargeThreshold: state.cooldownChargeThreshold,
- tokenSource: state.tokenSource, // "generator", "hybrid", or "manual"
- 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,
- paintTransparentPixels: state.paintTransparentPixels,
- resizeSettings: state.resizeSettings,
- paintUnavailablePixels: state.paintUnavailablePixels,
- coordinateMode: state.coordinateMode,
- coordinateDirection: state.coordinateDirection,
- coordinateSnake: state.coordinateSnake,
- blockWidth: state.blockWidth,
- blockHeight: state.blockHeight, // 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,
- notifyOnlyWhenUnfocused: state.notifyOnlyWhenUnfocused,
- notificationIntervalMinutes: state.notificationIntervalMinutes,
- originalImage: state.originalImage,
- };
- CONFIG.PAINTING_SPEED_ENABLED = settings.paintingSpeedEnabled;
- // AUTO_CAPTCHA_ENABLED is always true - no need to save/load
-
- localStorage.setItem('wplace-bot-settings', JSON.stringify(settings));
- } catch (e) {
- console.warn('Could not save bot settings:', e);
- }
- }
-
- function loadBotSettings() {
- try {
- const saved = localStorage.getItem('wplace-bot-settings');
- if (!saved) return;
- const settings = JSON.parse(saved);
-
- state.paintingSpeed = settings.paintingSpeed || CONFIG.PAINTING_SPEED.DEFAULT;
- state.batchMode = settings.batchMode || CONFIG.BATCH_MODE; // Default to "normal"
- state.randomBatchMin = settings.randomBatchMin || CONFIG.RANDOM_BATCH_RANGE.MIN;
- state.randomBatchMax = settings.randomBatchMax || CONFIG.RANDOM_BATCH_RANGE.MAX;
- state.cooldownChargeThreshold =
- settings.cooldownChargeThreshold || CONFIG.COOLDOWN_CHARGE_THRESHOLD;
- state.tokenSource = settings.tokenSource || CONFIG.TOKEN_SOURCE; // Default to "generator"
- state.minimized = settings.minimized ?? false;
- CONFIG.PAINTING_SPEED_ENABLED = settings.paintingSpeedEnabled ?? false;
- 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.paintTransparentPixels = settings.paintTransparentPixels ?? false;
- state.resizeSettings = settings.resizeSettings ?? null;
- state.originalImage = settings.originalImage ?? null;
- state.paintUnavailablePixels = settings.paintUnavailablePixels ?? CONFIG.PAINT_UNAVAILABLE;
- state.coordinateMode = settings.coordinateMode ?? CONFIG.COORDINATE_MODE;
- state.coordinateDirection = settings.coordinateDirection ?? CONFIG.COORDINATE_DIRECTION;
- state.coordinateSnake = settings.coordinateSnake ?? CONFIG.COORDINATE_SNAKE;
- state.blockWidth = settings.blockWidth ?? CONFIG.COORDINATE_BLOCK_WIDTH;
- state.blockHeight = settings.blockHeight ?? CONFIG.COORDINATE_BLOCK_HEIGHT;
- // 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 < bin.length; i++) arr[i] = bin.charCodeAt(i);
- state.resizeIgnoreMask = arr;
- } catch {
- state.resizeIgnoreMask = null;
- }
- } else {
- state.resizeIgnoreMask = null;
- }
- // Initialize coordinate generation UI
- const coordinateModeSelect = document.getElementById('coordinateModeSelect');
- if (coordinateModeSelect) coordinateModeSelect.value = state.coordinateMode;
-
- const coordinateDirectionSelect = document.getElementById('coordinateDirectionSelect');
- if (coordinateDirectionSelect) coordinateDirectionSelect.value = state.coordinateDirection;
-
- const coordinateSnakeToggle = document.getElementById('coordinateSnakeToggle');
- if (coordinateSnakeToggle) coordinateSnakeToggle.checked = state.coordinateSnake;
-
- const settingsContainer = document.getElementById('wplace-settings-container');
- const directionControls = settingsContainer.querySelector('#directionControls');
- const snakeControls = settingsContainer.querySelector('#snakeControls');
- const blockControls = settingsContainer.querySelector('#blockControls');
- Utils.updateCoordinateUI({
- mode: state.coordinateMode,
- directionControls,
- snakeControls,
- blockControls,
- });
-
- const paintUnavailablePixelsToggle = document.getElementById('paintUnavailablePixelsToggle');
- if (paintUnavailablePixelsToggle) {
- paintUnavailablePixelsToggle.checked = state.paintUnavailablePixels;
- }
-
- const settingsPaintWhiteToggle = settingsContainer.querySelector('#settingsPaintWhiteToggle');
- if (settingsPaintWhiteToggle) {
- settingsPaintWhiteToggle.checked = state.paintWhitePixels;
- }
-
- const settingsPaintTransparentToggle = settingsContainer.querySelector(
- '#settingsPaintTransparentToggle'
- );
- if (settingsPaintTransparentToggle) {
- settingsPaintTransparentToggle.checked = state.paintTransparentPixels;
- }
-
- const speedSlider = document.getElementById('speedSlider');
- const speedInput = document.getElementById('speedInput');
- const speedValue = document.getElementById('speedValue');
- if (speedSlider) speedSlider.value = state.paintingSpeed;
- if (speedInput) speedInput.value = state.paintingSpeed;
- if (speedValue) speedValue.textContent = `pixels`;
-
- const enableSpeedToggle = document.getElementById('enableSpeedToggle');
- if (enableSpeedToggle) enableSpeedToggle.checked = CONFIG.PAINTING_SPEED_ENABLED;
-
- // Batch mode UI initialization
- const batchModeSelect = document.getElementById('batchModeSelect');
- if (batchModeSelect) batchModeSelect.value = state.batchMode;
-
- const normalBatchControls = document.getElementById('normalBatchControls');
- const randomBatchControls = document.getElementById('randomBatchControls');
-
- // Show/hide appropriate controls based on batch mode
- if (normalBatchControls && randomBatchControls) {
- if (state.batchMode === 'random') {
- normalBatchControls.style.display = 'none';
- randomBatchControls.style.display = 'block';
- } else {
- normalBatchControls.style.display = 'block';
- randomBatchControls.style.display = 'none';
- }
- }
-
- const randomBatchMin = document.getElementById('randomBatchMin');
- if (randomBatchMin) randomBatchMin.value = state.randomBatchMin;
-
- const randomBatchMax = document.getElementById('randomBatchMax');
- if (randomBatchMax) randomBatchMax.value = state.randomBatchMax;
-
- // AUTO_CAPTCHA_ENABLED is always true - no toggle to set
-
- const cooldownSlider = document.getElementById('cooldownSlider');
- const cooldownInput = document.getElementById('cooldownInput');
- const cooldownValue = document.getElementById('cooldownValue');
- if (cooldownSlider) cooldownSlider.value = state.cooldownChargeThreshold;
- if (cooldownInput) cooldownInput.value = state.cooldownChargeThreshold;
- if (cooldownValue) cooldownValue.textContent = `${Utils.t('charges')}`;
-
- const overlayOpacitySlider = document.getElementById('overlayOpacitySlider');
- if (overlayOpacitySlider) overlayOpacitySlider.value = state.overlayOpacity;
- const overlayOpacityValue = document.getElementById('overlayOpacityValue');
- if (overlayOpacityValue)
- overlayOpacityValue.textContent = `${Math.round(state.overlayOpacity * 100)}%`;
- const enableBlueMarbleToggle = document.getElementById('enableBlueMarbleToggle');
- if (enableBlueMarbleToggle) enableBlueMarbleToggle.checked = state.blueMarbleEnabled;
-
- const tokenSourceSelect = document.getElementById('tokenSourceSelect');
- if (tokenSourceSelect) tokenSourceSelect.value = state.tokenSource;
-
- const colorAlgorithmSelect = document.getElementById('colorAlgorithmSelect');
- if (colorAlgorithmSelect) colorAlgorithmSelect.value = state.colorMatchingAlgorithm;
- const enableChromaPenaltyToggle = document.getElementById('enableChromaPenaltyToggle');
- if (enableChromaPenaltyToggle) enableChromaPenaltyToggle.checked = state.enableChromaPenalty;
- const chromaPenaltyWeightSlider = document.getElementById('chromaPenaltyWeightSlider');
- if (chromaPenaltyWeightSlider) chromaPenaltyWeightSlider.value = state.chromaPenaltyWeight;
- const chromaWeightValue = document.getElementById('chromaWeightValue');
- if (chromaWeightValue) chromaWeightValue.textContent = state.chromaPenaltyWeight;
- const transparencyThresholdInput = document.getElementById('transparencyThresholdInput');
- if (transparencyThresholdInput)
- transparencyThresholdInput.value = state.customTransparencyThreshold;
- const whiteThresholdInput = document.getElementById('whiteThresholdInput');
- if (whiteThresholdInput) whiteThresholdInput.value = state.customWhiteThreshold;
- // Notifications UI
- const notifEnabledToggle = document.getElementById('notifEnabledToggle');
- if (notifEnabledToggle) notifEnabledToggle.checked = state.notificationsEnabled;
- const notifOnChargesToggle = document.getElementById('notifOnChargesToggle');
- if (notifOnChargesToggle) notifOnChargesToggle.checked = state.notifyOnChargesReached;
- const notifOnlyUnfocusedToggle = document.getElementById('notifOnlyUnfocusedToggle');
- if (notifOnlyUnfocusedToggle)
- notifOnlyUnfocusedToggle.checked = state.notifyOnlyWhenUnfocused;
- const notifIntervalInput = document.getElementById('notifIntervalInput');
- if (notifIntervalInput) notifIntervalInput.value = state.notificationIntervalMinutes;
- NotificationManager.resetEdgeTracking();
- } catch (e) {
- console.warn('Could not load bot settings:', e);
- }
- }
-
- // Initialize Turnstile generator integration
- console.log('š WPlace Auto-Image with Turnstile Token Generator loaded');
- console.log('š Turnstile token generator: ALWAYS ENABLED (Background mode)');
- console.log('šÆ Manual pixel captcha solving: Available as fallback/alternative');
- console.log('š± Turnstile widgets: DISABLED - pure background token generation only!');
-
- // Generate fingerprint string for requests
- if (!window.WPlaceTokenManager) {
- console.error('ā WPlaceTokenManager not available - dependency loading issue');
- return;
- }
- const fpStr32 = tokenManager._randStr(32);
- console.log('š Generated fingerprint string for API requests');
-
- // Function to enable file operations after initial startup setup is complete
- function enableFileOperations() {
- state.initialSetupComplete = true;
-
- const loadBtn = document.querySelector('#loadBtn');
- const loadFromFileBtn = document.querySelector('#loadFromFileBtn');
- const uploadBtn = document.querySelector('#uploadBtn');
-
- if (loadBtn) {
- loadBtn.disabled = false;
- loadBtn.title = '';
- // Add a subtle animation to indicate the button is now available
- loadBtn.style.animation = 'pulse 0.6s ease-in-out';
- setTimeout(() => {
- if (loadBtn) loadBtn.style.animation = '';
- }, 600);
- console.log('ā
Load Progress button enabled after initial setup');
- }
-
- if (loadFromFileBtn) {
- loadFromFileBtn.disabled = false;
- loadFromFileBtn.title = '';
- // Add a subtle animation to indicate the button is now available
- loadFromFileBtn.style.animation = 'pulse 0.6s ease-in-out';
- setTimeout(() => {
- if (loadFromFileBtn) loadFromFileBtn.style.animation = '';
- }, 600);
- console.log('ā
Load from File button enabled after initial setup');
- }
-
- if (uploadBtn) {
- uploadBtn.disabled = false;
- uploadBtn.title = '';
- // Add a subtle animation to indicate the button is now available
- uploadBtn.style.animation = 'pulse 0.6s ease-in-out';
- setTimeout(() => {
- if (uploadBtn) uploadBtn.style.animation = '';
- }, 600);
- console.log('ā
Upload Image button enabled after initial setup');
- }
-
- // Enable Load Extracted button at the same time as Upload button
- const loadExtractedBtn = document.getElementById('loadExtractedBtn');
- if (loadExtractedBtn) {
- loadExtractedBtn.disabled = false;
- loadExtractedBtn.title = '';
- console.log('ā
Load Extracted button enabled after initial setup');
- }
-
- // Show a notification that file operations are now available
- Utils.showAlert(Utils.t('fileOperationsAvailable'), 'success');
- }
-
- // Optimized token initialization with better timing and error handling
- async function initializeTokenGenerator() {
- // Skip if already have valid token
- if (isTokenValid()) {
- console.log('ā
Valid token already available, skipping initialization');
- updateUI('tokenReady', 'success');
- enableFileOperations(); // Enable file operations since initial setup is complete
- return;
- }
-
- try {
- console.log('š§ Initializing Turnstile token generator...');
- updateUI('initializingToken', 'default');
-
- console.log('Attempting to load Turnstile script...');
- await Utils.loadTurnstile();
- console.log('Turnstile script loaded. Attempting to generate token...');
-
- // Use TokenManager's handleCaptchaWithRetry method instead
- const token = await tokenManager.handleCaptchaWithRetry();
- if (token) {
- setTurnstileToken(token);
- console.log('ā
Startup token generated successfully');
- updateUI('tokenReady', 'success');
- Utils.showAlert(Utils.t('tokenGeneratorReady'), 'success');
- enableFileOperations(); // Enable file operations since initial setup is complete
- } else {
- console.warn(
- 'ā ļø Startup token generation failed (no token received), will retry when needed'
- );
- updateUI('tokenRetryLater', 'warning');
- // Still enable file operations even if initial token generation fails
- // Users can load progress and use manual/hybrid modes
- enableFileOperations();
- }
- } catch (error) {
- console.error('ā Critical error during Turnstile initialization:', error); // More specific error
- updateUI('tokenRetryLater', 'warning');
- // Still enable file operations even if initial setup fails
- // Users can load progress and use manual/hybrid modes
- enableFileOperations();
- // Don't show error alert for initialization failures, just log them
- }
- }
-
- // Load theme preference immediately on startup before creating UI
- loadThemePreference();
- applyTheme();
-
- var pawtect_chunk = null;
-
- //find module if pawtect_chunk is null
- pawtect_chunk ??= await findTokenModule("pawtect_wasm_bg.wasm");
-
- async function createWasmToken(regionX, regionY, payload) {
- try {
- // Load the Pawtect module and WASM
- const mod = await import(new URL('/_app/immutable/chunks/' + pawtect_chunk, location.origin).href);
- let wasm;
- try {
- wasm = await mod._();
- console.log('ā
WASM initialized successfully');
- } catch (wasmError) {
- console.error('ā WASM initialization failed:', wasmError);
- return null;
- }
- try {
- try {
- const me = await fetch(`https://backend.wplace.live/me`, { credentials: 'include' }).then(r => r.ok ? r.json() : null);
- if (me?.id) {
- mod.i(me.id);
- console.log('ā
user ID set:', me.id);
- }
- } catch { }
- } catch (userIdError) {
- console.log('ā ļø Error setting user ID:', userIdError.message);
- }
- try {
- const testUrl = `https://backend.wplace.live/s0/pixel/${regionX}/${regionY}`;
- if (mod.r) {
- mod.r(testUrl);
- console.log('ā
Request URL set:', testUrl);
- } else {
- console.log('ā ļø request_url function (mod.r) not available');
- }
- } catch (urlError) {
- console.log('ā ļø Error setting request URL:', urlError.message);
- }
-
- // Create test payload
-
- console.log('š payload:', payload);
-
- // Encode payload
- const enc = new TextEncoder();
- const dec = new TextDecoder();
- const bodyStr = JSON.stringify(payload);
- const bytes = enc.encode(bodyStr);
- console.log('š Payload size:', bytes.length, 'bytes');
- console.log('š Payload string:', bodyStr);
-
- // Allocate WASM memory with validation
- let inPtr;
- try {
- if (!wasm.__wbindgen_malloc) {
- console.error('ā __wbindgen_malloc function not found');
- return null;
- }
-
- inPtr = wasm.__wbindgen_malloc(bytes.length, 1);
- console.log('ā
WASM memory allocated, pointer:', inPtr);
-
- // Copy data to WASM memory
- const wasmBuffer = new Uint8Array(wasm.memory.buffer, inPtr, bytes.length);
- wasmBuffer.set(bytes);
- console.log('ā
Data copied to WASM memory');
- } catch (memError) {
- console.error('ā Memory allocation error:', memError);
- return null;
- }
-
- // Call the WASM function
- console.log('š Calling get_pawtected_endpoint_payload...');
- let outPtr, outLen, token;
- try {
- const result = wasm.get_pawtected_endpoint_payload(inPtr, bytes.length);
- console.log('ā
Function called, result type:', typeof result, result);
-
- if (Array.isArray(result) && result.length === 2) {
- [outPtr, outLen] = result;
- console.log('ā
Got output pointer:', outPtr, 'length:', outLen);
-
- // Decode the result
- const outputBuffer = new Uint8Array(wasm.memory.buffer, outPtr, outLen);
- token = dec.decode(outputBuffer);
- console.log('ā
Token decoded successfully');
- } else {
- console.error('ā Unexpected function result format:', result);
- return null;
- }
- } catch (funcError) {
- console.error('ā Function call error:', funcError);
- console.error('Stack trace:', funcError.stack);
- return null;
- }
-
- // Cleanup memory
- try {
- if (wasm.__wbindgen_free && outPtr && outLen) {
- wasm.__wbindgen_free(outPtr, outLen, 1);
- console.log('ā
Output memory freed');
- }
- if (wasm.__wbindgen_free && inPtr) {
- wasm.__wbindgen_free(inPtr, bytes.length, 1);
- console.log('ā
Input memory freed');
- }
- } catch (cleanupError) {
- console.log('ā ļø Cleanup warning:', cleanupError.message);
- }
-
- // Display results
- console.log('');
- console.log('š SUCCESS!');
- console.log('š Full token:');
- console.log(token);
- return token;
- } catch (error) {
- console.error('ā Failed to generate fp parameter:', error);
- return null;
- }
- }
-
- async function findTokenModule(str) {
- console.log('š Searching for wasm Module...');
- const links = Array.from(document.querySelectorAll('link[rel="modulepreload"][href$=".js"]'));
-
- for (const link of links) {
- try {
- const url = new URL(link.getAttribute("href"), location.origin).href;
- const code = await fetch(url).then(r => r.text());
- if (code.includes(str)) {
- console.log('ā
Found wasm Module...');
- return url.split('/').pop();
- }
- } catch { }
- }
- console.error(`ā Could not find Pawtect chunk: `, error);
- }
-
- async function purchase(type) {
- // loadThemePreference()
- let id;
- let chargeMultiplier;
- if (type === "max_charges") {
- id = 70;
- chargeMultiplier = 5;
- } else if (type === "paint_charges") {
- id = 80;
- chargeMultiplier = 30;
- } else {
- console.error("Error: Invalid purchase type provided.");
- return;
- }
- const { droplets } = await WPlaceService.getCharges();
- console.log("There are currently : ", droplets, "droplets.");
- try {
- const amounts = Math.floor(droplets / 500);
- if (amounts < 1) {
- console.log("Not enough droplets to purchase.");
- return;
- }
- const payload = {
- "product": {
- "id": id,
- "amount": amounts
- }
- };
- const res = fetch("https://backend.wplace.live/purchase", {
- method: "POST",
- headers: {
- "Content-Type": "text/plain;charset=UTF-8"
- },
- body: JSON.stringify(payload),
- credentials: "include"
- });
- // POST request completed
- const { droplets: newDroplets } = await WPlaceService.getCharges();
- if (droplets != newDroplets) {
- console.log("Successfully bought", amounts * chargeMultiplier, type.replace('_', ' '), ".");
- }
- else {
- console.log("Failed to buy charges");
- }
- } catch (e) {
- console.error("An error occurred during the purchase:", e);
- }
- }
- async function swapAccountTrigger(token) {
- // STRICT GUARD: Only allow account switching during active painting sessions OR controlled refresh
- if (!state.running && !state.isFetchingAllAccounts) {
- console.warn('š Account switching blocked - only allowed during active painting or controlled refresh');
- console.warn('š Current state.running:', state.running, 'state.isFetchingAllAccounts:', state.isFetchingAllAccounts);
- return false;
- }
-
- localStorage.removeItem("lp");
- if (!token) {
- console.error('ā Cannot swap account: token is null or undefined');
- return false;
- }
-
- console.log(`š Triggering account swap with token: ${token.substring(0, 20)}...`);
- console.log('š¤ Sending setCookie message to extension...');
-
- try {
- window.postMessage({
- source: 'my-userscript',
- type: 'setCookie',
- value: token
- }, '*');
-
- console.log('ā
setCookie message sent successfully');
- return true;
- } catch (error) {
- console.error('ā Failed to send setCookie message:', error);
- return false;
- }
- }
- async function getAccounts() {
- return new Promise((resolve, reject) => {
- console.log("Requesting accounts from extension...");
- // Ask extension for accounts
- window.postMessage({
- source: "my-userscript",
- type: "getAccounts"
- }, "*");
- function handler(event) {
- if (event.source !== window) return;
- if (event.data.source !== "extension") return;
- if (event.data.type === "accountsData") {
- window.removeEventListener("message", handler);
- // Save to localStorage
- try {
- localStorage.setItem("accounts", JSON.stringify(event.data.accounts));
- console.log("ā
Accounts saved to localStorage:", event.data.accounts);
- } catch (e) {
- console.error("ā Failed to save accounts:", e);
- }
- resolve(event.data.accounts);
- }
- }
- window.addEventListener("message", handler);
- });
- }
- async function fetchAccount() {
- const { ID, Charges, Max, Droplets } = await WPlaceService.fetchCheck();
- console.log("User's ID :", ID);
- console.log("User's Charges :", Charges, "/", Max);
- console.log("User's Droplets :", Droplets);
- }
-
- async function fetchAllAccountDetails() {
- if (state.isFetchingAllAccounts) {
- Utils.showAlert("Already fetching account details.", "warning");
- return;
- }
-
- state.isFetchingAllAccounts = true;
-
- const refreshBtn = document.getElementById('refreshAllAccountsBtn');
- if (refreshBtn) {
- refreshBtn.innerHTML = ' ';
- refreshBtn.disabled = true;
- }
-
- const accountsListArea = document.getElementById('accountsListArea');
- if (accountsListArea) {
- accountsListArea.innerHTML = `Fetching account details...
`;
- }
-
- let originalToken = null;
- let originalAccountData = null;
-
- try {
- await getAccounts();
- const accountsTokens = JSON.parse(localStorage.getItem("accounts")) || [];
- if (accountsTokens.length === 0) {
- if (accountsListArea) accountsListArea.innerHTML = `No accounts found.
`;
- return;
- }
-
- // Store original account info to switch back later
- originalAccountData = await WPlaceService.getCharges();
- originalToken = accountsTokens.find(token => {
- try {
- const payload = JSON.parse(atob(token.split('.')[1]));
- return payload.userId === originalAccountData.id;
- } catch (e) {
- return false;
- }
- });
-
- state.allAccountsInfo = [];
- renderAccountsList();
-
- // Temporarily disable the strict guard for controlled refresh operation
- const originalRunningState = state.running;
- state.running = true; // Temporarily enable to allow controlled switching
-
- console.log('š Temporarily enabling controlled account switching for refresh');
-
- for (let i = 0; i < accountsTokens.length; i++) {
- const token = accountsTokens[i];
-
- console.log(`š Fetching details for account ${i + 1}/${accountsTokens.length}`);
-
- // Controlled account switch for data fetching
- const swapResult = await swapAccountTrigger(token);
- if (!swapResult) {
- console.warn(`ā ļø Failed to trigger swap for account ${i + 1}`);
- continue;
- }
-
- // Wait for account switch to complete
- let retries = 0;
- let accountData = null;
- while (retries < 8 && !accountData) {
- // await Utils.sleep(1000);
- try {
- const fetchedInfo = await WPlaceService.fetchCheck();
- const chargesInfo = await WPlaceService.getCharges();
- if (fetchedInfo.ID) {
- accountData = {
- ...fetchedInfo,
- Charges: Math.floor(chargesInfo.charges),
- Max: Math.floor(chargesInfo.max),
- Droplets: Math.floor(chargesInfo.droplets)
- };
- }
- } catch (e) {
- retries++;
- console.warn(`ā ļø Retry ${retries}/8 for account ${i + 1}`);
- }
- }
-
- if (accountData) {
- const actualName = accountData.Username || accountData.name || `User${accountData.ID}`;
- const isCurrentAccount = accountData.ID === originalAccountData.id;
-
- state.allAccountsInfo.push({
- ...accountData,
- token,
- displayName: actualName,
- isCurrent: isCurrentAccount
- });
-
- console.log(`ā
Fetched details for: ${actualName} (${accountData.Charges}/${accountData.Max} charges)`);
- } else {
- // Fallback for failed accounts
- const fallbackName = `Account ${i + 1}`;
- state.allAccountsInfo.push({
- token,
- ID: `...${token.slice(-4)}`,
- displayName: fallbackName,
- isCurrent: false,
- error: 'Failed to fetch details'
- });
- console.warn(`ā Failed to fetch details for account ${i + 1}`);
- }
-
- renderAccountsList();
- }
-
- // Restore original state and switch back to original account
- state.running = originalRunningState;
- console.log('š Restored original running state and switching back to original account');
-
- } catch (error) {
- console.error("Error fetching all account details:", error);
- if (accountsListArea) accountsListArea.innerHTML = `Error loading accounts.
`;
- } finally {
- // Switch back to original account
- if (originalToken) {
- console.log('š Switching back to original account...');
- const originalRunningState = state.running;
- state.running = true; // Temporarily enable for switch back
-
- await swapAccountTrigger(originalToken);
- await Utils.sleep(2000);
-
- // Verify we're back on the original account
- try {
- const verifyData = await WPlaceService.getCharges();
- if (verifyData.id === originalAccountData.id) {
- console.log('ā
Successfully switched back to original account');
- }
- } catch (e) {
- console.warn('ā ļø Could not verify switch back to original account');
- }
-
- state.running = originalRunningState; // Restore state
- }
-
- // Update current account info in the list
- if (originalAccountData) {
- const currentAccountInList = state.allAccountsInfo.find(acc => acc.ID === originalAccountData.id);
- if (currentAccountInList) {
- currentAccountInList.isCurrent = true;
- currentAccountInList.Charges = Math.floor(originalAccountData.charges);
- currentAccountInList.Max = Math.floor(originalAccountData.max);
- currentAccountInList.Droplets = Math.floor(originalAccountData.droplets);
- }
- }
-
- // Keep original account order - do NOT rearrange the array
- // Instead, assign IDs based on current account being ID 1
- const currentAccountIndex = state.allAccountsInfo.findIndex(acc => acc.isCurrent);
-
- if (currentAccountIndex !== -1) {
- console.log(`šÆ Current account found at array position ${currentAccountIndex}`);
-
- // Assign IDs: current account gets ID 1, others get sequential IDs
- state.allAccountsInfo.forEach((acc, index) => {
- if (acc.isCurrent) {
- acc.orderId = 1; // Current account always gets ID 1
- } else {
- // Other accounts get IDs based on their position relative to current
- const relativePosition = index > currentAccountIndex ?
- (index - currentAccountIndex + 1) :
- (state.allAccountsInfo.length - currentAccountIndex + index + 1);
- acc.orderId = relativePosition;
- }
- });
-
- // Sort the display order by orderId for UI, but keep original array intact
- const sortedForDisplay = [...state.allAccountsInfo].sort((a, b) => a.orderId - b.orderId);
-
- // Create the switching order array (ID 1 first, then sequential)
- state.originalAccountOrder = sortedForDisplay;
-
- // Find the current account's position in the switching order (should be 0 since it's ID 1)
- state.currentActiveIndex = 0; // Current account (ID 1) is always first in switching order
- state.accountIndex = currentAccountIndex; // Keep track of original array position
-
- console.log(`š Account IDs assigned:`);
- state.allAccountsInfo.forEach((acc) => {
- console.log(` Array pos ${state.allAccountsInfo.indexOf(acc)}: ID ${acc.orderId} - ${acc.displayName}${acc.isCurrent ? ' (CURRENT)' : ''}`);
- });
-
- console.log(`š Switching order (by ID):`);
- state.originalAccountOrder.forEach((acc, idx) => {
- console.log(` Switch pos ${idx}: ID ${acc.orderId} - ${acc.displayName}${acc.isCurrent ? ' (CURRENT)' : ''}`);
- });
- } else {
- console.warn('ā ļø No current account found, using original order');
- state.allAccountsInfo.forEach((acc, index) => {
- acc.orderId = index + 1;
- });
- state.originalAccountOrder = [...state.allAccountsInfo];
- state.currentActiveIndex = 0;
- state.accountIndex = 0;
- }
-
- await updateStats();
- renderAccountsList();
-
- state.isFetchingAllAccounts = false;
- if (refreshBtn) {
- refreshBtn.innerHTML = ' ';
- refreshBtn.disabled = false;
- }
-
- console.log('ā
Account list refresh completed with full details');
- }
- }
-
- // Function to update current account charges in the account list
- async function updateCurrentAccountInList() {
- if (state.allAccountsInfo.length === 0) return;
-
- // Find current account in the list and update its charges
- const currentAccountInList = state.allAccountsInfo.find(acc => acc.isCurrent);
- if (currentAccountInList) {
- const { charges, cooldown } = await WPlaceService.getCharges();
- state.displayCharges = Math.floor(charges);
- state.preciseCurrentCharges = charges;
- await updateStats();
- currentAccountInList.Charges = Math.floor(state.displayCharges || state.preciseCurrentCharges || 0);
- currentAccountInList.Max = state.maxCharges;
- // Re-render the account list to show updated charges
- renderAccountsList();
- }
- }
-
- // Function to update current account spotlight when switching during painting
- async function updateCurrentAccountSpotlight() {
- if (state.allAccountsInfo.length === 0) return;
- await Utils.sleep(500); // Wait a bit for the switch to take effect
- try {
- const currentAccountData = await WPlaceService.getCharges();
- console.log("Current account after switch:", currentAccountData);
- console.log(`š Switched to account with ID: ${currentAccountData.id}`);
-
- // Update all accounts to not be current
- state.allAccountsInfo.forEach(acc => acc.isCurrent = false);
-
- // Find and mark the new current account in the original array
- const newCurrentAccount = state.allAccountsInfo.find(acc => acc.ID === currentAccountData.id);
- if (newCurrentAccount) {
- const currentAccountInfo = await WPlaceService.fetchCheck();
- newCurrentAccount.isCurrent = true;
- newCurrentAccount.Charges = Math.floor(currentAccountData.charges);
- newCurrentAccount.Max = Math.floor(currentAccountData.max);
- newCurrentAccount.Droplets = Math.floor(currentAccountData.droplets);
- newCurrentAccount.displayName = currentAccountInfo.Username || currentAccountInfo.name || newCurrentAccount.displayName;
-
- console.log(`šÆ Updated current account spotlight: ${newCurrentAccount.displayName} (ID ${newCurrentAccount.orderId})`);
-
- // Update the switching order array to reflect the current account change
- const switchingOrderIndex = state.originalAccountOrder.findIndex(acc => acc.orderId === newCurrentAccount.orderId);
- if (switchingOrderIndex !== -1) {
- state.originalAccountOrder[switchingOrderIndex] = newCurrentAccount;
- // DON'T update currentActiveIndex here - it's managed by switchToNextAccount()
- console.log(`šÆ Updated account data at switching order index ${switchingOrderIndex} (ID ${newCurrentAccount.orderId})`);
- console.log(`š Current state: activeIndex=${state.currentActiveIndex}, expectedIndex=${switchingOrderIndex}`);
- }
- }
-
- // Re-render the account list to show new current account
- renderAccountsList();
-
- } catch (error) {
- console.warn('ā ļø Failed to update account spotlight:', error);
- }
- }
-
- function renderAccountsList() {
- const accountsListArea = document.getElementById('accountsListArea');
- if (!accountsListArea) return;
-
- accountsListArea.innerHTML = '';
- if (state.allAccountsInfo.length === 0) {
- accountsListArea.innerHTML = `No account data. Click to refresh.
`;
- return;
- }
-
- // Render accounts in switching order (sorted by orderId)
- state.originalAccountOrder.forEach((info, switchIndex) => {
- const item = document.createElement('div');
-
- // Determine if this is the current account or next in sequence
- const isCurrentAccount = info.isCurrent;
- const isNextInSequence = state.currentActiveIndex !== -1 &&
- ((state.currentActiveIndex + 1) % state.originalAccountOrder.length) === switchIndex;
-
- let itemClasses = 'wplace-account-item';
- if (isCurrentAccount) {
- itemClasses += ' current';
- } else if (isNextInSequence) {
- itemClasses += ' next-in-sequence';
- }
-
- item.className = itemClasses;
-
- // Create ordering number element with order ID
- const orderNumber = document.createElement('div');
- orderNumber.className = 'wplace-account-number';
- orderNumber.textContent = info.orderId; // Always show the assigned order ID
-
- // Add visual indicator for current position in sequence
- if (isCurrentAccount) {
- orderNumber.style.background = '#2ecc71'; // Green color
- orderNumber.style.color = 'white';
- orderNumber.style.boxShadow = '0 0 10px rgba(46, 204, 113, 0.8)';
- orderNumber.title = `Order ID ${info.orderId} - Currently active account`;
-
- // Add a subtle pulsing effect to the account number for extra visibility
- orderNumber.style.animation = 'currentAccountNumberPulse 1.5s ease-in-out infinite alternate';
- } else if (isNextInSequence) {
- orderNumber.style.background = '#f39c12'; // Orange color
- orderNumber.style.color = 'white';
- orderNumber.style.boxShadow = '0 0 8px rgba(243, 156, 18, 0.6)';
- orderNumber.title = `Order ID ${info.orderId} - Next account in switching sequence`;
- } else {
- orderNumber.style.background = ''; // Reset to default
- orderNumber.style.boxShadow = ''; // Remove any glow
- orderNumber.title = `Order ID ${info.orderId}`;
- orderNumber.style.animation = ''; // Remove any animation
- }
-
- const displayName = info.displayName || `Account ${index + 1}`;
-
- const details = document.createElement('div');
- details.className = 'wplace-account-details';
- const nameDiv = document.createElement('div');
- nameDiv.className = 'wplace-account-name';
- nameDiv.textContent = displayName;
- nameDiv.title = displayName;
- details.appendChild(nameDiv);
-
- let stats;
- if (info.error) {
- stats = document.createElement('div');
- stats.className = 'wplace-account-stats';
- stats.style.color = 'red';
- stats.textContent = 'Error';
- } else {
- stats = document.createElement('div');
- stats.className = 'wplace-account-stats';
- stats.innerHTML = `
- ${Math.floor(info.Charges || 0)}/${Math.floor(info.Max || 0)}
- ${Math.floor(info.Droplets || 0)}
- `;
- }
-
- item.appendChild(orderNumber);
- item.appendChild(details);
- item.appendChild(stats);
- accountsListArea.appendChild(item);
- });
- }
-
- // Account switching helper functions
- async function switchToNextAccount(accounts) {
- const previousActiveIndex = state.currentActiveIndex;
-
- // Move to next account in strict ID order (next position in switching order)
- const nextIndex = (state.currentActiveIndex + 1) % state.originalAccountOrder.length;
-
- // Get the account info from switching order
- const nextAccountInfo = state.originalAccountOrder[nextIndex];
- const prevAccountInfo = state.originalAccountOrder[previousActiveIndex];
-
- // Strict validation: ensure ID 1 always goes to ID 2
- if (prevAccountInfo?.orderId === 1 && nextAccountInfo?.orderId !== 2) {
- console.error(`ā STRICT ORDER VIOLATION: ID 1 must switch to ID 2, but found ID ${nextAccountInfo?.orderId}`);
- // Find ID 2 account
- const id2Account = state.originalAccountOrder.find(acc => acc.orderId === 2);
- if (id2Account) {
- const id2Index = state.originalAccountOrder.indexOf(id2Account);
- console.log(`š Correcting: Forcing switch from ID 1 to ID 2 (index ${id2Index})`);
- state.currentActiveIndex = id2Index;
- } else {
- console.error(`ā FATAL: No ID 2 account found in switching order!`);
- return false;
- }
- } else {
- state.currentActiveIndex = nextIndex;
- }
-
- // Re-get account info after potential correction
- const finalNextAccountInfo = state.originalAccountOrder[state.currentActiveIndex];
-
- console.log(`š Switching from ID ${prevAccountInfo?.orderId} (${prevAccountInfo?.displayName || 'Unknown'}) to ID ${finalNextAccountInfo?.orderId} (${finalNextAccountInfo?.displayName || 'Unknown'})`);
- console.log(`š Index progression: ${previousActiveIndex} ā ${state.currentActiveIndex} (order: ${prevAccountInfo?.orderId} ā ${finalNextAccountInfo?.orderId})`);
-
- // Find the token for this account by matching the account in the original allAccountsInfo array
- const originalAccountIndex = state.allAccountsInfo.findIndex(acc => acc.orderId === finalNextAccountInfo?.orderId);
- if (originalAccountIndex === -1) {
- console.warn(`ā ļø Could not find account with ID ${finalNextAccountInfo?.orderId} in original array`);
- return false;
- }
-
- const nextToken = finalNextAccountInfo?.token;
- console.log(`š Next token for ID ${finalNextAccountInfo?.orderId}: ${nextToken ? nextToken.substring(0, 20) + '...' : 'INVALID'}`);
- console.log(`š Expected account: ${finalNextAccountInfo?.displayName} (ID ${finalNextAccountInfo?.orderId})`);
-
- if (!nextToken) {
- console.warn(`ā ļø Invalid token for ID ${finalNextAccountInfo?.orderId}, skipping...`);
- return false;
- }
-
- // Update accountIndex to match the position in the accounts token array
- const accountsIndex = accounts.findIndex(token => token === nextToken);
- if (accountsIndex !== -1) {
- state.accountIndex = accountsIndex;
- console.log(`š Found token at accounts array index: ${accountsIndex}`);
- console.log(`š Token from accounts[${accountsIndex}]: ${accounts[accountsIndex].substring(0, 20)}...`);
- } else {
- console.warn(`ā ļø Token not found in accounts array for ID ${finalNextAccountInfo?.orderId}`);
- console.warn(`š Available accounts tokens: ${accounts.map((t, i) => `[${i}] ${t.substring(0, 20)}...`).join(', ')}`);
- return false;
- }
-
- return await switchToSpecificAccount(nextToken, state.currentActiveIndex);
- }
-
- async function switchToSpecificAccount(token, originalOrderIndex) {
- // Get account name and ID for better logging
- const accountInfo = state.originalAccountOrder && state.originalAccountOrder[originalOrderIndex]
- ? state.originalAccountOrder[originalOrderIndex]
- : null;
- const accountName = accountInfo?.displayName || `Account ${originalOrderIndex + 1}`;
- const accountId = accountInfo?.orderId || (originalOrderIndex + 1);
-
- console.log(`š Attempting to switch to ID ${accountId} (${accountName})`);
- console.log(`š Using token: ${token.substring(0, 20)}...`);
-
- await swapAccountTrigger(token);
-
- // Give more time for the initial swap to process (like Acc-Switch.js)
- // console.log('ā³ Waiting 5 seconds for initial swap processing...');
- // await new Promise(resolve => setTimeout(resolve, 5000));
-
- let maxRetries = 20; // Match Acc-Switch.js retry count
- let retryCount = 0;
- let swapSuccess = false;
-
- while (!swapSuccess && retryCount < maxRetries) {
- console.log(`ā³ Waiting for account swap... (Attempt ${retryCount + 1}/${maxRetries})`);
-
- // Wait for a short period before checking (like Acc-Switch.js)
- await new Promise(resolve => setTimeout(resolve, 1000));
-
- try {
- // Use the same verification method as Acc-Switch.js
- await fetchAccount();
- console.log('ā
Account swap confirmed.');
- swapSuccess = true;
- } catch (error) {
- console.warn('ā Account swap not yet successful. Retrying...', error);
- retryCount++;
-
- // Re-trigger swap every 5 attempts
- if (retryCount % 5 === 0) {
- console.log('š Re-triggering account swap...');
- await swapAccountTrigger(token);
- await new Promise(resolve => setTimeout(resolve, 2000));
- }
- }
- }
-
- if (swapSuccess) {
- const { charges, cooldown } = await WPlaceService.getCharges();
- state.displayCharges = Math.floor(charges);
- state.preciseCurrentCharges = charges;
- state.cooldown = cooldown;
- // Don't update state.accountIndex here as it should be managed by the calling function
- Utils.performSmartSave();
- await updateStats();
-
- // Update account list spotlight to show new current account
- await updateCurrentAccountSpotlight();
-
- console.log(`ā
Successfully switched to ID ${accountId} (${accountName}) with ${Math.floor(charges)} charges`);
- return true;
- } else {
- console.error(`ā Failed to swap to ID ${accountId} (${accountName}) after multiple retries.`);
- return false;
- }
- }
-
- // Wait for dependencies before initializing UI
- async function waitForDependenciesAndInitialize() {
- // Wait for all global managers to be available
- let attempts = 0;
- const maxAttempts = 50; // 5 seconds max wait
-
- while (attempts < maxAttempts) {
- const managers = {
- utilsManager: window.globalUtilsManager,
- imageProcessor: window.globalImageProcessor,
- overlayManager: window.globalOverlayManager,
- tokenManager: window.globalTokenManager
- };
-
- const missingManagers = Object.entries(managers)
- .filter(([name, manager]) => !manager)
- .map(([name]) => name);
-
- if (missingManagers.length === 0) {
- console.log('ā
All global managers are available, initializing UI...');
- break;
- }
-
- console.log(`ā³ Waiting for managers: ${missingManagers.join(', ')} (attempt ${attempts + 1}/${maxAttempts})`);
- await new Promise(resolve => setTimeout(resolve, 100));
- attempts++;
- }
-
- if (attempts >= maxAttempts) {
- console.warn('ā ļø Some global managers not available after waiting, proceeding anyway...');
- console.log('Available managers:', {
- utilsManager: !!window.globalUtilsManager,
- imageProcessor: !!window.globalImageProcessor,
- overlayManager: !!window.globalOverlayManager,
- tokenManager: !!window.globalTokenManager
- });
- }
-
- return createUI();
- }
-
- waitForDependenciesAndInitialize().then(() => {
- // Generate token automatically after UI is ready
- setTimeout(initializeTokenGenerator, 1000);
-
- // Auto-refresh account list on startup
- setTimeout(() => {
- console.log('š Auto-refreshing account list on startup...');
- fetchAllAccountDetails();
- }, 2000);
-
- // Attach advanced color matching listeners (resize dialog)
- const advancedInit = () => {
- const chromaSlider = document.getElementById('chromaPenaltyWeightSlider');
- const chromaValue = document.getElementById('chromaWeightValue');
- const resetBtn = document.getElementById('resetAdvancedColorBtn');
- const algoSelect = document.getElementById('colorAlgorithmSelect');
- const chromaToggle = document.getElementById('enableChromaPenaltyToggle');
- const transInput = document.getElementById('transparencyThresholdInput');
- const whiteInput = document.getElementById('whiteThresholdInput');
- const ditherToggle = document.getElementById('enableDitheringToggle');
- if (algoSelect)
- algoSelect.addEventListener('change', (e) => {
- 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 (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(Utils.t('advancedColorSettingsReset'), 'success');
- });
- };
- // Delay to ensure resize UI built
- setTimeout(advancedInit, 500);
-
- // Add cleanup on page unload
- window.addEventListener('beforeunload', () => {
- Utils.cleanupTurnstile();
- });
- });
-})();
-
-
diff --git a/Extensions/scripts/Auto-Repair.js b/Extensions/scripts/Auto-Repair.js
deleted file mode 100644
index 1b4e0f2..0000000
--- a/Extensions/scripts/Auto-Repair.js
+++ /dev/null
@@ -1,3331 +0,0 @@
-// eslint-disable-next-line prettier/prettier
-; (async () => {
- // CONFIGURATION CONSTANTS
- const CONFIG = {
- COOLDOWN_DEFAULT: 31000,
- TRANSPARENCY_THRESHOLD: 100,
- WHITE_THRESHOLD: 250,
- LOG_INTERVAL: 10,
- PAINTING_SPEED: {
- MIN: 1,
- MAX: 1000,
- DEFAULT: 5,
- },
- TOKEN_SOURCE: 'generator', // "generator", "manual", or "hybrid"
- AUTONOMOUS_MODE: true, // Enable autonomous operation
- AUTO_TOKEN_REFRESH: true, // Automatically refresh tokens
- TOKEN_PRELOAD_BUFFER: 60000, // Preload tokens 1 minute before expiry
- MAX_RETRIES: 10,
- RETRY_DELAY_BASE: 1000,
- // Auto-batch configuration
- AUTO_BATCH_ENABLED: true, // Enable auto pixel batch sizing
- MAX_BATCH_SIZE: 500, // Maximum pixels per batch
- MIN_BATCH_SIZE: 1, // Minimum pixels per batch
- BATCH_OPTIMIZATION: true, // Optimize batch size based on detected damage
- COLOR_MAP: {
- 0: { id: 1, name: 'Black', rgb: { r: 0, g: 0, b: 0 } },
- 1: { id: 2, name: 'Dark Gray', rgb: { r: 60, g: 60, b: 60 } },
- 2: { id: 3, name: 'Gray', rgb: { r: 120, g: 120, b: 120 } },
- 3: { id: 4, name: 'Light Gray', rgb: { r: 210, g: 210, b: 210 } },
- 4: { id: 5, name: 'White', rgb: { r: 255, g: 255, b: 255 } },
- 5: { id: 6, name: 'Deep Red', rgb: { r: 96, g: 0, b: 24 } },
- 6: { id: 7, name: 'Red', rgb: { r: 237, g: 28, b: 36 } },
- 7: { id: 8, name: 'Orange', rgb: { r: 255, g: 127, b: 39 } },
- 8: { id: 9, name: 'Gold', rgb: { r: 246, g: 170, b: 9 } },
- 9: { id: 10, name: 'Yellow', rgb: { r: 249, g: 221, b: 59 } },
- 10: { id: 11, name: 'Light Yellow', rgb: { r: 255, g: 250, b: 188 } },
- 11: { id: 12, name: 'Dark Green', rgb: { r: 14, g: 185, b: 104 } },
- 12: { id: 13, name: 'Green', rgb: { r: 19, g: 230, b: 123 } },
- 13: { id: 14, name: 'Light Green', rgb: { r: 135, g: 255, b: 94 } },
- 14: { id: 15, name: 'Dark Teal', rgb: { r: 12, g: 129, b: 110 } },
- 15: { id: 16, name: 'Teal', rgb: { r: 16, g: 174, b: 166 } },
- 16: { id: 17, name: 'Light Teal', rgb: { r: 19, g: 225, b: 190 } },
- 17: { id: 20, name: 'Cyan', rgb: { r: 96, g: 247, b: 242 } },
- 18: { id: 44, name: 'Light Cyan', rgb: { r: 187, g: 250, b: 242 } },
- 19: { id: 18, name: 'Dark Blue', rgb: { r: 40, g: 80, b: 158 } },
- 20: { id: 19, name: 'Blue', rgb: { r: 64, g: 147, b: 228 } },
- 21: { id: 21, name: 'Indigo', rgb: { r: 107, g: 80, b: 246 } },
- 22: { id: 22, name: 'Light Indigo', rgb: { r: 153, g: 177, b: 251 } },
- 23: { id: 23, name: 'Dark Purple', rgb: { r: 120, g: 12, b: 153 } },
- 24: { id: 24, name: 'Purple', rgb: { r: 170, g: 56, b: 185 } },
- 25: { id: 25, name: 'Light Purple', rgb: { r: 224, g: 159, b: 249 } },
- 26: { id: 26, name: 'Dark Pink', rgb: { r: 203, g: 0, b: 122 } },
- 27: { id: 27, name: 'Pink', rgb: { r: 236, g: 31, b: 128 } },
- 28: { id: 28, name: 'Light Pink', rgb: { r: 243, g: 141, b: 169 } },
- 29: { id: 29, name: 'Dark Brown', rgb: { r: 104, g: 70, b: 52 } },
- 30: { id: 30, name: 'Brown', rgb: { r: 149, g: 104, b: 42 } },
- 31: { id: 31, name: 'Beige', rgb: { r: 248, g: 178, b: 119 } },
- 32: { id: 52, name: 'Light Beige', rgb: { r: 255, g: 197, b: 165 } },
- 33: { id: 32, name: 'Medium Gray', rgb: { r: 170, g: 170, b: 170 } },
- 34: { id: 33, name: 'Dark Red', rgb: { r: 165, g: 14, b: 30 } },
- 35: { id: 34, name: 'Light Red', rgb: { r: 250, g: 128, b: 114 } },
- 36: { id: 35, name: 'Dark Orange', rgb: { r: 228, g: 92, b: 26 } },
- 37: { id: 37, name: 'Dark Goldenrod', rgb: { r: 156, g: 132, b: 49 } },
- 38: { id: 38, name: 'Goldenrod', rgb: { r: 197, g: 173, b: 49 } },
- 39: { id: 39, name: 'Light Goldenrod', rgb: { r: 232, g: 212, b: 95 } },
- 40: { id: 40, name: 'Dark Olive', rgb: { r: 74, g: 107, b: 58 } },
- 41: { id: 41, name: 'Olive', rgb: { r: 90, g: 148, b: 74 } },
- 42: { id: 42, name: 'Light Olive', rgb: { r: 132, g: 197, b: 115 } },
- 43: { id: 43, name: 'Dark Cyan', rgb: { r: 15, g: 121, b: 159 } },
- 44: { id: 45, name: 'Light Blue', rgb: { r: 125, g: 199, b: 255 } },
- 45: { id: 46, name: 'Dark Indigo', rgb: { r: 77, g: 49, b: 184 } },
- 46: { id: 47, name: 'Dark Slate Blue', rgb: { r: 74, g: 66, b: 132 } },
- 47: { id: 48, name: 'Slate Blue', rgb: { r: 122, g: 113, b: 196 } },
- 48: { id: 49, name: 'Light Slate Blue', rgb: { r: 181, g: 174, b: 241 } },
- 49: { id: 53, name: 'Dark Peach', rgb: { r: 155, g: 82, b: 73 } },
- 50: { id: 54, name: 'Peach', rgb: { r: 209, g: 128, b: 120 } },
- 51: { id: 55, name: 'Light Peach', rgb: { r: 250, g: 182, b: 164 } },
- 52: { id: 50, name: 'Light Brown', rgb: { r: 219, g: 164, b: 99 } },
- 53: { id: 56, name: 'Dark Tan', rgb: { r: 123, g: 99, b: 82 } },
- 54: { id: 57, name: 'Tan', rgb: { r: 156, g: 132, b: 107 } },
- 55: { id: 36, name: 'Light Tan', rgb: { r: 214, g: 181, b: 148 } },
- 56: { id: 51, name: 'Dark Beige', rgb: { r: 209, g: 128, b: 81 } },
- 57: { id: 61, name: 'Dark Stone', rgb: { r: 109, g: 100, b: 63 } },
- 58: { id: 62, name: 'Stone', rgb: { r: 148, g: 140, b: 107 } },
- 59: { id: 63, name: 'Light Stone', rgb: { r: 205, g: 197, b: 158 } },
- 60: { id: 58, name: 'Dark Slate', rgb: { r: 51, g: 57, b: 65 } },
- 61: { id: 59, name: 'Slate', rgb: { r: 109, g: 117, b: 141 } },
- 62: { id: 60, name: 'Light Slate', rgb: { r: 179, g: 185, b: 209 } },
- 63: { id: 0, name: 'Transparent', rgb: null },
- },
- };
-
- // GLOBAL STATE
- const state = {
- running: false,
- imageLoaded: false,
- totalPixels: 0,
- paintedPixels: 0,
- availableColors: [],
- displayCharges: 0,
- maxCharges: 1,
- cooldown: CONFIG.COOLDOWN_DEFAULT,
- imageData: null,
- stopFlag: false,
- startPosition: null,
- region: null,
- paintWhitePixels: true,
- paintTransparentPixels: true, // Changed to true to fix transparent pixel detection
- autoRepairEnabled: false,
- autoRepairInterval: 30,
- autoRepairTimer: null,
- debugLogs: [],
- customTransparencyThreshold: CONFIG.TRANSPARENCY_THRESHOLD,
- customWhiteThreshold: CONFIG.WHITE_THRESHOLD,
- tokenSource: CONFIG.TOKEN_SOURCE,
- autonomousMode: CONFIG.AUTONOMOUS_MODE,
- autoTokenRefresh: CONFIG.AUTO_TOKEN_REFRESH,
- tokenPreloadBuffer: CONFIG.TOKEN_PRELOAD_BUFFER,
- retryCount: 0,
- tokenRetryTimer: null,
- tokenPreloadTimer: null,
- windowMinimized: false,
- lastAttackState: null,
- initialSetupComplete: false,
- // Auto-batch state
- autoBatchEnabled: CONFIG.AUTO_BATCH_ENABLED,
- currentBatchSize: CONFIG.MIN_BATCH_SIZE,
- batchOptimization: CONFIG.BATCH_OPTIMIZATION,
- };
-
- // Random string generator
- const randStr = (len, chars = 'abcdefghijklmnopqrstuvwxyz0123456789') =>
- [...Array(len)].map(() => chars[(crypto?.getRandomValues?.(new Uint32Array(1))[0] % chars.length) || Math.floor(Math.random() * chars.length)]).join('');
-
- const fpStr32 = randStr(32);
-
- // Enhanced Turnstile token handling - Actualizado con la lógica de new.txt lol
- let turnstileToken = null;
- let tokenExpiryTime = 0;
- let tokenGenerationInProgress = false;
- let _resolveToken = null;
- let tokenPromise = new Promise((resolve) => {
- _resolveToken = resolve;
- });
- let retryCount = 0;
- const MAX_RETRIES = 10;
- const MAX_BATCH_RETRIES = 10;
- const TOKEN_LIFETIME = 240000; // 4 minutes (tokens typically last 5 min, use 4 for safety)
-
- function setTurnstileToken(token) {
- if (_resolveToken) {
- _resolveToken(token);
- _resolveToken = null;
- }
- turnstileToken = token;
- tokenExpiryTime = Date.now() + TOKEN_LIFETIME;
- console.log('ā
Turnstile token set successfully');
- Utils.addDebugLog('Token cached successfully', 'success');
- }
-
- function isTokenValid() {
- return turnstileToken && Date.now() < tokenExpiryTime;
- }
-
- function invalidateToken() {
- turnstileToken = null;
- tokenExpiryTime = 0;
- console.log('šļø Token invalidated, will force fresh generation');
- Utils.addDebugLog('Token invalidated, will force fresh generation', 'warning');
- }
-
- async function ensureToken(forceRefresh = false) {
- // Return cached token if still valid and not forcing refresh
- if (isTokenValid() && !forceRefresh) {
- return turnstileToken;
- }
-
- // Invalidate token if forcing refresh
- if (forceRefresh) invalidateToken();
-
- // Avoid multiple simultaneous token generations
- if (tokenGenerationInProgress) {
- console.log('š Token generation already in progress, waiting...');
- await Utils.sleep(2000);
- return isTokenValid() ? turnstileToken : null;
- }
-
- tokenGenerationInProgress = true;
-
- try {
- console.log('š Token expired or missing, generating new one...');
- const token = await handleCaptchaWithRetry();
- if (token && token.length > 20) {
- setTurnstileToken(token);
- console.log('ā
Token captured and cached successfully');
- return token;
- }
-
- console.log('ā ļø Invisible Turnstile failed, forcing browser automation...');
- const fallbackToken = await handleCaptchaFallback();
- if (fallbackToken && fallbackToken.length > 20) {
- setTurnstileToken(fallbackToken);
- console.log('ā
Fallback token captured successfully');
- return fallbackToken;
- }
-
- console.log('ā All token generation methods failed');
- return null;
- } finally {
- tokenGenerationInProgress = false;
- }
- }
-
- async function handleCaptchaWithRetry() {
- const startTime = performance.now();
-
- try {
- const { sitekey, token: preGeneratedToken } = await Utils.obtainSitekeyAndToken();
-
- if (!sitekey) {
- throw new Error('No valid sitekey found');
- }
-
- console.log('š Using sitekey:', sitekey);
-
- if (typeof window !== 'undefined' && window.navigator) {
- console.log(
- 'š§ UA:',
- window.navigator.userAgent.substring(0, 50) + '...',
- 'Platform:',
- window.navigator.platform
- );
- }
-
- let token = null;
-
- if (
- preGeneratedToken &&
- typeof preGeneratedToken === 'string' &&
- preGeneratedToken.length > 20
- ) {
- console.log('ā»ļø Reusing pre-generated Turnstile token');
- token = preGeneratedToken;
- } else {
- if (isTokenValid()) {
- console.log('ā»ļø Using existing cached token (from previous session)');
- token = turnstileToken;
- } else {
- console.log('š Generating new token with executeTurnstile...');
- token = await Utils.executeTurnstile(sitekey, 'paint');
- if (token) setTurnstileToken(token);
- }
- }
-
- if (token && typeof token === 'string' && token.length > 20) {
- const elapsed = Math.round(performance.now() - startTime);
- console.log(`ā
Turnstile token generated successfully in ${elapsed}ms`);
- return token;
- } else {
- throw new Error(`Invalid or empty token received - Length: ${token?.length || 0}`);
- }
- } catch (error) {
- const elapsed = Math.round(performance.now() - startTime);
- console.error(`ā Turnstile token generation failed after ${elapsed}ms:`, error);
- throw error;
- }
- }
-
- async function handleCaptchaFallback() {
- return new Promise(async (resolve, reject) => {
- try {
- // Ensure we have a fresh promise to await for a new token capture
- if (!_resolveToken) {
- tokenPromise = new Promise((res) => {
- _resolveToken = res;
- });
- }
- const timeoutPromise = Utils.sleep(20000).then(() =>
- reject(new Error('Auto-CAPTCHA timed out.'))
- );
-
- const solvePromise = (async () => {
- 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);
-
- 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);
-
- const canvas = await Utils.waitForSelector('canvas', 200, 5000);
- if (!canvas) throw new Error('Could not find the canvas element.');
-
- canvas.setAttribute('tabindex', '0');
- canvas.focus();
- const rect = canvas.getBoundingClientRect();
- const centerX = Math.round(rect.left + rect.width / 2);
- const centerY = Math.round(rect.top + rect.height / 2);
-
- canvas.dispatchEvent(
- new MouseEvent('mousemove', {
- clientX: centerX,
- clientY: centerY,
- bubbles: true,
- })
- );
- canvas.dispatchEvent(
- new KeyboardEvent('keydown', {
- key: ' ',
- code: 'Space',
- bubbles: true,
- })
- );
- await Utils.sleep(50);
- canvas.dispatchEvent(
- new KeyboardEvent('keyup', {
- key: ' ',
- code: 'Space',
- bubbles: true,
- })
- );
- await Utils.sleep(500);
-
- // 800ms delay before sending confirmation
- await Utils.sleep(800);
-
- // Keep confirming until token is captured
- const confirmLoop = async () => {
- while (!turnstileToken) {
- 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'));
- confirmBtn = allPrimary.length ? allPrimary[allPrimary.length - 1] : null;
- }
- if (confirmBtn) {
- confirmBtn.click();
- }
- await Utils.sleep(500); // 500ms delay between confirmation attempts
- }
- };
-
- // Start confirmation loop and wait for token
- confirmLoop();
- const token = await tokenPromise;
- await Utils.sleep(300); // small delay after token is captured
- resolve(token);
- })();
-
- await Promise.race([solvePromise, timeoutPromise]);
- } catch (error) {
- console.error('Auto-CAPTCHA process failed:', error);
- reject(error);
- }
- });
- }
-
- // NUEVA LĆGICA DE INYECCIĆN - ACTUALIZADA DESDE new.txt
- function inject(callback) {
- try {
- const script = document.createElement('script');
- script.textContent = `(${callback})();`;
- document.documentElement?.appendChild(script);
- script.remove();
- } catch (error) {
- console.error('ā Injection error:', error);
- }
- }
-
- 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) {
- // š Debug log
- console.log(
- `šā
Turnstile Token Captured - Type: ${typeof payload.t}, Value: ${payload.t
- ? typeof payload.t === 'string'
- ? payload.t.length > 50
- ? payload.t.substring(0, 50) + '...'
- : payload.t
- : JSON.stringify(payload.t)
- : 'null/undefined'
- }, Length: ${payload.t?.length || 0}`
- );
- 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);
- Utils.addDebugLog('Token captured from injection system', 'success');
- updateTokenStatus();
- }
- });
-
- // NUEVO SISTEMA WASM - ACTUALIZADO DESDE new.txt
- var pawtect_chunk = null;
-
- // Find module if pawtect_chunk is null
- pawtect_chunk ??= await findTokenModule("pawtect_wasm_bg.wasm");
-
- async function createWasmToken(regionX, regionY, payload) {
- try {
- // Load the Pawtect module and WASM
- const mod = await import(new URL('/_app/immutable/chunks/'+pawtect_chunk, location.origin).href);
- let wasm;
- try {
- wasm = await mod._();
- console.log('ā
WASM initialized successfully');
- } catch (wasmError) {
- console.error('ā WASM initialization failed:', wasmError);
- return null;
- }
- try {
- try {
- const me = await fetch(`https://backend.wplace.live/me`, { credentials: 'include' }).then(r => r.ok ? r.json() : null);
- if (me?.id) {
- mod.i(me.id);
- console.log('ā
user ID set:', me.id);
- }
- } catch { }
- } catch (userIdError) {
- console.log('ā ļø Error setting user ID:', userIdError.message);
- }
- try {
- const testUrl = `https://backend.wplace.live/s0/pixel/${regionX}/${regionY}`;
- if (mod.r) {
- mod.r(testUrl);
- console.log('ā
Request URL set:', testUrl);
- } else {
- console.log('ā ļø request_url function (mod.r) not available');
- }
- } catch (urlError) {
- console.log('ā ļø Error setting request URL:', urlError.message);
- }
-
- console.log('š payload:', payload);
-
- // Encode payload
- const enc = new TextEncoder();
- const dec = new TextDecoder();
- const bodyStr = JSON.stringify(payload);
- const bytes = enc.encode(bodyStr);
- console.log('š Payload size:', bytes.length, 'bytes');
- console.log('š Payload string:', bodyStr);
-
- // Allocate WASM memory with validation
- let inPtr;
- try {
- if (!wasm.__wbindgen_malloc) {
- console.error('ā __wbindgen_malloc function not found');
- return null;
- }
-
- inPtr = wasm.__wbindgen_malloc(bytes.length, 1);
- console.log('ā
WASM memory allocated, pointer:', inPtr);
-
- // Copy data to WASM memory
- const wasmBuffer = new Uint8Array(wasm.memory.buffer, inPtr, bytes.length);
- wasmBuffer.set(bytes);
- console.log('ā
Data copied to WASM memory');
- } catch (memError) {
- console.error('ā Memory allocation error:', memError);
- return null;
- }
-
- // Call the WASM function
- console.log('š Calling get_pawtected_endpoint_payload...');
- let outPtr, outLen, token;
- try {
- const result = wasm.get_pawtected_endpoint_payload(inPtr, bytes.length);
- console.log('ā
Function called, result type:', typeof result, result);
-
- if (Array.isArray(result) && result.length === 2) {
- [outPtr, outLen] = result;
- console.log('ā
Got output pointer:', outPtr, 'length:', outLen);
-
- // Decode the result
- const outputBuffer = new Uint8Array(wasm.memory.buffer, outPtr, outLen);
- token = dec.decode(outputBuffer);
- console.log('ā
Token decoded successfully');
- } else {
- console.error('ā Unexpected function result format:', result);
- return null;
- }
- } catch (funcError) {
- console.error('ā Function call error:', funcError);
- console.error('Stack trace:', funcError.stack);
- return null;
- }
-
- // Cleanup memory
- try {
- if (wasm.__wbindgen_free && outPtr && outLen) {
- wasm.__wbindgen_free(outPtr, outLen, 1);
- console.log('ā
Output memory freed');
- }
- if (wasm.__wbindgen_free && inPtr) {
- wasm.__wbindgen_free(inPtr, bytes.length, 1);
- console.log('ā
Input memory freed');
- }
- } catch (cleanupError) {
- console.log('ā ļø Cleanup warning:', cleanupError.message);
- }
-
- console.log('š SUCCESS!');
- console.log('š Full token:', token);
- return token;
- } catch (error) {
- console.error('ā Failed to generate fp parameter:', error);
- return null;
- }
- }
-
- async function findTokenModule(str) {
- console.log('š Searching for wasm Module...');
- const links = Array.from(document.querySelectorAll('link[rel="modulepreload"][href$=".js"]'));
-
- for (const link of links) {
- try {
- const url = new URL(link.getAttribute("href"), location.origin).href;
- const code = await fetch(url).then(r => r.text());
- if (code.includes(str)) {
- console.log('ā
Found wasm Module...');
- return url.split('/').pop();
- }
- } catch { }
- }
- console.error(`ā Could not find Pawtect chunk with string: ${str}`);
- }
-
- // Audio notification system
- function playNotificationSound() {
- try {
- const audio = new Audio('https://cdn.pixabay.com/download/audio/2025/03/21/audio_9bec51b17f.mp3?filename=glass-break-316720.mp3');
- audio.volume = 0.5;
- audio.play().catch(() => {
- console.warn('Could not play notification sound');
- });
- } catch (error) {
- console.warn('Audio notification failed:', error);
- }
- }
-
- // Notification system for attacks and repairs
- function showAttackNotification(type, count = 0) {
- const existing = document.getElementById('attack-notification');
- if (existing) existing.remove();
-
- const notification = document.createElement('div');
- notification.id = 'attack-notification';
- notification.style.cssText = `
- position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);
- z-index: 15000; background: rgba(0,0,0,0.9); color: white;
- border-radius: 15px; padding: 20px; text-align: center;
- box-shadow: 0 10px 30px rgba(0,0,0,0.8);
- border: 2px solid ${type === 'attack' ? '#ff4444' : '#44ff44'};
- min-width: 350px; max-width: 450px;
- `;
-
- if (type === 'attack') {
- notification.innerHTML = `
-
-
-
-
- PIXEL ART ATTACKED!
-
-
- Detected ${count} damaged pixels!
-
- `;
- } else if (type === 'repaired') {
- notification.innerHTML = `
-
-
-
-
- COMPLETELY REPAIRED!
-
-
- All pixels have been restored!
-
- `;
- }
-
- document.body.appendChild(notification);
-
- setTimeout(() => {
- if (notification.parentNode) {
- notification.style.transition = 'opacity 0.5s ease';
- notification.style.opacity = '0';
- setTimeout(() => {
- if (notification.parentNode) notification.remove();
- }, 500);
- }
- }, 3000);
- }
-
- // Peaceful state display in main window
- function showPeacefulState() {
- const statusDiv = document.getElementById('status');
- if (statusDiv && !state.running) {
- statusDiv.innerHTML = `
-
-
-
No attacks detected - All secure
-
- `;
- }
- }
-
- // Typewriter effect for title
- function createTypewriterTitle() {
- const titleText = "WPlace Autonomous Repair Tool";
- let currentIndex = 0;
- const titleElement = document.getElementById('main-title');
-
- if (!titleElement) return;
-
- function typeNext() {
- if (currentIndex < titleText.length) {
- titleElement.textContent = titleText.substring(0, currentIndex + 1);
- currentIndex++;
- setTimeout(typeNext, 100);
- } else {
- setTimeout(() => {
- currentIndex = 0;
- titleElement.textContent = '';
- setTimeout(typeNext, 1000);
- }, 3000);
- }
- }
-
- typeNext();
- }
-
- // Fallback translations
- const TEXTS = {
- title: 'WPlace Autonomous Repair Tool',
- loadFromFile: 'Load Progress File',
- repairPixels: 'Repair Pixels',
- enableAutoRepair: 'Enable Auto Repair',
- repairInterval: 'Check Interval (seconds)',
- debug: 'Debug Console',
- clearDebug: 'Clear Debug',
- scanningForDamage: 'Scanning for damaged pixels...',
- damageDetected: 'Damage detected: {count} pixels',
- noDamageDetected: 'No damage found',
- repairingPixels: 'Repairing {count} damaged pixels...',
- repairComplete: 'Repair completed: {repaired} pixels fixed',
- autoRepairStarted: 'Auto repair started (every {interval}s)',
- autoRepairStopped: 'Auto repair stopped',
- fileLoaded: 'Progress file loaded successfully',
- invalidFile: 'Invalid file format',
- noImageData: 'No image data found in file',
- turnstileInstructions: 'Complete the verification',
- hideTurnstileBtn: 'Hide',
- tokenCapturedSuccess: 'Token captured successfully',
- autonomousModeActive: 'Autonomous mode active',
- tokenSystemReady: 'Advanced token system ready',
- fileOperationsAvailable: 'File operations available',
- initializingToken: 'Initializing token system...',
- tokenReady: 'Token system ready',
- };
-
- // UTILIDADES ACTUALIZADAS CON NUEVA IMPLEMENTACIĆN TURNSTILE
- const Utils = {
- sleep: (ms) => new Promise((r) => setTimeout(r, ms)),
-
- t: (key, params = {}) => {
- let text = TEXTS[key] || key;
- Object.keys(params).forEach((param) => {
- text = text.replace(`{${param}}`, params[param]);
- });
- return text;
- },
-
- showAlert: (message, type = 'info') => {
- const alertDiv = document.createElement('div');
- alertDiv.style.cssText = `
- position: fixed; top: 20px; right: 20px; z-index: 10000;
- padding: 12px 16px; border-radius: 8px; color: white; font-weight: 500;
- background: ${type === 'success' ? '#28a745' : type === 'error' ? '#dc3545' : type === 'warning' ? '#ffc107' : '#17a2b8'};
- box-shadow: 0 4px 12px rgba(0,0,0,0.3);
- max-width: 300px; word-wrap: break-word;
- `;
- alertDiv.textContent = message;
- document.body.appendChild(alertDiv);
-
- setTimeout(() => {
- alertDiv.style.transition = 'opacity 0.3s ease';
- alertDiv.style.opacity = '0';
- setTimeout(() => {
- if (alertDiv.parentNode) alertDiv.remove();
- }, 300);
- }, 4000);
- },
-
- addDebugLog: (message, type = 'info') => {
- const timestamp = new Date().toLocaleTimeString();
- state.debugLogs.push({ timestamp, message, type });
-
- if (state.debugLogs.length > 150) {
- state.debugLogs = state.debugLogs.slice(-150);
- }
-
- updateDebugConsole();
- console.log(`[${timestamp}] ${message}`);
- },
-
- waitForSelector: async (selector, interval = 200, timeout = 5000) => {
- const start = Date.now();
- while (Date.now() - start < timeout) {
- const el = document.querySelector(selector);
- if (el) return el;
- await Utils.sleep(interval);
- }
- return null;
- },
-
- 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'));
- }
- };
- reader.onerror = () => reject(new Error('File reading error'));
- reader.readAsText(file);
- } else {
- reject(new Error('No file selected'));
- }
- };
- input.click();
- }),
-
- isWhitePixel: (r, g, b) => {
- const wt = state.customWhiteThreshold || CONFIG.WHITE_THRESHOLD;
- return r >= wt && g >= wt && b >= wt;
- },
-
- resolveColor(targetRgb, availableColors) {
- if (!availableColors || availableColors.length === 0) {
- return { id: null, rgb: targetRgb };
- }
-
- let bestId = availableColors[0].id;
- let bestRgb = [...availableColors[0].rgb];
- let bestScore = Infinity;
-
- for (let i = 0; i < availableColors.length; i++) {
- const c = availableColors[i];
- const [r, g, b] = c.rgb;
- const rmean = (r + targetRgb[0]) / 2;
- const rdiff = r - targetRgb[0];
- const gdiff = g - targetRgb[1];
- const bdiff = b - targetRgb[2];
- const dist = Math.sqrt(
- (((512 + rmean) * rdiff * rdiff) >> 8) +
- 4 * gdiff * gdiff +
- (((767 - rmean) * bdiff * bdiff) >> 8)
- );
- if (dist < bestScore) {
- bestScore = dist;
- bestId = c.id;
- bestRgb = [...c.rgb];
- if (dist === 0) break;
- }
- }
-
- return { id: bestId, rgb: bestRgb };
- },
-
- calculateTileRange(
- startRegionX,
- startRegionY,
- startPixelX,
- startPixelY,
- width,
- height,
- tileSize = 1000
- ) {
- const endPixelX = startPixelX + width;
- const endPixelY = startPixelY + height;
-
- return {
- startTileX: startRegionX + Math.floor(startPixelX / tileSize),
- startTileY: startRegionY + Math.floor(startPixelY / tileSize),
- endTileX: startRegionX + Math.floor((endPixelX - 1) / tileSize),
- endTileY: startRegionY + Math.floor((endPixelY - 1) / tileSize),
- };
- },
-
- dynamicSleep: async function (tickAndGetRemainingMs) {
- let remaining = Math.max(0, await tickAndGetRemainingMs());
- while (remaining > 0) {
- const interval = remaining > 5000 ? 2000 : remaining > 1000 ? 500 : 100;
- await this.sleep(Math.min(interval, remaining));
- remaining = Math.max(0, await tickAndGetRemainingMs());
- }
- },
-
- // NUEVA IMPLEMENTACIĆN TURNSTILE COMPLETA - ACTUALIZADA DESDE new.txt
- turnstileLoaded: false,
- _turnstileContainer: null,
- _turnstileOverlay: null,
- _turnstileWidgetId: null,
- _lastSitekey: null,
- _cachedSitekey: null,
-
- async loadTurnstile() {
- if (window.turnstile) {
- this.turnstileLoaded = true;
- return Promise.resolve();
- }
-
- return new Promise((resolve, reject) => {
- if (document.querySelector('script[src^="https://challenges.cloudflare.com/turnstile/v0/api.js"]')) {
- const checkReady = () => {
- if (window.turnstile) {
- this.turnstileLoaded = true;
- resolve();
- } else {
- setTimeout(checkReady, 100);
- }
- };
- return checkReady();
- }
-
- const script = document.createElement('script');
- script.src = 'https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit';
- script.async = true;
- script.defer = true;
- script.onload = () => {
- this.turnstileLoaded = true;
- Utils.addDebugLog('Turnstile script loaded successfully', 'success');
- resolve();
- };
- script.onerror = () => {
- Utils.addDebugLog('Failed to load Turnstile script', 'error');
- reject(new Error('Failed to load Turnstile'));
- };
- document.head.appendChild(script);
- });
- },
-
- ensureTurnstileContainer() {
- if (!this._turnstileContainer || !document.body.contains(this._turnstileContainer)) {
- if (this._turnstileContainer) {
- this._turnstileContainer.remove();
- }
-
- this._turnstileContainer = document.createElement('div');
- this._turnstileContainer.className = 'wplace-turnstile-hidden';
- this._turnstileContainer.setAttribute('aria-hidden', 'true');
- this._turnstileContainer.id = 'turnstile-widget-container';
- document.body.appendChild(this._turnstileContainer);
- }
- return this._turnstileContainer;
- },
-
- ensureTurnstileOverlayContainer() {
- if (this._turnstileOverlay && document.body.contains(this._turnstileOverlay)) {
- return this._turnstileOverlay;
- }
-
- const overlay = document.createElement('div');
- overlay.id = 'turnstile-overlay-container';
- overlay.className = 'wplace-turnstile-overlay wplace-overlay-hidden';
-
- const title = document.createElement('div');
- title.textContent = Utils.t('turnstileInstructions');
- title.className = 'wplace-turnstile-title';
-
- const host = document.createElement('div');
- host.id = 'turnstile-overlay-host';
- host.className = 'wplace-turnstile-host';
-
- const hideBtn = document.createElement('button');
- hideBtn.textContent = Utils.t('hideTurnstileBtn');
- hideBtn.className = 'wplace-turnstile-hide-btn';
- hideBtn.addEventListener('click', () => overlay.remove());
-
- overlay.appendChild(title);
- overlay.appendChild(host);
- overlay.appendChild(hideBtn);
- document.body.appendChild(overlay);
-
- this._turnstileOverlay = overlay;
- return overlay;
- },
-
- async executeTurnstile(sitekey, action = 'paint') {
- await this.loadTurnstile();
-
- // Try reusing existing widget first if sitekey matches
- if (this._turnstileWidgetId && this._lastSitekey === sitekey && window.turnstile?.execute) {
- try {
- console.log('š Reusing existing Turnstile widget...');
- const token = await Promise.race([
- window.turnstile.execute(this._turnstileWidgetId, { action }),
- new Promise((_, reject) =>
- setTimeout(() => reject(new Error('Execute timeout')), 15000)
- ),
- ]);
- if (token && token.length > 20) {
- console.log('ā
Token generated via widget reuse');
- return token;
- }
- } catch (error) {
- console.log(' Widget reuse failed, will create a fresh widget:', error.message);
- }
- }
-
- // Try invisible widget first
- const invisibleToken = await this.createTurnstileWidget(sitekey, action);
- if (invisibleToken && invisibleToken.length > 20) {
- return invisibleToken;
- }
-
- console.log(' Falling back to interactive Turnstile (visible).');
- return await this.createTurnstileWidgetInteractive(sitekey, action);
- },
-
- async createTurnstileWidget(sitekey, action) {
- return new Promise((resolve) => {
- try {
- // Force cleanup of any existing widget
- if (this._turnstileWidgetId && window.turnstile?.remove) {
- 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,
- action,
- size: 'invisible',
- retry: 'auto',
- 'retry-interval': 8000,
- callback: (token) => {
- console.log('ā
Invisible Turnstile callback');
- resolve(token);
- },
- 'error-callback': () => resolve(null),
- 'timeout-callback': () => resolve(null),
- });
-
- this._turnstileWidgetId = widgetId;
- this._lastSitekey = sitekey;
-
- if (!widgetId) {
- return resolve(null);
- }
-
- // Execute the widget and race with timeout
- Promise.race([
- window.turnstile.execute(widgetId, { action }),
- new Promise((_, reject) =>
- setTimeout(() => reject(new Error('Invisible execute timeout')), 12000)
- ),
- ])
- .then(resolve)
- .catch(() => resolve(null));
- } catch (e) {
- console.error('ā Invisible Turnstile creation failed:', e);
- resolve(null);
- }
- });
- },
-
- 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);
- } 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,
- size: 'normal',
- theme: 'light',
- callback: (token) => {
- clearTimeout(timeout);
- 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 {
- console.warn('ā Invalid token from interactive widget');
- resolve(null);
- }
- },
- 'error-callback': (error) => {
- clearTimeout(timeout);
- 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);
- } else {
- console.log('ā
Interactive Turnstile widget created, waiting for user interaction...');
- }
- } catch (e) {
- console.error('ā Interactive Turnstile creation failed:', e);
- resolve(null);
- }
- });
- },
-
- cleanupTurnstile() {
- if (this._turnstileWidgetId && window.turnstile?.remove) {
- try {
- window.turnstile.remove(this._turnstileWidgetId);
- } catch (e) {
- 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;
- this._lastSitekey = null;
- },
-
- // DETECCIĆN DE SITEKEY MEJORADA - ACTUALIZADA DESDE new.txt
- async obtainSitekeyAndToken(fallback = '0x4AAAAAABpqJe8FO0N84q0F') {
- // Cache sitekey to avoid repeated DOM queries
- if (this._cachedSitekey) {
- console.log('š Using cached sitekey:', this._cachedSitekey);
-
- return isTokenValid()
- ? {
- sitekey: this._cachedSitekey,
- token: turnstileToken,
- }
- : { sitekey: this._cachedSitekey, token: null };
- }
-
- // List of potential sitekeys to try
- const potentialSitekeys = [
- '0x4AAAAAABpqJe8FO0N84q0F', // WPlace common sitekey
- '0x4AAAAAAAJ7xjKAp6Mt_7zw', // Alternative WPlace sitekey
- '0x4AAAAAADm5QWx6Ov2LNF2g', // Another common sitekey
- ];
- const trySitekey = async (sitekey, source) => {
- if (!sitekey || sitekey.length < 10) return null;
-
- console.log(`š Testing sitekey from ${source}:`, sitekey);
- const token = await this.executeTurnstile(sitekey);
-
- if (token && token.length >= 20) {
- console.log(`ā
Valid token generated from ${source} sitekey`);
- setTurnstileToken(token);
- this._cachedSitekey = sitekey;
- return { sitekey, token };
- } else {
- console.log(`ā Failed to get token from ${source} sitekey`);
- return null;
- }
- };
-
- try {
- // 1ļøā£ data-sitekey attribute
- const sitekeySel = document.querySelector('[data-sitekey]');
- if (sitekeySel) {
- const sitekey = sitekeySel.getAttribute('data-sitekey');
- const result = await trySitekey(sitekey, 'data attribute');
- if (result) {
- return result;
- }
- }
-
- // 2ļøā£ Turnstile element
- const turnstileEl = document.querySelector('.cf-turnstile');
- if (turnstileEl?.dataset?.sitekey) {
- const sitekey = turnstileEl.dataset.sitekey;
- const result = await trySitekey(sitekey, 'turnstile element');
- if (result) {
- return result;
- }
- }
-
- // 3ļøā£ Meta tags
- const metaTags = document.querySelectorAll(
- 'meta[name*="turnstile"], meta[property*="turnstile"]'
- );
- for (const meta of metaTags) {
- const content = meta.getAttribute('content');
- const result = await trySitekey(content, 'meta tag');
- if (result) {
- return result;
- }
- }
-
- // 4ļøā£ Global variable
- if (window.__TURNSTILE_SITEKEY) {
- const result = await trySitekey(window.__TURNSTILE_SITEKEY, 'global variable');
- if (result) {
- return result;
- }
- }
-
- // 5ļøā£ Script tags
- const scripts = document.querySelectorAll('script');
- for (const script of scripts) {
- const content = script.textContent || script.innerHTML;
- const match = content.match(
- /(?:sitekey|data-sitekey)['"\s\[\]:\=\(]*['"]?([0-9a-zA-Z_-]{20,})['"]?/i
- );
- if (match && match[1]) {
- const extracted = match[1].replace(/['"]/g, '');
- const result = await trySitekey(extracted, 'script content');
- if (result) {
- return result;
- }
- }
- }
-
- // 6ļøā£ Known potential sitekeys
- console.log('š Testing known potential sitekeys...');
- for (const testSitekey of potentialSitekeys) {
- const result = await trySitekey(testSitekey, 'known list');
- if (result) {
- return result;
- }
- }
- } catch (error) {
- console.warn('ā ļø Error during sitekey detection:', error);
- }
-
- // 7ļøā£ Fallback
- console.log('š§ Trying fallback sitekey:', fallback);
- const fallbackResult = await trySitekey(fallback, 'fallback');
- if (fallbackResult) {
- return fallbackResult;
- }
-
- console.error('ā No working sitekey or token found.');
- return { sitekey: null, token: null };
- },
- };
-
- // Enhanced Overlay Manager for pixel detection with autonomous capabilities
- class OverlayManager {
- constructor() {
- this.originalTiles = new Map();
- this.originalTilesData = new Map();
- this.tileSize = 1000;
- this.loadingPromises = new Map();
- this.autonomousMode = state.autonomousMode;
- }
-
- async processAndRespondToTileRequest(eventData) {
- const { endpoint, blobID, blobData } = eventData;
-
- 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}`;
-
- try {
- const originalBitmap = await createImageBitmap(blobData);
- this.originalTiles.set(tileKey, originalBitmap);
-
- try {
- let canvas, ctx;
- if (typeof OffscreenCanvas !== 'undefined') {
- canvas = new OffscreenCanvas(originalBitmap.width, originalBitmap.height);
- ctx = canvas.getContext('2d');
- } else {
- canvas = document.createElement('canvas');
- canvas.width = originalBitmap.width;
- canvas.height = originalBitmap.height;
- ctx = canvas.getContext('2d');
- }
- ctx.imageSmoothingEnabled = false;
- ctx.drawImage(originalBitmap, 0, 0);
- const imgData = ctx.getImageData(0, 0, originalBitmap.width, originalBitmap.height);
-
- this.originalTilesData.set(tileKey, {
- w: originalBitmap.width,
- h: originalBitmap.height,
- data: new Uint8ClampedArray(imgData.data),
- });
-
- if (this.autonomousMode) {
- Utils.addDebugLog(`Auto-cached tile: ${tileKey} (${originalBitmap.width}x${originalBitmap.height})`, 'info');
- }
- } catch (e) {
- Utils.addDebugLog(`Failed to cache tile ImageData: ${tileKey} - ${e.message}`, 'warning');
- }
- } catch (e) {
- Utils.addDebugLog(`Failed to create tile bitmap: ${tileKey} - ${e.message}`, 'error');
- }
- }
-
- window.postMessage(
- {
- source: 'auto-image-overlay',
- blobID: blobID,
- blobData: blobData,
- },
- '*'
- );
- }
-
- async getTilePixelColor(tileX, tileY, pixelX, pixelY) {
- const tileKey = `${tileX},${tileY}`;
- const alphaThresh = state.customTransparencyThreshold || CONFIG.TRANSPARENCY_THRESHOLD;
-
- const cached = this.originalTilesData.get(tileKey);
- if (cached && cached.data && cached.w > 0 && cached.h > 0) {
- const x = Math.max(0, Math.min(cached.w - 1, pixelX));
- const y = Math.max(0, Math.min(cached.h - 1, pixelY));
- const idx = (y * cached.w + x) * 4;
- const d = cached.data;
- const a = d[idx + 3];
-
- // Always return pixel data, let caller decide about transparency
- return [d[idx], d[idx + 1], d[idx + 2], a];
- }
-
- const bitmap = this.originalTiles.get(tileKey);
- if (!bitmap) {
- if (this.autonomousMode) {
- Utils.addDebugLog(`Tile ${tileKey} not available, requesting load...`, 'warning');
- }
- return null;
- }
-
- try {
- let canvas, ctx;
- if (typeof OffscreenCanvas !== 'undefined') {
- canvas = new OffscreenCanvas(bitmap.width, bitmap.height);
- ctx = canvas.getContext('2d');
- } else {
- canvas = document.createElement('canvas');
- canvas.width = bitmap.width;
- canvas.height = bitmap.height;
- ctx = canvas.getContext('2d');
- }
- ctx.imageSmoothingEnabled = false;
- ctx.drawImage(bitmap, 0, 0);
-
- const x = Math.max(0, Math.min(bitmap.width - 1, pixelX));
- const y = Math.max(0, Math.min(bitmap.height - 1, pixelY));
- const data = ctx.getImageData(x, y, 1, 1).data;
- const a = data[3];
-
- // Always return pixel data, let caller decide about transparency
- return [data[0], data[1], data[2], a];
- } catch (e) {
- Utils.addDebugLog(`Error reading pixel from tile ${tileKey}: ${e.message}`, 'error');
- return null;
- }
- }
-
- async waitForTiles(startRegionX, startRegionY, pixelWidth, pixelHeight, startPixelX = 0, startPixelY = 0, timeoutMs = 15000) {
- const { startTileX, startTileY, endTileX, endTileY } = Utils.calculateTileRange(
- startRegionX,
- startRegionY,
- startPixelX,
- startPixelY,
- pixelWidth,
- pixelHeight,
- this.tileSize
- );
-
- const requiredTiles = [];
- for (let ty = startTileY; ty <= endTileY; ty++) {
- for (let tx = startTileX; tx <= endTileX; tx++) {
- requiredTiles.push(`${tx},${ty}`);
- }
- }
-
- if (requiredTiles.length === 0) return true;
-
- Utils.addDebugLog(`Waiting for ${requiredTiles.length} tiles (autonomous: ${this.autonomousMode})...`, 'info');
-
- const startTime = Date.now();
- let lastProgress = 0;
-
- while (Date.now() - startTime < timeoutMs) {
- if (state.stopFlag) {
- Utils.addDebugLog('waitForTiles: stopped by user', 'warning');
- return false;
- }
-
- const loaded = requiredTiles.filter((k) => this.originalTiles.has(k)).length;
- const progress = Math.round((loaded / requiredTiles.length) * 100);
-
- if (progress !== lastProgress && progress % 20 === 0) {
- Utils.addDebugLog(`Tile loading progress: ${loaded}/${requiredTiles.length} (${progress}%)`, 'info');
- lastProgress = progress;
- }
-
- if (loaded === requiredTiles.length) {
- Utils.addDebugLog(`All ${requiredTiles.length} required tiles are loaded`, 'success');
- return true;
- }
-
- await Utils.sleep(this.autonomousMode ? 500 : 1000);
- }
-
- const loaded = requiredTiles.filter((k) => this.originalTiles.has(k)).length;
- Utils.addDebugLog(`Timeout waiting for tiles: ${loaded}/${requiredTiles.length} loaded`, 'warning');
-
- if (this.autonomousMode && loaded > requiredTiles.length * 0.8) {
- Utils.addDebugLog(`Autonomous mode: proceeding with ${loaded}/${requiredTiles.length} tiles (80%+ loaded)`, 'warning');
- return true;
- }
-
- return loaded > 0;
- }
- }
-
- const overlayManager = new OverlayManager();
-
- // Enhanced WPlace API Service with auto-batch functionality
- const WPlaceService = {
- async paintPixelInRegion(regionX, regionY, pixelX, pixelY, color, retryCount = 0) {
- try {
- await ensureToken();
- if (!turnstileToken) {
- Utils.addDebugLog('No valid token available for paint request', 'error');
- return 'token_error';
- }
-
- const payload = {
- coords: [pixelX, pixelY],
- colors: [color],
- t: turnstileToken,
- fp: fpStr32,
- };
-
- const wasmToken = await createWasmToken(regionX, regionY, payload);
- if (!wasmToken) {
- Utils.addDebugLog('Failed to generate WASM token', 'error');
- return false;
- }
-
- const res = await fetch(`https://backend.wplace.live/s0/pixel/${regionX}/${regionY}`, {
- method: 'POST',
- headers: {
- 'Content-Type': 'text/plain;charset=UTF-8',
- 'x-pawtect-token': wasmToken
- },
- credentials: 'include',
- body: JSON.stringify(payload),
- });
-
- if (res.status === 403) {
- Utils.addDebugLog(`403 Forbidden for pixel (${pixelX},${pixelY}). Token invalid/expired.`, 'error');
- invalidateToken();
-
- if (retryCount < 2 && state.autonomousMode) {
- Utils.addDebugLog(`Autonomous retry ${retryCount + 1}/2 for pixel (${pixelX},${pixelY})`, 'warning');
- await Utils.sleep(1000);
- return await this.paintPixelInRegion(regionX, regionY, pixelX, pixelY, color, retryCount + 1);
- }
-
- return 'token_error';
- }
-
- if (!res.ok) {
- Utils.addDebugLog(`Paint request failed with status ${res.status}`, 'error');
- return false;
- }
-
- const data = await res.json();
- const success = data?.painted === 1;
-
- if (success) {
- Utils.addDebugLog(`Paint SUCCESS for (${pixelX},${pixelY}) with color ${color}`, 'success');
- } else {
- Utils.addDebugLog(`Paint FAILED for (${pixelX},${pixelY}) - server response: ${JSON.stringify(data)}`, 'error');
- }
-
- return success;
- } catch (e) {
- Utils.addDebugLog(`Paint request error for (${pixelX},${pixelY}): ${e.message}`, 'error');
- return false;
- }
- },
-
- // NEW: Auto-batch pixel painting functionality
- async paintPixelBatchInRegion(regionX, regionY, pixelBatch, retryCount = 0) {
- try {
- await ensureToken();
- if (!turnstileToken) {
- Utils.addDebugLog('No valid token available for batch paint request', 'error');
- return 'token_error';
- }
-
- // Prepare batch coordinates and colors
- const coords = [];
- const colors = [];
-
- for (const pixel of pixelBatch) {
- coords.push(pixel.pixelX, pixel.pixelY);
- colors.push(pixel.color);
- }
-
- const payload = {
- coords: coords,
- colors: colors,
- t: turnstileToken,
- fp: fpStr32,
- };
-
- const wasmToken = await createWasmToken(regionX, regionY, payload);
- if (!wasmToken) {
- Utils.addDebugLog('Failed to generate WASM token for batch', 'error');
- return false;
- }
-
- const res = await fetch(`https://backend.wplace.live/s0/pixel/${regionX}/${regionY}`, {
- method: 'POST',
- headers: {
- 'Content-Type': 'text/plain;charset=UTF-8',
- 'x-pawtect-token': wasmToken
- },
- credentials: 'include',
- body: JSON.stringify(payload),
- });
-
- if (res.status === 403) {
- Utils.addDebugLog(`403 Forbidden for batch (${pixelBatch.length} pixels). Token invalid/expired.`, 'error');
- invalidateToken();
-
- if (retryCount < 2 && state.autonomousMode) {
- Utils.addDebugLog(`Autonomous batch retry ${retryCount + 1}/2 for ${pixelBatch.length} pixels`, 'warning');
- await Utils.sleep(1000);
- return await this.paintPixelBatchInRegion(regionX, regionY, pixelBatch, retryCount + 1);
- }
-
- return 'token_error';
- }
-
- if (!res.ok) {
- Utils.addDebugLog(`Batch paint request failed with status ${res.status}`, 'error');
- return false;
- }
-
- const data = await res.json();
- const successCount = data?.painted || 0;
-
- Utils.addDebugLog(`Batch paint result: ${successCount}/${pixelBatch.length} pixels painted successfully`,
- successCount === pixelBatch.length ? 'success' : 'warning');
-
- return {
- success: successCount > 0,
- painted: successCount,
- total: pixelBatch.length
- };
-
- } catch (e) {
- Utils.addDebugLog(`Batch paint request error for ${pixelBatch.length} pixels: ${e.message}`, 'error');
- return false;
- }
- },
-
- async getCharges() {
- try {
- const res = await fetch('https://backend.wplace.live/me', {
- credentials: 'include',
- });
- if (!res.ok) return { charges: 0, max: 1, cooldown: CONFIG.COOLDOWN_DEFAULT };
- const data = await res.json();
- return {
- charges: data.charges?.count ?? 0,
- max: data.charges?.max ?? 1,
- cooldown: data.charges?.cooldownMs ?? CONFIG.COOLDOWN_DEFAULT,
- };
- } catch (e) {
- Utils.addDebugLog(`Error fetching charges: ${e.message}`, 'warning');
- return { charges: 0, max: 1, cooldown: CONFIG.COOLDOWN_DEFAULT };
- }
- },
- };
-
- // Enhanced anti-grief repair system with improved transparent pixel detection
- async function scanForDamage() {
- if (!state.imageData || !state.startPosition || !state.region) {
- Utils.addDebugLog('No image data or position for scanning', 'warning');
- return [];
- }
-
- Utils.addDebugLog('Starting enhanced damage scan with transparent pixel detection...', 'info');
- updateStatus(Utils.t('scanningForDamage'));
-
- const damagedPixels = [];
- const { width, height, pixels } = state.imageData;
-
- // Check if we're working with restored data - skip tile waiting for restored saves
- const isRestoredData = state.availableColors && state.availableColors.length > 0 && state.colorsChecked;
-
- if (!isRestoredData) {
- const ready = await overlayManager.waitForTiles(
- state.region.x,
- state.region.y,
- width,
- height,
- state.startPosition.x,
- state.startPosition.y,
- state.autonomousMode ? 20000 : 15000
- );
-
- if (!ready) {
- Utils.addDebugLog('Failed to load required tiles for scanning', 'error');
- if (state.autonomousMode) {
- Utils.addDebugLog('Autonomous mode: will retry scan in 30 seconds', 'warning');
- setTimeout(() => {
- if (state.autoRepairEnabled && !state.stopFlag) {
- scanForDamage();
- }
- }, 30000);
- }
- return [];
- }
- } else {
- Utils.addDebugLog('Using restored data - skipping tile wait for damage scan', 'info');
- }
-
- Utils.addDebugLog(`Scanning ${width}x${height} image for damage (transparent detection: ${state.paintTransparentPixels})...`, 'info');
-
- let scannedPixels = 0;
- let transparentPixelsDetected = 0;
- let wrongColorPixelsDetected = 0;
- let lastProgressUpdate = Date.now();
-
- for (let y = 0; y < height; y++) {
- for (let x = 0; x < width; x++) {
- if (state.stopFlag) break;
-
- const idx = (y * width + x) * 4;
- const originalR = pixels[idx];
- const originalG = pixels[idx + 1];
- const originalB = pixels[idx + 2];
- const originalA = pixels[idx + 3];
-
- // Enhanced transparent pixel detection
- const isOriginalTransparent = originalA < state.customTransparencyThreshold;
-
- // Skip white pixels if configured
- if (!state.paintWhitePixels && Utils.isWhitePixel(originalR, originalG, originalB)) {
- continue;
- }
-
- scannedPixels++;
-
- const absX = state.startPosition.x + x;
- const absY = state.startPosition.y + y;
- const tileX = state.region.x + Math.floor(absX / 1000);
- const tileY = state.region.y + Math.floor(absY / 1000);
- const pixelX = absX % 1000;
- const pixelY = absY % 1000;
-
- try {
- const currentPixel = await overlayManager.getTilePixelColor(tileX, tileY, pixelX, pixelY);
-
- // Handle missing current pixel data (could indicate transparent area)
- if (!currentPixel) {
- if (state.paintTransparentPixels && !isOriginalTransparent) {
- // Original pixel is not transparent but current is missing/transparent
- const targetColor = Utils.resolveColor([originalR, originalG, originalB], state.availableColors);
- damagedPixels.push({
- x,
- y,
- originalColor: targetColor,
- currentColor: { id: 63, name: 'Transparent', rgb: [0, 0, 0] }, // Transparent color
- originalRgb: [originalR, originalG, originalB],
- currentRgb: [0, 0, 0],
- isDamagedTransparent: true
- });
- transparentPixelsDetected++;
-
- if (!state.autonomousMode || transparentPixelsDetected <= 10) {
- Utils.addDebugLog(`Missing/Transparent pixel at (${x},${y}): expected color ${targetColor.id}, found transparent/missing`, 'warning');
- }
- }
- continue;
- }
-
- const currentIsTransparent = currentPixel[3] < state.customTransparencyThreshold;
-
- // Case 1: Original is not transparent, but current is transparent (damaged)
- if (!isOriginalTransparent && currentIsTransparent && state.paintTransparentPixels) {
- const targetColor = Utils.resolveColor([originalR, originalG, originalB], state.availableColors);
- damagedPixels.push({
- x,
- y,
- originalColor: targetColor,
- currentColor: { id: 63, name: 'Transparent', rgb: [0, 0, 0] },
- originalRgb: [originalR, originalG, originalB],
- currentRgb: currentPixel.slice(0, 3),
- isDamagedTransparent: true
- });
- transparentPixelsDetected++;
-
- if (!state.autonomousMode || transparentPixelsDetected <= 10) {
- Utils.addDebugLog(`Transparent damage at (${x},${y}): expected color ${targetColor.id}, found transparent`, 'warning');
- }
- continue;
- }
-
- // Case 2: Original is transparent, current is not transparent (wrong placement)
- if (isOriginalTransparent && !currentIsTransparent && state.paintTransparentPixels) {
- damagedPixels.push({
- x,
- y,
- originalColor: { id: 63, name: 'Transparent', rgb: null },
- currentColor: Utils.resolveColor(currentPixel.slice(0, 3), state.availableColors),
- originalRgb: [originalR, originalG, originalB],
- currentRgb: currentPixel.slice(0, 3),
- isDamagedTransparent: true
- });
- transparentPixelsDetected++;
-
- if (!state.autonomousMode || transparentPixelsDetected <= 10) {
- Utils.addDebugLog(`Wrong placement at (${x},${y}): expected transparent, found color`, 'warning');
- }
- continue;
- }
-
- // Case 3: Both are not transparent, check color match (normal damage detection)
- if (!isOriginalTransparent && !currentIsTransparent) {
- const targetColor = Utils.resolveColor([originalR, originalG, originalB], state.availableColors);
- const currentColor = Utils.resolveColor(currentPixel.slice(0, 3), state.availableColors);
-
- if (targetColor.id === currentColor.id) {
- // Pixel is already correctly painted - skip it
- continue;
- }
-
- // Pixel has wrong color - mark as damaged
- damagedPixels.push({
- x,
- y,
- originalColor: targetColor,
- currentColor: currentColor,
- originalRgb: [originalR, originalG, originalB],
- currentRgb: currentPixel.slice(0, 3),
- isDamagedTransparent: false
- });
- wrongColorPixelsDetected++;
-
- if (!state.autonomousMode || wrongColorPixelsDetected <= 10) {
- Utils.addDebugLog(`Color damage at (${x},${y}): expected color ${targetColor.id}, found color ${currentColor.id}`, 'warning');
- }
- }
-
- } catch (e) {
- if (!state.autonomousMode) {
- Utils.addDebugLog(`Error checking pixel (${x},${y}): ${e.message}`, 'error');
- }
- }
- }
-
- if (state.autonomousMode && Date.now() - lastProgressUpdate > 5000) {
- Utils.addDebugLog(`Scan progress: ${y}/${height} rows (${Math.round((y / height) * 100)}%), found ${damagedPixels.length} damaged (${transparentPixelsDetected} transparent, ${wrongColorPixelsDetected} wrong color)`, 'info');
- lastProgressUpdate = Date.now();
- } else if (!state.autonomousMode && y % 10 === 0) {
- Utils.addDebugLog(`Scan progress: ${y}/${height} rows (${Math.round((y / height) * 100)}%)`, 'info');
- }
- }
-
- const logLevel = damagedPixels.length > 0 ? 'warning' : 'success';
- Utils.addDebugLog(`Enhanced scan complete. Checked ${scannedPixels} pixels, found ${damagedPixels.length} damaged (${transparentPixelsDetected} transparent issues, ${wrongColorPixelsDetected} wrong colors)`, logLevel);
-
- // Smart auto-adjust batch size: if 6 pixels detected, batch size = 6
- if (state.autoBatchEnabled && state.batchOptimization && damagedPixels.length > 0) {
- const newBatchSize = Math.min(CONFIG.MAX_BATCH_SIZE, Math.max(CONFIG.MIN_BATCH_SIZE, damagedPixels.length));
- if (newBatchSize !== state.currentBatchSize) {
- state.currentBatchSize = newBatchSize;
- Utils.addDebugLog(`Smart batch: Auto-adjusted batch size to ${newBatchSize} to match ${damagedPixels.length} detected damaged pixels`, 'info');
- updateBatchInfo(); // Update UI display
- }
- }
-
- return damagedPixels;
- }
-
- async function repairDamagedPixels(damagedPixels) {
- if (damagedPixels.length === 0) {
- updateStatus(Utils.t('noDamageDetected'));
- return 0;
- }
-
- Utils.addDebugLog(`Starting enhanced repair of ${damagedPixels.length} pixels with auto-batch (batch size: ${state.currentBatchSize})`, 'info');
- updateStatus(Utils.t('repairingPixels', { count: damagedPixels.length }));
-
- let repairedCount = 0;
- let consecutiveFailures = 0;
- const maxConsecutiveFailures = state.autonomousMode ? 5 : 3;
-
- // Group pixels by region for batch processing
- const pixelsByRegion = new Map();
-
- for (const pixel of damagedPixels) {
- const absX = state.startPosition.x + pixel.x;
- const absY = state.startPosition.y + pixel.y;
- const regionX = state.region.x + Math.floor(absX / 1000);
- const regionY = state.region.y + Math.floor(absY / 1000);
- const regionKey = `${regionX},${regionY}`;
-
- if (!pixelsByRegion.has(regionKey)) {
- pixelsByRegion.set(regionKey, []);
- }
-
- pixelsByRegion.get(regionKey).push({
- ...pixel,
- regionX,
- regionY,
- pixelX: absX % 1000,
- pixelY: absY % 1000,
- color: pixel.isDamagedTransparent && pixel.originalColor.id === 63 ? 63 : pixel.originalColor.id
- });
- }
-
- Utils.addDebugLog(`Grouped ${damagedPixels.length} pixels into ${pixelsByRegion.size} regions for batch processing`, 'info');
-
- // Process each region
- for (const [regionKey, regionPixels] of pixelsByRegion) {
- if (state.stopFlag) {
- Utils.addDebugLog('Repair stopped by user request', 'warning');
- break;
- }
-
- const [regionX, regionY] = regionKey.split(',').map(Number);
- Utils.addDebugLog(`Processing region ${regionKey} with ${regionPixels.length} pixels`, 'info');
-
- // Process pixels in batches
- for (let i = 0; i < regionPixels.length; i += state.currentBatchSize) {
- if (state.stopFlag) break;
-
- const batch = regionPixels.slice(i, i + state.currentBatchSize);
- const actualBatchSize = Math.min(batch.length, state.currentBatchSize);
-
- // Enhanced charge management for batch processing
- await updateCharges();
- let chargeWaitAttempts = 0;
- const maxChargeWaitAttempts = state.autonomousMode ? 20 : 10;
-
- while (state.displayCharges < actualBatchSize && !state.stopFlag && chargeWaitAttempts < maxChargeWaitAttempts) {
- chargeWaitAttempts++;
- const waitTime = state.autonomousMode ? Math.min(state.cooldown, 10000) : state.cooldown;
-
- if (chargeWaitAttempts === 1) {
- Utils.addDebugLog(`Waiting for charges... (need ${actualBatchSize}, have ${state.displayCharges}/${state.maxCharges})`, 'info');
- }
-
- await Utils.dynamicSleep(() => {
- if (state.displayCharges >= actualBatchSize) return 0;
- if (state.stopFlag) return 0;
- return waitTime;
- });
- await updateCharges();
- }
-
- if (state.stopFlag) break;
-
- if (state.displayCharges < actualBatchSize) {
- Utils.addDebugLog(`Insufficient charges for batch (need ${actualBatchSize}, have ${state.displayCharges}), falling back to single pixel repair`, 'warning');
-
- // Fall back to single pixel repair
- for (const pixel of batch) {
- if (state.stopFlag) break;
-
- await updateCharges();
- if (state.displayCharges < 1) {
- Utils.addDebugLog(`No charges available, skipping pixel (${pixel.x},${pixel.y})`, 'warning');
- continue;
- }
-
- const success = await repairSinglePixel(pixel);
- if (success) {
- repairedCount++;
- consecutiveFailures = 0;
- } else {
- consecutiveFailures++;
- }
-
- if (consecutiveFailures >= maxConsecutiveFailures) break;
- await Utils.sleep(100);
- }
- } else {
- // Use batch repair
- const batchResult = await repairPixelBatch(regionX, regionY, batch);
-
- if (batchResult && batchResult.success) {
- repairedCount += batchResult.painted;
- consecutiveFailures = 0;
-
- Utils.addDebugLog(`Batch repair: ${batchResult.painted}/${batchResult.total} pixels repaired successfully [Total: ${repairedCount}/${damagedPixels.length}]`, 'success');
- } else {
- consecutiveFailures++;
- Utils.addDebugLog(`Batch repair failed for ${batch.length} pixels [${consecutiveFailures}/${maxConsecutiveFailures} consecutive failures]`, 'error');
-
- if (consecutiveFailures >= maxConsecutiveFailures) {
- Utils.addDebugLog(`Too many consecutive batch failures (${consecutiveFailures}), pausing repair`, 'error');
- if (state.autonomousMode) {
- Utils.addDebugLog('Autonomous mode: will retry repair in 60 seconds', 'warning');
- setTimeout(() => {
- if (state.autoRepairEnabled && !state.stopFlag) {
- performRepairCheck();
- }
- }, 60000);
- }
- break;
- }
- }
- }
-
- await updateCharges();
-
- // Dynamic delay based on mode and success rate
- const baseDelay = state.autonomousMode ? 100 : 200;
- const adaptiveDelay = consecutiveFailures > 0 ? baseDelay * (consecutiveFailures + 1) : baseDelay;
- await Utils.sleep(adaptiveDelay);
- }
-
- if (consecutiveFailures >= maxConsecutiveFailures) break;
- }
-
- const message = Utils.t('repairComplete', { repaired: repairedCount });
- updateStatus(message);
- Utils.addDebugLog(`${message} (${damagedPixels.length - repairedCount} remaining) - Used auto-batch with size ${state.currentBatchSize}`, 'success');
-
- return repairedCount;
- }
-
- async function repairPixelBatch(regionX, regionY, pixelBatch) {
- try {
- Utils.addDebugLog(`Attempting batch repair of ${pixelBatch.length} pixels in region ${regionX},${regionY}`, 'info');
-
- const result = await WPlaceService.paintPixelBatchInRegion(regionX, regionY, pixelBatch);
-
- if (result === 'token_error') {
- Utils.addDebugLog('Token error during batch repair, refreshing token...', 'warning');
- await ensureToken(true);
- await Utils.sleep(state.autonomousMode ? 500 : 1000);
-
- // Retry once with new token
- const retryResult = await WPlaceService.paintPixelBatchInRegion(regionX, regionY, pixelBatch);
- return retryResult;
- }
-
- return result;
- } catch (e) {
- Utils.addDebugLog(`Error during batch repair: ${e.message}`, 'error');
- return false;
- }
- }
-
- async function repairSinglePixel(pixel) {
- const { x, y, originalColor, regionX, regionY, pixelX, pixelY, color } = pixel;
-
- try {
- const result = await WPlaceService.paintPixelInRegion(
- regionX,
- regionY,
- pixelX,
- pixelY,
- color || originalColor.id
- );
-
- if (result === 'token_error') {
- Utils.addDebugLog('Token error during repair, refreshing token...', 'warning');
- await ensureToken(true);
- await Utils.sleep(state.autonomousMode ? 500 : 1000);
-
- // Retry once with new token
- const retryResult = await WPlaceService.paintPixelInRegion(regionX, regionY, pixelX, pixelY, color || originalColor.id);
- return retryResult === true;
- }
-
- if (result === true) {
- if (pixel.isDamagedTransparent) {
- Utils.addDebugLog(`Repaired transparent pixel (${x},${y}) with color ${color || originalColor.id}`, 'success');
- }
- }
-
- return result === true;
- } catch (e) {
- Utils.addDebugLog(`Error repairing pixel (${x},${y}): ${e.message}`, 'error');
- return false;
- }
- }
-
- async function performRepairCheck() {
- if (state.running) {
- Utils.addDebugLog('Repair check skipped - manual repair in progress', 'info');
- return;
- }
-
- try {
- Utils.addDebugLog('Autonomous repair check triggered', 'info');
-
- // Ensure we have a valid token before starting
- if (!isTokenValid()) {
- Utils.addDebugLog('No valid token for autonomous repair, generating...', 'warning');
- await ensureToken(true);
- if (!isTokenValid()) {
- Utils.addDebugLog('Failed to generate token for autonomous repair, will retry next cycle', 'error');
- return;
- }
- }
-
- const damagedPixels = await scanForDamage();
-
- if (damagedPixels.length > 0) {
- Utils.addDebugLog(`Autonomous repair: Found ${damagedPixels.length} damaged pixels, starting repair`, 'warning');
- updateStatus(Utils.t('damageDetected', { count: damagedPixels.length }));
-
- // Show attack notification
- showAttackNotification('attack', damagedPixels.length);
- state.lastAttackState = 'attacked';
-
- const repairedCount = await repairDamagedPixels(damagedPixels);
-
- // Show repair complete notification if all pixels were repaired
- if (repairedCount === damagedPixels.length) {
- showAttackNotification('repaired');
- state.lastAttackState = 'repaired';
- }
- } else {
- updateStatus(Utils.t('noDamageDetected'));
-
- // Show peaceful state if we're not under attack
- if (state.lastAttackState !== 'peaceful') {
- showPeacefulState();
- state.lastAttackState = 'peaceful';
- }
-
- if (!state.autonomousMode) {
- Utils.addDebugLog('Autonomous repair: No damage detected', 'success');
- }
- }
- } catch (error) {
- Utils.addDebugLog(`Autonomous repair error: ${error.message}`, 'error');
-
- if (state.autonomousMode) {
- Utils.addDebugLog('Autonomous mode: will retry repair check in 120 seconds due to error', 'warning');
- setTimeout(() => {
- if (state.autoRepairEnabled && !state.stopFlag) {
- performRepairCheck();
- }
- }, 120000);
- }
- }
- }
-
- function startAutoRepair() {
- if (state.autoRepairTimer) {
- clearInterval(state.autoRepairTimer);
- }
-
- const intervalMs = state.autoRepairInterval * 1000;
- state.autoRepairTimer = setInterval(performRepairCheck, intervalMs);
-
- const message = Utils.t('autoRepairStarted', { interval: state.autoRepairInterval });
- Utils.addDebugLog(`${message} (autonomous: ${state.autonomousMode})`, 'info');
- Utils.showAlert(message, 'success');
-
- // Perform first check immediately
- setTimeout(performRepairCheck, 2000);
- }
-
- function stopAutoRepair() {
- if (state.autoRepairTimer) {
- clearInterval(state.autoRepairTimer);
- state.autoRepairTimer = null;
- }
-
- const message = Utils.t('autoRepairStopped');
- Utils.addDebugLog(`${message}`, 'info');
- Utils.showAlert(message, 'info');
- }
-
- // FUNCIONES DE UI MEJORADAS CON GESTIĆN DE CONFIGURACIĆN INICIAL
- function enableFileOperations() {
- state.initialSetupComplete = true;
-
- const loadBtn = document.querySelector('#loadFileBtn');
- if (loadBtn) {
- loadBtn.disabled = false;
- loadBtn.title = '';
- loadBtn.style.animation = 'pulse 0.6s ease-in-out';
- setTimeout(() => {
- if (loadBtn) loadBtn.style.animation = '';
- }, 600);
- Utils.addDebugLog('Load Progress button enabled after initial setup', 'success');
- }
-
- Utils.showAlert(Utils.t('fileOperationsAvailable'), 'success');
- }
-
- async function initializeTokenGenerator() {
- if (isTokenValid()) {
- Utils.addDebugLog('Valid token already available, skipping initialization', 'success');
- updateTokenStatus();
- enableFileOperations();
- return;
- }
-
- try {
- Utils.addDebugLog('Initializing Turnstile token generator...', 'info');
- updateStatus(Utils.t('initializingToken'));
-
- await Utils.loadTurnstile();
- Utils.addDebugLog('Turnstile script loaded successfully', 'success');
-
- if (state.autoTokenRefresh) {
- Utils.addDebugLog('Pre-generating token for autonomous operations...', 'info');
- await ensureToken();
- }
-
- state.initialSetupComplete = true;
- Utils.addDebugLog('Enhanced token generator initialization complete', 'success');
- updateStatus(Utils.t('tokenReady'));
- enableFileOperations();
-
- } catch (error) {
- Utils.addDebugLog('Token system initialization failed: ' + error.message, 'warning');
- Utils.addDebugLog('Manual token generation will be required', 'info');
- enableFileOperations();
- }
- }
-
- function updateStatus(message) {
- const statusEl = document.getElementById('status');
- if (statusEl) {
- if (typeof message === 'string') {
- statusEl.innerHTML = message;
- } else {
- statusEl.textContent = message;
- }
- }
- }
-
- function updateDebugConsole() {
- const debugConsole = document.getElementById('debugConsole');
- if (!debugConsole) return;
-
- const logsHtml = state.debugLogs.map(log => {
- const colorClass = log.type === 'error' ? 'neon-text danger' :
- log.type === 'warning' ? 'neon-text warning' :
- log.type === 'success' ? 'neon-text success' :
- log.type === 'info' ? 'neon-text info' : 'neon-text';
-
- return `
- [${log.timestamp}] ${log.message}
-
`;
- }).join('');
-
- debugConsole.innerHTML = logsHtml;
- debugConsole.scrollTop = debugConsole.scrollHeight;
- }
-
- async function updateCharges() {
- try {
- const { charges, max, cooldown } = await WPlaceService.getCharges();
- state.displayCharges = Math.floor(charges);
- state.maxCharges = Math.max(1, Math.floor(max));
- state.cooldown = cooldown;
-
- updateChargesDisplay();
- } catch (error) {
- if (!state.autonomousMode) {
- Utils.addDebugLog(`Error updating charges: ${error.message}`, 'error');
- }
- }
- }
-
- function updateChargesDisplay() {
- const chargesEl = document.getElementById('chargesInfo');
- if (chargesEl) {
- chargesEl.textContent = `Charges: ${state.displayCharges}/${state.maxCharges} (cooldown: ${Math.round(state.cooldown / 1000)}s)`;
- }
- }
-
- function updateTokenStatus() {
- const tokenEl = document.getElementById('tokenInfo');
- if (tokenEl) {
- // Remove existing classes first
- tokenEl.className = '';
-
- if (isTokenValid()) {
- const remaining = Math.round((tokenExpiryTime - Date.now()) / 1000);
- tokenEl.textContent = `Token: Valid (expires in ${remaining}s)`;
- tokenEl.className = 'neon-text success';
- } else {
- tokenEl.textContent = 'Token: Not generated or expired';
- tokenEl.className = 'neon-text warning';
- }
- }
- }
-
- // Update batch information display for new UI
- function updateBatchInfo() {
- const batchSizeEl = document.getElementById('batchSize');
- const batchStatusEl = document.getElementById('batchStatus');
- const smartModeEl = document.getElementById('smartBatchMode');
- const manualModeEl = document.getElementById('manualBatchMode');
- const manualControlsEl = document.getElementById('manualBatchControls');
-
- // Update radio button states
- if (smartModeEl && manualModeEl) {
- smartModeEl.checked = state.autoBatchEnabled && state.batchOptimization;
- manualModeEl.checked = !state.autoBatchEnabled || !state.batchOptimization;
- }
-
- // Update manual controls visibility
- if (manualControlsEl) {
- manualControlsEl.style.display = (!state.autoBatchEnabled || !state.batchOptimization) ? 'flex' : 'none';
- }
-
- // Update batch size input
- if (batchSizeEl) {
- batchSizeEl.value = state.currentBatchSize;
- }
-
- // Update status display
- if (batchStatusEl) {
- if (state.autoBatchEnabled && state.batchOptimization) {
- batchStatusEl.textContent = `š§ Smart Batch: AUTO (Current: ${state.currentBatchSize})`;
- batchStatusEl.className = 'status-info';
- } else {
- batchStatusEl.textContent = `ā Manual Batch: ${state.currentBatchSize} pixels fixed`;
- batchStatusEl.className = 'status-warning';
- }
- }
- }
-
- function updateSystemStatus() {
- const systemEl = document.getElementById('systemInfo');
- if (systemEl) {
- const autonomousStatus = state.autonomousMode ? 'AUTONOMOUS' : 'MANUAL';
- const tokenMode = state.tokenSource.toUpperCase();
- systemEl.textContent = `System: ${autonomousStatus} | Token: ${tokenMode} | Auto-refresh: ${state.autoTokenRefresh ? 'ON' : 'OFF'}`;
- }
- }
-
- // Window dragging functionality
- function makeWindowDraggable() {
- const container = document.getElementById('wplace-repair-tool');
- const header = document.getElementById('window-header');
-
- let isDragging = false;
- let currentX;
- let currentY;
- let initialX;
- let initialY;
- let xOffset = 0;
- let yOffset = 0;
-
- function dragStart(e) {
- if (e.target.tagName === 'BUTTON' || e.target.tagName === 'INPUT' || e.target.tagName === 'SELECT') {
- return;
- }
-
- initialX = e.clientX - xOffset;
- initialY = e.clientY - yOffset;
-
- if (e.target === header || header.contains(e.target)) {
- isDragging = true;
- container.style.cursor = 'grabbing';
- }
- }
-
- function dragEnd() {
- initialX = currentX;
- initialY = currentY;
- isDragging = false;
- container.style.cursor = 'default';
- }
-
- function drag(e) {
- if (isDragging) {
- e.preventDefault();
- currentX = e.clientX - initialX;
- currentY = e.clientY - initialY;
- xOffset = currentX;
- yOffset = currentY;
-
- container.style.transform = `translate(${currentX}px, ${currentY}px)`;
- }
- }
-
- header.addEventListener('mousedown', dragStart);
- document.addEventListener('mouseup', dragEnd);
- document.addEventListener('mousemove', drag);
- }
-
- // Enhanced UI Creation with NEW IMPLEMENTATION
- function createUI() {
- const existing = document.getElementById('wplace-repair-tool');
- if (existing) existing.remove();
-
- const container = document.createElement('div');
- container.id = 'wplace-repair-tool';
- container.style.cssText = `
- position: fixed; top: 20px; left: 20px; z-index: 10000;
- background: #1a1a2e;
- border: 3px solid #00ff41;
- border-radius: 0;
- min-width: 600px; max-width: 700px;
- box-shadow:
- 0 0 30px rgb(0 255 65 / 50%),
- inset 0 0 30px rgb(0 255 65 / 10%),
- 0 0 0 1px #00ff41;
- color: #00ff41;
- font-family: 'Press Start 2P', monospace, 'Courier New';
- resize: both; overflow: hidden;
- animation: neon-pulse 2s ease-in-out infinite alternate;
- `;
-
- // Add Neon theme styles
- const neonStyles = document.createElement('style');
- neonStyles.id = 'auto-repair-neon-styles';
- neonStyles.textContent = `
- @import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');
-
- #wplace-repair-tool::before {
- content: '';
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- height: 2px;
- background: linear-gradient(90deg, transparent, #00ff41, transparent);
- z-index: 1;
- pointer-events: none;
- animation: scanline 3s linear infinite;
- opacity: 0.7;
- }
-
- .neon-btn {
- background: #16213e !important;
- border: 2px solid #00ff41 !important;
- border-radius: 0 !important;
- color: #00ff41 !important;
- padding: 8px 15px !important;
- font-family: 'Press Start 2P', monospace !important;
- font-size: 8px !important;
- text-transform: uppercase !important;
- cursor: pointer !important;
- transition: all 0.3s ease !important;
- text-shadow: 0 0 8px #00ff41 !important;
- letter-spacing: 1px !important;
- margin: 4px !important;
- }
-
- .neon-btn:hover {
- background: rgba(0,255,65,0.1) !important;
- box-shadow: 0 0 20px rgb(0 255 65 / 60%) !important;
- animation: pixel-blink 0.5s infinite !important;
- }
-
- .neon-btn:disabled {
- background: #0d1117 !important;
- border-color: #555 !important;
- color: #555 !important;
- text-shadow: none !important;
- cursor: not-allowed !important;
- }
-
- .neon-btn.danger {
- border-color: #ff073a !important;
- color: #ff073a !important;
- text-shadow: 0 0 8px #ff073a !important;
- }
-
- .neon-btn.danger:hover {
- background: rgba(255, 7, 58, 0.1) !important;
- box-shadow: 0 0 20px rgb(255 7 58 / 60%) !important;
- }
-
- .neon-btn.warning {
- border-color: #ff6b35 !important;
- color: #ff6b35 !important;
- text-shadow: 0 0 8px #ff6b35 !important;
- }
-
- .neon-btn.warning:hover {
- background: rgba(255, 107, 53, 0.1) !important;
- box-shadow: 0 0 20px rgb(255 107 53 / 60%) !important;
- }
-
- .neon-input {
- background: #16213e !important;
- border: 2px solid #00ff41 !important;
- border-radius: 0 !important;
- color: #00ff41 !important;
- padding: 6px 10px !important;
- font-family: 'Press Start 2P', monospace !important;
- font-size: 8px !important;
- text-shadow: 0 0 5px #00ff41 !important;
- }
-
- .neon-input:focus {
- outline: none !important;
- box-shadow: 0 0 15px rgb(0 255 65 / 60%) !important;
- animation: pixel-blink 0.5s infinite !important;
- }
-
- .neon-select {
- background: #16213e !important;
- border: 2px solid #00ff41 !important;
- border-radius: 0 !important;
- color: #00ff41 !important;
- padding: 4px 8px !important;
- font-family: 'Press Start 2P', monospace !important;
- font-size: 7px !important;
- text-shadow: 0 0 5px #00ff41 !important;
- }
-
- .neon-checkbox {
- accent-color: #00ff41 !important;
- transform: scale(1.2) !important;
- margin-right: 8px !important;
- }
-
- /* Enhanced Batch Control Styles */
- .batch-control-group {
- display: flex !important;
- align-items: center !important;
- gap: 8px !important;
- margin: 5px 0 !important;
- padding: 8px !important;
- background: rgba(0, 255, 65, 0.05) !important;
- border: 1px solid rgba(0, 255, 65, 0.3) !important;
- border-radius: 3px !important;
- }
-
- .batch-control-header {
- text-align: center !important;
- margin-bottom: 10px !important;
- padding: 5px !important;
- border-bottom: 1px solid rgba(0, 255, 65, 0.3) !important;
- }
-
- .batch-control-label {
- color: #00ff41 !important;
- font-family: 'Press Start 2P', monospace !important;
- font-size: 6px !important;
- text-shadow: 0 0 3px #00ff41 !important;
- white-space: nowrap !important;
- }
-
- .batch-mode-selection {
- margin: 10px 0 !important;
- }
-
- .batch-mode-option {
- margin: 8px 0 !important;
- padding: 6px !important;
- background: rgba(0, 255, 65, 0.03) !important;
- border: 1px solid rgba(0, 255, 65, 0.2) !important;
- border-radius: 3px !important;
- transition: all 0.3s ease !important;
- }
-
- .batch-mode-option:hover {
- background: rgba(0, 255, 65, 0.08) !important;
- border-color: rgba(0, 255, 65, 0.4) !important;
- }
-
- .batch-mode-label {
- color: #00ff41 !important;
- font-family: 'Press Start 2P', monospace !important;
- font-size: 6px !important;
- text-shadow: 0 0 3px #00ff41 !important;
- margin-left: 8px !important;
- }
-
- .neon-radio {
- accent-color: #00ff41 !important;
- transform: scale(1.2) !important;
- margin-right: 8px !important;
- }
-
- .batch-manual-controls {
- display: flex !important;
- align-items: center !important;
- gap: 8px !important;
- margin: 8px 0 !important;
- padding: 8px !important;
- background: rgba(0, 255, 65, 0.05) !important;
- border: 1px solid rgba(0, 255, 65, 0.3) !important;
- border-radius: 3px !important;
- }
-
- .batch-size-control {
- display: flex !important;
- align-items: center !important;
- gap: 2px !important;
- border: 1px solid #00ff41 !important;
- border-radius: 3px !important;
- background: #16213e !important;
- overflow: hidden !important;
- }
-
- .batch-adjust-btn {
- width: 20px !important;
- height: 24px !important;
- background: #16213e !important;
- border: none !important;
- color: #00ff41 !important;
- font-family: 'Press Start 2P', monospace !important;
- font-size: 8px !important;
- cursor: pointer !important;
- transition: all 0.3s ease !important;
- text-shadow: 0 0 3px #00ff41 !important;
- display: flex !important;
- align-items: center !important;
- justify-content: center !important;
- }
-
- .batch-adjust-btn:hover {
- background: rgba(0, 255, 65, 0.1) !important;
- box-shadow: 0 0 5px rgba(0, 255, 65, 0.3) !important;
- }
-
- .batch-adjust-btn:active {
- background: rgba(0, 255, 65, 0.2) !important;
- transform: scale(0.95) !important;
- }
-
- .batch-size-input {
- width: 50px !important;
- height: 24px !important;
- background: #16213e !important;
- border: none !important;
- color: #00ff41 !important;
- padding: 0 4px !important;
- font-family: 'Press Start 2P', monospace !important;
- font-size: 6px !important;
- text-align: center !important;
- text-shadow: 0 0 3px #00ff41 !important;
- outline: none !important;
- /* Hide browser default spinner buttons */
- -webkit-appearance: none !important;
- -moz-appearance: textfield !important;
- }
-
- /* Hide Chrome spinner buttons */
- .batch-size-input::-webkit-outer-spin-button,
- .batch-size-input::-webkit-inner-spin-button {
- -webkit-appearance: none !important;
- margin: 0 !important;
- }
-
- /* Hide Firefox spinner buttons */
- .batch-size-input[type=number] {
- -moz-appearance: textfield !important;
- }
-
- .status-info {
- color: #00ff41 !important;
- }
-
- .status-warning {
- color: #ff6b35 !important;
- }
-
- .neon-panel {
- background: rgba(22, 33, 62, 0.3) !important;
- border: 1px solid #00ff41 !important;
- border-radius: 0 !important;
- padding: 15px !important;
- margin: 10px 0 !important;
- box-shadow: inset 0 0 15px rgba(0, 255, 65, 0.1) !important;
- }
-
- .neon-text {
- color: #00ff41 !important;
- text-shadow: 0 0 5px #00ff41 !important;
- font-family: 'Press Start 2P', monospace !important;
- }
-
- .neon-text.success {
- color: #39ff14 !important;
- text-shadow: 0 0 5px #39ff14 !important;
- }
-
- .neon-text.warning {
- color: #ff6b35 !important;
- text-shadow: 0 0 5px #ff6b35 !important;
- }
-
- .neon-text.danger {
- color: #ff073a !important;
- text-shadow: 0 0 5px #ff073a !important;
- }
-
- .neon-text.info {
- color: #00ccff !important;
- text-shadow: 0 0 5px #00ccff !important;
- }
-
- /* Custom scrollbar for debug console */
- #debugConsole::-webkit-scrollbar {
- width: 12px;
- }
-
- #debugConsole::-webkit-scrollbar-track {
- background: #16213e;
- border: 1px solid #00ff41;
- }
-
- #debugConsole::-webkit-scrollbar-thumb {
- background: #00ff41;
- border-radius: 0;
- box-shadow: 0 0 10px #00ff41;
- }
-
- #debugConsole::-webkit-scrollbar-thumb:hover {
- background: #39ff14;
- box-shadow: 0 0 15px #39ff14;
- }
-
- /* Animations */
- @keyframes neon-pulse {
- 0% { box-shadow: 0 0 30px rgb(0 255 65 / 50%), inset 0 0 30px rgb(0 255 65 / 10%), 0 0 0 1px #00ff41; }
- 100% { box-shadow: 0 0 40px rgb(0 255 65 / 70%), inset 0 0 40px rgb(0 255 65 / 15%), 0 0 0 1px #00ff41; }
- }
-
- @keyframes pixel-blink {
- 0%, 50% { opacity: 1; }
- 51%, 100% { opacity: 0.7; }
- }
-
- @keyframes scanline {
- 0% { transform: translateY(-100%); }
- 100% { transform: translateY(400px); }
- }
-
- @keyframes text-glow {
- 0% { text-shadow: 0 0 15px #00ff41; }
- 100% { text-shadow: 0 0 25px #00ff41, 0 0 35px #00ff41; }
- }
- `;
-
- if (!document.getElementById('auto-repair-neon-styles')) {
- document.head.appendChild(neonStyles);
- }
-
- container.innerHTML = `
-
-
-
-
-
- ${Utils.t('autonomousModeActive')}
-
-
- Charges: 0/1 (cooldown: 31s)
-
-
- Token: Initializing...
-
-
- System: AUTONOMOUS | Token: GENERATOR | Auto-refresh: ON
-
-
- Auto-Batch: ON | Size: 5 | Opt: ON
-
-
-
-
-
- š ${Utils.t('loadFromFile')}
-
-
-
- š§ ${Utils.t('repairPixels')}
-
-
-
- šÆ Force Token
-
-
-
-
-
-
-
- ${Utils.t('enableAutoRepair')} (Enhanced)
-
-
-
-
- ${Utils.t('repairInterval')}:
-
- seconds
-
-
-
-
-
- Auto Token Refresh
-
-
- Generator
- Hybrid
- Manual
-
-
-
-
-
-
-
-
-
-
-
-
-
Batch Size:
-
- -
-
- +
-
-
pixels
-
-
-
-
- š§ Repair Transparent Pixels
-
-
-
-
-
-
Image: Not loaded
-
Position: Not set
-
Colors: 0 available
-
Batch Mode: Auto (${state.currentBatchSize} pixels)
-
-
-
-
-
-
- š ${Utils.t('debug')} (Enhanced System)
-
-
- ${Utils.t('clearDebug')}
-
-
-
-
-
[Ready] WPlace Autonomous Repair Tool v3.0
-
[Info] NEW Turnstile implementation active
-
[Info] Enhanced token management with preloading
-
[Info] ${Utils.t('tokenSystemReady')}
-
-
-
- `;
-
- document.body.appendChild(container);
- makeWindowDraggable();
- setupEventListeners();
-
- // Start typewriter effect
- setTimeout(() => {
- createTypewriterTitle();
- }, 1000);
-
- // Start autonomous systems
- initializeAutonomousSystems();
- }
-
- function initializeAutonomousSystems() {
- // Update charges periodically
- updateCharges();
- setInterval(updateCharges, 10000);
-
- // Update token status
- updateTokenStatus();
- setInterval(updateTokenStatus, 5000);
-
- // Update system status
- updateSystemStatus();
- setInterval(updateSystemStatus, 15000);
-
- // Update batch information
- updateBatchInfo();
-
- // Initialize token generator
- initializeTokenGenerator();
- }
-
- function setupEventListeners() {
- // Window controls
- document.getElementById('minimizeBtn').addEventListener('click', () => {
- const content = document.getElementById('window-content');
- state.windowMinimized = !state.windowMinimized;
-
- if (state.windowMinimized) {
- content.style.display = 'none';
- document.getElementById('minimizeBtn').textContent = '+';
- } else {
- content.style.display = 'block';
- document.getElementById('minimizeBtn').textContent = 'ā';
- }
- });
-
- document.getElementById('closeBtn').addEventListener('click', () => {
- const container = document.getElementById('wplace-repair-tool');
- if (container) {
- container.style.transition = 'opacity 0.3s ease, transform 0.3s ease';
- container.style.opacity = '0';
- container.style.transform = 'scale(0.8)';
- setTimeout(() => {
- container.remove();
- cleanup();
- }, 300);
- }
- });
-
- // Load file
- document.getElementById('loadFileBtn').addEventListener('click', async () => {
- try {
- Utils.addDebugLog('Loading progress file...', 'info');
- const data = await Utils.createFileUploader();
-
- if (!data || !data.state || !data.imageData) {
- throw new Error(Utils.t('invalidFile'));
- }
-
- const migratedData = migrateProgressData(data);
-
- Object.assign(state, migratedData.state);
- state.imageData = {
- ...migratedData.imageData,
- pixels: new Uint8ClampedArray(migratedData.imageData.pixels),
- };
-
- state.imageLoaded = true;
- document.getElementById('repairBtn').disabled = false;
-
- // Update info displays
- document.getElementById('imageInfo').textContent = `${state.imageData.width}x${state.imageData.height}`;
- document.getElementById('positionInfo').textContent = state.startPosition ?
- `(${state.startPosition.x}, ${state.startPosition.y}) in region (${state.region.x}, ${state.region.y})` : 'Not set';
- document.getElementById('colorsInfo').textContent = `${state.availableColors?.length || 0} available`;
-
- updateStatus(Utils.t('fileLoaded'));
- Utils.addDebugLog(`Loaded image: ${state.imageData.width}x${state.imageData.height}`, 'success');
- Utils.addDebugLog(`Position: (${state.startPosition?.x}, ${state.startPosition?.y})`, 'info');
- Utils.addDebugLog(`Region: (${state.region?.x}, ${state.region?.y})`, 'info');
- Utils.addDebugLog(`Available colors: ${state.availableColors?.length || 0}`, 'info');
-
- if (migratedData.state.paintWhitePixels !== undefined) {
- state.paintWhitePixels = migratedData.state.paintWhitePixels;
- Utils.addDebugLog(`Paint white pixels: ${state.paintWhitePixels}`, 'info');
- }
- if (migratedData.state.paintTransparentPixels !== undefined) {
- state.paintTransparentPixels = migratedData.state.paintTransparentPixels;
- Utils.addDebugLog(`Paint transparent pixels: ${state.paintTransparentPixels}`, 'info');
- }
-
- Utils.showAlert(Utils.t('fileLoaded'), 'success');
-
- // If autonomous mode and auto repair is enabled, start it
- if (state.autonomousMode && document.getElementById('autoRepairEnabled').checked) {
- Utils.addDebugLog('Autonomous mode: starting auto repair after file load', 'info');
- setTimeout(() => {
- state.autoRepairEnabled = true;
- startAutoRepair();
- }, 5000);
- }
-
- } catch (error) {
- Utils.addDebugLog(`Load error: ${error.message}`, 'error');
- Utils.showAlert(error.message, 'error');
- }
- });
-
- // Generate Token button
- document.getElementById('generateTokenBtn').addEventListener('click', async () => {
- const btn = document.getElementById('generateTokenBtn');
- const originalText = btn.textContent;
-
- try {
- btn.disabled = true;
- btn.textContent = 'š Generating...';
- btn.style.background = 'linear-gradient(135deg, #ffa726 0%, #ff9800 100%)';
-
- Utils.addDebugLog('Manual token generation requested', 'info');
-
- const token = await ensureToken(true);
-
- if (token) {
- Utils.addDebugLog('Token generation succeeded!', 'success');
- Utils.showAlert('Token generated successfully!', 'success');
- updateTokenStatus();
- } else {
- throw new Error('Token generation failed');
- }
- } catch (error) {
- Utils.addDebugLog(`Token generation failed: ${error.message}`, 'error');
- Utils.showAlert('Token generation failed', 'error');
- playNotificationSound();
- } finally {
- btn.disabled = false;
- btn.textContent = originalText;
- btn.style.background = 'linear-gradient(135deg, #43e97b 0%, #38f9d7 100%)';
- }
- });
-
- // Manual repair
- let repairRunning = false;
- document.getElementById('repairBtn').addEventListener('click', async () => {
- if (!state.imageLoaded) {
- Utils.showAlert('Please load a progress file first', 'warning');
- return;
- }
-
- if (repairRunning) {
- state.stopFlag = true;
- Utils.addDebugLog('Manual repair stop requested', 'warning');
- return;
- }
-
- repairRunning = true;
- state.running = true;
- state.stopFlag = false;
- const repairBtn = document.getElementById('repairBtn');
- repairBtn.textContent = 'āøļø Stop Repair';
- repairBtn.style.background = 'linear-gradient(135deg, #ff6b35 0%, #f7931e 100%)';
-
- try {
- Utils.addDebugLog('Starting manual repair with enhanced autonomous system...', 'info');
-
- if (!isTokenValid()) {
- Utils.addDebugLog('No valid token, generating...', 'info');
- await ensureToken(true);
- }
-
- const damagedPixels = await scanForDamage();
-
- if (damagedPixels.length > 0) {
- updateStatus(Utils.t('damageDetected', { count: damagedPixels.length }));
- showAttackNotification('attack', damagedPixels.length);
-
- const repairedCount = await repairDamagedPixels(damagedPixels);
-
- if (repairedCount === damagedPixels.length) {
- showAttackNotification('repaired');
- }
- } else {
- updateStatus(Utils.t('noDamageDetected'));
- showPeacefulState();
- }
- } catch (error) {
- Utils.addDebugLog(`Repair error: ${error.message}`, 'error');
- Utils.showAlert('Repair failed', 'error');
- } finally {
- repairRunning = false;
- state.running = false;
- state.stopFlag = false;
- repairBtn.textContent = 'š§ ' + Utils.t('repairPixels');
- repairBtn.style.background = 'linear-gradient(135deg, #f093fb 0%, #f5576c 100%)';
- }
- });
-
- // Auto repair toggle
- document.getElementById('autoRepairEnabled').addEventListener('change', (e) => {
- state.autoRepairEnabled = e.target.checked;
-
- if (state.autoRepairEnabled) {
- if (!state.imageLoaded) {
- Utils.showAlert('Please load a progress file first', 'warning');
- e.target.checked = false;
- state.autoRepairEnabled = false;
- return;
- }
- startAutoRepair();
- } else {
- stopAutoRepair();
- }
- });
-
- // Repair interval change
- document.getElementById('repairInterval').addEventListener('change', (e) => {
- const value = parseInt(e.target.value) || 30;
- state.autoRepairInterval = Math.max(10, Math.min(3600, value));
- e.target.value = state.autoRepairInterval;
-
- Utils.addDebugLog(`Repair interval changed to ${state.autoRepairInterval} seconds`, 'info');
-
- if (state.autoRepairEnabled && state.imageLoaded) {
- Utils.addDebugLog('Restarting auto repair with new interval', 'info');
- startAutoRepair();
- }
- });
-
- // Auto token refresh toggle
- document.getElementById('autoTokenRefresh').addEventListener('change', (e) => {
- state.autoTokenRefresh = e.target.checked;
- Utils.addDebugLog(`Auto token refresh ${state.autoTokenRefresh ? 'enabled' : 'disabled'}`, 'info');
- updateSystemStatus();
-
- if (state.autoTokenRefresh && state.autonomousMode && !isTokenValid()) {
- ensureToken();
- }
- });
-
- // Token source selection
- document.getElementById('tokenSourceSelect').addEventListener('change', (e) => {
- state.tokenSource = e.target.value;
- Utils.addDebugLog(`Token source changed to: ${state.tokenSource}`, 'info');
- updateSystemStatus();
-
- // Invalidate current token to force new generation with new method
- if (isTokenValid()) {
- Utils.addDebugLog('Invalidating current token due to source change', 'info');
- invalidateToken();
- }
- });
-
- // NEW: Enhanced batch mode controls
- document.getElementById('smartBatchMode').addEventListener('change', (e) => {
- if (e.target.checked) {
- state.autoBatchEnabled = true;
- state.batchOptimization = true;
- document.getElementById('manualBatchControls').style.display = 'none';
- Utils.addDebugLog('Smart batch mode enabled - will auto-adjust to damage count', 'info');
- updateBatchInfo();
- }
- });
-
- document.getElementById('manualBatchMode').addEventListener('change', (e) => {
- if (e.target.checked) {
- state.autoBatchEnabled = false;
- state.batchOptimization = false;
- document.getElementById('manualBatchControls').style.display = 'flex';
- Utils.addDebugLog('Manual batch mode enabled - using fixed batch size', 'info');
- updateBatchInfo();
- }
- });
-
- // Manual batch size controls with +/- buttons
- document.getElementById('batchSizeIncrease').addEventListener('click', () => {
- const newSize = Math.min(CONFIG.MAX_BATCH_SIZE, state.currentBatchSize + 1);
- if (newSize !== state.currentBatchSize) {
- state.currentBatchSize = newSize;
- document.getElementById('batchSize').value = state.currentBatchSize;
- Utils.addDebugLog(`Increased batch size to ${state.currentBatchSize} pixels`, 'info');
- updateBatchInfo();
- }
- });
-
- document.getElementById('batchSizeDecrease').addEventListener('click', () => {
- const newSize = Math.max(CONFIG.MIN_BATCH_SIZE, state.currentBatchSize - 1);
- if (newSize !== state.currentBatchSize) {
- state.currentBatchSize = newSize;
- document.getElementById('batchSize').value = state.currentBatchSize;
- Utils.addDebugLog(`Decreased batch size to ${state.currentBatchSize} pixels`, 'info');
- updateBatchInfo();
- }
- });
-
- // Manual input allows direct typing for precise control
- document.getElementById('batchSize').addEventListener('input', (e) => {
- const value = parseInt(e.target.value);
- if (!isNaN(value)) {
- const clampedValue = Math.max(CONFIG.MIN_BATCH_SIZE, Math.min(CONFIG.MAX_BATCH_SIZE, value));
- if (clampedValue !== state.currentBatchSize) {
- state.currentBatchSize = clampedValue;
- if (clampedValue !== value) {
- e.target.value = state.currentBatchSize;
- }
- Utils.addDebugLog(`Manual batch size set to ${state.currentBatchSize} pixels`, 'info');
- updateBatchInfo();
- }
- }
- });
-
- document.getElementById('paintTransparentPixels').addEventListener('change', (e) => {
- state.paintTransparentPixels = e.target.checked;
- Utils.addDebugLog(`Transparent pixel repair ${state.paintTransparentPixels ? 'enabled' : 'disabled'}`, 'info');
- });
-
- // Clear debug
- document.getElementById('clearDebugBtn').addEventListener('click', () => {
- state.debugLogs = [];
- updateDebugConsole();
- Utils.addDebugLog('Debug console cleared', 'info');
- Utils.addDebugLog('Enhanced WPlace Autonomous Repair Tool ready', 'info');
- });
- }
-
- // Enhanced data migration helper
- function migrateProgressData(data) {
- if (!data.version || data.version < '2.0') {
- Utils.addDebugLog('Migrating old progress data format...', 'info');
-
- if (!data.state) data.state = {};
- if (!data.imageData) data.imageData = {};
-
- data.state.paintWhitePixels = data.state.paintWhitePixels ?? true;
- data.state.paintTransparentPixels = data.state.paintTransparentPixels ?? false;
-
- data.version = '2.0';
- }
-
- // Additional migrations for autonomous features
- if (data.version === '2.0') {
- data.state.autonomousMode = data.state.autonomousMode ?? CONFIG.AUTONOMOUS_MODE;
- data.state.autoTokenRefresh = data.state.autoTokenRefresh ?? CONFIG.AUTO_TOKEN_REFRESH;
- data.state.tokenSource = data.state.tokenSource ?? CONFIG.TOKEN_SOURCE;
- data.version = '3.0';
- Utils.addDebugLog('Migrated to autonomous features v3.0', 'info');
- }
-
- return data;
- }
-
- // Main initialization function
- async function initialize() {
- Utils.addDebugLog('Starting WPlace Autonomous Repair Tool v3.0...', 'info');
-
- // Create UI first
- createUI();
-
- Utils.addDebugLog('NEW Enhanced Features Initialized:', 'info');
- Utils.addDebugLog('⢠NEW Turnstile implementation with widget reuse', 'info');
- Utils.addDebugLog('⢠Enhanced sitekey detection from multiple sources', 'info');
- Utils.addDebugLog('⢠Sophisticated token management with preloading', 'info');
- Utils.addDebugLog('⢠Autonomous operation with smart retry logic', 'info');
- Utils.addDebugLog('⢠Enhanced tile interception and caching', 'info');
- Utils.addDebugLog('⢠Adaptive repair algorithms with failure handling', 'info');
- Utils.addDebugLog('⢠Visual notifications and audio alerts', 'info');
- Utils.addDebugLog('⢠Draggable window with minimize/close controls', 'info');
-
- if (state.autonomousMode) {
- Utils.addDebugLog('AUTONOMOUS MODE ACTIVE - Tool will operate independently', 'success');
- Utils.addDebugLog('Instructions for autonomous operation:', 'info');
- Utils.addDebugLog('1. Wait for initial setup to complete', 'info');
- Utils.addDebugLog('2. Load a progress file using "Load Progress File"', 'info');
- Utils.addDebugLog('3. Enable "Auto Repair" to start autonomous monitoring', 'info');
- Utils.addDebugLog('4. Tool will automatically scan and repair damage', 'info');
- Utils.addDebugLog('5. Tokens will be generated and refreshed automatically', 'info');
- Utils.addDebugLog('6. Monitor debug console for autonomous operation logs', 'info');
- } else {
- Utils.addDebugLog('Manual mode active - use controls for manual operation', 'info');
- }
-
- // Show autonomous mode status
- updateStatus(state.autonomousMode ? Utils.t('autonomousModeActive') : 'Manual mode ready - Load file and enable auto repair to start');
-
- Utils.addDebugLog('WPlace Autonomous Repair Tool ready for operation', 'success');
- }
-
- // Enhanced cleanup function
- function cleanup() {
- Utils.addDebugLog('Cleaning up autonomous systems...', 'info');
-
- // Clear all timers
- if (state.autoRepairTimer) {
- clearInterval(state.autoRepairTimer);
- state.autoRepairTimer = null;
- }
-
- if (state.tokenRetryTimer) {
- clearTimeout(state.tokenRetryTimer);
- state.tokenRetryTimer = null;
- }
-
- if (state.tokenPreloadTimer) {
- clearTimeout(state.tokenPreloadTimer);
- state.tokenPreloadTimer = null;
- }
-
- // Cleanup Turnstile
- Utils.cleanupTurnstile();
-
- // Set stop flag
- state.stopFlag = true;
-
- Utils.addDebugLog('Cleanup completed', 'info');
- }
-
- // Add cleanup on page unload
- window.addEventListener('beforeunload', cleanup);
-
- // Handle page visibility changes for autonomous mode
- document.addEventListener('visibilitychange', () => {
- if (state.autonomousMode) {
- if (document.hidden) {
- Utils.addDebugLog('Page hidden - autonomous systems continue running', 'info');
- } else {
- Utils.addDebugLog('Page visible - autonomous systems active', 'info');
- // Refresh status when page becomes visible
- updateCharges();
- updateTokenStatus();
- }
- }
- });
-
- // Enhanced error handling for autonomous mode
- window.addEventListener('error', (event) => {
- if (state.autonomousMode) {
- Utils.addDebugLog(`Global error in autonomous mode: ${event.error?.message || 'Unknown error'}`, 'error');
- }
- });
-
- window.addEventListener('unhandledrejection', (event) => {
- if (state.autonomousMode) {
- Utils.addDebugLog(`Unhandled promise rejection in autonomous mode: ${event.reason}`, 'error');
- }
- });
-
- // Start the enhanced autonomous application
- initialize().catch(error => {
- console.error('Failed to initialize autonomous repair tool:', error);
- Utils.addDebugLog(`Initialization failed: ${error.message}`, 'error');
- });
-
-})();
diff --git a/Extensions/scripts/image-processor.js b/Extensions/scripts/image-processor.js
deleted file mode 100644
index 37beb2e..0000000
--- a/Extensions/scripts/image-processor.js
+++ /dev/null
@@ -1,1315 +0,0 @@
-// ==UserScript==
-// @name WPlace Image Processor
-// @namespace http://tampermonkey.net/
-// @version 2025-09-16.1
-// @description Image processing and color management for WPlace AutoBot
-// @author Wbot
-// @match https://wplace.live/*
-// @grant none
-// ==/UserScript==
-
-/**
- * ImageProcessor - Handles image loading, processing, color conversion, and dithering for WPlace AutoBot
- * Extracted from Auto-Image.js for better modularity and reusabilit
- */
-class ImageProcessor {
- constructor(imageSrc = null) {
- this.imageSrc = imageSrc;
- this.img = null;
- this.canvas = null;
- this.ctx = null;
-
- // Dithering buffers
- this._ditherWorkBuf = null;
- this._ditherEligibleBuf = null;
-
- // Color cache for performance optimization
- this._colorCache = new Map();
- this._labCache = new Map();
- this._hsvCache = new Map();
- this._hslCache = new Map();
- this._xyzCache = new Map();
- this._luvCache = new Map();
- this._yuvCache = new Map();
- this._oklabCache = new Map();
- this._lchCache = new Map();
-
- // Configuration constants
- this.TRANSPARENCY_THRESHOLD = 128;
- this.WHITE_THRESHOLD = 230;
- this.COLOR_CACHE_LIMIT = 15000;
- }
-
- /**
- * Load image from source
- * @returns {Promise}
- */
- async load() {
- if (!this.imageSrc) {
- throw new Error('No image source provided');
- }
-
- return new Promise((resolve, reject) => {
- this.img = new Image();
- this.img.crossOrigin = 'anonymous';
- this.img.onload = () => {
- this.canvas = document.createElement('canvas');
- this.ctx = this.canvas.getContext('2d');
- this.canvas.width = this.img.width;
- this.canvas.height = this.img.height;
- this.ctx.drawImage(this.img, 0, 0);
- resolve();
- };
- this.img.onerror = reject;
- this.img.src = this.imageSrc;
- });
- }
-
- /**
- * Get image dimensions
- * @returns {{width: number, height: number}}
- */
- getDimensions() {
- if (!this.canvas) {
- throw new Error('Image not loaded. Call load() first.');
- }
- return {
- width: this.canvas.width,
- height: this.canvas.height,
- };
- }
-
- /**
- * Get pixel data from the image
- * @returns {Uint8ClampedArray} RGBA pixel data
- */
- getPixelData() {
- if (!this.ctx) {
- throw new Error('Image not loaded. Call load() first.');
- }
- return this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height).data;
- }
-
- /**
- * Resize image to new dimensions
- * @param {number} newWidth - Target width
- * @param {number} newHeight - Target height
- * @returns {Uint8ClampedArray} Resized image data
- */
- resize(newWidth, newHeight) {
- if (!this.canvas || !this.ctx) {
- throw new Error('Image not loaded. Call load() first.');
- }
-
- const tempCanvas = document.createElement('canvas');
- const tempCtx = tempCanvas.getContext('2d');
-
- tempCanvas.width = newWidth;
- tempCanvas.height = newHeight;
-
- tempCtx.imageSmoothingEnabled = false;
- tempCtx.drawImage(this.canvas, 0, 0, newWidth, newHeight);
-
- this.canvas.width = newWidth;
- this.canvas.height = newHeight;
- this.ctx.imageSmoothingEnabled = false;
- this.ctx.drawImage(tempCanvas, 0, 0);
-
- return this.ctx.getImageData(0, 0, newWidth, newHeight).data;
- }
-
- /**
- * Generate preview with resizing
- * @param {number} width - Preview width
- * @param {number} height - Preview height
- * @returns {string} Base64 data URL of the preview
- */
- generatePreview(width, height) {
- if (!this.canvas) {
- throw new Error('Image not loaded. Call load() first.');
- }
-
- const previewCanvas = document.createElement('canvas');
- const previewCtx = previewCanvas.getContext('2d');
- previewCanvas.width = width;
- previewCanvas.height = height;
-
- previewCtx.imageSmoothingEnabled = false;
- previewCtx.drawImage(this.canvas, 0, 0, width, height);
-
- return previewCanvas.toDataURL();
- }
-
- // =============================================
- // COLOR PROCESSING METHODS
- // =============================================
-
- /**
- * Convert RGB to LAB color space
- * @param {number} r - Red value (0-255)
- * @param {number} g - Green value (0-255)
- * @param {number} b - Blue value (0-255)
- * @returns {number[]} LAB values [L, a, b]
- * @private
- */
- _rgbToLab(r, g, b) {
- // Normalize RGB values
- let x = r / 255.0;
- let y = g / 255.0;
- let z = b / 255.0;
-
- // Apply gamma correction
- x = x > 0.04045 ? Math.pow((x + 0.055) / 1.055, 2.4) : x / 12.92;
- y = y > 0.04045 ? Math.pow((y + 0.055) / 1.055, 2.4) : y / 12.92;
- z = z > 0.04045 ? Math.pow((z + 0.055) / 1.055, 2.4) : z / 12.92;
-
- // Convert to XYZ using sRGB matrix
- let X = x * 0.4124564 + y * 0.3575761 + z * 0.1804375;
- let Y = x * 0.2126729 + y * 0.7151522 + z * 0.0721750;
- let Z = x * 0.0193339 + y * 0.1191920 + z * 0.9503041;
-
- // Normalize for D65 illuminant
- X /= 0.95047;
- Y /= 1.00000;
- Z /= 1.08883;
-
- // Convert to LAB
- X = X > 0.008856 ? Math.pow(X, 1/3) : (7.787 * X + 16/116);
- Y = Y > 0.008856 ? Math.pow(Y, 1/3) : (7.787 * Y + 16/116);
- Z = Z > 0.008856 ? Math.pow(Z, 1/3) : (7.787 * Z + 16/116);
-
- const L = 116 * Y - 16;
- const a = 500 * (X - Y);
- const b2 = 200 * (Y - Z);
-
- return [L, a, b2];
- }
-
- /**
- * Get LAB values with caching
- * @param {number} r - Red value
- * @param {number} g - Green value
- * @param {number} b - Blue value
- * @returns {number[]} Cached LAB values
- * @private
- */
- _getLab(r, g, b) {
- const key = (r << 16) | (g << 8) | b;
- let v = this._labCache.get(key);
- if (!v) {
- v = this._rgbToLab(r, g, b);
- this._labCache.set(key, v);
- }
- return v;
- }
-
- /**
- * Convert RGB to HSV color space
- * @param {number} r - Red value (0-255)
- * @param {number} g - Green value (0-255)
- * @param {number} b - Blue value (0-255)
- * @returns {number[]} HSV values [H (0-360), S (0-1), V (0-1)]
- * @private
- */
- _rgbToHsv(r, g, b) {
- r /= 255;
- g /= 255;
- b /= 255;
-
- const max = Math.max(r, g, b);
- const min = Math.min(r, g, b);
- const delta = max - min;
-
- let h = 0;
- const s = max === 0 ? 0 : delta / max;
- const v = max;
-
- if (delta !== 0) {
- if (max === r) {
- h = ((g - b) / delta) % 6;
- } else if (max === g) {
- h = (b - r) / delta + 2;
- } else {
- h = (r - g) / delta + 4;
- }
- h *= 60;
- if (h < 0) h += 360;
- }
-
- return [h, s, v];
- }
-
- /**
- * Convert HSV to RGB color space
- * @param {number} h - Hue value (0-360)
- * @param {number} s - Saturation value (0-1)
- * @param {number} v - Value/Brightness (0-1)
- * @returns {number[]} RGB values [R, G, B] (0-255)
- * @private
- */
- _hsvToRgb(h, s, v) {
- const c = v * s;
- const x = c * (1 - Math.abs(((h / 60) % 2) - 1));
- const m = v - c;
-
- let r = 0, g = 0, b = 0;
-
- if (h >= 0 && h < 60) {
- r = c; g = x; b = 0;
- } else if (h >= 60 && h < 120) {
- r = x; g = c; b = 0;
- } else if (h >= 120 && h < 180) {
- r = 0; g = c; b = x;
- } else if (h >= 180 && h < 240) {
- r = 0; g = x; b = c;
- } else if (h >= 240 && h < 300) {
- r = x; g = 0; b = c;
- } else if (h >= 300 && h < 360) {
- r = c; g = 0; b = x;
- }
-
- return [
- Math.round((r + m) * 255),
- Math.round((g + m) * 255),
- Math.round((b + m) * 255)
- ];
- }
-
- /**
- * Convert RGB to HSL color space
- * @param {number} r - Red value (0-255)
- * @param {number} g - Green value (0-255)
- * @param {number} b - Blue value (0-255)
- * @returns {number[]} HSL values [H (0-360), S (0-1), L (0-1)]
- * @private
- */
- _rgbToHsl(r, g, b) {
- r /= 255;
- g /= 255;
- b /= 255;
-
- const max = Math.max(r, g, b);
- const min = Math.min(r, g, b);
- const delta = max - min;
-
- let h = 0;
- let s = 0;
- const l = (max + min) / 2;
-
- if (delta !== 0) {
- s = l > 0.5 ? delta / (2 - max - min) : delta / (max + min);
-
- if (max === r) {
- h = ((g - b) / delta) % 6;
- } else if (max === g) {
- h = (b - r) / delta + 2;
- } else {
- h = (r - g) / delta + 4;
- }
- h *= 60;
- if (h < 0) h += 360;
- }
-
- return [h, s, l];
- }
-
- /**
- * Convert HSL to RGB color space
- * @param {number} h - Hue value (0-360)
- * @param {number} s - Saturation value (0-1)
- * @param {number} l - Lightness value (0-1)
- * @returns {number[]} RGB values [R, G, B] (0-255)
- * @private
- */
- _hslToRgb(h, s, l) {
- const c = (1 - Math.abs(2 * l - 1)) * s;
- const x = c * (1 - Math.abs(((h / 60) % 2) - 1));
- const m = l - c / 2;
-
- let r = 0, g = 0, b = 0;
-
- if (h >= 0 && h < 60) {
- r = c; g = x; b = 0;
- } else if (h >= 60 && h < 120) {
- r = x; g = c; b = 0;
- } else if (h >= 120 && h < 180) {
- r = 0; g = c; b = x;
- } else if (h >= 180 && h < 240) {
- r = 0; g = x; b = c;
- } else if (h >= 240 && h < 300) {
- r = x; g = 0; b = c;
- } else if (h >= 300 && h < 360) {
- r = c; g = 0; b = x;
- }
-
- return [
- Math.round((r + m) * 255),
- Math.round((g + m) * 255),
- Math.round((b + m) * 255)
- ];
- }
-
- /**
- * Convert RGB to XYZ color space
- * @param {number} r - Red value (0-255)
- * @param {number} g - Green value (0-255)
- * @param {number} b - Blue value (0-255)
- * @returns {number[]} XYZ values [X, Y, Z]
- * @private
- */
- _rgbToXyz(r, g, b) {
- // Normalize RGB values
- let x = r / 255.0;
- let y = g / 255.0;
- let z = b / 255.0;
-
- // Apply gamma correction
- x = x > 0.04045 ? Math.pow((x + 0.055) / 1.055, 2.4) : x / 12.92;
- y = y > 0.04045 ? Math.pow((y + 0.055) / 1.055, 2.4) : y / 12.92;
- z = z > 0.04045 ? Math.pow((z + 0.055) / 1.055, 2.4) : z / 12.92;
-
- // Convert to XYZ using sRGB matrix
- const X = x * 0.4124564 + y * 0.3575761 + z * 0.1804375;
- const Y = x * 0.2126729 + y * 0.7151522 + z * 0.0721750;
- const Z = x * 0.0193339 + y * 0.1191920 + z * 0.9503041;
-
- return [X, Y, Z];
- }
-
- /**
- * Convert XYZ to RGB color space
- * @param {number} x - X value
- * @param {number} y - Y value
- * @param {number} z - Z value
- * @returns {number[]} RGB values [R, G, B] (0-255)
- * @private
- */
- _xyzToRgb(x, y, z) {
- // Convert XYZ to linear RGB
- let r = x * 3.2404542 + y * -1.5371385 + z * -0.4985314;
- let g = x * -0.9692660 + y * 1.8760108 + z * 0.0415560;
- let b = x * 0.0556434 + y * -0.2040259 + z * 1.0572252;
-
- // Apply gamma correction
- r = r > 0.0031308 ? 1.055 * Math.pow(r, 1.0 / 2.4) - 0.055 : 12.92 * r;
- g = g > 0.0031308 ? 1.055 * Math.pow(g, 1.0 / 2.4) - 0.055 : 12.92 * g;
- b = b > 0.0031308 ? 1.055 * Math.pow(b, 1.0 / 2.4) - 0.055 : 12.92 * b;
-
- // Clamp and convert to 0-255 range
- return [
- Math.round(Math.max(0, Math.min(1, r)) * 255),
- Math.round(Math.max(0, Math.min(1, g)) * 255),
- Math.round(Math.max(0, Math.min(1, b)) * 255)
- ];
- }
-
- /**
- * Convert RGB to LUV color space
- * @param {number} r - Red value (0-255)
- * @param {number} g - Green value (0-255)
- * @param {number} b - Blue value (0-255)
- * @returns {number[]} LUV values [L, u, v]
- * @private
- */
- _rgbToLuv(r, g, b) {
- const [X, Y, Z] = this._rgbToXyz(r, g, b);
-
- // Reference white D65
- const Xn = 0.95047;
- const Yn = 1.00000;
- const Zn = 1.08883;
-
- const yr = Y / Yn;
- const L = yr > 0.008856 ? 116 * Math.pow(yr, 1/3) - 16 : 903.3 * yr;
-
- const denom = X + 15 * Y + 3 * Z;
- const denomN = Xn + 15 * Yn + 3 * Zn;
-
- const up = denom === 0 ? 0 : (4 * X) / denom;
- const vp = denom === 0 ? 0 : (9 * Y) / denom;
- const upN = (4 * Xn) / denomN;
- const vpN = (9 * Yn) / denomN;
-
- const u = 13 * L * (up - upN);
- const v = 13 * L * (vp - vpN);
-
- return [L, u, v];
- }
-
- /**
- * Convert LUV to RGB color space
- * @param {number} l - L value
- * @param {number} u - u value
- * @param {number} v - v value
- * @returns {number[]} RGB values [R, G, B] (0-255)
- * @private
- */
- _luvToRgb(l, u, v) {
- // Reference white D65
- const Xn = 0.95047;
- const Yn = 1.00000;
- const Zn = 1.08883;
-
- const upN = (4 * Xn) / (Xn + 15 * Yn + 3 * Zn);
- const vpN = (9 * Yn) / (Xn + 15 * Yn + 3 * Zn);
-
- const Y = l > 8 ? Yn * Math.pow((l + 16) / 116, 3) : Yn * l / 903.3;
-
- const up = u / (13 * l) + upN;
- const vp = v / (13 * l) + vpN;
-
- const X = Y * 9 * up / (4 * vp);
- const Z = Y * (12 - 3 * up - 20 * vp) / (4 * vp);
-
- return this._xyzToRgb(X, Y, Z);
- }
-
- /**
- * Convert RGB to YUV color space
- * @param {number} r - Red value (0-255)
- * @param {number} g - Green value (0-255)
- * @param {number} b - Blue value (0-255)
- * @returns {number[]} YUV values [Y, U, V]
- * @private
- */
- _rgbToYuv(r, g, b) {
- const Y = 0.299 * r + 0.587 * g + 0.114 * b;
- const U = -0.14713 * r - 0.28886 * g + 0.436 * b;
- const V = 0.615 * r - 0.51499 * g - 0.10001 * b;
-
- return [Y, U, V];
- }
-
- /**
- * Convert YUV to RGB color space
- * @param {number} y - Y (luminance) value
- * @param {number} u - U (chrominance) value
- * @param {number} v - V (chrominance) value
- * @returns {number[]} RGB values [R, G, B] (0-255)
- * @private
- */
- _yuvToRgb(y, u, v) {
- const r = y + 1.13983 * v;
- const g = y - 0.39465 * u - 0.58060 * v;
- const b = y + 2.03211 * u;
-
- return [
- Math.round(Math.max(0, Math.min(255, r))),
- Math.round(Math.max(0, Math.min(255, g))),
- Math.round(Math.max(0, Math.min(255, b)))
- ];
- }
-
- /**
- * Convert RGB to Oklab color space (improved LAB)
- * @param {number} r - Red value (0-255)
- * @param {number} g - Green value (0-255)
- * @param {number} b - Blue value (0-255)
- * @returns {number[]} Oklab values [L, a, b]
- * @private
- */
- _rgbToOklab(r, g, b) {
- // Normalize RGB
- r /= 255;
- g /= 255;
- b /= 255;
-
- // Apply gamma correction
- r = r > 0.04045 ? Math.pow((r + 0.055) / 1.055, 2.4) : r / 12.92;
- g = g > 0.04045 ? Math.pow((g + 0.055) / 1.055, 2.4) : g / 12.92;
- b = b > 0.04045 ? Math.pow((b + 0.055) / 1.055, 2.4) : b / 12.92;
-
- // Linear RGB to Oklab
- const l = 0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b;
- const m = 0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b;
- const s = 0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b;
-
- const l_ = Math.cbrt(l);
- const m_ = Math.cbrt(m);
- const s_ = Math.cbrt(s);
-
- return [
- 0.2104542553 * l_ + 0.7936177850 * m_ - 0.0040720468 * s_,
- 1.9779984951 * l_ - 2.4285922050 * m_ + 0.4505937099 * s_,
- 0.0259040371 * l_ + 0.7827717662 * m_ - 0.8086757660 * s_
- ];
- }
-
- /**
- * Convert Oklab to RGB color space
- * @param {number} l - L value
- * @param {number} a - a value
- * @param {number} b - b value
- * @returns {number[]} RGB values [R, G, B] (0-255)
- * @private
- */
- _oklabToRgb(l, a, b) {
- const l_ = l + 0.3963377774 * a + 0.2158037573 * b;
- const m_ = l - 0.1055613458 * a - 0.0638541728 * b;
- const s_ = l - 0.0894841775 * a - 1.2914855480 * b;
-
- const l3 = l_ * l_ * l_;
- const m3 = m_ * m_ * m_;
- const s3 = s_ * s_ * s_;
-
- let r = 4.0767416621 * l3 - 3.3077115913 * m3 + 0.2309699292 * s3;
- let g = -1.2684380046 * l3 + 2.6097574011 * m3 - 0.3413193965 * s3;
- let b2 = -0.0041960863 * l3 - 0.7034186147 * m3 + 1.7076147010 * s3;
-
- // Apply gamma correction
- r = r > 0.0031308 ? 1.055 * Math.pow(r, 1.0 / 2.4) - 0.055 : 12.92 * r;
- g = g > 0.0031308 ? 1.055 * Math.pow(g, 1.0 / 2.4) - 0.055 : 12.92 * g;
- b2 = b2 > 0.0031308 ? 1.055 * Math.pow(b2, 1.0 / 2.4) - 0.055 : 12.92 * b2;
-
- return [
- Math.round(Math.max(0, Math.min(1, r)) * 255),
- Math.round(Math.max(0, Math.min(1, g)) * 255),
- Math.round(Math.max(0, Math.min(1, b2)) * 255)
- ];
- }
-
- /**
- * Convert LAB to LCH color space (cylindrical representation)
- * @param {number} l - L value
- * @param {number} a - a value
- * @param {number} b - b value
- * @returns {number[]} LCH values [L, C, H]
- * @private
- */
- _labToLch(l, a, b) {
- const c = Math.sqrt(a * a + b * b);
- let h = Math.atan2(b, a) * 180 / Math.PI;
- if (h < 0) h += 360;
-
- return [l, c, h];
- }
-
- /**
- * Convert LCH to LAB color space
- * @param {number} l - L value
- * @param {number} c - C (chroma) value
- * @param {number} h - H (hue) value (0-360)
- * @returns {number[]} LAB values [L, a, b]
- * @private
- */
- _lchToLab(l, c, h) {
- const hRad = h * Math.PI / 180;
- const a = c * Math.cos(hRad);
- const b = c * Math.sin(hRad);
-
- return [l, a, b];
- }
-
- /**
- * Find closest color in palette using specified algorithm
- * @param {number} r - Red value
- * @param {number} g - Green value
- * @param {number} b - Blue value
- * @param {Array} palette - Array of RGB arrays [[r,g,b], ...]
- * @param {string} algorithm - 'legacy' or 'lab'
- * @param {Object} options - Additional options for color matching
- * @returns {number[]} Closest RGB color [r, g, b]
- */
- findClosestPaletteColor(r, g, b, palette, algorithm = 'lab', options = {}) {
- if (!palette || palette.length === 0) {
- return [0, 0, 0];
- }
-
- const { enableChromaPenalty = false, chromaPenaltyWeight = 0.15 } = options;
-
- if (algorithm === 'legacy') {
- let menorDist = Infinity;
- let cor = [0, 0, 0];
- for (let i = 0; i < palette.length; i++) {
- const [pr, pg, pb] = palette[i];
- const rmean = (pr + r) / 2;
- const rdiff = pr - r;
- const gdiff = pg - g;
- const bdiff = pb - b;
- const dist = Math.sqrt(
- (((512 + rmean) * rdiff * rdiff) >> 8) +
- 4 * gdiff * gdiff +
- (((767 - rmean) * bdiff * bdiff) >> 8)
- );
- if (dist < menorDist) {
- menorDist = dist;
- cor = [pr, pg, pb];
- }
- }
- return cor;
- }
-
- // HSV algorithm
- if (algorithm === 'hsv') {
- const [ht, st, vt] = this._rgbToHsv(r, g, b);
- let best = null;
- let bestDist = Infinity;
-
- for (let i = 0; i < palette.length; i++) {
- const [pr, pg, pb] = palette[i];
- const [hp, sp, vp] = this._rgbToHsv(pr, pg, pb);
- const dh = Math.min(Math.abs(ht - hp), 360 - Math.abs(ht - hp)) / 360; // Normalize hue distance
- const ds = st - sp;
- const dv = vt - vp;
- const dist = dh * dh + ds * ds + dv * dv;
-
- if (dist < bestDist) {
- bestDist = dist;
- best = palette[i];
- if (bestDist === 0) break;
- }
- }
- return best || [0, 0, 0];
- }
-
- // HSL algorithm
- if (algorithm === 'hsl') {
- const [ht, st, lt] = this._rgbToHsl(r, g, b);
- let best = null;
- let bestDist = Infinity;
-
- for (let i = 0; i < palette.length; i++) {
- const [pr, pg, pb] = palette[i];
- const [hp, sp, lp] = this._rgbToHsl(pr, pg, pb);
- const dh = Math.min(Math.abs(ht - hp), 360 - Math.abs(ht - hp)) / 360; // Normalize hue distance
- const ds = st - sp;
- const dl = lt - lp;
- const dist = dh * dh + ds * ds + dl * dl;
-
- if (dist < bestDist) {
- bestDist = dist;
- best = palette[i];
- if (bestDist === 0) break;
- }
- }
- return best || [0, 0, 0];
- }
-
- // XYZ algorithm
- if (algorithm === 'xyz') {
- const [xt, yt, zt] = this._rgbToXyz(r, g, b);
- let best = null;
- let bestDist = Infinity;
-
- for (let i = 0; i < palette.length; i++) {
- const [pr, pg, pb] = palette[i];
- const [xp, yp, zp] = this._rgbToXyz(pr, pg, pb);
- const dx = xt - xp;
- const dy = yt - yp;
- const dz = zt - zp;
- const dist = dx * dx + dy * dy + dz * dz;
-
- if (dist < bestDist) {
- bestDist = dist;
- best = palette[i];
- if (bestDist === 0) break;
- }
- }
- return best || [0, 0, 0];
- }
-
- // LUV algorithm
- if (algorithm === 'luv') {
- const [lt, ut, vt] = this._rgbToLuv(r, g, b);
- let best = null;
- let bestDist = Infinity;
-
- for (let i = 0; i < palette.length; i++) {
- const [pr, pg, pb] = palette[i];
- const [lp, up, vp] = this._rgbToLuv(pr, pg, pb);
- const dl = lt - lp;
- const du = ut - up;
- const dv = vt - vp;
- const dist = dl * dl + du * du + dv * dv;
-
- if (dist < bestDist) {
- bestDist = dist;
- best = palette[i];
- if (bestDist === 0) break;
- }
- }
- return best || [0, 0, 0];
- }
-
- // YUV algorithm
- if (algorithm === 'yuv') {
- const [yt, ut, vt] = this._rgbToYuv(r, g, b);
- let best = null;
- let bestDist = Infinity;
-
- for (let i = 0; i < palette.length; i++) {
- const [pr, pg, pb] = palette[i];
- const [yp, up, vp] = this._rgbToYuv(pr, pg, pb);
- const dy = yt - yp;
- const du = ut - up;
- const dv = vt - vp;
- const dist = dy * dy + du * du + dv * dv;
-
- if (dist < bestDist) {
- bestDist = dist;
- best = palette[i];
- if (bestDist === 0) break;
- }
- }
- return best || [0, 0, 0];
- }
-
- // Oklab algorithm
- if (algorithm === 'oklab') {
- const [lt, at, bt] = this._rgbToOklab(r, g, b);
- let best = null;
- let bestDist = Infinity;
-
- for (let i = 0; i < palette.length; i++) {
- const [pr, pg, pb] = palette[i];
- const [lp, ap, bp] = this._rgbToOklab(pr, pg, pb);
- const dl = lt - lp;
- const da = at - ap;
- const db = bt - bp;
- const dist = dl * dl + da * da + db * db;
-
- if (dist < bestDist) {
- bestDist = dist;
- best = palette[i];
- if (bestDist === 0) break;
- }
- }
- return best || [0, 0, 0];
- }
-
- // LCH algorithm
- if (algorithm === 'lch') {
- const [Lt, at, bt] = this._getLab(r, g, b);
- const [lt, ct, ht] = this._labToLch(Lt, at, bt);
- let best = null;
- let bestDist = Infinity;
-
- for (let i = 0; i < palette.length; i++) {
- const [pr, pg, pb] = palette[i];
- const [Lp, ap, bp] = this._getLab(pr, pg, pb);
- const [lp, cp, hp] = this._labToLch(Lp, ap, bp);
- const dl = lt - lp;
- const dc = ct - cp;
- const dh = Math.min(Math.abs(ht - hp), 360 - Math.abs(ht - hp)); // Circular hue distance
- const dist = dl * dl + dc * dc + (dh * dh) / 360; // Normalize hue
-
- if (dist < bestDist) {
- bestDist = dist;
- best = palette[i];
- if (bestDist === 0) break;
- }
- }
- return best || [0, 0, 0];
- }
-
- // LAB algorithm (default)
- const [Lt, at, bt] = this._getLab(r, g, b);
- const targetChroma = Math.sqrt(at * at + bt * bt);
- let best = null;
- let bestDist = Infinity;
-
- for (let i = 0; i < palette.length; i++) {
- const [pr, pg, pb] = palette[i];
- const [Lp, ap, bp] = this._getLab(pr, pg, pb);
- const dL = Lt - Lp;
- const da = at - ap;
- const db = bt - bp;
- let dist = dL * dL + da * da + db * db;
-
- if (enableChromaPenalty && targetChroma > 20) {
- const candChroma = Math.sqrt(ap * ap + bp * bp);
- if (candChroma < targetChroma) {
- const chromaDiff = targetChroma - candChroma;
- dist += chromaDiff * chromaDiff * chromaPenaltyWeight;
- }
- }
-
- if (dist < bestDist) {
- bestDist = dist;
- best = palette[i];
- if (bestDist === 0) break;
- }
- }
-
- return best || [0, 0, 0];
- }
-
- /**
- * Check if a pixel is considered white based on threshold
- * @param {number} r - Red value
- * @param {number} g - Green value
- * @param {number} b - Blue value
- * @param {number} threshold - White threshold (default: 230)
- * @returns {boolean} True if pixel is white
- */
- isWhitePixel(r, g, b, threshold = this.WHITE_THRESHOLD) {
- return r >= threshold && g >= threshold && b >= threshold;
- }
-
- /**
- * Resolve target RGB to closest available color with caching
- * @param {number[]} targetRgb - Target RGB array [r, g, b]
- * @param {Array} availableColors - Available colors with id and rgb properties
- * @param {Object} options - Matching options
- * @returns {Object} {id: number|null, rgb: number[]}
- */
- resolveColor(targetRgb, availableColors, options = {}) {
- const {
- exactMatch = false,
- algorithm = 'lab',
- enableChromaPenalty = false,
- chromaPenaltyWeight = 0.15,
- whiteThreshold = this.WHITE_THRESHOLD
- } = options;
-
- if (!availableColors || availableColors.length === 0) {
- return { id: null, rgb: targetRgb };
- }
-
- const cacheKey = `${targetRgb[0]},${targetRgb[1]},${targetRgb[2]}|${algorithm}|${enableChromaPenalty ? 'c' : 'nc'}|${chromaPenaltyWeight}|${exactMatch ? 'exact' : 'closest'}`;
-
- if (this._colorCache.has(cacheKey)) {
- return this._colorCache.get(cacheKey);
- }
-
- // Check for exact match
- if (exactMatch) {
- const match = availableColors.find(
- (c) => c.rgb[0] === targetRgb[0] && c.rgb[1] === targetRgb[1] && c.rgb[2] === targetRgb[2]
- );
- const result = match ? { id: match.id, rgb: [...match.rgb] } : { id: null, rgb: targetRgb };
- this._colorCache.set(cacheKey, result);
- return result;
- }
-
- // Check for white pixel matching
- if (
- targetRgb[0] >= whiteThreshold &&
- targetRgb[1] >= whiteThreshold &&
- targetRgb[2] >= whiteThreshold
- ) {
- const whiteEntry = availableColors.find(
- (c) => c.rgb[0] >= whiteThreshold && c.rgb[1] >= whiteThreshold && c.rgb[2] >= whiteThreshold
- );
- if (whiteEntry) {
- const result = { id: whiteEntry.id, rgb: [...whiteEntry.rgb] };
- this._colorCache.set(cacheKey, result);
- return result;
- }
- }
-
- // Find nearest color
- let bestId = availableColors[0].id;
- let bestRgb = [...availableColors[0].rgb];
- let bestScore = Infinity;
-
- if (algorithm === 'legacy') {
- for (let i = 0; i < availableColors.length; i++) {
- const c = availableColors[i];
- const [r, g, b] = c.rgb;
- const rmean = (r + targetRgb[0]) / 2;
- const rdiff = r - targetRgb[0];
- const gdiff = g - targetRgb[1];
- const bdiff = b - targetRgb[2];
- const dist = Math.sqrt(
- (((512 + rmean) * rdiff * rdiff) >> 8) +
- 4 * gdiff * gdiff +
- (((767 - rmean) * bdiff * bdiff) >> 8)
- );
- if (dist < bestScore) {
- bestScore = dist;
- bestId = c.id;
- bestRgb = [...c.rgb];
- if (dist === 0) break;
- }
- }
- } else {
- const [Lt, at, bt] = this._getLab(targetRgb[0], targetRgb[1], targetRgb[2]);
- const targetChroma = Math.sqrt(at * at + bt * bt);
- const penaltyWeight = enableChromaPenalty ? chromaPenaltyWeight : 0;
-
- for (let i = 0; i < availableColors.length; i++) {
- const c = availableColors[i];
- const [r, g, b] = c.rgb;
- const [L2, a2, b2] = this._getLab(r, g, b);
- const dL = Lt - L2;
- const da = at - a2;
- const db = bt - b2;
- let dist = dL * dL + da * da + db * db;
-
- if (penaltyWeight > 0 && targetChroma > 20) {
- const candChroma = Math.sqrt(a2 * a2 + b2 * b2);
- if (candChroma < targetChroma) {
- const cd = targetChroma - candChroma;
- dist += cd * cd * penaltyWeight;
- }
- }
-
- if (dist < bestScore) {
- bestScore = dist;
- bestId = c.id;
- bestRgb = [...c.rgb];
- if (dist === 0) break;
- }
- }
- }
-
- const result = { id: bestId, rgb: bestRgb };
- this._colorCache.set(cacheKey, result);
-
- // Limit cache size
- if (this._colorCache.size > this.COLOR_CACHE_LIMIT) {
- const firstKey = this._colorCache.keys().next().value;
- this._colorCache.delete(firstKey);
- }
-
- return result;
- }
-
- // =============================================
- // DITHERING METHODS (Floyd-Steinberg)
- // =============================================
-
- /**
- * Ensure dithering buffers are properly sized
- * @param {number} n - Number of pixels
- * @returns {Object} {work: Float32Array, eligible: Uint8Array}
- * @private
- */
- _ensureDitherBuffers(n) {
- if (!this._ditherWorkBuf || this._ditherWorkBuf.length !== n * 3) {
- this._ditherWorkBuf = new Float32Array(n * 3);
- }
- if (!this._ditherEligibleBuf || this._ditherEligibleBuf.length !== n) {
- this._ditherEligibleBuf = new Uint8Array(n);
- }
- return { work: this._ditherWorkBuf, eligible: this._ditherEligibleBuf };
- }
-
- /**
- * Apply Floyd-Steinberg dithering to image data
- * @param {Uint8ClampedArray} imageData - RGBA image data
- * @param {number} width - Image width
- * @param {number} height - Image height
- * @param {Array} palette - Color palette for dithering
- * @param {Object} options - Dithering options
- * @returns {Object} {data: Uint8ClampedArray, totalValidPixels: number}
- */
- applyFloydSteinbergDithering(imageData, width, height, palette, options = {}) {
- const {
- paintTransparentPixels = false,
- paintWhitePixels = true,
- transparencyThreshold = this.TRANSPARENCY_THRESHOLD,
- whiteThreshold = this.WHITE_THRESHOLD,
- algorithm = 'lab',
- enableChromaPenalty = false,
- chromaPenaltyWeight = 0.15,
- mask = null
- } = options;
-
- const data = new Uint8ClampedArray(imageData);
- const n = width * height;
- const { work, eligible } = this._ensureDitherBuffers(n);
- let totalValidPixels = 0;
-
- // Initialize working buffers
- for (let y = 0; y < height; y++) {
- for (let x = 0; x < width; x++) {
- const idx = y * width + x;
- const i4 = idx * 4;
- const r = data[i4];
- const g = data[i4 + 1];
- const b = data[i4 + 2];
- const a = data[i4 + 3];
- const masked = mask && mask[idx];
-
- const isEligible =
- !masked &&
- (paintTransparentPixels || a >= transparencyThreshold) &&
- (paintWhitePixels || !this.isWhitePixel(r, g, b, whiteThreshold));
-
- eligible[idx] = isEligible ? 1 : 0;
- work[idx * 3] = r;
- work[idx * 3 + 1] = g;
- work[idx * 3 + 2] = b;
-
- if (!isEligible) {
- data[i4 + 3] = 0; // Make ineligible pixels transparent
- }
- }
- }
-
- // Error diffusion function
- const diffuse = (nx, ny, er, eg, eb, factor) => {
- if (nx < 0 || nx >= width || ny < 0 || ny >= height) return;
- const nidx = ny * width + nx;
- if (!eligible[nidx]) return;
- const base = nidx * 3;
- work[base] = Math.min(255, Math.max(0, work[base] + er * factor));
- work[base + 1] = Math.min(255, Math.max(0, work[base + 1] + eg * factor));
- work[base + 2] = Math.min(255, Math.max(0, work[base + 2] + eb * factor));
- };
-
- // Apply Floyd-Steinberg dithering
- for (let y = 0; y < height; y++) {
- for (let x = 0; x < width; x++) {
- const idx = y * width + x;
- if (!eligible[idx]) continue;
-
- const base = idx * 3;
- const r0 = work[base];
- const g0 = work[base + 1];
- const b0 = work[base + 2];
-
- const [nr, ng, nb] = this.findClosestPaletteColor(
- r0, g0, b0, palette, algorithm, { enableChromaPenalty, chromaPenaltyWeight }
- );
-
- const i4 = idx * 4;
- data[i4] = nr;
- data[i4 + 1] = ng;
- data[i4 + 2] = nb;
- data[i4 + 3] = 255;
- totalValidPixels++;
-
- // Calculate and diffuse error
- const er = r0 - nr;
- const eg = g0 - ng;
- const eb = b0 - nb;
-
- diffuse(x + 1, y, er, eg, eb, 7 / 16);
- diffuse(x - 1, y + 1, er, eg, eb, 3 / 16);
- diffuse(x, y + 1, er, eg, eb, 5 / 16);
- diffuse(x + 1, y + 1, er, eg, eb, 1 / 16);
- }
- }
-
- return { data, totalValidPixels };
- }
-
- /**
- * Apply simple color quantization without dithering
- * @param {Uint8ClampedArray} imageData - RGBA image data
- * @param {number} width - Image width
- * @param {number} height - Image height
- * @param {Array} palette - Color palette for quantization
- * @param {Object} options - Quantization options
- * @returns {Object} {data: Uint8ClampedArray, totalValidPixels: number}
- */
- applySimpleQuantization(imageData, width, height, palette, options = {}) {
- const {
- paintTransparentPixels = false,
- paintWhitePixels = true,
- transparencyThreshold = this.TRANSPARENCY_THRESHOLD,
- whiteThreshold = this.WHITE_THRESHOLD,
- algorithm = 'lab',
- enableChromaPenalty = false,
- chromaPenaltyWeight = 0.15
- } = options;
-
- const data = new Uint8ClampedArray(imageData);
- let totalValidPixels = 0;
-
- for (let i = 0; i < data.length; i += 4) {
- const r = data[i];
- const g = data[i + 1];
- const b = data[i + 2];
- const a = data[i + 3];
-
- const isEligible =
- (paintTransparentPixels || a >= transparencyThreshold) &&
- (paintWhitePixels || !this.isWhitePixel(r, g, b, whiteThreshold));
-
- if (!isEligible) {
- data[i + 3] = 0; // Make ineligible pixels transparent
- continue;
- }
-
- const [nr, ng, nb] = this.findClosestPaletteColor(
- r, g, b, palette, algorithm, { enableChromaPenalty, chromaPenaltyWeight }
- );
-
- data[i] = nr;
- data[i + 1] = ng;
- data[i + 2] = nb;
- data[i + 3] = 255;
- totalValidPixels++;
- }
-
- return { data, totalValidPixels };
- }
-
- // =============================================
- // UTILITY METHODS
- // =============================================
-
- /**
- * Extract available colors from DOM elements
- * @returns {Array|null} Array of color objects with id and rgb properties
- */
- extractAvailableColors() {
- const colorElements = document.querySelectorAll('.color-option, [data-color-id]');
- if (colorElements.length === 0) return null;
-
- const colors = [];
- colorElements.forEach((element) => {
- const colorId = element.dataset.colorId || element.getAttribute('data-color-id');
- const computedStyle = window.getComputedStyle(element);
- const backgroundColor = computedStyle.backgroundColor;
-
- if (backgroundColor && colorId) {
- const rgbMatch = backgroundColor.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/);
- if (rgbMatch) {
- colors.push({
- id: parseInt(colorId),
- rgb: [parseInt(rgbMatch[1]), parseInt(rgbMatch[2]), parseInt(rgbMatch[3])]
- });
- }
- }
- });
-
- return colors.length > 0 ? colors : null;
- }
-
- /**
- * Create file uploader for images
- * @returns {Promise} Promise resolving to image data URL
- */
- createImageUploader() {
- return new Promise((resolve) => {
- const input = document.createElement('input');
- input.type = 'file';
- input.accept = 'image/png,image/jpeg';
- input.onchange = () => {
- if (input.files && input.files[0]) {
- const fr = new FileReader();
- fr.onload = () => resolve(fr.result);
- fr.readAsDataURL(input.files[0]);
- } else {
- resolve(null);
- }
- };
- input.click();
- });
- }
-
- /**
- * Extract available colors from DOM palette
- * @param {Object} colorMap - The color map object (optional, will use window.CONFIG if not provided)
- * @returns {Array|null} Array of available colors or null if none found
- */
- extractAvailableColors(colorMap = null) {
- const colorElements = document.querySelectorAll('.tooltip button[id^="color-"]');
- if (colorElements.length === 0) {
- console.log('ā No color elements found on page');
- return null;
- }
-
- // Use provided colorMap or fallback to window.CONFIG
- const effectiveColorMap = colorMap || (window.CONFIG?.COLOR_MAP);
-
- // Separate available and unavailable colors
- const availableColors = [];
- const unavailableColors = [];
-
- Array.from(colorElements).forEach((el) => {
- const id = Number.parseInt(el.id.replace('color-', ''));
- if (id === 0) return; // Skip transparent color
-
- const rgbStr = el.style.backgroundColor.match(/\d+/g);
- if (!rgbStr || rgbStr.length < 3) {
- console.warn(`Skipping color element ${el.id} ā cannot parse RGB`);
- return;
- }
- const rgb = rgbStr.map(Number);
-
- // Find color name from COLOR_MAP
- let name = `Unknown Color ${id}`;
- if (effectiveColorMap) {
- const colorInfo = Object.values(effectiveColorMap).find((color) => color.id === id);
- name = colorInfo ? colorInfo.name : name;
- }
-
- const colorData = { id, name, rgb };
-
- // Check if color is available (no SVG overlay means available)
- if (!el.querySelector('svg')) {
- availableColors.push(colorData);
- } else {
- unavailableColors.push(colorData);
- }
- });
-
- // Console log detailed color information
- console.log('=== CAPTURED COLORS STATUS ===');
- console.log(`Total available colors: ${availableColors.length}`);
- console.log(`Total unavailable colors: ${unavailableColors.length}`);
- console.log(`Total colors scanned: ${availableColors.length + unavailableColors.length}`);
-
- if (availableColors.length > 0) {
- console.log('\n--- AVAILABLE COLORS ---');
- availableColors.forEach((color, index) => {
- console.log(
- `${index + 1
- }. ID: ${color.id}, Name: "${color.name}", RGB: (${color.rgb[0]}, ${color.rgb[1]}, ${color.rgb[2]})`
- );
- });
- }
-
- if (unavailableColors.length > 0) {
- console.log('\n--- UNAVAILABLE COLORS ---');
- unavailableColors.forEach((color, index) => {
- console.log(
- `${index + 1
- }. ID: ${color.id}, Name: "${color.name}", RGB: (${color.rgb[0]}, ${color.rgb[1]}, ${color.rgb[2]}) [LOCKED]`
- );
- });
- }
-
- console.log('=== END COLOR STATUS ===');
-
- return availableColors;
- }
-
- /**
- * Clear all caches to free memory
- */
- clearCaches() {
- this._colorCache.clear();
- this._labCache.clear();
- this._hsvCache?.clear();
- this._hslCache?.clear();
- this._xyzCache?.clear();
- this._luvCache?.clear();
- this._yuvCache?.clear();
- this._oklabCache?.clear();
- this._lchCache?.clear();
- this._ditherWorkBuf = null;
- this._ditherEligibleBuf = null;
- }
-
- /**
- * Clean up resources
- */
- cleanup() {
- this.clearCaches();
- if (this.canvas) {
- this.canvas.width = 0;
- this.canvas.height = 0;
- }
- this.img = null;
- this.canvas = null;
- this.ctx = null;
- }
-}
-
-// Create global instance
-window.WPlaceImageProcessor = ImageProcessor;
-
-// Create global instance for Auto-Image.js compatibility
-window.globalImageProcessor = new ImageProcessor();
-
-// Legacy compatibility - expose key methods globally for backward compatibility
-window.ImageProcessor = ImageProcessor;
-
-console.log('ā
WPlace Image Processor loaded and ready');
diff --git a/Extensions/scripts/overlay-manager.js b/Extensions/scripts/overlay-manager.js
deleted file mode 100644
index b3290ef..0000000
--- a/Extensions/scripts/overlay-manager.js
+++ /dev/null
@@ -1,837 +0,0 @@
-// ==UserScript==
-// @name WPlace Overlay Manager
-// @namespace http://tampermonkey.net/
-// @version 2025-09-16.1
-// @description Overlay management for WPlace AutoBot - handles tile processing, chunking, and rendering
-// @author Wbot
-// @match https://wplace.live/*
-// @grant none
-// ==/UserScript==
-
-/**
- * OverlayManager - Handles overlay processing, tile chunking, and canvas operations for WPlace AutoBot
- * Extracted from Auto-Image.js for better modularity and reusabilityc
- */
-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.originalTiles = new Map(); // Map<"tileX,ttileY", ImageBitmap> store latest original tile bitmaps
- this.originalTilesData = new Map(); // Map<"tileX,tileY", {w,h,data:Uint8ClampedArray}> cache full ImageData for fast pixel reads
- this.tileSize = 1000;
- this.processPromise = null; // Track ongoing processing
- this.lastProcessedHash = null; // Cache invalidation
- this.workerPool = null; // Web worker pool for heavy processing
- this.neededTilesForSave = null; // Set of tiles needed for save file restoration
- }
-
- /**
- * Toggle overlay enabled state
- * @returns {boolean} New enabled state
- */
- toggle() {
- this.isEnabled = !this.isEnabled;
- console.log(`Overlay ${this.isEnabled ? 'enabled' : 'disabled'}.`);
- return this.isEnabled;
- }
-
- /**
- * Enable overlay
- */
- enable() {
- this.isEnabled = true;
-
- // Force canvas refresh if script was loaded manually (not on startup)
- // This ensures overlay works even when tiles are already cached
- this._triggerCanvasRefreshIfNeeded();
-
- // Also try direct canvas overlay approach as backup
- this._attemptDirectCanvasOverlay();
- }
-
- /**
- * Attempt to directly overlay on the existing canvas
- * @private
- */
- _attemptDirectCanvasOverlay() {
- // Only try this if we have overlay data and it's been more than 5 seconds since page load
- const timeSinceLoad = Date.now() - (window.performance?.timing?.navigationStart || 0);
-
- if (timeSinceLoad > 5000 && this.chunkedTiles.size > 0) {
- setTimeout(() => {
- this._drawDirectOverlay();
- }, 500);
- }
- }
-
- /**
- * Draw overlay directly on the main canvas
- * @private
- */
- _drawDirectOverlay() {
- try {
- const canvas = document.querySelector('canvas');
- if (!canvas) {
- return;
- }
-
- const ctx = canvas.getContext('2d');
- if (!ctx) {
- return;
- }
-
- // Store original composite operation
- const originalComposite = ctx.globalCompositeOperation;
- const originalAlpha = ctx.globalAlpha;
-
- // Set overlay blending
- ctx.globalCompositeOperation = 'source-over';
- ctx.globalAlpha = window.state?.overlayOpacity || 0.6;
-
- // Draw overlay chunks directly on canvas
- this.chunkedTiles.forEach((bitmap, tileKey) => {
- const [tileX, tileY] = tileKey.split(',').map(Number);
-
- // Convert tile coordinates to canvas coordinates
- // This assumes WPlace uses 1000px tiles
- const canvasX = tileX * 1000;
- const canvasY = tileY * 1000;
-
- try {
- ctx.drawImage(bitmap, canvasX, canvasY);
- } catch (error) {
- // Ignore individual tile draw errors
- }
- });
-
- // Restore original settings
- ctx.globalCompositeOperation = originalComposite;
- ctx.globalAlpha = originalAlpha;
-
- } catch (error) {
- // Ignore direct overlay errors silently
- }
- }
-
- /**
- * Trigger canvas refresh for manual script execution
- * @private
- */
- _triggerCanvasRefreshIfNeeded() {
- // Only trigger refresh if we have overlay data and it's been more than 5 seconds since page load
- // (indicates manual execution rather than startup execution)
- const timeSinceLoad = Date.now() - (window.performance?.timing?.navigationStart || 0);
-
- if (timeSinceLoad > 5000 && this.chunkedTiles.size > 0) {
- // Try multiple methods to refresh the canvas tiles
- setTimeout(() => {
- this._attemptCanvasRefresh();
- }, 100);
- }
- }
-
- /**
- * Attempt to refresh canvas tiles using various methods
- * @private
- */
- _attemptCanvasRefresh() {
- try {
- // Method 1: Clear browser cache for tile URLs
- if ('caches' in window) {
- caches.keys().then(cacheNames => {
- cacheNames.forEach(cacheName => {
- caches.delete(cacheName);
- });
- });
- }
-
- // Method 2: Force browser to bypass cache by adding cache-busting parameter
- // We need to intercept and modify tile requests to add cache-busting
- const originalFetch = window.fetch;
- let fetchOverrideActive = true;
-
- window.fetch = async function(...args) {
- const url = args[0] instanceof Request ? args[0].url : args[0];
-
- if (fetchOverrideActive && typeof url === 'string' && url.match(/\/\d+\/\d+\.png/)) {
- // Add cache-busting parameter
- const separator = url.includes('?') ? '&' : '?';
- const cacheBustUrl = `${url}${separator}_cb=${Date.now()}`;
-
- if (args[0] instanceof Request) {
- args[0] = new Request(cacheBustUrl, args[0]);
- } else {
- args[0] = cacheBustUrl;
- }
- }
-
- return originalFetch.apply(this, args);
- };
-
- // Disable cache-busting after 10 seconds
- setTimeout(() => {
- fetchOverrideActive = false;
- }, 10000);
-
- // Method 3: Try to trigger a slight zoom change
- const canvas = document.querySelector('canvas');
- if (canvas) {
- // Dispatch a wheel event to trigger tile refresh
- const wheelEvent = new WheelEvent('wheel', {
- deltaY: 1,
- deltaMode: 0,
- bubbles: true,
- cancelable: true,
- view: window
- });
-
- canvas.dispatchEvent(wheelEvent);
-
- // Immediately counter it
- setTimeout(() => {
- const counterEvent = new WheelEvent('wheel', {
- deltaY: -1,
- deltaMode: 0,
- bubbles: true,
- cancelable: true,
- view: window
- });
- canvas.dispatchEvent(counterEvent);
- }, 50);
- }
-
- // Method 4: Try to trigger resize event
- window.dispatchEvent(new Event('resize'));
-
- } catch (error) {
- console.warn('ā ļø Could not trigger canvas refresh:', error);
- }
- }
-
- /**
- * Disable overlay
- */
- disable() {
- this.isEnabled = false;
- }
-
- /**
- * Clear all overlay data and disable
- */
- clear() {
- this.disable();
- this.imageBitmap = null;
- this.chunkedTiles.clear();
- this.originalTiles.clear();
- this.originalTilesData.clear();
- this.lastProcessedHash = null;
- if (this.processPromise) {
- this.processPromise = null;
- }
- }
-
- /**
- * Set the image bitmap for overlay processing
- * @param {ImageBitmap} imageBitmap - The image to use for overlay
- */
- async setImage(imageBitmap) {
- this.imageBitmap = imageBitmap;
- this.lastProcessedHash = null; // Invalidate cache
- if (this.imageBitmap && this.startCoords) {
- await this.processImageIntoChunks();
- }
- }
-
- /**
- * Set the position for overlay rendering
- * @param {Object} startPosition - Starting pixel position {x, y}
- * @param {Object} region - Region coordinates {x, y}
- */
- async setPosition(startPosition, region) {
- if (!startPosition || !region) {
- this.startCoords = null;
- this.chunkedTiles.clear();
- this.lastProcessedHash = null;
- return;
- }
- this.startCoords = { region, pixel: startPosition };
- this.lastProcessedHash = null; // Invalidate cache
- if (this.imageBitmap) {
- await this.processImageIntoChunks();
- }
- }
-
- /**
- * Generate hash for cache invalidation
- * @returns {string|null} Hash string or null if no data
- * @private
- */
- _generateProcessHash() {
- if (!this.imageBitmap || !this.startCoords) return null;
- const { width, height } = this.imageBitmap;
- const { x: px, y: py } = this.startCoords.pixel;
- const { x: rx, y: ry } = this.startCoords.region;
-
- // Access global state for overlay settings
- const blueMarbleEnabled = window.state?.blueMarbleEnabled || false;
- const overlayOpacity = window.state?.overlayOpacity || 0.6;
-
- return `${width}x${height}_${px},${py}_${rx},${ry}_${blueMarbleEnabled}_${overlayOpacity}`;
- }
-
- /**
- * Process image into chunks with caching and batch processing
- */
- async processImageIntoChunks() {
- if (!this.imageBitmap || !this.startCoords) {
- console.warn('š« Cannot process chunks - missing imageBitmap or startCoords');
- return;
- }
-
- console.log('š Processing image into chunks...', {
- imageSize: `${this.imageBitmap.width}x${this.imageBitmap.height}`,
- startCoords: this.startCoords
- });
-
- // Check if we're already processing to avoid duplicate work
- if (this.processPromise) {
- console.log('ā³ Already processing chunks - waiting...');
- return this.processPromise;
- }
-
- // Check cache validity
- const currentHash = this._generateProcessHash();
- if (this.lastProcessedHash === currentHash && this.chunkedTiles.size > 0) {
- // Using cached overlay chunks
- return;
- }
-
- // Start processing
- console.log('š Starting new chunk processing...');
- this.processPromise = this._doProcessImageIntoChunks();
- try {
- await this.processPromise;
- this.lastProcessedHash = currentHash;
- console.log(`ā
Chunk processing complete - ${this.chunkedTiles.size} tiles created`);
- } finally {
- this.processPromise = null;
- }
- }
-
- /**
- * Internal method to process image into chunks
- * @private
- */
- 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;
-
- const { startTileX, startTileY, endTileX, endTileY } = this.calculateTileRange(
- startRegionX,
- startRegionY,
- startPixelX,
- startPixelY,
- imageWidth,
- imageHeight,
- this.tileSize
- );
-
- const totalTiles = (endTileX - startTileX + 1) * (endTileY - startTileY + 1);
- console.log(`š Processing ${totalTiles} overlay tiles...`);
-
- // Process tiles in batches to avoid blocking the main thread
- const batchSize = 4; // Process 4 tiles at a time
- const tilesToProcess = [];
-
- for (let ty = startTileY; ty <= endTileY; ty++) {
- for (let tx = startTileX; tx <= endTileX; tx++) {
- tilesToProcess.push({ tx, ty });
- }
- }
-
- // 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
- );
- if (chunkBitmap) {
- this.chunkedTiles.set(tileKey, chunkBitmap);
- }
- })
- );
-
- // Yield control to prevent blocking
- if (i + batchSize < tilesToProcess.length) {
- await new Promise((resolve) => setTimeout(resolve, 0));
- }
- }
-
- const processingTime = performance.now() - startTime;
- console.log(
- `ā
Overlay processed ${this.chunkedTiles.size} tiles in ${Math.round(processingTime)}ms`
- );
- }
-
- /**
- * Process a single tile
- * @param {number} tx - Tile X coordinate
- * @param {number} ty - Tile Y coordinate
- * @param {number} imageWidth - Image width
- * @param {number} imageHeight - Image height
- * @param {number} startPixelX - Starting pixel X
- * @param {number} startPixelY - Starting pixel Y
- * @param {number} startRegionX - Starting region X
- * @param {number} startRegionY - Starting region Y
- * @returns {Promise} Processed tile bitmap
- * @private
- */
- async _processTile(
- tx,
- ty,
- imageWidth,
- imageHeight,
- startPixelX,
- startPixelY,
- startRegionX,
- startRegionY
- ) {
- 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) return null;
-
- // 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);
-
- // Blue marble effect with faster pixel manipulation
- const blueMarbleEnabled = window.state?.blueMarbleEnabled || false;
- if (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);
- }
-
- return await chunkCanvas.transferToImageBitmap();
- }
-
- /**
- * Process and respond to tile requests with overlay compositing
- * @param {Object} eventData - Event data containing endpoint, blobID, and blobData
- */
- 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);
- // Also store the original tile bitmap for later pixel color checks
- try {
- const originalBitmap = await createImageBitmap(blobData);
- this.originalTiles.set(tileKey, originalBitmap);
- // Cache full ImageData for fast pixel access (avoid repeated drawImage/getImageData)
- try {
- let canvas, ctx;
- if (typeof OffscreenCanvas !== 'undefined') {
- canvas = new OffscreenCanvas(originalBitmap.width, originalBitmap.height);
- ctx = canvas.getContext('2d');
- } else {
- canvas = document.createElement('canvas');
- canvas.width = originalBitmap.width;
- canvas.height = originalBitmap.height;
- ctx = canvas.getContext('2d');
- }
- ctx.imageSmoothingEnabled = false;
- ctx.drawImage(originalBitmap, 0, 0);
- const imgData = ctx.getImageData(0, 0, originalBitmap.width, originalBitmap.height);
- // Store typed array copy to avoid retaining large canvas
- this.originalTilesData.set(tileKey, {
- w: originalBitmap.width,
- h: originalBitmap.height,
- data: new Uint8ClampedArray(imgData.data),
- });
- } catch (e) {
- // If ImageData extraction fails, still keep the bitmap as fallback
- console.warn('OverlayManager: could not cache ImageData for', tileKey, e);
- }
- } catch (e) {
- console.warn('OverlayManager: could not create original bitmap for', tileKey, e);
- }
-
- if (chunkBitmap) {
- try {
- // Use faster compositing for better performance
- finalBlob = await this._compositeTileOptimized(blobData, chunkBitmap);
- } catch (e) {
- console.error('Error compositing overlay:', e);
- // Fallback to original tile on error
- finalBlob = blobData;
- }
- }
- }
- }
-
- // Send the (possibly modified) blob back to the injected script
- window.postMessage(
- {
- source: 'auto-image-overlay',
- blobID: blobID,
- blobData: finalBlob,
- },
- '*'
- );
- }
-
- /**
- * Get pixel color from a tile
- * @param {number} tileX - Tile X coordinate
- * @param {number} tileY - Tile Y coordinate
- * @param {number} pixelX - Pixel X within tile
- * @param {number} pixelY - Pixel Y within tile
- * @returns {Promise} [r,g,b,a] or null
- */
- async getTilePixelColor(tileX, tileY, pixelX, pixelY) {
- const tileKey = `${tileX},${tileY}`;
-
- // Get transparency threshold from global state or config
- const alphaThresh = window.state?.customTransparencyThreshold ||
- window.CONFIG?.TRANSPARENCY_THRESHOLD || 10;
-
- // 1. Prefer cached ImageData if available
- const cached = this.originalTilesData.get(tileKey);
- if (cached && cached.data && cached.w > 0 && cached.h > 0) {
- const x = Math.max(0, Math.min(cached.w - 1, pixelX));
- const y = Math.max(0, Math.min(cached.h - 1, pixelY));
- const idx = (y * cached.w + x) * 4;
- const d = cached.data;
- const a = d[idx + 3];
-
- const paintTransparentPixels = window.state?.paintTransparentPixels || false;
- if (!paintTransparentPixels && a < alphaThresh) {
- // Treat as transparent / unavailable
- return null;
- }
- return [d[idx], d[idx + 1], d[idx + 2], a];
- }
-
- // 2. Fallback: use bitmap, with retry
- const maxRetries = 3;
- for (let attempt = 1; attempt <= maxRetries; attempt++) {
- const bitmap = this.originalTiles.get(tileKey);
- if (!bitmap) {
- if (attempt === maxRetries) {
- console.warn('OverlayManager: no bitmap for', tileKey, 'after', maxRetries, 'attempts');
- } else {
- await this._sleep(50 * attempt); // exponential delay
- }
- continue;
- }
-
- try {
- let canvas, ctx;
- if (typeof OffscreenCanvas !== 'undefined') {
- canvas = new OffscreenCanvas(bitmap.width, bitmap.height);
- ctx = canvas.getContext('2d');
- } else {
- canvas = document.createElement('canvas');
- canvas.width = bitmap.width;
- canvas.height = bitmap.height;
- ctx = canvas.getContext('2d');
- }
- ctx.imageSmoothingEnabled = false;
- ctx.drawImage(bitmap, 0, 0);
-
- const x = Math.max(0, Math.min(bitmap.width - 1, pixelX));
- const y = Math.max(0, Math.min(bitmap.height - 1, pixelY));
- const data = ctx.getImageData(x, y, 1, 1).data;
- const a = data[3];
-
- const paintTransparentPixels = window.state?.paintTransparentPixels || false;
- if (!paintTransparentPixels && a < alphaThresh) {
- return null;
- }
-
- return [data[0], data[1], data[2], a];
- } catch (e) {
- console.warn('OverlayManager: failed to read pixel (attempt', attempt, ')', tileKey, e);
- if (attempt < maxRetries) {
- await this._sleep(50 * attempt);
- } else {
- console.error(
- 'OverlayManager: failed to read pixel after',
- maxRetries,
- 'attempts',
- tileKey
- );
- }
- }
- }
-
- // 3. If everything fails, return null
- return null;
- }
-
- /**
- * Composite tile with overlay using optimized rendering
- * @param {Blob} originalBlob - Original tile blob
- * @param {ImageBitmap} overlayBitmap - Overlay bitmap
- * @returns {Promise} Composited tile blob
- * @private
- */
- async _compositeTileOptimized(originalBlob, overlayBitmap) {
- 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;
-
- // Draw original tile first
- ctx.drawImage(originalBitmap, 0, 0);
-
- // Set opacity and draw overlay with optimized blend mode
- const overlayOpacity = window.state?.overlayOpacity || 0.6;
- ctx.globalAlpha = overlayOpacity;
- ctx.globalCompositeOperation = 'source-over';
- ctx.drawImage(overlayBitmap, 0, 0);
-
- // Use faster blob conversion with compression settings
- return await canvas.convertToBlob({
- type: 'image/png',
- quality: 0.95, // Slight compression for faster processing
- });
- }
-
- /**
- * Wait until all required tiles are loaded and cached
- * @param {number} startRegionX - Starting region X
- * @param {number} startRegionY - Starting region Y
- * @param {number} pixelWidth - Image width in pixels
- * @param {number} pixelHeight - Image height in pixels
- * @param {number} startPixelX - Starting pixel X offset
- * @param {number} startPixelY - Starting pixel Y offset
- * @param {number} timeoutMs - Timeout in milliseconds
- * @returns {Promise} True if tiles are ready
- */
- async waitForTiles(
- startRegionX,
- startRegionY,
- pixelWidth,
- pixelHeight,
- startPixelX = 0,
- startPixelY = 0,
- timeoutMs = 10000
- ) {
- const { startTileX, startTileY, endTileX, endTileY } = this.calculateTileRange(
- startRegionX,
- startRegionY,
- startPixelX,
- startPixelY,
- pixelWidth,
- pixelHeight,
- this.tileSize
- );
-
- const requiredTiles = [];
- for (let ty = startTileY; ty <= endTileY; ty++) {
- for (let tx = startTileX; tx <= endTileX; tx++) {
- requiredTiles.push(`${tx},${ty}`);
- }
- }
-
- if (requiredTiles.length === 0) return true;
-
- const startTime = Date.now();
-
- while (Date.now() - startTime < timeoutMs) {
- const stopFlag = window.state?.stopFlag || false;
- if (stopFlag) {
- console.log('waitForTiles: stopped by user');
- return false;
- }
-
- const missing = requiredTiles.filter((key) => !this.originalTiles.has(key));
- if (missing.length === 0) {
- console.log(`ā
All ${requiredTiles.length} required tiles are loaded`);
- return true;
- }
-
- await this._sleep(100);
- }
-
- console.warn(`ā Timeout waiting for tiles: ${requiredTiles.length} required,
- ${requiredTiles.filter((k) => this.originalTiles.has(k)).length} loaded`);
- return false;
- }
-
- /**
- * Calculate tile range for given parameters
- * @param {number} startRegionX - Starting region X
- * @param {number} startRegionY - Starting region Y
- * @param {number} startPixelX - Starting pixel X
- * @param {number} startPixelY - Starting pixel Y
- * @param {number} width - Width in pixels
- * @param {number} height - Height in pixels
- * @param {number} tileSize - Size of each tile (default 1000)
- * @returns {{startTileX: number, startTileY: number, endTileX: number, endTileY: number}}
- */
- calculateTileRange(
- startRegionX,
- startRegionY,
- startPixelX,
- startPixelY,
- width,
- height,
- tileSize = 1000
- ) {
- const endPixelX = startPixelX + width;
- const endPixelY = startPixelY + height;
-
- return {
- startTileX: startRegionX + Math.floor(startPixelX / tileSize),
- startTileY: startRegionY + Math.floor(startPixelY / tileSize),
- endTileX: startRegionX + Math.floor((endPixelX - 1) / tileSize),
- endTileY: startRegionY + Math.floor((endPixelY - 1) / tileSize),
- };
- }
-
- /**
- * Preload tiles for save file coordinates to populate originalTiles cache
- * This fixes the "no bitmap" error when loading save files
- * @param {Object} startPosition - The start position from save file
- * @param {Object} region - The region from save file
- * @param {number} width - Image width
- * @param {number} height - Image height
- */
- /**
- * Preload tiles for save file coordinates to populate originalTiles cache
- * This fixes the "no bitmap" error when loading save files
- * For WPlace, we can't manually fetch tiles like r/place - we need to trigger natural tile loading
- * @param {Object} startPosition - The start position from save file
- * @param {Object} region - The region from save file
- * @param {number} width - Image width
- * @param {number} height - Image height
- */
- async preloadTilesForSaveFile(startPosition, region, width, height) {
- if (!startPosition || !region || !width || !height) {
- console.warn('OverlayManager: Missing parameters for tile preload');
- return;
- }
-
- try {
- // Calculate tiles that need to be loaded based on image area
- const tileSize = 1000; // WPlace uses 1000px tiles, not 256px like r/place
- const tiles = this.calculateTileRange(
- region.x, region.y,
- startPosition.x, startPosition.y,
- width, height,
- tileSize
- );
-
- console.log(`š WPlace: Need to preload ${(tiles.endTileX - tiles.startTileX + 1) * (tiles.endTileY - tiles.startTileY + 1)} tiles for save file...`);
-
- // For WPlace, instead of manually fetching tiles, we need to wait for natural tile loading
- // The browser will request tiles as the user navigates, and we intercept them
- // For now, just log the tiles we need and let the overlay work with what's available
- const neededTiles = [];
- for (let tileX = tiles.startTileX; tileX <= tiles.endTileX; tileX++) {
- for (let tileY = tiles.startTileY; tileY <= tiles.endTileY; tileY++) {
- neededTiles.push(`${tileX},${tileY}`);
- }
- }
-
- console.log(`š WPlace: Will use tiles when available: ${neededTiles.slice(0, 5).join(', ')}${neededTiles.length > 5 ? ` and ${neededTiles.length - 5} more...` : ''}`);
-
- // Store the needed tiles list for reference
- this.neededTilesForSave = new Set(neededTiles);
-
- console.log('ā
WPlace: Tile preload setup complete - tiles will populate as user navigates');
- } catch (error) {
- console.error('ā Failed to setup tile preload for save file:', error);
- }
- }
-
- /**
- * Sleep utility
- * @param {number} ms - Milliseconds to sleep
- * @returns {Promise}
- * @private
- */
- _sleep(ms) {
- return new Promise((resolve) => setTimeout(resolve, ms));
- }
-
- /**
- * Clean up resources
- */
- cleanup() {
- this.clear();
- if (this.workerPool) {
- // Clean up worker pool if implemented
- this.workerPool = null;
- }
- }
-}
-
-// Create global instance
-window.WPlaceOverlayManager = OverlayManager;
-
-// Create global instance for Auto-Image.js compatibility
-window.globalOverlayManager = new OverlayManager();
-
-// Legacy compatibility - expose key methods globally for backward compatibility
-window.OverlayManager = OverlayManager;
-
-console.log('ā
WPlace Overlay Manager loaded and ready');
\ No newline at end of file
diff --git a/Extensions/scripts/token-manager.js b/Extensions/scripts/token-manager.js
deleted file mode 100644
index 874bfe4..0000000
--- a/Extensions/scripts/token-manager.js
+++ /dev/null
@@ -1,846 +0,0 @@
-// ==UserScript==
-// @name WPlace Token Manager
-// @namespace http://tampermonkey.net/
-// @version 2025-09-16.1
-// @description Turnstile token management for WPlace AutoBot
-// @author Wbot
-// @match https://wplace.live/*
-// @grant nones
-// ==/UserScript==
-
-/**
- * TokenManager - Handles Turnstile token generation, caching, and validation for WPlace AutoBot
- * Extracted from Auto-Image.js for better modularity and reusabilitys
- */
-class TokenManager {
- constructor() {
- // Token state
- this.turnstileToken = null;
- this.tokenExpiryTime = 0;
- this.tokenGenerationInProgress = false;
- this._resolveToken = null;
- this.tokenPromise = new Promise((resolve) => {
- this._resolveToken = resolve;
- });
-
- // Configuration constants
- this.TOKEN_LIFETIME = 240000; // 4 minutes (tokens typically last 5 min, use 4 for safety)
- this.MAX_RETRIES = 10;
- this.MAX_BATCH_RETRIES = 10; // Maximum attempts for batch sending
-
- // Turnstile widget state
- this.turnstileLoaded = false;
- this._turnstileContainer = null;
- this._turnstileOverlay = null;
- this._turnstileWidgetId = null;
- this._lastSitekey = null;
- this._cachedSitekey = null;
-
- // Retry counter
- this.retryCount = 0;
-
- // Initialize message listener for token capture
- this._initializeTokenCapture();
- }
-
- /**
- * Initialize token capture from injected script
- */
- _initializeTokenCapture() {
- window.addEventListener('message', (event) => {
- const { source, token } = event.data;
- if (source === 'turnstile-capture' && token) {
- this.setTurnstileToken(token);
- // Show success message if UI is expecting it
- if (document.querySelector('#statusText')?.textContent.includes('CAPTCHA')) {
- this._showAlert('Token captured successfully', 'success');
- }
- }
- });
- }
-
- /**
- * Set a new Turnstile token with expiry
- * @param {string} token - The Turnstile token
- */
- setTurnstileToken(token) {
- if (this._resolveToken) {
- this._resolveToken(token);
- this._resolveToken = null;
- }
- this.turnstileToken = token;
- this.tokenExpiryTime = Date.now() + this.TOKEN_LIFETIME;
- console.log('ā
Turnstile token set successfully');
- }
-
- /**
- * Check if current token is valid
- * @returns {boolean} True if token exists and not expired
- */
- isTokenValid() {
- return this.turnstileToken && Date.now() < this.tokenExpiryTime;
- }
-
- /**
- * Invalidate current token
- */
- invalidateToken() {
- this.turnstileToken = null;
- this.tokenExpiryTime = 0;
- console.log('šļø Token invalidated, will force fresh generation');
- }
-
- /**
- * Ensure a valid token is available, generating if necessary
- * @param {boolean} forceRefresh - Force generation of new token
- * @returns {Promise} The token or null if failed
- */
- async ensureToken(forceRefresh = false) {
- // Return cached token if still valid and not forcing refresh
- if (this.isTokenValid() && !forceRefresh) {
- return this.turnstileToken;
- }
-
- // Invalidate token if forcing refresh
- if (forceRefresh) this.invalidateToken();
-
- // Avoid multiple simultaneous token generations
- if (this.tokenGenerationInProgress) {
- console.log('š Token generation already in progress, waiting...');
- await this._sleep(2000);
- return this.isTokenValid() ? this.turnstileToken : null;
- }
-
- this.tokenGenerationInProgress = true;
-
- try {
- console.log('š Token expired or missing, generating new one...');
- const token = await this.handleCaptchaWithRetry();
- if (token && token.length > 20) {
- this.setTurnstileToken(token);
- console.log('ā
Token captured and cached successfully');
- return token;
- }
-
- console.log('ā ļø Invisible Turnstile failed, forcing browser automation...');
- const fallbackToken = await this.handleCaptchaFallback();
- if (fallbackToken && fallbackToken.length > 20) {
- this.setTurnstileToken(fallbackToken);
- console.log('ā
Fallback token captured successfully');
- return fallbackToken;
- }
-
- console.log('ā All token generation methods failed');
- return null;
- } finally {
- this.tokenGenerationInProgress = false;
- }
- }
-
- /**
- * Handle captcha generation with retry logic
- * @returns {Promise} The token or null if failed
- */
- async handleCaptchaWithRetry() {
- const startTime = performance.now();
-
- try {
- const { sitekey, token: preGeneratedToken } = await this.obtainSitekeyAndToken();
-
- if (!sitekey) {
- throw new Error('No valid sitekey found');
- }
-
- console.log('š Using sitekey:', sitekey);
-
- if (typeof window !== 'undefined' && window.navigator) {
- console.log(
- 'š§ UA:',
- window.navigator.userAgent.substring(0, 50) + '...',
- 'Platform:',
- window.navigator.platform
- );
- }
-
- let token = null;
-
- if (
- preGeneratedToken &&
- typeof preGeneratedToken === 'string' &&
- preGeneratedToken.length > 20
- ) {
- console.log('ā»ļø Reusing pre-generated Turnstile token');
- token = preGeneratedToken;
- } else {
- if (this.isTokenValid()) {
- console.log('ā»ļø Using existing cached token (from previous session)');
- token = this.turnstileToken;
- } else {
- console.log('š Generating new token with executeTurnstile...');
- token = await this.executeTurnstile(sitekey, 'paint');
- if (token) this.setTurnstileToken(token);
- }
- }
-
- if (token && typeof token === 'string' && token.length > 20) {
- const elapsed = Math.round(performance.now() - startTime);
- console.log(`ā
Turnstile token generated successfully in ${elapsed}ms`);
- return token;
- } else {
- throw new Error(`Invalid or empty token received - Length: ${token?.length || 0}`);
- }
- } catch (error) {
- const elapsed = Math.round(performance.now() - startTime);
- console.error(`ā Turnstile token generation failed after ${elapsed}ms:`, error);
- throw error;
- }
- }
-
- /**
- * Fallback token generation method using pixel placement automation
- * @returns {Promise} The token or null if failed
- */
- async handleCaptchaFallback() {
- return new Promise(async (resolve, reject) => {
- try {
- // Ensure we have a fresh promise to await for a new token capture
- if (!this._resolveToken) {
- this.tokenPromise = new Promise((res) => {
- this._resolveToken = res;
- });
- }
-
- const timeoutPromise = this._sleep(20000).then(() =>
- reject(new Error('Auto-CAPTCHA timed out.'))
- );
-
- const solvePromise = (async () => {
- // Wait for main paint button
- const mainPaintBtn = await this._waitForSelector(
- 'button.btn.btn-primary.btn-lg, button.btn.btn-primary.sm\\:btn-xl',
- 200,
- 10000
- );
- if (!mainPaintBtn) throw new Error('Could not find the main paint button.');
- mainPaintBtn.click();
- await this._sleep(500);
-
- // Click transparent color button
- const transBtn = await this._waitForSelector('button#color-0', 200, 5000);
- if (!transBtn) throw new Error('Could not find the transparent color button.');
- transBtn.click();
- await this._sleep(500);
-
- // Find canvas and simulate painting
- const canvas = await this._waitForSelector('canvas', 200, 5000);
- if (!canvas) throw new Error('Could not find the canvas element.');
-
- canvas.setAttribute('tabindex', '0');
- canvas.focus();
- const rect = canvas.getBoundingClientRect();
- const centerX = Math.round(rect.left + rect.width / 2);
- const centerY = Math.round(rect.top + rect.height / 2);
-
- // Simulate mouse movement and spacebar press
- canvas.dispatchEvent(
- new MouseEvent('mousemove', {
- clientX: centerX,
- clientY: centerY,
- bubbles: true,
- })
- );
- canvas.dispatchEvent(
- new KeyboardEvent('keydown', {
- key: ' ',
- code: 'Space',
- bubbles: true,
- })
- );
- await this._sleep(50);
- canvas.dispatchEvent(
- new KeyboardEvent('keyup', {
- key: ' ',
- code: 'Space',
- bubbles: true,
- })
- );
- await this._sleep(500);
-
- // Delay before sending confirmation
- await this._sleep(800);
-
- // Keep confirming until token is captured
- const confirmLoop = async () => {
- while (!this.turnstileToken) {
- let confirmBtn = await this._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'));
- confirmBtn = allPrimary.length ? allPrimary[allPrimary.length - 1] : null;
- }
- if (confirmBtn) {
- confirmBtn.click();
- }
- await this._sleep(500); // 500ms delay between confirmation attempts
- }
- };
-
- // Start confirmation loop and wait for token
- confirmLoop();
- const token = await this.tokenPromise;
- await this._sleep(300); // small delay after token is captured
- resolve(token);
- })();
-
- await Promise.race([solvePromise, timeoutPromise]);
- } catch (error) {
- console.error('Auto-CAPTCHA process failed:', error);
- reject(error);
- }
- });
- }
-
- /**
- * Load Turnstile script if not already loaded
- * @returns {Promise}
- */
- async loadTurnstile() {
- // If Turnstile is already present, just resolve.
- if (window.turnstile) {
- 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"]'
- )
- ) {
- const checkReady = () => {
- if (window.turnstile) {
- this.turnstileLoaded = true;
- resolve();
- } else {
- setTimeout(checkReady, 100);
- }
- };
- return checkReady();
- }
-
- const script = document.createElement('script');
- script.src = 'https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit';
- script.async = true;
- script.defer = true;
- script.onload = () => {
- this.turnstileLoaded = true;
- console.log('ā
Turnstile script loaded successfully');
- resolve();
- };
- script.onerror = () => {
- console.error('ā Failed to load Turnstile script');
- reject(new Error('Failed to load Turnstile'));
- };
- document.head.appendChild(script);
- });
- }
-
- /**
- * Create or reuse the turnstile container - completely hidden for token generation
- * @returns {HTMLElement} The container element
- */
- ensureTurnstileContainer() {
- if (!this._turnstileContainer || !document.body.contains(this._turnstileContainer)) {
- // Clean up old container if it exists
- if (this._turnstileContainer) {
- this._turnstileContainer.remove();
- }
-
- this._turnstileContainer = document.createElement('div');
- this._turnstileContainer.className = 'wplace-turnstile-hidden';
- this._turnstileContainer.setAttribute('aria-hidden', 'true');
- this._turnstileContainer.id = 'turnstile-widget-container';
- document.body.appendChild(this._turnstileContainer);
- }
- return this._turnstileContainer;
- }
-
- /**
- * Interactive overlay container for visible widgets when needed
- * @returns {HTMLElement} The overlay element
- */
- ensureTurnstileOverlayContainer() {
- if (this._turnstileOverlay && document.body.contains(this._turnstileOverlay)) {
- return this._turnstileOverlay;
- }
-
- const overlay = document.createElement('div');
- overlay.id = 'turnstile-overlay-container';
- overlay.className = 'wplace-turnstile-overlay wplace-overlay-hidden';
-
- const title = document.createElement('div');
- title.textContent = this._getText('turnstileInstructions', 'Complete the verification');
- title.className = 'wplace-turnstile-title';
-
- const host = document.createElement('div');
- host.id = 'turnstile-overlay-host';
- host.className = 'wplace-turnstile-host';
-
- const hideBtn = document.createElement('button');
- hideBtn.textContent = this._getText('hideTurnstileBtn', 'Hide');
- hideBtn.className = 'wplace-turnstile-hide-btn';
- hideBtn.addEventListener('click', () => overlay.remove());
-
- overlay.appendChild(title);
- overlay.appendChild(host);
- overlay.appendChild(hideBtn);
- document.body.appendChild(overlay);
-
- this._turnstileOverlay = overlay;
- return overlay;
- }
-
- /**
- * Execute Turnstile widget to generate token
- * @param {string} sitekey - The site key
- * @param {string} action - The action name (default: 'paint')
- * @returns {Promise} The token or null if failed
- */
- async executeTurnstile(sitekey, action = 'paint') {
- await this.loadTurnstile();
-
- // Try reusing existing widget first if sitekey matches
- if (this._turnstileWidgetId && this._lastSitekey === sitekey && window.turnstile?.execute) {
- try {
- console.log('š Reusing existing Turnstile widget...');
- const token = await Promise.race([
- window.turnstile.execute(this._turnstileWidgetId, { action }),
- new Promise((_, reject) =>
- setTimeout(() => reject(new Error('Execute timeout')), 15000)
- ),
- ]);
- if (token && token.length > 20) {
- console.log('ā
Token generated via widget reuse');
- return token;
- }
- } catch (error) {
- console.log('ā ļø Widget reuse failed, will create a fresh widget:', error.message);
- }
- }
-
- // Try invisible widget first
- const invisibleToken = await this.createTurnstileWidget(sitekey, action);
- if (invisibleToken && invisibleToken.length > 20) {
- return invisibleToken;
- }
-
- console.log('ā ļø Falling back to interactive Turnstile (visible).');
- return await this.createTurnstileWidgetInteractive(sitekey, action);
- }
-
- /**
- * Create invisible Turnstile widget
- * @param {string} sitekey - The site key
- * @param {string} action - The action name
- * @returns {Promise} The token or null if failed
- */
- async createTurnstileWidget(sitekey, action) {
- return new Promise((resolve) => {
- try {
- // Force cleanup of any existing widget
- if (this._turnstileWidgetId && window.turnstile?.remove) {
- 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,
- action,
- size: 'invisible',
- retry: 'auto',
- 'retry-interval': 8000,
- callback: (token) => {
- console.log('ā
Invisible Turnstile callback');
- resolve(token);
- },
- 'error-callback': () => resolve(null),
- 'timeout-callback': () => resolve(null),
- });
-
- this._turnstileWidgetId = widgetId;
- this._lastSitekey = sitekey;
-
- if (!widgetId) {
- return resolve(null);
- }
-
- // Execute the widget and race with timeout
- Promise.race([
- window.turnstile.execute(widgetId, { action }),
- new Promise((_, reject) =>
- setTimeout(() => reject(new Error('Invisible execute timeout')), 12000)
- ),
- ])
- .then(resolve)
- .catch(() => resolve(null));
- } catch (e) {
- console.error('ā Invisible Turnstile creation failed:', e);
- resolve(null);
- }
- });
- }
-
- /**
- * Create interactive Turnstile widget (visible)
- * @param {string} sitekey - The site key
- * @param {string} action - The action name
- * @returns {Promise} The token or null if failed
- */
- 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);
- } 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,
- size: 'normal',
- theme: 'light',
- callback: (token) => {
- clearTimeout(timeout);
- 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 {
- console.warn('ā Invalid token from interactive widget');
- resolve(null);
- }
- },
- 'error-callback': (error) => {
- clearTimeout(timeout);
- 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);
- } else {
- console.log('ā
Interactive Turnstile widget created, waiting for user interaction...');
- }
- } catch (e) {
- console.error('ā Interactive Turnstile creation failed:', e);
- resolve(null);
- }
- });
- }
-
- /**
- * Cleanup method for when the script is disabled/reloaded
- */
- cleanupTurnstile() {
- if (this._turnstileWidgetId && window.turnstile?.remove) {
- try {
- window.turnstile.remove(this._turnstileWidgetId);
- } catch (e) {
- 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;
- this._lastSitekey = null;
- }
-
- /**
- * Obtain sitekey and token
- * @param {string} fallback - Fallback sitekey
- * @returns {Promise<{sitekey: string, token: string|null}>}
- */
- async obtainSitekeyAndToken(fallback = '0x4AAAAAABpqJe8FO0N84q0F') {
- // Cache sitekey to avoid repeated DOM queries
- if (this._cachedSitekey) {
- console.log('š Using cached sitekey:', this._cachedSitekey);
-
- return this.isTokenValid()
- ? {
- sitekey: this._cachedSitekey,
- token: this.turnstileToken,
- }
- : { sitekey: this._cachedSitekey, token: null };
- }
-
- // List of potential sitekeys to try
- const potentialSitekeys = [
- '0x4AAAAAABpqJe8FO0N84q0F', // WPlace common sitekey
- '0x4AAAAAAAJ7xjKAp6Mt_7zw', // Alternative WPlace sitekey
- '0x4AAAAAADm5QWx6Ov2LNF2g', // Another common sitekey
- ];
-
- const trySitekey = async (sitekey, source) => {
- if (!sitekey || sitekey.length < 10) return null;
-
- console.log(`š Testing sitekey from ${source}:`, sitekey);
- const token = await this.executeTurnstile(sitekey);
-
- if (token && token.length >= 20) {
- console.log(`ā
Valid token generated from ${source} sitekey`);
- this.setTurnstileToken(token);
- this._cachedSitekey = sitekey;
- return { sitekey, token };
- } else {
- console.log(`ā Failed to get token from ${source} sitekey`);
- return null;
- }
- };
-
- try {
- // 1ļøā£ data-sitekey attribute
- const sitekeySel = document.querySelector('[data-sitekey]');
- if (sitekeySel) {
- const sitekey = sitekeySel.getAttribute('data-sitekey');
- const result = await trySitekey(sitekey, 'data attribute');
- if (result) {
- return result;
- }
- }
-
- // 2ļøā£ Turnstile element
- const turnstileEl = document.querySelector('.cf-turnstile');
- if (turnstileEl?.dataset?.sitekey) {
- const sitekey = turnstileEl.dataset.sitekey;
- const result = await trySitekey(sitekey, 'turnstile element');
- if (result) {
- return result;
- }
- }
-
- // 3ļøā£ Meta tags
- const metaTags = document.querySelectorAll(
- 'meta[name*="turnstile"], meta[property*="turnstile"]'
- );
- for (const meta of metaTags) {
- const content = meta.getAttribute('content');
- const result = await trySitekey(content, 'meta tag');
- if (result) {
- return result;
- }
- }
-
- // 4ļøā£ Global variable
- if (window.__TURNSTILE_SITEKEY) {
- const result = await trySitekey(window.__TURNSTILE_SITEKEY, 'global variable');
- if (result) {
- return result;
- }
- }
-
- // 5ļøā£ Script tags
- const scripts = document.querySelectorAll('script');
- for (const script of scripts) {
- const content = script.textContent || script.innerHTML;
- const match = content.match(
- /(?:sitekey|data-sitekey)['"\s\[\]:\=\(]*['"]?([0-9a-zA-Z_-]{20,})['"]?/i
- );
- if (match && match[1]) {
- const extracted = match[1].replace(/['"]/g, '');
- const result = await trySitekey(extracted, 'script content');
- if (result) {
- return result;
- }
- }
- }
-
- // 6ļøā£ Known potential sitekeys
- console.log('š Testing known potential sitekeys...');
- for (const testSitekey of potentialSitekeys) {
- const result = await trySitekey(testSitekey, 'known list');
- if (result) {
- return result;
- }
- }
- } catch (error) {
- console.warn('ā ļø Error during sitekey detection:', error);
- }
-
- // 7ļøā£ Fallback
- console.log('š§ Trying fallback sitekey:', fallback);
- const fallbackResult = await trySitekey(fallback, 'fallback');
- if (fallbackResult) {
- return fallbackResult;
- }
-
- // 8ļøā£ Ultimate fallback with no token
- console.warn('ā All sitekey detection methods failed, using fallback without token validation');
- this._cachedSitekey = fallback;
- return { sitekey: fallback, token: null };
- }
-
- // =============================================
- // UTILITY METHODS
- // =============================================
-
- /**
- * Sleep utility
- * @param {number} ms - Milliseconds to sleep
- * @returns {Promise}
- */
- _sleep(ms) {
- return new Promise((resolve) => setTimeout(resolve, ms));
- }
-
- /**
- * Wait for DOM element to appear
- * @param {string} selector - CSS selector
- * @param {number} pollInterval - Polling interval in ms
- * @param {number} timeout - Timeout in ms
- * @returns {Promise} The element or null if timeout
- * @private
- */
- _waitForSelector(selector, pollInterval = 200, timeout = 10000) {
- return new Promise((resolve) => {
- const endTime = Date.now() + timeout;
- const poll = () => {
- const element = document.querySelector(selector);
- if (element) {
- resolve(element);
- } else if (Date.now() < endTime) {
- setTimeout(poll, pollInterval);
- } else {
- resolve(null);
- }
- };
- poll();
- });
- }
-
- /**
- * Simple translation helper (fallback to English)
- * @param {string} key - Translation key
- * @param {string} fallback - Fallback text
- * @returns {string} Translated text or fallback
- */
- _getText(key, fallback = '') {
- // Try to use external translation function if available
- if (typeof window.Utils?.t === 'function') {
- return window.Utils.t(key);
- }
-
- // Fallback translations
- const translations = {
- turnstileInstructions: 'Complete the verification',
- hideTurnstileBtn: 'Hide',
- tokenCapturedSuccess: 'Token captured successfully'
- };
-
- return translations[key] || fallback || key;
- }
-
- /**
- * Show alert helper (uses external alert if available)
- * @param {string} message - Message to show
- * @param {string} type - Alert type
- */
- _showAlert(message, type = 'info') {
- // Try to use external alert function if available
- if (typeof window.Utils?.showAlert === 'function') {
- window.Utils.showAlert(message, type);
- } else {
- console.log(`${type.toUpperCase()}: ${message}`);
- }
- }
-
- /**
- * Generate random string utility
- * @param {number} len - Length of string
- * @param {string} chars - Character set
- * @returns {string} Random string
- */
- _randStr(len, chars = 'abcdefghijklmnopqrstuvwxyz0123456789') {
- return [...Array(len)]
- .map(() =>
- chars[
- (crypto?.getRandomValues?.(new Uint32Array(1))[0] % chars.length) ||
- Math.floor(Math.random() * chars.length)
- ]
- )
- .join('');
- }
-}
-
-// Create global instance
-window.WPlaceTokenManager = new TokenManager();
-
-// Create global instance alias for Auto-Image.js compatibility
-window.globalTokenManager = window.WPlaceTokenManager;
-
-// Legacy compatibility - expose key methods globally for backward compatibility
-window.setTurnstileToken = (token) => window.WPlaceTokenManager.setTurnstileToken(token);
-window.ensureToken = (forceRefresh) => window.WPlaceTokenManager.ensureToken(forceRefresh);
-window.isTokenValid = () => window.WPlaceTokenManager.isTokenValid();
-window.invalidateToken = () => window.WPlaceTokenManager.invalidateToken();
-
-console.log('ā
WPlace Token Manager loaded and ready');
\ No newline at end of file
diff --git a/Extensions/scripts/utils-manager.js b/Extensions/scripts/utils-manager.js
deleted file mode 100644
index e0f458a..0000000
--- a/Extensions/scripts/utils-manager.js
+++ /dev/null
@@ -1,1258 +0,0 @@
-/**
- * WPlace AutoBOT - Utils Manager
- * Centralized utility functions for the WPlace automation systemds
- */
-
-class WPlaceUtilsManager {
- constructor() {
- this.translationCache = new Map();
- this._labCache = new Map();
-
- // Available languages
- this.AVAILABLE_LANGUAGES = [
- 'en', 'ru', 'pt', 'vi', 'fr', 'id', 'tr',
- 'zh-CN', 'zh-TW', 'ja', 'ko', 'uk'
- ];
- }
-
- // Basic utility functions
- sleep = (ms) => new Promise((r) => setTimeout(r, ms));
-
- async dynamicSleep(tickAndGetRemainingMs) {
- let remaining = Math.max(0, await tickAndGetRemainingMs());
- while (remaining > 0) {
- const interval = remaining > 5000 ? 2000 : remaining > 1000 ? 500 : 100;
- await this.sleep(Math.min(interval, remaining));
- remaining = Math.max(0, await tickAndGetRemainingMs());
- }
- }
-
- async waitForSelector(selector, interval = 200, timeout = 5000) {
- const start = Date.now();
- while (Date.now() - start < timeout) {
- const el = document.querySelector(selector);
- if (el) return el;
- await this.sleep(interval);
- }
- return null;
- }
-
- msToTimeText(ms) {
- const totalSeconds = Math.ceil(ms / 1000);
- const hours = Math.floor(totalSeconds / 3600);
- const minutes = Math.floor((totalSeconds % 3600) / 60);
- const seconds = totalSeconds % 60;
-
- if (hours > 0) return `${hours}h ${minutes}m ${seconds}s`;
- if (minutes > 0) return `${minutes}m ${seconds}s`;
- return `${seconds}s`;
- }
-
- // Debounced scroll-to-adjust handler for sliders
- createScrollToAdjust(element, updateCallback, min, max, step = 1) {
- let debounceTimer = null;
-
- const handleWheel = (e) => {
- if (e.target !== element) return;
-
- e.preventDefault();
- e.stopPropagation();
-
- if (debounceTimer) {
- clearTimeout(debounceTimer);
- }
-
- debounceTimer = setTimeout(() => {
- const currentValue = parseInt(element.value) || 0;
- const delta = e.deltaY > 0 ? -step : step;
- const newValue = Math.max(min, Math.min(max, currentValue + delta));
-
- if (newValue !== currentValue) {
- element.value = newValue;
- updateCallback(newValue);
- }
- }, 50);
- };
-
- element.addEventListener('wheel', handleWheel, { passive: false });
-
- return () => {
- if (debounceTimer) clearTimeout(debounceTimer);
- element.removeEventListener('wheel', handleWheel);
- };
- }
-
- // Tile range calculation delegation
- calculateTileRange(startRegionX, startRegionY, startPixelX, startPixelY, width, height, tileSize = 1000) {
- return window.globalOverlayManager.calculateTileRange(
- startRegionX, startRegionY, startPixelX, startPixelY, width, height, tileSize
- );
- }
-
- // Token management delegation
- loadTurnstile() { return window.globalTokenManager.loadTurnstile(); }
- ensureTurnstileContainer() { return window.globalTokenManager.ensureTurnstileContainer(); }
- ensureTurnstileOverlayContainer() { return window.globalTokenManager.ensureTurnstileOverlayContainer(); }
- executeTurnstile(sitekey, action) { return window.globalTokenManager.executeTurnstile(sitekey, action); }
- createTurnstileWidget(sitekey, action) { return window.globalTokenManager.createTurnstileWidget(sitekey, action); }
- createTurnstileWidgetInteractive(sitekey, action) { return window.globalTokenManager.createTurnstileWidgetInteractive(sitekey, action); }
- cleanupTurnstile() { return window.globalTokenManager.cleanupTurnstile(); }
- obtainSitekeyAndToken(fallback) { return window.globalTokenManager.obtainSitekeyAndToken(fallback); }
-
- // DOM utilities
- createElement(tag, props = {}, children = []) {
- const element = document.createElement(tag);
-
- Object.entries(props).forEach(([key, value]) => {
- if (key === 'style' && typeof value === 'object') {
- Object.assign(element.style, value);
- } else if (key === 'className') {
- element.className = value;
- } else if (key === 'innerHTML') {
- element.innerHTML = value;
- } else {
- element.setAttribute(key, value);
- }
- });
-
- if (typeof children === 'string') {
- element.textContent = children;
- } else if (Array.isArray(children)) {
- children.forEach((child) => {
- if (typeof child === 'string') {
- element.appendChild(document.createTextNode(child));
- } else {
- element.appendChild(child);
- }
- });
- }
-
- return element;
- }
-
- createButton(id, text, icon, onClick, style = window.CONFIG.CSS_CLASSES.BUTTON_PRIMARY) {
- const button = this.createElement('button', {
- id: id,
- style: style,
- innerHTML: `${icon ? ` ` : ''}${text} `,
- });
- if (onClick) button.addEventListener('click', onClick);
- return button;
- }
-
- // Translation function
- t(key, params = {}) {
- const state = window.state;
- const translationCache = this.translationCache;
- const loadedTranslations = window.loadedTranslations || {};
-
- // Safety check: ensure state and language exist
- if (!state || !state.language) {
- // Fallback: try to get translation directly or return key
- if (loadedTranslations[key]) {
- let text = loadedTranslations[key];
- Object.keys(params).forEach((param) => {
- text = text.replace(`{${param}}`, params[param]);
- });
- return text;
- }
- return key; // Return the key as fallback
- }
-
- const cacheKey = `${state.language}_${key}`;
- if (translationCache.has(cacheKey)) {
- let text = translationCache.get(cacheKey);
- Object.keys(params).forEach((param) => {
- text = text.replace(`{${param}}`, params[param]);
- });
- return text;
- }
-
- if (loadedTranslations[state.language]?.[key]) {
- let text = loadedTranslations[state.language][key];
- translationCache.set(cacheKey, text);
- Object.keys(params).forEach((param) => {
- text = text.replace(`{${param}}`, params[param]);
- });
- return text;
- }
-
- // Try English fallback if available and not using English
- if (state.language && state.language !== 'en' && loadedTranslations['en']?.[key]) {
- let text = loadedTranslations['en'][key];
- Object.keys(params).forEach((param) => {
- text = text.replace(`{${param}}`, params[param]);
- });
- return text;
- }
-
- // Final fallback to FALLBACK_TEXT or key
- const language = state.language || 'en';
- let text = window.FALLBACK_TEXT?.[language]?.[key] || window.FALLBACK_TEXT?.en?.[key] || key;
- Object.keys(params).forEach((param) => {
- text = text.replace(new RegExp(`\\{${param}\\}`, 'g'), params[param]);
- });
-
- if (text === key && key !== 'undefined') {
- console.warn(`ā ļø Missing translation for key: ${key} (language: ${language})`);
- }
-
- return text;
- }
-
- showAlert(message, type = 'info') {
- const alertDiv = document.createElement('div');
- alertDiv.className = `wplace-alert-base wplace-alert-${type}`;
- alertDiv.textContent = message;
- document.body.appendChild(alertDiv);
-
- setTimeout(() => {
- alertDiv.style.animation = 'slide-down 0.3s ease-out reverse';
- setTimeout(() => {
- document.body.removeChild(alertDiv);
- }, 300);
- }, 4000);
- }
-
- // Color utilities
- colorDistance(a, b) {
- return Math.sqrt(Math.pow(a[0] - b[0], 2) + Math.pow(a[1] - b[1], 2) + Math.pow(a[2] - b[2], 2));
- }
-
- _rgbToLab(r, g, b) {
- const srgbToLinear = (v) => {
- v /= 255;
- return v <= 0.04045 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
- };
- const rl = srgbToLinear(r);
- const gl = srgbToLinear(g);
- const bl = srgbToLinear(b);
- let X = rl * 0.4124 + gl * 0.3576 + bl * 0.1805;
- let Y = rl * 0.2126 + gl * 0.7152 + bl * 0.0722;
- let Z = rl * 0.0193 + gl * 0.1192 + bl * 0.9505;
- X /= 0.95047;
- Y /= 1.0;
- Z /= 1.08883;
- const f = (t) => (t > 0.008856 ? Math.cbrt(t) : 7.787 * t + 16 / 116);
- const fX = f(X), fY = f(Y), fZ = f(Z);
- const L = 116 * fY - 16;
- const a = 500 * (fX - fY);
- const b2 = 200 * (fY - fZ);
- return [L, a, b2];
- }
-
- _lab(r, g, b) {
- const key = (r << 16) | (g << 8) | b;
- let v = this._labCache.get(key);
- if (!v) {
- v = this._rgbToLab(r, g, b);
- this._labCache.set(key, v);
- }
- return v;
- }
-
- findClosestPaletteColor(r, g, b, palette) {
- // Use provided palette or derive from COLOR_MAP
- if (!palette || palette.length === 0) {
- palette = Object.values(window.CONFIG.COLOR_MAP)
- .filter((c) => c.rgb)
- .map((c) => [c.rgb.r, c.rgb.g, c.rgb.b]);
- }
-
- // Use modular ImageProcessor for color matching if available
- if (window.globalImageProcessor) {
- return window.globalImageProcessor.findClosestPaletteColor(
- r, g, b, palette,
- window.state.colorMatchingAlgorithm || 'lab',
- {
- enableChromaPenalty: window.state.enableChromaPenalty || false,
- chromaPenaltyWeight: window.state.chromaPenaltyWeight || 0.15
- }
- );
- }
-
- // Fallback: simple color distance matching
- let closestColor = null;
- let minDistance = Infinity;
-
- for (const color of palette) {
- const distance = this.colorDistance([r, g, b], color);
- if (distance < minDistance) {
- minDistance = distance;
- closestColor = color;
- }
- }
-
- return closestColor || [r, g, b];
- }
-
- isWhitePixel(r, g, b) {
- const wt = window.state.customWhiteThreshold || window.CONFIG.WHITE_THRESHOLD;
- if (window.globalImageProcessor) {
- return window.globalImageProcessor.isWhitePixel(r, g, b, wt);
- }
- // Fallback calculation if image processor not available
- return r >= wt && g >= wt && b >= wt;
- }
-
- resolveColor(targetRgb, availableColors, exactMatch = false) {
- if (window.globalImageProcessor) {
- return window.globalImageProcessor.resolveColor(targetRgb, availableColors, {
- exactMatch,
- algorithm: window.state.colorMatchingAlgorithm || 'lab',
- enableChromaPenalty: window.state.enableChromaPenalty || false,
- chromaPenaltyWeight: window.state.chromaPenaltyWeight || 0.15,
- whiteThreshold: window.state.customWhiteThreshold || window.CONFIG.WHITE_THRESHOLD
- });
- }
-
- // Fallback: simple distance-based matching
- if (!availableColors || availableColors.length === 0) return null;
-
- let closestColor = null;
- let minDistance = Infinity;
-
- for (const color of availableColors) {
- const distance = this.colorDistance(targetRgb, color.rgb || [color.rgb.r, color.rgb.g, color.rgb.b]);
- if (distance < minDistance) {
- minDistance = distance;
- closestColor = color;
- }
- }
-
- return closestColor;
- }
-
- createImageUploader() {
- if (window.globalImageProcessor) {
- return window.globalImageProcessor.createImageUploader();
- }
-
- // Fallback implementation if image processor not available
- return new Promise((resolve, reject) => {
- const input = document.createElement('input');
- input.type = 'file';
- input.accept = 'image/*';
- input.onchange = (e) => {
- const file = e.target.files[0];
- if (file) {
- const reader = new FileReader();
- reader.onload = () => resolve(reader.result);
- reader.onerror = () => reject(new Error('File reading error'));
- reader.readAsDataURL(file);
- } else {
- reject(new Error('No file selected'));
- }
- };
- input.click();
- });
- }
-
- // File utilities
- createFileDownloader(data, filename) {
- const blob = new Blob([data], { type: 'application/json' });
- const url = URL.createObjectURL(blob);
- const a = document.createElement('a');
- a.href = url;
- a.download = filename;
- document.body.appendChild(a);
- a.click();
- document.body.removeChild(a);
- URL.revokeObjectURL(url);
- }
-
- createFileUploader() {
- return 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'));
- }
- };
- input.click();
- });
- }
-
- extractAvailableColors() {
- if (window.globalImageProcessor) {
- return window.globalImageProcessor.extractAvailableColors(window.CONFIG.COLOR_MAP);
- }
-
- // Fallback: extract colors directly from CONFIG if image processor not available
- if (window.CONFIG && window.CONFIG.COLOR_MAP) {
- return Object.values(window.CONFIG.COLOR_MAP)
- .filter(color => color.rgb !== null)
- .map(color => ({
- id: color.id,
- name: color.name,
- rgb: [color.rgb.r, color.rgb.g, color.rgb.b]
- }));
- }
-
- return [];
- }
-
- formatTime(ms) {
- // Handle invalid or infinite values
- if (!Number.isFinite(ms) || ms < 0) {
- return '--:--:--';
- }
-
- // Handle very large values (more than 999 days)
- if (ms > 999 * 24 * 60 * 60 * 1000) {
- return '999d+';
- }
-
- const seconds = Math.floor((ms / 1000) % 60);
- const minutes = Math.floor((ms / (1000 * 60)) % 60);
- const hours = Math.floor((ms / (1000 * 60 * 60)) % 24);
- const days = Math.floor(ms / (1000 * 60 * 60 * 24));
-
- let result = '';
- if (days > 0) result += `${days}d `;
- if (hours > 0 || days > 0) result += `${hours}h `;
- if (minutes > 0 || hours > 0 || days > 0) result += `${minutes}m `;
- result += `${seconds}s`;
-
- return result;
- }
-
- calculateEstimatedTime(remainingPixels, charges, cooldown) {
- // Safety checks for input parameters
- if (!Number.isFinite(remainingPixels) || remainingPixels <= 0) return 0;
- if (!Number.isFinite(charges) || charges <= 0) charges = 1;
- if (!Number.isFinite(cooldown) || cooldown <= 0) cooldown = 30000; // Default 30s
-
- const paintingSpeed = window.state?.paintingSpeed || 5;
- if (!Number.isFinite(paintingSpeed) || paintingSpeed <= 0) {
- // Fallback calculation without painting speed
- const cyclesNeeded = Math.ceil(remainingPixels / Math.max(charges, 1));
- const timeFromCharges = cyclesNeeded * cooldown;
- return Math.min(timeFromCharges, 999 * 24 * 60 * 60 * 1000); // Cap at 999 days
- }
-
- const paintingSpeedDelay = 1000 / paintingSpeed;
- const timeFromSpeed = remainingPixels * paintingSpeedDelay;
-
- const cyclesNeeded = Math.ceil(remainingPixels / Math.max(charges, 1));
- const timeFromCharges = cyclesNeeded * cooldown;
-
- const totalTime = timeFromSpeed + timeFromCharges;
-
- // Safety check to prevent infinity and cap at reasonable maximum
- if (!Number.isFinite(totalTime) || totalTime < 0) {
- return 0;
- }
-
- // Cap at 999 days to prevent display issues
- return Math.min(totalTime, 999 * 24 * 60 * 60 * 1000);
- }
-
- // Painted pixel tracking helpers
- initializePaintedMap(width, height) {
- if (!window.state.paintedMap || window.state.paintedMap.length !== height) {
- window.state.paintedMap = Array(height)
- .fill()
- .map(() => Array(width).fill(false));
- }
- }
-
- markPixelPainted(x, y, regionX = 0, regionY = 0) {
- const actualX = x + regionX;
- const actualY = y + regionY;
-
- if (
- window.state.paintedMap &&
- window.state.paintedMap[actualY] &&
- actualX >= 0 &&
- actualX < window.state.paintedMap[actualY].length
- ) {
- window.state.paintedMap[actualY][actualX] = true;
- }
- }
-
- isPixelPainted(x, y, regionX = 0, regionY = 0) {
- const actualX = x + regionX;
- const actualY = y + regionY;
-
- if (
- window.state.paintedMap &&
- window.state.paintedMap[actualY] &&
- actualX >= 0 &&
- actualX < window.state.paintedMap[actualY].length
- ) {
- return window.state.paintedMap[actualY][actualX];
- }
- return false;
- }
-
- // Alias for isPixelPainted - used in pre-filtering logic
- isPixelMarkedPainted(x, y, regionX = 0, regionY = 0) {
- return this.isPixelPainted(x, y, regionX, regionY);
- }
-
- // Smart save
- shouldAutoSave() {
- const now = Date.now();
- const pixelsSinceLastSave = window.state.paintedPixels - window.state._lastSavePixelCount;
- const timeSinceLastSave = now - window.state._lastSaveTime;
-
- return !window.state._saveInProgress && pixelsSinceLastSave >= 25 && timeSinceLastSave >= 30000;
- }
-
- performSmartSave() {
- if (!this.shouldAutoSave()) return false;
-
- window.state._saveInProgress = true;
- const success = this.saveProgress();
-
- if (success) {
- window.state._lastSavePixelCount = window.state.paintedPixels;
- window.state._lastSaveTime = Date.now();
- }
-
- window.state._saveInProgress = false;
- return success;
- }
-
- // Data compression helpers
- packPaintedMapToBase64(paintedMap, width, height) {
- if (!paintedMap || !width || !height) return null;
- const totalBits = width * height;
- const byteLen = Math.ceil(totalBits / 8);
- const bytes = new Uint8Array(byteLen);
- let bitIndex = 0;
- for (let y = 0; y < height; y++) {
- const row = paintedMap[y];
- for (let x = 0; x < width; x++) {
- const bit = row && row[x] ? 1 : 0;
- const b = bitIndex >> 3;
- const o = bitIndex & 7;
- if (bit) bytes[b] |= 1 << o;
- bitIndex++;
- }
- }
- let binary = '';
- const chunk = 0x8000;
- for (let i = 0; i < bytes.length; i += chunk) {
- binary += String.fromCharCode.apply(
- null,
- bytes.subarray(i, Math.min(i + chunk, bytes.length))
- );
- }
- return btoa(binary);
- }
-
- unpackPaintedMapFromBase64(base64, width, height) {
- if (!base64 || !width || !height) return null;
- const binary = atob(base64);
- const bytes = new Uint8Array(binary.length);
- for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
- const map = Array(height)
- .fill()
- .map(() => Array(width).fill(false));
- let bitIndex = 0;
- for (let y = 0; y < height; y++) {
- for (let x = 0; x < width; x++) {
- const b = bitIndex >> 3;
- const o = bitIndex & 7;
- map[y][x] = ((bytes[b] >> o) & 1) === 1;
- bitIndex++;
- }
- }
- return map;
- }
-
- // Migration helpers
- 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;
-
- try {
- const migrated = { ...saved };
- const width = migrated.imageData?.width;
- const height = migrated.imageData?.height;
- if (migrated.paintedMap && width && height) {
- const data = this.packPaintedMapToBase64(migrated.paintedMap, width, height);
- migrated.paintedMapPacked = { width, height, data };
- }
- delete migrated.paintedMap;
- migrated.version = '2';
- return migrated;
- } catch (e) {
- console.warn('Migration to v2 failed, using original data:', e);
- return saved;
- }
- }
-
- migrateProgressToV21(saved) {
- if (!saved) return saved;
- if (saved.version === '2.1') return saved;
- const isV2 = saved.version === '2' || saved.version === '2.0';
- const isV1 = !saved.version || saved.version === '1' || saved.version === '1.0' || saved.version === '1.1';
- if (!isV2 && !isV1) return saved;
- try {
- const migrated = { ...saved };
- if (isV1) {
- const width = migrated.imageData?.width;
- const height = migrated.imageData?.height;
- if (migrated.paintedMap && width && height) {
- const data = this.packPaintedMapToBase64(migrated.paintedMap, width, height);
- migrated.paintedMapPacked = { width, height, data };
- }
- delete migrated.paintedMap;
- }
- migrated.version = '2.1';
- return migrated;
- } catch (e) {
- console.warn('Migration to v2.1 failed, using original data:', e);
- return saved;
- }
- }
-
- migrateProgressToV22(data) {
- try {
- const migrated = { ...data };
- migrated.version = '2.2';
-
- if (!migrated.state.coordinateMode) {
- migrated.state.coordinateMode = window.CONFIG.COORDINATE_MODE;
- }
- if (!migrated.state.coordinateDirection) {
- migrated.state.coordinateDirection = window.CONFIG.COORDINATE_DIRECTION;
- }
- if (!migrated.state.coordinateSnake) {
- migrated.state.coordinateSnake = window.CONFIG.COORDINATE_SNAKE;
- }
- if (!migrated.state.blockWidth) {
- migrated.state.blockWidth = window.CONFIG.COORDINATE_BLOCK_WIDTH;
- }
- if (!migrated.state.blockHeight) {
- migrated.state.blockHeight = window.CONFIG.COORDINATE_BLOCK_HEIGHT;
- }
-
- return migrated;
- } catch (e) {
- console.warn('Migration to v2.2 failed, using original data:', e);
- return data;
- }
- }
-
- buildPaintedMapPacked() {
- if (window.state.paintedMap && window.state.imageData) {
- const data = this.packPaintedMapToBase64(
- window.state.paintedMap,
- window.state.imageData.width,
- window.state.imageData.height
- );
- if (data) {
- return {
- width: window.state.imageData.width,
- height: window.state.imageData.height,
- data: data,
- };
- }
- }
- return null;
- }
-
- buildProgressData() {
- return {
- timestamp: Date.now(),
- version: '2.2',
- state: {
- totalPixels: window.state.totalPixels,
- paintedPixels: window.state.paintedPixels,
- lastPosition: window.state.lastPosition,
- startPosition: window.state.startPosition,
- region: window.state.region,
- imageLoaded: window.state.imageLoaded,
- colorsChecked: window.state.colorsChecked,
- coordinateMode: window.state.coordinateMode,
- coordinateDirection: window.state.coordinateDirection,
- coordinateSnake: window.state.coordinateSnake,
- blockWidth: window.state.blockWidth,
- blockHeight: window.state.blockHeight,
- availableColors: window.state.availableColors,
- },
- imageData: window.state.imageData
- ? {
- width: window.state.imageData.width,
- height: window.state.imageData.height,
- pixels: Array.from(window.state.imageData.pixels),
- totalPixels: window.state.imageData.totalPixels,
- }
- : null,
- paintedMapPacked: this.buildPaintedMapPacked(),
- };
- }
-
- migrateProgress(saved) {
- if (!saved) return null;
-
- let data = saved;
- const ver = data.version;
-
- if (!ver || ver === '1' || ver === '1.0' || ver === '1.1') {
- data = this.migrateProgressToV2(data);
- }
-
- if (data.version === '2' || data.version === '2.0') {
- data = this.migrateProgressToV21(data);
- }
-
- if (data.version === '2.1') {
- data = this.migrateProgressToV22(data);
- }
-
- return data;
- }
-
- saveProgress() {
- try {
- const progressData = this.buildProgressData();
- localStorage.setItem('wplace-bot-progress', JSON.stringify(progressData));
- return true;
- } catch (error) {
- console.error('Error saving progress:', error);
- return false;
- }
- }
-
- loadProgress() {
- try {
- const saved = localStorage.getItem('wplace-bot-progress');
- if (!saved) return null;
- let data = JSON.parse(saved);
- const migrated = this.migrateProgress(data);
-
- if (migrated && migrated !== data) {
- try {
- localStorage.setItem('wplace-bot-progress', JSON.stringify(migrated));
- } catch { }
- }
- return migrated;
- } catch (error) {
- console.error('Error loading progress:', error);
- return null;
- }
- }
-
- clearProgress() {
- try {
- localStorage.removeItem('wplace-bot-progress');
- window.state.paintedMap = null;
- window.state._lastSavePixelCount = 0;
- window.state._lastSaveTime = 0;
- window.state.coordinateMode = window.CONFIG.COORDINATE_MODE;
- window.state.coordinateDirection = window.CONFIG.COORDINATE_DIRECTION;
- window.state.coordinateSnake = window.CONFIG.COORDINATE_SNAKE;
- window.state.blockWidth = window.CONFIG.COORDINATE_BLOCK_WIDTH;
- window.state.blockHeight = window.CONFIG.COORDINATE_BLOCK_HEIGHT;
- return true;
- } catch (error) {
- console.error('Error clearing progress:', error);
- return false;
- }
- }
-
- restoreProgress(savedData) {
- try {
- Object.assign(window.state, savedData.state);
-
- // Restore coordinate generation settings
- if (savedData.state.coordinateMode) {
- window.state.coordinateMode = savedData.state.coordinateMode;
- }
- if (savedData.state.coordinateDirection) {
- window.state.coordinateDirection = savedData.state.coordinateDirection;
- }
- if (savedData.state.coordinateSnake !== undefined) {
- window.state.coordinateSnake = savedData.state.coordinateSnake;
- }
- if (savedData.state.blockWidth) {
- window.state.blockWidth = savedData.state.blockWidth;
- }
- if (savedData.state.blockHeight) {
- window.state.blockHeight = savedData.state.blockHeight;
- }
-
- // Restore available colors from old save files (backward compatibility)
- if (savedData.state.availableColors && Array.isArray(savedData.state.availableColors)) {
- window.state.availableColors = savedData.state.availableColors;
- window.state.colorsChecked = true; // Mark colors as checked
- }
-
- if (savedData.imageData) {
- window.state.imageData = {
- width: savedData.imageData.width,
- height: savedData.imageData.height,
- pixels: savedData.imageData.pixels, // Keep as Array - will convert in ImageData constructor
- totalPixels: savedData.imageData.totalPixels,
- };
-
- try {
- const canvas = document.createElement('canvas');
- canvas.width = window.state.imageData.width;
- canvas.height = window.state.imageData.height;
- const ctx = canvas.getContext('2d');
- const imageData = new ImageData(
- window.state.imageData.pixels,
- window.state.imageData.width,
- window.state.imageData.height
- );
- ctx.putImageData(imageData, 0, 0);
-
- // Create ImageProcessor directly like the original - no delegation
- if (window.ImageProcessor) {
- const proc = new window.ImageProcessor('');
- proc.img = canvas;
- proc.canvas = canvas;
- proc.ctx = ctx;
- window.state.imageData.processor = proc;
- } else if (window.WPlaceImageProcessor) {
- const proc = new window.WPlaceImageProcessor('');
- proc.img = canvas;
- proc.canvas = canvas;
- proc.ctx = ctx;
- window.state.imageData.processor = proc;
- }
- } catch (e) {
- console.warn('Could not rebuild processor from saved image data:', e);
- }
- }
-
- // Prefer packed form if available; fallback to legacy paintedMap array for backward compatibility
- if (savedData.paintedMapPacked && savedData.paintedMapPacked.data) {
- const { width, height, data } = savedData.paintedMapPacked;
- window.state.paintedMap = this.unpackPaintedMapFromBase64(data, width, height);
- } else if (savedData.paintedMap) {
- window.state.paintedMap = savedData.paintedMap.map((row) => Array.from(row));
- }
-
- // CRITICAL FIX: Notify all modules that state has been restored
- this._syncModulesAfterStateRestore();
-
- return true;
- } catch (error) {
- console.error('Error restoring progress:', error);
- return false;
- }
- }
-
- /**
- * Synchronize all modules after state restore to prevent timing issues
- * This ensures modules reflect the updated state properly
- * @private
- */
- _syncModulesAfterStateRestore() {
- try {
- console.log('š Synchronizing modules after state restore...');
-
- // Sync image processor if available
- if (window.globalImageProcessor && window.state.imageData) {
- // Force refresh image processor state
- if (typeof window.globalImageProcessor.updateState === 'function') {
- window.globalImageProcessor.updateState(window.state);
- }
- }
-
- // Sync overlay manager if available
- if (window.globalOverlayManager) {
- // Force refresh overlay state - this ensures overlay knows about the new state
- if (typeof window.globalOverlayManager.updateState === 'function') {
- window.globalOverlayManager.updateState(window.state);
- }
- }
-
- // Extract available colors if not already set (required for color palette functionality)
- if (!window.state.availableColors || window.state.availableColors.length === 0) {
- console.log('šØ Extracting available colors for save file...');
- try {
- const availableColors = this.extractAvailableColors();
- if (availableColors && availableColors.length > 0) {
- window.state.availableColors = availableColors;
- window.state.colorsChecked = true;
- console.log(`ā
Extracted ${availableColors.length} available colors`);
- } else {
- console.warn('ā ļø No colors available - this might cause issues with overlay display');
- }
- } catch (error) {
- console.warn('ā ļø Failed to extract colors:', error);
- }
- }
-
- // Force window.state reference update for any stale references
- window.state = window.state; // Trigger any watchers
-
- console.log('ā
Module synchronization complete');
- } catch (error) {
- console.warn('ā ļø Module synchronization failed:', error);
- }
- }
-
- /**
- * Force navigation to saved position to display overlay immediately
- * @param {Object} region - The region coordinates
- * @param {Object} position - The pixel position within region
- * @private
- */
- async _forceNavigateToSavedPosition(region, position) {
- try {
- console.log(`š§ Forcing navigation to region (${region.x}, ${region.y}) pixel (${position.x}, ${position.y})...`);
-
- // Calculate world coordinates (region * 1000 + pixel position)
- const worldX = region.x * 1000 + position.x;
- const worldY = region.y * 1000 + position.y;
-
- console.log(`š World coordinates: (${worldX}, ${worldY})`);
-
- // Method 1: Try to find WPlace's camera/viewport controls
- const attemptCameraControl = () => {
- // Look for WPlace's camera control functions in global scope
- const possibleControls = [
- 'camera', 'viewport', 'map', 'game', 'wplace', 'app'
- ];
-
- for (const control of possibleControls) {
- if (window[control]) {
- console.log(`šÆ Found potential control: ${control}`, window[control]);
-
- // Try common navigation method names
- const methods = ['setPosition', 'moveTo', 'panTo', 'setView', 'goTo', 'navigate'];
- for (const method of methods) {
- if (typeof window[control][method] === 'function') {
- console.log(`š Attempting ${control}.${method}(${worldX}, ${worldY})`);
- try {
- window[control][method](worldX, worldY);
- return true;
- } catch (e) {
- console.warn(`ā ${control}.${method} failed:`, e);
- }
- }
- }
- }
- }
- return false;
- };
-
- // Method 2: Try updating URL hash (common for canvas-based apps)
- const attemptUrlNavigation = () => {
- try {
- const newHash = `#${worldX},${worldY}`;
- console.log(`š Attempting URL navigation: ${newHash}`);
- window.location.hash = newHash;
-
- // Also try alternative formats
- setTimeout(() => {
- const altHash = `#x=${worldX}&y=${worldY}`;
- console.log(`š Attempting alternative URL: ${altHash}`);
- window.location.hash = altHash;
- }, 100);
-
- return true;
- } catch (e) {
- console.warn('ā URL navigation failed:', e);
- return false;
- }
- };
-
- // Method 3: Try triggering scroll/wheel events to force tile loading
- const attemptScrollTrigger = () => {
- try {
- console.log('š±ļø Attempting scroll trigger to force tile loading...');
-
- // Find canvas or main container
- const canvas = document.querySelector('canvas') || document.body;
-
- // Simulate mouse events at target coordinates
- ['mousedown', 'mousemove', 'mouseup', 'wheel'].forEach(eventType => {
- const event = new MouseEvent(eventType, {
- bubbles: true,
- clientX: worldX % window.innerWidth,
- clientY: worldY % window.innerHeight,
- deltaY: eventType === 'wheel' ? 1 : 0
- });
- canvas.dispatchEvent(event);
- });
-
- return true;
- } catch (e) {
- console.warn('ā Scroll trigger failed:', e);
- return false;
- }
- };
-
- // Try all methods
- const success = attemptCameraControl() || attemptUrlNavigation() || attemptScrollTrigger();
-
- if (success) {
- console.log('ā
Navigation triggered - overlay should display in a moment');
-
- // Wait a bit for tiles to load, then show success message
- setTimeout(() => {
- if (typeof this.showAlert === 'function') {
- this.showAlert('ā
Overlay loaded and positioned automatically!', 'success');
- }
- }, 1000);
- } else {
- console.warn('ā ļø Automatic navigation failed - showing manual instruction');
- if (typeof this.showAlert === 'function') {
- this.showAlert(`š Navigate to region (${region.x}, ${region.y}) to see your restored overlay`, 'info');
- }
- }
-
- } catch (error) {
- console.error('ā Failed to force navigation:', error);
- if (typeof this.showAlert === 'function') {
- this.showAlert(`š Navigate to region (${region.x}, ${region.y}) to see your overlay`, 'info');
- }
- }
- }
-
- saveProgressToFile() {
- try {
- const progressData = this.buildProgressData();
- const filename = `wplace-bot-progress-${new Date()
- .toISOString()
- .slice(0, 19)
- .replace(/:/g, '-')}.json`;
- this.createFileDownloader(JSON.stringify(progressData, null, 2), filename);
- return true;
- } catch (error) {
- console.error('Error saving to file:', error);
- return false;
- }
- }
-
- async loadProgressFromFile() {
- try {
- const data = await this.createFileUploader();
- if (!data || !data.state) {
- throw new Error('Invalid file format');
- }
- const migrated = this.migrateProgress(data);
- const success = this.restoreProgress(migrated);
- return success;
- } catch (error) {
- console.error('Error loading from file:', error);
- throw error;
- }
- }
-
- async restoreOverlayFromData() {
- if (!window.state.imageLoaded || !window.state.imageData || !window.state.startPosition || !window.state.region) {
- console.warn('ā Missing required data for overlay restoration');
- return false;
- }
-
- try {
- console.log('š Restoring overlay from saved data...');
-
- // Recreate ImageBitmap from loaded pixel data (match old version exactly)
- const imageData = new ImageData(
- window.state.imageData.pixels,
- window.state.imageData.width,
- window.state.imageData.height
- );
-
- const canvas = new OffscreenCanvas(window.state.imageData.width, window.state.imageData.height);
- const ctx = canvas.getContext('2d');
- ctx.putImageData(imageData, 0, 0);
- const imageBitmap = await canvas.transferToImageBitmap();
-
- // Set up overlay with restored data (use the Auto-Image.js overlayManager instance)
- if (window.autoImageOverlayManager) {
- console.log('š Setting image bitmap...');
- await window.autoImageOverlayManager.setImage(imageBitmap);
-
- console.log('š Setting position and region...');
- await window.autoImageOverlayManager.setPosition(window.state.startPosition, window.state.region);
-
- console.log('ļæ½ Enabling overlay...');
- window.autoImageOverlayManager.enable();
-
- // Update overlay button state
- const toggleOverlayBtn = document.getElementById('toggleOverlayBtn');
- if (toggleOverlayBtn) {
- toggleOverlayBtn.disabled = false;
- toggleOverlayBtn.classList.add('active');
- }
-
- console.log('ā
Overlay restored from data');
- return true;
- } else {
- console.error('ā autoImageOverlayManager not available');
- return false;
- }
- } catch (error) {
- console.error('ā Failed to restore overlay from data:', error);
- return false;
- }
- }
-
- updateCoordinateUI({ mode, directionControls, snakeControls, blockControls }) {
- const isLinear = mode === 'rows' || mode === 'columns';
- const isBlock = mode === 'blocks' || mode === 'shuffle-blocks';
-
- if (directionControls) directionControls.style.display = isLinear ? 'block' : 'none';
- if (snakeControls) snakeControls.style.display = isLinear ? 'block' : 'none';
- if (blockControls) blockControls.style.display = isBlock ? 'block' : 'none';
- }
-
- /**
- * Force navigation to saved position to display overlay immediately
- * @param {Object} region - The region coordinates
- * @param {Object} position - The pixel position within region
- * @private
- */
- async _forceNavigateToSavedPosition(region, position) {
- try {
- console.log(`š§ Forcing navigation to region (${region.x}, ${region.y}) pixel (${position.x}, ${position.y})...`);
-
- // Calculate world coordinates (region * 1000 + pixel position)
- const worldX = region.x * 1000 + position.x;
- const worldY = region.y * 1000 + position.y;
-
- console.log(`š World coordinates: (${worldX}, ${worldY})`);
-
- // Method 1: Try to find WPlace's camera/viewport controls
- const attemptCameraControl = () => {
- // Look for WPlace's camera control functions in global scope
- const possibleControls = [
- 'camera', 'viewport', 'map', 'game', 'wplace', 'app'
- ];
-
- for (const control of possibleControls) {
- if (window[control]) {
- console.log(`šÆ Found potential control: ${control}`, window[control]);
-
- // Try common navigation method names
- const methods = ['setPosition', 'moveTo', 'panTo', 'setView', 'goTo', 'navigate'];
- for (const method of methods) {
- if (typeof window[control][method] === 'function') {
- console.log(`š Attempting ${control}.${method}(${worldX}, ${worldY})`);
- try {
- window[control][method](worldX, worldY);
- return true;
- } catch (e) {
- console.warn(`ā ${control}.${method} failed:`, e);
- }
- }
- }
- }
- }
- return false;
- };
-
- // Method 2: Try updating URL hash (common for canvas-based apps)
- const attemptUrlNavigation = () => {
- try {
- const newHash = `#${worldX},${worldY}`;
- console.log(`š Attempting URL navigation: ${newHash}`);
- window.location.hash = newHash;
-
- // Also try alternative formats
- setTimeout(() => {
- const altHash = `#x=${worldX}&y=${worldY}`;
- console.log(`š Attempting alternative URL: ${altHash}`);
- window.location.hash = altHash;
- }, 100);
-
- return true;
- } catch (e) {
- console.warn('ā URL navigation failed:', e);
- return false;
- }
- };
-
- // Method 3: Try triggering scroll/wheel events to force tile loading
- const attemptScrollTrigger = () => {
- try {
- console.log('š±ļø Attempting scroll trigger to force tile loading...');
-
- // Find canvas or main container
- const canvas = document.querySelector('canvas') || document.body;
-
- // Simulate mouse events at target coordinates
- ['mousedown', 'mousemove', 'mouseup', 'wheel'].forEach(eventType => {
- const event = new MouseEvent(eventType, {
- bubbles: true,
- clientX: worldX % window.innerWidth,
- clientY: worldY % window.innerHeight,
- deltaY: eventType === 'wheel' ? 1 : 0
- });
- canvas.dispatchEvent(event);
- });
-
- return true;
- } catch (e) {
- console.warn('ā Scroll trigger failed:', e);
- return false;
- }
- };
-
- // Try all methods
- const success = attemptCameraControl() || attemptUrlNavigation() || attemptScrollTrigger();
-
- if (success) {
- console.log('ā
Navigation triggered - overlay should display in a moment');
-
- // Wait a bit for tiles to load, then show success message
- setTimeout(() => {
- if (typeof this.showAlert === 'function') {
- this.showAlert('ā
Overlay loaded and positioned automatically!', 'success');
- }
- }, 1000);
- } else {
- console.warn('ā ļø Automatic navigation failed - showing manual instruction');
- if (typeof this.showAlert === 'function') {
- this.showAlert(`š Navigate to region (${region.x}, ${region.y}) to see your restored overlay`, 'info');
- }
- }
-
- } catch (error) {
- console.error('ā Failed to force navigation:', error);
- if (typeof this.showAlert === 'function') {
- this.showAlert(`š Navigate to region (${region.x}, ${region.y}) to see your overlay`, 'info');
- }
- }
- }
-}
-
-// Global instance with safety checks
-try {
- window.globalUtilsManager = new WPlaceUtilsManager();
- console.log('ā
WPlaceUtilsManager initialized successfully');
-} catch (error) {
- console.error('ā Failed to initialize WPlaceUtilsManager:', error);
-}
-
-// Additional safety: ensure the instance is available for other scripts
-if (!window.globalUtilsManager) {
- console.warn('ā ļø globalUtilsManager not available, attempting to reinitialize...');
- setTimeout(() => {
- try {
- window.globalUtilsManager = new WPlaceUtilsManager();
- console.log('ā
WPlaceUtilsManager reinitialized successfully');
- } catch (error) {
- console.error('ā Failed to reinitialize WPlaceUtilsManager:', error);
- }
- }, 100);
-}
\ No newline at end of file
diff --git a/Extensions/themes/acrylic.css b/Extensions/themes/acrylic.css
deleted file mode 100644
index edee14c..0000000
--- a/Extensions/themes/acrylic.css
+++ /dev/null
@@ -1,883 +0,0 @@
-/* WPlace Auto-Image Bot - Acrylic Theme */
-/* Beautiful theme with translucent blurred panels */
-
-@import url('https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap');
-
-@font-face {
- font-family: 'Apple Color Emoji';
- src: url('https://raw.githubusercontent.com/GORAlexComp/AppleColorEmojiFont/refs/heads/main/fonts/AppleColorEmoji.ttf');
-}
-
-/* Multi-account panel styling */
-.wplace-theme-acrylic #wplace-accounts-container {
- background: var(--wplace-primary);
- color: var(--wplace-text);
- border-radius: var(--wplace-radius);
- box-shadow: none;
- backdrop-filter: var(--wplace-backdrop);
- border: var(--wplace-border-width) var(--wplace-border-style) var(--wplace-border-color);
- min-width: 250px;
-}
-
-.wplace-theme-acrylic #wplace-accounts-container * {
- font-size: 15px;
- line-height: normal;
-}
-
-/* Active account styles */
-.wplace-theme-acrylic .wplace-accounts-container .active-account {
- --active-bg: color-mix(in srgb, white 15%, transparent);
- --active-color: var(--wplace-success);
- --inactive-color: var(--wplace-text);
- --progress-bg: rgb(255 255 255 / 10%);
- --progress-fill: white;
-}
-
-/* Main Container */
-.wplace-theme-acrylic #wplace-image-bot-container {
- background: var(--wplace-primary);
- color: var(--wplace-text);
- border-radius: var(--wplace-radius);
- box-shadow: none;
- backdrop-filter: var(--wplace-backdrop);
- border: var(--wplace-border-width) var(--wplace-border-style) var(--wplace-border-color);
- min-width: 300px;
-}
-
-.wplace-theme-acrylic #wplace-image-bot-container * {
- font-size: 15px;
- line-height: normal;
-}
-
-.wplace-theme-acrylic .wplace-content {
- display: flex;
- flex-direction: column;
- /* gap: 10px; */
- padding: 10px;
-}
-
-#wplace-image-bot-container.wplace-dragging {
- transition: none !important;
- box-shadow: none !important;
- transform: none !important;
-}
-
-/* Stats */
-.wplace-theme-acrylic #wplace-stats-container {
- background: var(--wplace-primary);
- color: var(--wplace-text);
- border-radius: var(--wplace-radius);
- box-shadow: none;
- backdrop-filter: var(--wplace-backdrop);
- border: var(--wplace-border-width) var(--wplace-border-style) var(--wplace-border-color);
-}
-
-.wplace-theme-acrylic .wplace-stats {
- margin-bottom: 0;
-}
-
-.wplace-theme-acrylic .wplace-colors-section {
- border-top: none;
- margin: 0;
-}
-
-/* Settings Container */
-.wplace-theme-acrylic #wplace-settings-container {
- background: var(--wplace-primary) !important;
- color: var(--wplace-text) !important;
- border-radius: var(--wplace-radius) !important;
- box-shadow: none !important;
- backdrop-filter: var(--wplace-backdrop) !important;
- border: var(--wplace-border-width) var(--wplace-border-style) var(--wplace-border-color) !important;
- max-width: 400px !important;
-}
-
-.wplace-theme-acrylic .wplace-settings-header {
- background: transparent;
- padding: 0;
-}
-
-.wplace-theme-acrylic .wplace-settings-header:hover {
- background: transparent !important;
-}
-
-.wplace-theme-acrylic .wplace-settings-icon {
- animation: none;
- font-size: 15px;
-}
-
-.wplace-theme-acrylic .wplace-settings-content {
- padding: 10px;
- display: flex;
- flex-direction: column;
- gap: 10px;
-}
-
-.wplace-theme-acrylic .wplace-settings-footer {
- border-top: none;
- padding: 10px;
-}
-
-.wplace-theme-acrylic .wplace-settings-section {
- margin: 0;
-}
-
-.wplace-theme-acrylic .wplace-settings-section-wrapper,
-.wplace-theme-acrylic .wplace-overlay-wrapper,
-.wplace-theme-acrylic .wplace-batch-controls {
- padding: 0 !important;
- background: transparent !important;
- border: none !important;
-}
-
-/* Headers */
-.wplace-theme-acrylic .wplace-header,
-.wplace-theme-acrylic .wplace-settings-title-wrapper {
- height: 40px;
- padding: 7px 7px 7px 10px;
- font-weight: 600;
- background: var(--wplace-accent);
- color: var(--wplace-highlight);
-}
-
-.wplace-theme-acrylic .wplace-header span,
-.wplace-theme-acrylic .wplace-settings-title {
- max-width: 150px;
- font-size: 15px;
- font-weight: 600;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
-}
-
-.wplace-theme-acrylic .wplace-header-controls #compactBtn {
- display: none;
-}
-
-.wplace-theme-acrylic .wplace-header-btn,
-.wplace-theme-acrylic .wplace-settings-close-btn {
- background: #2e2e2e;
- color: var(--wplace-highlight);
- border: none;
- font-size: 12px;
- border-radius: 4px;
- width: 24px;
- height: 24px;
-}
-
-.wplace-theme-acrylic .wplace-header-btn i {
- font-size: 12px !important;
-}
-
-.wplace-theme-acrylic .wplace-header-btn:hover,
-.wplace-theme-acrylic .wplace-settings-close-btn:hover {
- background: transparent;
- transform: none;
-}
-
-/* Sections */
-.wplace-theme-acrylic .wplace-section {
- display: flex;
- flex-direction: column;
- gap: 8px;
- background: transparent;
- padding: 0;
- border: none;
- margin: 0;
-}
-
-.wplace-theme-acrylic .wplace-section-title {
- color: var(--wplace-highlight);
- text-transform: capitalize;
- font-size: 13px;
- font-weight: 600;
- pointer-events: none;
- letter-spacing: unset;
-}
-
-.wplace-theme-acrylic .wplace-section-title i {
- display: none;
-}
-
-.wplace-theme-acrylic .wplace-status-section {
- background: transparent;
- padding: 0;
- margin: 0;
- border: none;
- display: flex;
- flex-direction: column;
- gap: 8px;
-}
-
-/* Status Styling */
-.wplace-theme-acrylic .wplace-stats {
- background: transparent;
- padding: 0;
- border: none;
-}
-
-.wplace-theme-acrylic .wplace-stat-value {
- color: var(--wplace-highlight);
-}
-
-.wplace-theme-acrylic .wplace-status {
- border-radius: var(--wplace-radius);
- border: none;
- box-shadow: none;
- padding: 10px;
- font-weight: 500;
-}
-
-.wplace-theme-acrylic .status-default {
- background: color-mix(in srgb, white 15%, transparent) !important;
-}
-
-.wplace-theme-acrylic .status-success {
- background: color-mix(in srgb, var(--wplace-success) 15%, transparent);
- color: var(--wplace-success);
-}
-
-.wplace-theme-acrylic .status-error {
- background: color-mix(in srgb, var(--wplace-error) 15%, transparent);
- color: var(--wplace-error);
-}
-
-.wplace-theme-acrylic .status-warning {
- background: color-mix(in srgb, var(--wplace-warning) 15%, transparent);
- color: var(--wplace-warning);
-}
-
-/* Buttons */
-.wplace-theme-acrylic .wplace-btn {
- border-radius: 8px;
- border: none;
- height: max-content;
- padding: 10px;
- gap: 5px;
- flex-direction: column;
- align-items: flex-start;
- line-height: normal;
- text-align: left;
- font-size: 15px !important;
- box-shadow: none;
-}
-
-.wplace-theme-acrylic .wplace-btn::before {
- display: none;
-}
-
-.wplace-theme-acrylic .wplace-btn:disabled {
- box-shadow: none !important;
-}
-
-.wplace-theme-acrylic .wplace-btn:hover:not(:disabled) {
- transform: none;
- animation: none;
- box-shadow: none !important;
- filter: brightness(0.9);
-}
-
-.wplace-theme-acrylic .wplace-btn-primary,
-.wplace-theme-acrylic .wplace-btn-upload {
- background: white !important;
- color: black !important;
- border: none !important;
-}
-
-.wplace-theme-acrylic .wplace-btn-upload:hover:not(:disabled) {
- background: revert !important;
-}
-
-.wplace-theme-acrylic .wplace-btn-secondary,
-.wplace-theme-acrylic .wplace-btn-select,
-.wplace-theme-acrylic .wplace-btn-file,
-.wplace-theme-acrylic .wplace-btn-overlay {
- background: var(--wplace-secondary);
-}
-
-.wplace-theme-acrylic .wplace-btn-start {
- background: var(--wplace-success);
- color: white;
-}
-
-.wplace-theme-acrylic .wplace-btn-stop {
- background: var(--wplace-error);
- color: white;
-}
-
-.wplace-theme-acrylic .wplace-btn.active {
- background: white;
- color: black;
- box-shadow: none !important;
-}
-
-.wplace-theme-acrylic .wplace-btn.active i {
- filter: none;
-}
-
-.wplace-theme-acrylic .wplace-settings-apply-btn {
- background: white;
- color: black;
- border-radius: 8px;
-}
-
-/* Compact Input Buttons */
-.wplace-theme-acrylic .wplace-input-btn-small,
-.wplace-theme-acrylic .wplace-input-btn-small:hover,
-.wplace-theme-acrylic .wplace-input-btn-small:active {
- background: white;
- color: black;
- border: none;
- transform: none;
- border-radius: 4px;
- box-shadow: none;
- font-weight: 500;
- width: 25px;
- height: 25px;
-}
-
-/* Alerts */
-.wplace-theme-acrylic .wplace-alert-base {
- border-radius: var(--wplace-radius);
- backdrop-filter: var(--wplace-backdrop);
- border: none;
- box-shadow: none;
-}
-
-.wplace-theme-acrylic .wplace-alert-info {
- background: color-mix(in srgb, #0000e5 50%, transparent);
-}
-
-.wplace-theme-acrylic .wplace-alert-success {
- background: color-mix(in srgb, var(--wplace-success) 50%, transparent);
-}
-
-.wplace-theme-acrylic .wplace-alert-error {
- background: color-mix(in srgb, var(--wplace-error) 50%, transparent);
-}
-
-.wplace-theme-acrylic .wplace-alert-warning {
- background: color-mix(in srgb, var(--wplace-warning) 50%, transparent);
-}
-
-/* Progress Bar */
-.wplace-theme-acrylic .wplace-progress {
- background: var(--wplace-secondary);
- border-radius: 4px;
- margin: 0;
- border: none;
-}
-
-.wplace-theme-acrylic .wplace-progress-bar {
- background: white;
-}
-
-.wplace-theme-acrylic .wplace-progress-bar::after {
- display: none;
-}
-
-/* Form Controls */
-.wplace-theme-acrylic .wplace-settings-checkbox,
-.wplace-theme-acrylic .wplace-notification-checkbox,
-.wplace-theme-acrylic .wplace-speed-checkbox {
- cursor: pointer;
- width: 20px;
- height: 20px;
- flex-shrink: 0;
- accent-color: var(--wplace-highlight);
-}
-
-.wplace-theme-acrylic .wplace-settings-select,
-.wplace-theme-acrylic .wplace-settings-number-input,
-.wplace-theme-acrylic .wplace-cooldown-input,
-.wplace-theme-acrylic .wplace-notification-interval-input {
- background: rgb(255 255 255 / 10%);
- color: white;
- border: 1px solid rgb(255 255 255 / 25%);
- border-radius: 8px;
- box-shadow: none;
- transition-duration: 0.3s;
-}
-
-.wplace-theme-acrylic .wplace-settings-select:hover,
-.wplace-theme-acrylic .wplace-settings-number-input:hover,
-.wplace-theme-acrylic .wplace-cooldown-input:hover,
-.wplace-theme-acrylic .wplace-notification-interval-input:hover {
- border-color: rgb(255 255 255 / 50%);
-}
-
-.wplace-theme-acrylic .wplace-settings-select:focus,
-.wplace-theme-acrylic .wplace-settings-number-input:focus,
-.wplace-theme-acrylic .wplace-cooldown-input:focus,
-.wplace-theme-acrylic .wplace-notification-interval-input:focus {
- border-color: white;
-}
-
-.wplace-theme-acrylic .wplace-settings-option {
- background: #2d3748;
- color: white;
-}
-
-.wplace-theme-acrylic .wplace-settings-select option {
- background: #2d3748;
- color: white;
-}
-
-.wplace-theme-acrylic .wplace-cooldown-input {
- border-radius: 4px !important;
-}
-
-.wplace-theme-acrylic .wplace-cooldown-input:focus {
- background: rgb(255 255 255 / 10%);
- box-shadow: none;
-}
-
-.wplace-theme-acrylic .wplace-overlay-opacity-value {
- background-color: var(--wplace-secondary) !important;
- border: none !important;
-}
-
-/* Sliders */
-.wplace-theme-acrylic .wplace-slider,
-.wplace-theme-acrylic .wplace-speed-slider,
-.wplace-theme-acrylic .wplace-overlay-opacity-slider {
- background: var(--wplace-secondary) !important;
- border-radius: 3px !important;
- height: 6px !important;
-}
-
-.wplace-theme-acrylic .wplace-slider::-webkit-slider-thumb,
-.wplace-theme-acrylic .resize-slider::-webkit-slider-thumb,
-.wplace-theme-acrylic .wplace-speed-slider::-webkit-slider-thumb,
-.wplace-theme-acrylic .wplace-overlay-opacity-slider::-webkit-slider-thumb {
- width: 16px !important;
- height: 16px !important;
- border-radius: 4px !important;
- background: white !important;
- box-shadow: none !important;
- transform: none !important;
-}
-
-.wplace-theme-acrylic .wplace-speed-value {
- background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
- border-radius: 8px;
- color: white;
- box-shadow: 0 3px 10px rgb(79 172 254 / 30%);
- border: 1px solid rgb(255 255 255 / 20%);
-}
-
-.wplace-theme-acrylic .resize-slider {
- -webkit-appearance: revert;
-}
-
-/* Layout Controls */
-.wplace-theme-acrylic .wplace-slider-container {
- display: flex;
- flex-direction: row;
- align-items: center;
- gap: 8px;
- margin: 0;
-}
-
-.wplace-theme-acrylic .wplace-input-label-compact {
- color: rgba(255, 255, 255, 0.75);
- margin-left: 4px;
- text-transform: lowercase;
- white-space: nowrap;
-}
-
-.wplace-theme-acrylic .wplace-cooldown-control {
- margin: 0;
- display: flex;
- flex-direction: column;
- gap: 8px;
-}
-
-.wplace-theme-acrylic .wplace-cooldown-control label {
- font-weight: 400;
- margin-bottom: 0;
- display: block;
-}
-
-.wplace-theme-acrylic .wplace-cooldown-input-group {
- margin: 0;
- background: transparent;
-}
-
-/* Color Elements */
-.wplace-theme-acrylic .wplace-color-item-name {
- color: #ccc;
-}
-
-.wplace-theme-acrylic .wplace-color-swatch {
- border: 1px solid rgb(255 255 255 / 20%);
- border-radius: 4px;
-}
-
-.wplace-theme-acrylic .wplace-color-swatch:not(.active) {
- filter: grayscale(80%);
-}
-
-.wplace-theme-acrylic .wplace-color-swatch.unavailable {
- border-color: #666;
-}
-
-.wplace-theme-acrylic .wplace-color-swatch.unavailable:not(.active) {
- filter: grayscale(90%);
-}
-
-.wplace-theme-acrylic .wplace-color-swatch.active::after {
- text-shadow: 0 0 3px black;
-}
-
-.wplace-theme-acrylic .wplace-stat-color-swatch {
- border-radius: 3px;
- border: 1px solid rgb(255 255 255 / 10%);
- box-shadow: inset 0 0 2px rgb(0 0 0 / 50%);
-}
-
-.wplace-theme-acrylic .wplace-stat-colors-grid {
- background: var(--wplace-secondary);
- border-radius: 8px;
-}
-
-.wplace-theme-acrylic .wplace-color-divider {
- background: rgb(255 255 255 / 10%);
-}
-
-/* Icons */
-.wplace-theme-acrylic .wplace-icon-key,
-.wplace-theme-acrylic .wplace-icon-robot,
-.wplace-theme-acrylic .wplace-icon-speed,
-.wplace-theme-acrylic .wplace-icon-bell,
-.wplace-theme-acrylic .wplace-icon-palette,
-.wplace-theme-acrylic .wplace-icon-globe,
-.wplace-theme-acrylic .wplace-icon-paint,
-.wplace-theme-acrylic .wplace-icon-eye,
-.wplace-theme-acrylic .wplace-icon-compass,
-.wplace-theme-acrylic .wplace-icon-route,
-.wplace-theme-acrylic .wplace-icon-table,
-.wplace-theme-acrylic .wplace-icon-dice {
- color: white;
-}
-
-/* Typography */
-.wplace-theme-acrylic .wplace-cooldown-unit {
- display: none;
-}
-
-.wplace-theme-acrylic .wplace-settings-toggle-title,
-.wplace-theme-acrylic .wplace-overlay-opacity-label {
- font-size: 15px !important;
-}
-
-.wplace-theme-acrylic .wplace-settings-description,
-.wplace-theme-acrylic .wplace-random-batch-description,
-.wplace-theme-acrylic .wplace-settings-toggle-description {
- font-size: 12px;
- color: rgb(255 255 255 / 75%) !important;
-}
-
-/* Turnstile Overlay */
-.wplace-theme-acrylic .wplace-turnstile-overlay {
- background: var(--wplace-primary);
- border-radius: var(--wplace-radius);
- box-shadow: none;
- backdrop-filter: var(--wplace-backdrop);
- border: none;
- padding: 16px !important;
- color: var(--wplace-text) !important;
-}
-
-.wplace-theme-acrylic .wplace-turnstile-hide-btn {
- border-radius: 8px !important;
- backdrop-filter: var(--wplace-backdrop) !important;
- background: var(--wplace-primary) !important;
- top: -25px !important;
- right: 0 !important;
- transition: all 0.3s ease !important;
-}
-
-.wplace-theme-acrylic .wplace-turnstile-hide-btn:hover {
- background: white !important;
- color: black !important;
-}
-
-/* Resize Tools */
-.wplace-theme-acrylic .resize-tools button {
- border-radius: 6px;
- border: 1px solid rgb(255 255 255 / 20%);
- background: rgb(255 255 255 / 6%);
- color: #fff;
-}
-
-.wplace-theme-acrylic .resize-overlay {
- background: rgb(0 0 0 / 50%);
-}
-
-/* TODO: More resize menu styles */
-.wplace-theme-acrylic .resize-container {
- background: var(--wplace-secondary);
- backdrop-filter: var(--wplace-backdrop);
- border: none;
- border-radius: var(--wplace-radius);
- color: var(--wplace-text);
-}
-
-/* Account switching UI styles */
-.wplace-theme-acrylic .wplace-section {
- margin: 5px;
- border-radius: var(--wplace-radius);
- background: rgba(255, 255, 255, 0.08);
- padding: 5px;
- border: 1px solid rgba(255, 255, 255, 0.15);
- backdrop-filter: var(--wplace-backdrop);
-}
-
-.wplace-theme-acrylic .wplace-section-title {
- display: flex;
- align-items: center;
- gap: 8px;
- font-weight: 600;
- color: var(--wplace-text);
- margin: 2px;
- font-size: 14px;
-}
-
-/* Auto-swap toggle switch */
-.wplace-theme-acrylic .wplace-switch {
- position: relative;
- display: inline-block;
- width: 50px;
- height: 24px;
-}
-
-.wplace-theme-acrylic .wplace-switch input {
- opacity: 0;
- width: 0;
- height: 0;
-}
-
-.wplace-theme-acrylic .wplace-slider-round {
- position: absolute;
- cursor: pointer;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background-color: rgba(255, 255, 255, 0.2);
- transition: 0.4s;
- border-radius: 24px;
- backdrop-filter: blur(10px);
-}
-
-.wplace-theme-acrylic .wplace-slider-round:before {
- position: absolute;
- content: "";
- height: 18px;
- width: 18px;
- left: 3px;
- bottom: 3px;
- background-color: white;
- transition: 0.4s;
- border-radius: 50%;
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
-}
-
-.wplace-theme-acrylic .wplace-switch input:checked+.wplace-slider-round {
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-}
-
-.wplace-theme-acrylic .wplace-switch input:checked+.wplace-slider-round:before {
- transform: translateX(26px);
-}
-
-/* Account list container */
-.wplace-theme-acrylic .accounts-list-container {
- max-height: 500px;
- overflow-y: auto;
- /* margin-top: 10px; */
- padding: 0 6px;
-}
-
-/* Individual account item */
-.wplace-theme-acrylic .wplace-account-item {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 6px 8px;
- margin: 4px 0;
- background: rgba(255, 255, 255, 0.05);
- border-radius: 8px;
- border: 1px solid rgba(255, 255, 255, 0.15);
- transition: all 0.3s ease;
- backdrop-filter: blur(10px);
- position: relative;
-}
-
-/* Account ordering number */
-.wplace-theme-acrylic .wplace-account-number {
- position: absolute;
- top: 3px;
- left: 3px;
- width: 16px;
- height: 16px;
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
- color: white;
- border-radius: 50%;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 9px;
- font-weight: bold;
- z-index: 1;
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
-}
-
-.wplace-theme-acrylic .wplace-account-item:hover {
- background: rgba(255, 255, 255, 0.12);
- border-color: rgba(255, 255, 255, 0.3);
- transform: translateY(-1px);
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
-}
-
-.wplace-theme-acrylic .wplace-account-item.is-current {
- background: linear-gradient(135deg, rgba(102, 126, 234, 0.3) 0%, rgba(118, 75, 162, 0.3) 100%);
- border-color: #667eea;
- box-shadow: 0 0 20px rgba(102, 126, 234, 0.4);
-}
-
-/* Account details */
-.wplace-theme-acrylic .wplace-account-details {
- flex: 1;
- margin-left: 25px;
-}
-
-.wplace-theme-acrylic .wplace-account-name {
- font-weight: 600;
- color: var(--wplace-text);
- font-size: 13px;
- margin-bottom: 2px;
-}
-
-/* Account statistics */
-.wplace-theme-acrylic .wplace-account-stats {
- display: flex;
- gap: 12px;
- align-items: center;
- font-size: 12px;
- color: rgba(255, 255, 255, 0.75);
-}
-
-.wplace-theme-acrylic .wplace-account-stats i {
- margin-right: 4px;
-}
-
-.wplace-theme-acrylic .wplace-account-stats .fas.fa-bolt {
- color: #ffd700;
-}
-
-.wplace-theme-acrylic .wplace-account-stats .fas.fa-tint {
- color: #00bfff;
-}
-
-/* Move Artwork Panel Styles for Acrylic Theme */
-.wplace-theme-acrylic .wplace-move-panel {
- position: fixed;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- background: var(--wplace-primary);
- border: var(--wplace-border-width) var(--wplace-border-style) var(--wplace-border-color);
- border-radius: var(--wplace-radius);
- padding: 24px;
- z-index: 10000;
- backdrop-filter: var(--wplace-backdrop);
- min-width: 240px;
- text-align: center;
- font-family: var(--wplace-font);
- box-shadow: 0 20px 50px rgba(0, 0, 0, 0.15);
-}
-
-.wplace-theme-acrylic .wplace-move-panel-title {
- color: var(--wplace-text);
- margin: 0 0 20px 0;
- font-size: 18px;
- font-weight: 600;
- letter-spacing: 0.5px;
-}
-
-.wplace-theme-acrylic .wplace-move-controls {
- display: grid;
- grid-template-columns: 1fr 1fr 1fr;
- gap: 12px;
- margin-bottom: 20px;
-}
-
-.wplace-theme-acrylic .wplace-move-btn {
- padding: 12px;
- background: rgba(255, 255, 255, 0.2);
- color: var(--wplace-text);
- border: 1px solid rgba(255, 255, 255, 0.3);
- border-radius: 12px;
- cursor: pointer;
- font-family: var(--wplace-font);
- font-size: 16px;
- font-weight: 600;
- transition: all 0.3s ease;
- backdrop-filter: blur(10px);
-}
-
-.wplace-theme-acrylic .wplace-move-btn:hover {
- background: rgba(255, 255, 255, 0.3);
- border-color: rgba(255, 255, 255, 0.5);
- transform: translateY(-2px);
- box-shadow: 0 8px 20px rgba(0, 0, 0, 0.1);
-}
-
-.wplace-theme-acrylic .wplace-move-btn:active {
- transform: translateY(0);
- background: rgba(255, 255, 255, 0.4);
-}
-
-.wplace-theme-acrylic .wplace-move-center {
- color: var(--wplace-text);
- padding: 12px;
- font-size: 14px;
- font-weight: 600;
- display: flex;
- align-items: center;
- justify-content: center;
- opacity: 0.8;
-}
-
-.wplace-theme-acrylic .wplace-move-close-btn {
- background: rgba(255, 59, 48, 0.8);
- color: white;
- border: 1px solid rgba(255, 59, 48, 0.3);
- padding: 12px 20px;
- border-radius: 12px;
- cursor: pointer;
- font-family: var(--wplace-font);
- font-size: 15px;
- font-weight: 600;
- transition: all 0.3s ease;
- backdrop-filter: blur(10px);
-}
-
-.wplace-theme-acrylic .wplace-move-close-btn:hover {
- background: rgba(255, 59, 48, 1);
- transform: translateY(-1px);
- box-shadow: 0 8px 20px rgba(255, 59, 48, 0.3);
-}
-
-.wplace-theme-acrylic .wplace-move-overlay {
- position: fixed;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- background: rgba(0, 0, 0, 0.4);
- z-index: 9999;
- backdrop-filter: blur(8px);
-}
\ No newline at end of file
diff --git a/Extensions/themes/classic-light.css b/Extensions/themes/classic-light.css
deleted file mode 100644
index 5d2ecff..0000000
--- a/Extensions/themes/classic-light.css
+++ /dev/null
@@ -1,877 +0,0 @@
-/* WPlace Auto-Image Bot - Classic Light Theme */
-
-/* Clean, bright theme based on classic design with light backgrounds */
-
-.wplace-theme-classic-light {
- /* === CORE COLORS === */
- --wplace-primary: #E0E0E1; /* Quite white */
- --wplace-secondary: #FBFBFB; /* Light white */
- --wplace-accent: #F3F3F3; /* Darker white */
- --wplace-text: #203C5D; /* Dark blue text */
- --wplace-highlight: #203C5D; /* Dark blue highlight */
- --wplace-success: #28a745; /* Bootstrap green */
- --wplace-error: #dc3545; /* Bootstrap red */
- --wplace-warning: #ffc107; /* Bootstrap yellow */
-
- /* === UI PROPERTIES === */
- --wplace-radius: 8px;
- --wplace-btn-radius: 12px;
- --wplace-border-style: solid;
- --wplace-border-width: 1px;
- --wplace-border-color: #415a77;
- --wplace-shadow: 0 8px 32px rgb(0 0 0 / 15%), 0 0 0 1px rgb(0 0 0 / 8%);
- --wplace-backdrop: blur(10px);
- --wplace-font: 'Segoe UI', roboto, sans-serif;
-
- /* === EXTENDED COLOR PALETTE === */
-
- /* Icon colors */
- --wplace-icon-primary: #4facfe; /* Light blue */
- --wplace-icon-secondary: #00f2fe; /* Cyan */
- --wplace-icon-palette: #003BFF; /* Pink */
-
- /* Additional UI colors */
- --wplace-danger: #dc3545; /* Bootstrap red */
- --wplace-danger-dark: #c82333; /* Darker red */
- --wplace-muted-text: rgb(33 37 41 / 70%);
- --wplace-highlight-secondary: #203C5D;
-
- /* Text variants */
- --wplace-text-secondary: rgb(33 37 41 / 90%);
- --wplace-text-muted: rgb(33 37 41 / 70%);
- --wplace-text-dim: rgb(33 37 41 / 60%);
- --wplace-text-faded: rgb(33 37 41 / 80%);
-
- /* Background variants */
- --wplace-bg-input: #f3f3f3; /* neutral-100 */
- --wplace-bg-subtle: #f3f3f3; /* neutral-100 ā hover */
- --wplace-bg-faint: #e0e0e0; /* neutral-200 */
- --wplace-bg-ghost: #d1d1d1; /* neutral-300 */
- --wplace-bg-whisper: #e0e0e0; /* can be used as whisper on hover */
-
- /* Border variants */
- --wplace-border-subtle: #d1d1d1; /* neutral-300 */
- --wplace-border-faint: #e0e0e0; /* neutral-200 */
- --wplace-border-ghost: #e0e0e0; /* neutral-200 */
- --wplace-border-ultra-faint: #e0e0e0; /* neutral-200 ā very faint */
-
- /* Shadow variants */
- --wplace-shadow-drag: 0 12px 40px rgb(0 0 0 / 20%), 0 0 0 2px rgb(32 60 93 / 30%);
- --wplace-shadow-notification: 0 4px 12px rgb(0 0 0 / 15%);
- --wplace-shadow-slider-thumb: 0 3px 6px rgb(0 0 0 / 20%), 0 0 0 2px var(--wplace-icon-primary);
- --wplace-shadow-slider-hover: 0 4px 8px rgb(0 0 0 / 25%), 0 0 0 3px var(--wplace-icon-primary);
-
- /* Animation colors */
- --wplace-pulse-start: rgb(40 167 69 / 70%);
- --wplace-pulse-mid: rgb(40 167 69 / 0%);
- --wplace-pulse-end: rgb(40 167 69 / 0%);
-
- /* Slider colors */
- --wplace-slider-thumb-bg: #203C5D;
- --wplace-slider-track-bg: linear-gradient(to right, #4facfe 0%, #00f2fe 100%);
-}
-
-/* === COMPONENT STYLING === */
-
-/* Multi-account panel styling */
-.wplace-theme-classic-light #wplace-accounts-container {
- background: linear-gradient(135deg, #fff 0%, #f8f9fa 100%) !important;
- color: var(--wplace-text) !important;
- border: 1px solid rgb(0 0 0 / 15%) !important;
- border-radius: 12px !important;
- box-shadow: 0 8px 32px rgb(0 0 0 / 15%), 0 0 0 1px rgb(0 0 0 / 8%) !important;
- font-family: 'Segoe UI', Roboto, sans-serif !important;
- backdrop-filter: blur(10px) !important;
-}
-
-/* Active account styles */
-.wplace-theme-classic-light .wplace-accounts-container .active-account {
- --active-bg: var(--wplace-secondary) !important;
- --active-color: var(--wplace-success) !important;
- --inactive-color: var(--wplace-text) !important;
- --progress-bg: rgb(0 0 0 / 10%) !important;
- --progress-fill: var(--wplace-highlight) !important;
-}
-
-/* Main container with clean light styling */
-.wplace-theme-classic-light #wplace-image-bot-container {
- background: linear-gradient(135deg, #E0E0E1 0%, #FBFBFB 100%) !important;
- color: var(--wplace-text) !important;
- border: 1px solid rgb(0 0 0 / 15%) !important;
- border-radius: 12px !important;
- box-shadow: 0 8px 32px rgb(0 0 0 / 15%), 0 0 0 1px rgb(0 0 0 / 8%) !important;
- font-family: 'Segoe UI', Roboto, sans-serif !important;
- backdrop-filter: blur(10px) !important;
-}
-
-
-/* Stats container with proper contrast */
-.wplace-theme-classic-light #wplace-stats-container {
- background: linear-gradient(135deg, #FBFBFB 0%, #F3F3F3 100%) !important;
- border: 1px solid rgb(0 0 0 / 20%) !important;
- border-radius: 12px !important;
- box-shadow: 0 8px 32px rgb(0 0 0 / 15%) !important;
- font-family: 'Segoe UI', Roboto, sans-serif !important;
- color: var(--wplace-text) !important;
- position: fixed !important;
- overflow: hidden !important;
- z-index: 9998 !important;
-}
-
-/* Headers with light gradient */
-.wplace-theme-classic-light .wplace-header {
- background: linear-gradient(135deg, #FBFBFB 0%, #F3F3F3 100%) !important;
- color: var(--wplace-highlight) !important;
- border-bottom: 1px solid rgb(0 0 0 / 15%) !important;
- text-shadow: none !important;
-}
-
-/* Stats header styling */
-.wplace-theme-classic-light #wplace-stats-container .wplace-header {
- background: linear-gradient(135deg, #F3F3F3 0%, #E0E0E1 100%) !important;
- color: var(--wplace-text) !important;
- border-bottom: 1px solid rgb(0 0 0 / 20%) !important;
- text-shadow: none !important;
- font-weight: 600 !important;
-}
-
-/* Stats title specific styling */
-.wplace-theme-classic-light #wplace-stats-container .wplace-header .wplace-stats-title {
- color: var(--wplace-text) !important;
- text-shadow: none !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,
-.wplace-theme-classic-light .wplace-section,
-.wplace-theme-classic-light .wplace-controls,
-.wplace-theme-classic-light .wplace-data-management,
-.wplace-theme-classic-light .wplace-cooldown-settings {
- color: var(--wplace-text) !important;
-}
-
-/* Buttons with light styling */
-.wplace-theme-classic-light .wplace-btn {
- background: linear-gradient(135deg, #FBFBFB 0%, #F3F3F3 100%) !important;
- border: 1px solid rgb(0 0 0 / 20%) !important;
- border-radius: 12px !important;
- color: var(--wplace-text) !important;
- font-family: 'Segoe UI', Roboto, sans-serif !important;
- font-weight: 500 !important;
- transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
-}
-
-/* Data management section buttons */
-.wplace-theme-classic-light .wplace-data-management .wplace-btn {
- background: linear-gradient(135deg, #FBFBFB 0%, #F3F3F3 100%) !important;
- border: 1px solid rgb(0 0 0 / 20%) !important;
- color: #203C5D !important;
- font-weight: 500 !important;
-}
-
-.wplace-theme-classic-light .wplace-btn:hover:not(:disabled) {
- background: linear-gradient(135deg, #F3F3F3 0%, #E0E0E1 100%) !important;
- box-shadow: 0 4px 12px rgb(0 0 0 / 15%) !important;
- transform: translateY(-1px) !important;
-}
-
-.wplace-theme-classic-light .wplace-data-management .wplace-btn:hover:not(:disabled) {
- background: linear-gradient(135deg, #F3F3F3 0%, #E0E0E1 100%) !important;
- box-shadow: 0 4px 12px rgb(0 0 0 / 15%) !important;
-}
-
-/* Settings dialog */
-.wplace-theme-classic-light #wplace-settings-container {
- background: linear-gradient(135deg, #E0E0E1 0%, #FBFBFB 100%) !important;
- border: 1px solid rgb(0 0 0 / 15%) !important;
- border-radius: 12px !important;
- box-shadow: 0 16px 48px rgb(0 0 0 / 20%), 0 0 0 1px rgb(0 0 0 / 8%) !important;
- font-family: 'Segoe UI', Roboto, sans-serif !important;
- backdrop-filter: blur(10px) !important;
- color: var(--wplace-text) !important;
-}
-
-/* Settings dialog text elements */
-.wplace-theme-classic-light .wplace-stat-colors-grid * {
- color: var(--wplace-text) !important;
- font-weight: 500 !important;
-}
-
-.wplace-theme-classic-light #wplace-settings-container * {
- color: var(--wplace-text) !important;
-}
-
-.wplace-theme-classic-light #wplace-settings-container .wplace-settings-section-wrapper * {
- color: var(--wplace-text) !important;
-}
-
-/* Color palette text labels */
-.wplace-theme-classic-light .wplace-color-label {
- color: var(--wplace-text) !important;
- font-weight: 500 !important;
- text-shadow: none !important;
-}
-
-.wplace-theme-classic-light .wplace-color-name {
- color: var(--wplace-text) !important;
- font-weight: 500 !important;
-}
-
-.wplace-theme-classic-light .wplace-color-item-name {
- color: #000 !important;
- font-weight: 600 !important;
- text-shadow: none !important;
-}
-
-/* Resize dialog */
-.wplace-theme-classic-light .resize-container {
- background: linear-gradient(135deg, #E0E0E1 0%, #FBFBFB 100%) !important;
- border: 1px solid rgb(0 0 0 / 15%) !important;
- border-radius: 12px !important;
- box-shadow: 0 16px 48px rgb(0 0 0 / 20%), 0 0 0 1px rgb(0 0 0 / 8%) !important;
- font-family: 'Segoe UI', Roboto, sans-serif !important;
- backdrop-filter: blur(10px) !important;
-}
-
-/* Settings header */
-.wplace-theme-classic-light .wplace-settings-header {
- background: linear-gradient(135deg, #FBFBFB 0%, #F3F3F3 100%) !important;
- border-bottom: 1px solid rgb(0 0 0 / 15%) !important;
-}
-
-.wplace-theme-classic-light .wplace-settings-title {
- color: var(--wplace-text) !important;
- text-shadow: none !important;
-}
-
-.wplace-theme-classic-light .wplace-settings-close-btn {
- background: rgb(0 0 0 / 5%) !important;
- border: 1px solid rgb(0 0 0 / 20%) !important;
- border-radius: 50% !important;
- color: var(--wplace-text) !important;
- transition: all 0.3s ease !important;
-}
-
-.wplace-theme-classic-light .wplace-settings-close-btn:hover {
- background: rgb(220 53 69 / 10%) !important;
- border-color: var(--wplace-error) !important;
- box-shadow: 0 0 12px rgb(220 53 69 / 30%) !important;
-}
-
-/* Section titles */
-.wplace-theme-classic-light .wplace-section-title {
- color: var(--wplace-highlight) !important;
- text-shadow: none !important;
- font-weight: 600 !important;
-}
-
-/* Button variants with classic light colors */
-.wplace-theme-classic-light .wplace-btn-start {
- background: linear-gradient(135deg, #28a745 0%, #20c997 100%) !important;
- color: white !important;
- font-weight: 600 !important;
-}
-
-.wplace-theme-classic-light .wplace-btn-start:hover:not(:disabled) {
- box-shadow: 0 4px 12px rgb(40 167 69 / 40%) !important;
-}
-
-.wplace-theme-classic-light .wplace-btn-stop {
- background: linear-gradient(135deg, #dc3545 0%, #c82333 100%) !important;
- color: white !important;
- font-weight: 600 !important;
-}
-
-.wplace-theme-classic-light .wplace-btn-stop:hover:not(:disabled) {
- box-shadow: 0 4px 12px rgb(220 53 69 / 40%) !important;
-}
-
-.wplace-theme-classic-light .wplace-btn-upload {
- background: rgb(32 60 93 / 10%) !important;
- border: 2px dashed var(--wplace-highlight) !important;
- color: var(--wplace-highlight) !important;
-}
-
-.wplace-theme-classic-light .wplace-btn-upload:hover:not(:disabled) {
- background: rgb(32 60 93 / 15%) !important;
- box-shadow: 0 4px 12px rgb(32 60 93 / 20%) !important;
-}
-
-/* Progress bars with clean light styling */
-.wplace-theme-classic-light .wplace-progress {
- background: rgb(0 0 0 / 10%) !important;
- border: 1px solid rgb(0 0 0 / 15%) !important;
- border-radius: 12px !important;
-}
-
-.wplace-theme-classic-light .wplace-progress-bar {
- background: linear-gradient(135deg, #203C5D 0%, #203C5D 100%) !important;
- box-shadow: none !important;
-}
-
-.wplace-theme-classic-light .wplace-progress-bar::after {
- background: linear-gradient(90deg, transparent, rgb(255 255 255 / 30%), transparent) !important;
-}
-
-/* Status indicators with light colors */
-.wplace-theme-classic-light .status-success {
- background: rgb(40 167 69 / 10%) !important;
- border-color: var(--wplace-success) !important;
- color: var(--wplace-success) !important;
- box-shadow: 0 0 15px rgb(40 167 69 / 20%) !important;
- text-shadow: none !important;
-}
-
-.wplace-theme-classic-light .status-error {
- background: rgb(220 53 69 / 10%) !important;
- border-color: var(--wplace-error) !important;
- color: var(--wplace-error) !important;
- box-shadow: 0 0 15px rgb(220 53 69 / 20%) !important;
- text-shadow: none !important;
-}
-
-.wplace-theme-classic-light .status-default {
- background: rgb(32 60 93 / 10%) !important;
- border-color: var(--wplace-highlight) !important;
- color: var(--wplace-highlight) !important;
- text-shadow: none !important;
-}
-
-.wplace-theme-classic-light .wplace-stat-label {
- color: var(--wplace-text) !important;
- text-shadow: none !important;
- font-weight: 500 !important;
-}
-
-.wplace-theme-classic-light .wplace-stat-value {
- color: var(--wplace-highlight) !important;
- text-shadow: none !important;
- font-weight: 600 !important;
-}
-
-/* Sections with light styling */
-.wplace-theme-classic-light .wplace-section {
- background: rgb(0 0 0 / 3%) !important;
- border: 1px solid rgb(0 0 0 / 10%) !important;
- border-radius: 12px !important;
-}
-
-.wplace-theme-classic-light .wplace-status-section {
- background: rgb(0 0 0 / 3%) !important;
- border: 1px solid rgb(0 0 0 / 10%) !important;
- border-radius: 12px !important;
-}
-
-.wplace-theme-classic-light .wplace-settings-section-wrapper {
- background: rgb(0 0 0 / 3%) !important;
- border: 1px solid rgb(0 0 0 / 10%) !important;
- border-radius: 12px !important;
-}
-
-/* Form controls with light styling */
-.wplace-theme-classic-light .wplace-settings-select {
- background: #E0E0E1 !important;
- border: 1px solid rgb(0 0 0 / 20%) !important;
- border-radius: 8px !important;
- color: var(--wplace-text) !important;
- font-family: 'Segoe UI', Roboto, sans-serif !important;
- box-shadow: 0 2px 8px rgb(0 0 0 / 10%) !important;
-}
-
-.wplace-theme-classic-light .wplace-settings-select:focus {
- border-color: var(--wplace-highlight) !important;
- box-shadow: 0 0 0 2px rgb(32 60 93 / 30%) !important;
-}
-
-/* Dropdown menu options */
-.wplace-theme-classic-light .wplace-settings-select option {
- background: #E0E0E1 !important;
- color: var(--wplace-text) !important;
-}
-
-.wplace-theme-classic-light .wplace-settings-option {
- background: #E0E0E1 !important;
- color: var(--wplace-text) !important;
-}
-
-/* Sliders with classic gradient */
-.wplace-theme-classic-light .wplace-speed-slider {
- background: linear-gradient(to right, #4facfe 0%, #32C8FE 100%) !important;
- border-radius: 4px !important;
-}
-
-.wplace-theme-classic-light .wplace-overlay-opacity-slider {
- background: linear-gradient(to right, #4facfe 0%, #32C8FE 100%) !important;
- border-radius: 4px !important;
-}
-
-.wplace-theme-classic-light .wplace-slider {
- background: linear-gradient(to right, #4facfe 0%, #32C8FE 100%) !important;
- 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 rgb(0 0 0 / 20%);
- cursor: pointer;
- transition: all 0.2s ease;
- box-shadow: 0 2px 4px rgb(0 0 0 / 20%);
-}
-
-.wplace-theme-classic-light .wplace-speed-value {
- background: linear-gradient(135deg, #4facfe 0%, #32C8FE 100%) !important;
- border-radius: 8px !important;
- color: white !important;
- font-weight: 600 !important;
- box-shadow: 0 3px 10px rgb(79 172 254 / 30%) !important;
- border: 1px solid rgb(0 0 0 / 10%) !important;
-}
-
-/* Settings labels */
-.wplace-theme-classic-light .wplace-settings-section-label {
- color: var(--wplace-highlight) !important;
- text-shadow: none !important;
- font-family: 'Segoe UI', Roboto, sans-serif !important;
- font-weight: 600 !important;
-}
-
-/* Icon colors for classic light theme */
-.wplace-theme-classic-light .wplace-icon-key { color: #4facfe; }
-.wplace-theme-classic-light .wplace-icon-robot { color: #4facfe; }
-.wplace-theme-classic-light .wplace-icon-speed { color: #4facfe; }
-.wplace-theme-classic-light .wplace-icon-bell { color: #ffc107; }
-.wplace-theme-classic-light .wplace-icon-palette { color: #003BFF; }
-.wplace-theme-classic-light .wplace-icon-globe { color: #ffeaa7; }
-.wplace-theme-classic-light .wplace-icon-paint { color: #4facfe; }
-.wplace-theme-classic-light .wplace-icon-eye { color: #203C5D; }
-
-/* Clean light theme animations */
-@keyframes light-shimmer {
- 0% {
- transform: translateX(-100%);
- }
-
- 100% {
- transform: translateX(200%);
- }
-}
-
-/* Turnstile/CF checkbox overlay - CRITICAL FIX */
-.wplace-theme-classic-light .wplace-turnstile-overlay {
- background: rgb(255 255 255 / 98%) !important;
- border-radius: 12px !important;
- box-shadow: 0 8px 32px rgb(0 0 0 / 30%) !important;
- backdrop-filter: blur(10px) !important;
- border: 1px solid rgb(0 0 0 / 20%) !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 rgb(0 0 0 / 20%) !important;
- border-radius: 6px !important;
- background: rgb(0 0 0 / 5%) !important;
-}
-
-.wplace-theme-classic-light .wplace-turnstile-hide-btn:hover {
- background: rgb(0 0 0 / 10%) !important;
-}
-
-/* Dual control layout styling for classic light theme */
-.wplace-theme-classic-light .wplace-dual-control-compact {
- display: flex;
- flex-direction: row;
- align-items: center;
- gap: 12px;
- margin: 8px 0;
- flex-wrap: wrap;
-}
-
-.wplace-theme-classic-light .wplace-slider-container-compact {
- display: flex;
- align-items: center;
- gap: 8px;
- flex: 1;
- min-width: 160px;
-}
-
-.wplace-theme-classic-light .wplace-speed-slider-container-compact {
- display: flex;
- align-items: center;
- gap: 8px;
- flex: 1;
- min-width: 160px;
-}
-
-.wplace-theme-classic-light .wplace-input-group-compact {
- display: flex;
- align-items: center;
- gap: 4px;
- flex-shrink: 0;
-}
-
-.wplace-theme-classic-light .wplace-input-btn-compact {
- background: linear-gradient(135deg, #4facfe 0%, #32C8FE 100%);
- color: white;
- border: none;
- border-radius: 4px;
- width: 22px;
- height: 22px;
- cursor: pointer;
- font-weight: bold;
- font-size: 12px;
- display: flex;
- align-items: center;
- justify-content: center;
- transition: all 0.2s ease;
- box-shadow: 0 2px 4px rgba(0,0,0,0.2);
-}
-
-.wplace-theme-classic-light .wplace-input-btn-compact:hover {
- transform: translateY(-1px);
- box-shadow: 0 4px 8px rgba(0,0,0,0.3);
- background: linear-gradient(135deg, #32C8FE 0%, #4facfe 100%);
-}
-
-.wplace-theme-classic-light .wplace-input-btn-compact:active {
- transform: translateY(0);
- box-shadow: 0 2px 4px rgba(0,0,0,0.2);
-}
-
-.wplace-theme-classic-light .wplace-number-input-compact {
- background: #E0E0E1;
- border: 1px solid rgb(0 0 0 / 20%);
- border-radius: 4px;
- color: var(--wplace-text);
- padding: 4px 8px;
- font-size: 12px;
- width: 65px;
- text-align: center;
- transition: all 0.2s ease;
- height: 22px;
-}
-
-.wplace-theme-classic-light .wplace-number-input-compact:focus {
- outline: none;
- border-color: #4facfe;
- box-shadow: 0 0 0 3px rgba(79, 172, 254, 0.3);
- background: #FBFBFB;
-}
-
-.wplace-theme-classic-light .wplace-input-label-compact {
- color: var(--wplace-text);
- font-size: 11px;
- margin-left: 4px;
- white-space: nowrap;
-}
-
-.wplace-theme-classic-light .wplace-batch-size-header {
- margin-bottom: 8px;
-}
-
-.wplace-theme-classic-light .wplace-batch-size-label {
- color: var(--wplace-highlight);
- font-weight: 500;
- font-size: 13px;
-}
-
-/* Cooldown control styling for classic light theme */
-.wplace-theme-classic-light .wplace-cooldown-control {
- margin: 12px 0;
-}
-
-.wplace-theme-classic-light .wplace-cooldown-control label {
- color: var(--wplace-highlight);
- font-weight: 500;
- font-size: 13px;
- margin-bottom: 8px;
- display: block;
-}
-
-/* Color swatches with light styling */
-.wplace-theme-classic-light .wplace-color-swatch {
- border: 1px solid rgb(0 0 0 / 20%) !important;
- border-radius: 4px !important;
- box-shadow: 0 2px 8px rgb(0 0 0 / 10%) !important;
-}
-
-.wplace-theme-classic-light .wplace-color-swatch:hover {
- box-shadow: 0 4px 16px rgb(32 60 93 / 30%) !important;
- transform: translateY(-1px);
-}
-
-.wplace-theme-classic-light .wplace-stat-colors-grid {
- background: rgb(0 0 0 / 5%) !important;
- border-radius: 8px !important;
- border: 1px solid rgb(0 0 0 / 10%) !important;
-}
-
-/* Account switching UI styles */
-.wplace-theme-classic-light .wplace-section {
- margin: 15px 0;
- border-radius: var(--wplace-radius);
- background: rgba(0, 0, 0, 0.03);
- padding: 15px;
- border: 1px solid rgba(0, 0, 0, 0.1);
-}
-
-.wplace-theme-classic-light .wplace-section-title {
- display: flex;
- align-items: center;
- gap: 8px;
- font-weight: 600;
- color: var(--wplace-text);
- margin-bottom: 10px;
- font-size: 14px;
-}
-
-/* Auto-swap toggle switch */
-.wplace-theme-classic-light .wplace-switch {
- position: relative;
- display: inline-block;
- width: 50px;
- height: 24px;
-}
-
-.wplace-theme-classic-light .wplace-switch input {
- opacity: 0;
- width: 0;
- height: 0;
-}
-
-.wplace-theme-classic-light .wplace-slider-round {
- position: absolute;
- cursor: pointer;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background-color: #ccc;
- transition: 0.4s;
- border-radius: 24px;
-}
-
-.wplace-theme-classic-light .wplace-slider-round:before {
- position: absolute;
- content: "";
- height: 18px;
- width: 18px;
- left: 3px;
- bottom: 3px;
- background-color: white;
- transition: 0.4s;
- border-radius: 50%;
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
-}
-
-.wplace-theme-classic-light .wplace-switch input:checked + .wplace-slider-round {
- background-color: #4facfe;
-}
-
-.wplace-theme-classic-light .wplace-switch input:checked + .wplace-slider-round:before {
- transform: translateX(26px);
-}
-
-/* Account list container */
-.wplace-theme-classic-light .accounts-list-container {
- max-height: 200px;
- overflow-y: auto;
- margin-top: 10px;
- padding: 0 6px;
-}
-
-/* Individual account item */
-.wplace-theme-classic-light .wplace-account-item {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 6px 8px;
- margin: 4px 0;
- background: rgba(0, 0, 0, 0.02);
- border-radius: 8px;
- border: 1px solid rgba(0, 0, 0, 0.08);
- transition: all 0.3s ease;
- position: relative;
-}
-
-/* Account ordering number */
-.wplace-theme-classic-light .wplace-account-number {
- position: absolute;
- top: 3px;
- left: 3px;
- width: 16px;
- height: 16px;
- background: #4facfe;
- color: white;
- border-radius: 50%;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 9px;
- font-weight: bold;
- z-index: 1;
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
-}
-
-.wplace-theme-classic-light .wplace-account-item:hover {
- background: rgba(0, 0, 0, 0.05);
- border-color: rgba(0, 0, 0, 0.15);
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
-}
-
-.wplace-theme-classic-light .wplace-account-item.is-current {
- background: rgba(79, 172, 254, 0.1);
- border-color: #4facfe;
- box-shadow: 0 0 10px rgba(79, 172, 254, 0.2);
-}
-
-/* Account details */
-.wplace-theme-classic-light .wplace-account-details {
- flex: 1;
- margin-left: 20px;
- min-width: 0;
-}
-
-.wplace-theme-classic-light .wplace-account-name {
- font-weight: 600;
- color: var(--wplace-text);
- font-size: 12px;
- margin-bottom: 1px;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
-}
-
-/* Account statistics */
-.wplace-theme-classic-light .wplace-account-stats {
- display: flex;
- gap: 8px;
- align-items: center;
- font-size: 11px;
- color: rgba(0, 0, 0, 0.6);
- flex-shrink: 0;
-}
-
-.wplace-theme-classic-light .wplace-account-stats i {
- margin-right: 4px;
-}
-
-.wplace-theme-classic-light .wplace-account-stats .fas.fa-bolt {
- color: #ff9800;
-}
-
-.wplace-theme-classic-light .wplace-account-stats .fas.fa-tint {
- color: #2196f3;
-}
-
-/* Move Artwork Panel Styles for Classic Light Theme */
-.wplace-theme-classic-light .wplace-move-panel {
- position: fixed;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- background: var(--wplace-primary);
- border: 2px solid var(--wplace-border-color);
- border-radius: var(--wplace-radius);
- padding: 20px;
- z-index: 10000;
- box-shadow: var(--wplace-shadow);
- min-width: 200px;
- text-align: center;
- backdrop-filter: var(--wplace-backdrop);
- font-family: var(--wplace-font);
-}
-
-.wplace-theme-classic-light .wplace-move-panel-title {
- color: var(--wplace-text);
- margin: 0 0 15px 0;
- font-size: 16px;
- font-weight: 600;
-}
-
-.wplace-theme-classic-light .wplace-move-controls {
- display: grid;
- grid-template-columns: 1fr 1fr 1fr;
- gap: 8px;
- margin-bottom: 15px;
-}
-
-.wplace-theme-classic-light .wplace-move-btn {
- padding: 10px;
- background: linear-gradient(135deg, #4CAF50 0%, #45a049 100%);
- color: white;
- border: 1px solid var(--wplace-border-color);
- border-radius: var(--wplace-btn-radius);
- cursor: pointer;
- font-family: var(--wplace-font);
- font-size: 14px;
- transition: all 0.2s ease;
- box-shadow: 0 2px 4px rgb(0 0 0 / 10%);
-}
-
-.wplace-theme-classic-light .wplace-move-btn:hover {
- background: linear-gradient(135deg, #45a049 0%, #4CAF50 100%);
- transform: translateY(-1px);
- box-shadow: 0 4px 8px rgb(0 0 0 / 15%);
-}
-
-.wplace-theme-classic-light .wplace-move-btn:active {
- transform: translateY(0);
- box-shadow: 0 2px 4px rgb(0 0 0 / 10%);
-}
-
-.wplace-theme-classic-light .wplace-move-center {
- color: var(--wplace-highlight);
- padding: 10px;
- font-size: 12px;
- font-weight: bold;
- display: flex;
- align-items: center;
- justify-content: center;
-}
-
-.wplace-theme-classic-light .wplace-move-close-btn {
- background: linear-gradient(135deg, #f44336 0%, #d32f2f 100%);
- color: white;
- border: 1px solid var(--wplace-border-color);
- padding: 10px 16px;
- border-radius: var(--wplace-btn-radius);
- cursor: pointer;
- font-family: var(--wplace-font);
- transition: all 0.2s ease;
- box-shadow: 0 2px 4px rgb(0 0 0 / 10%);
-}
-
-.wplace-theme-classic-light .wplace-move-close-btn:hover {
- background: linear-gradient(135deg, #d32f2f 0%, #f44336 100%);
- transform: translateY(-1px);
- box-shadow: 0 4px 8px rgb(0 0 0 / 15%);
-}
-
-.wplace-theme-classic-light .wplace-move-overlay {
- position: fixed;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- background: rgba(0, 0, 0, 0.5);
- z-index: 9999;
- backdrop-filter: blur(4px);
-}
-
-
-
-
-
-
diff --git a/Extensions/themes/classic.css b/Extensions/themes/classic.css
deleted file mode 100644
index 8aca765..0000000
--- a/Extensions/themes/classic.css
+++ /dev/null
@@ -1,1028 +0,0 @@
-/* WPlace Auto-Image Bot - Classic Theme */
-/* Clean, modern UI with gradients and subtle effects */
-
-.wplace-theme-classic {
- --wplace-primary: linear-gradient(135deg, #000 0%, #1a1a1a 100%);
- --wplace-secondary: linear-gradient(135deg, #111 0%, #2a2a2a 100%);
- --wplace-accent: #222;
- --wplace-text: #fff;
- --wplace-highlight: #203C5D;
- --wplace-success: #0f0;
- --wplace-error: #f00;
- --wplace-warning: #fa0;
- --wplace-radius: 12px;
- --wplace-btn-radius: 16px;
- --wplace-shadow: 0 8px 32px rgb(0 0 0 / 60%), 0 0 0 1px rgb(255 255 255 / 10%);
- --wplace-backdrop: blur(10px);
- --wplace-font: 'Segoe UI', roboto, sans-serif;
- --wplace-scanline: 0;
- --wplace-pixel-blink: 0;
-
- /* Icon colors */
- --wplace-icon-primary: #4facfe;
- --wplace-icon-secondary: #00f2fe;
- --wplace-icon-palette: #f093fb;
-
- /* Additional UI colors */
- --wplace-danger: #ff6a6a;
- --wplace-danger-dark: #ff4757;
- --wplace-muted-text: #fffB;
- --wplace-highlight-secondary: #203C5D;
-
- /* Slider colors */
- --wplace-slider-track-bg: linear-gradient(to right, #4facfe 0%, #00f2fe 100%);
-}
-
-/* Classic theme container styling */
-/* Multi-account panel styling */
-:root #wplace-accounts-container,
-.wplace-theme-classic #wplace-accounts-container {
- background: var(--wplace-primary);
- color: var(--wplace-text);
- border-radius: var(--wplace-radius);
- box-shadow: var(--wplace-shadow);
- font-family: var(--wplace-font);
- backdrop-filter: var(--wplace-backdrop);
- border: var(--wplace-border-width) var(--wplace-border-style) var(--wplace-border-color);
-}
-
-/* Active account styles */
-:root .wplace-accounts-container .active-account,
-.wplace-theme-classic .wplace-accounts-container .active-account {
- --active-bg: var(--wplace-secondary);
- --active-color: var(--wplace-success);
- --inactive-color: var(--wplace-text);
- --progress-bg: rgb(255 255 255 / 10%);
- --progress-fill: var(--wplace-highlight);
-}
-
-/* Base container styling */
-:root #wplace-image-bot-container,
-.wplace-theme-classic #wplace-image-bot-container {
- background: var(--wplace-primary);
- color: var(--wplace-text);
- border-radius: var(--wplace-radius);
- box-shadow: var(--wplace-shadow);
- font-family: var(--wplace-font);
- backdrop-filter: var(--wplace-backdrop);
- border: var(--wplace-border-width) var(--wplace-border-style) var(--wplace-border-color);
-}
-
-:root #wplace-stats-container,
-.wplace-theme-classic #wplace-stats-container {
- background: var(--wplace-primary);
- color: var(--wplace-text);
- border-radius: var(--wplace-radius);
- box-shadow: var(--wplace-shadow);
- font-family: var(--wplace-font);
- backdrop-filter: var(--wplace-backdrop);
- border: var(--wplace-border-width) var(--wplace-border-style) var(--wplace-border-color);
-}
-
-:root #wplace-settings-container,
-.wplace-theme-classic #wplace-settings-container {
- background: var(--wplace-primary);
- color: var(--wplace-text);
- border-radius: var(--wplace-radius);
- box-shadow: var(--wplace-shadow);
- font-family: var(--wplace-font);
- backdrop-filter: var(--wplace-backdrop);
- border: var(--wplace-border-width) var(--wplace-border-style) var(--wplace-border-color);
-}
-
-:root .wplace-header,
-.wplace-theme-classic .wplace-header {
- background: var(--wplace-secondary);
- color: var(--wplace-highlight);
- text-shadow: 0 1px 2px rgb(0 0 0 / 50%);
-}
-
-:root .wplace-section-title,
-.wplace-theme-classic .wplace-section-title {
- color: var(--wplace-highlight);
-}
-
-:root .wplace-stat-value,
-.wplace-theme-classic .wplace-stat-value {
- color: var(--wplace-highlight);
-}
-
-:root .wplace-status-section,
-.wplace-theme-classic .wplace-status-section {
- border-radius: var(--wplace-radius);
- background: rgb(255 255 255 / 3%);
- border: 1px solid rgb(255 255 255 / 10%);
-}
-
-:root .wplace-section,
-.wplace-theme-classic .wplace-section {
- border-radius: var(--wplace-radius);
- background: rgb(255 255 255 / 3%);
-}
-
-:root .wplace-stats,
-.wplace-theme-classic .wplace-stats {
- border-radius: var(--wplace-radius);
- background: rgb(255 255 255 / 3%);
- border: 1px solid rgb(255 255 255 / 10%);
-}
-
-:root .wplace-status,
-.wplace-theme-classic .wplace-status {
- border-radius: var(--wplace-radius);
-}
-
-:root .wplace-alert-base,
-.wplace-theme-classic .wplace-alert-base {
- border-radius: var(--wplace-radius);
- font-family: var(--wplace-font);
-}
-
-:root .wplace-settings-container-base,
-.wplace-theme-classic .wplace-settings-container-base {
- font-family: var(--wplace-font);
- border-radius: var(--wplace-radius);
- background: var(--wplace-primary);
- color: var(--wplace-text);
- box-shadow: var(--wplace-shadow);
- backdrop-filter: var(--wplace-backdrop);
-}
-
-/* Button styling for classic theme */
-:root .wplace-btn,
-.wplace-theme-classic .wplace-btn {
- background: linear-gradient(135deg, #222 0%, #4a4a4a 100%);
- border: 1px solid rgb(255 255 255 / 10%);
- color: var(--wplace-text);
- border-radius: var(--wplace-btn-radius);
- font-family: var(--wplace-font);
-}
-
-:root .wplace-btn::before,
-.wplace-theme-classic .wplace-btn::before {
- background: linear-gradient(90deg, transparent, rgb(255 255 255 / 10%), transparent);
-}
-
-:root .wplace-btn:disabled,
-.wplace-theme-classic .wplace-btn:disabled {
- box-shadow: none !important;
-}
-
-:root .wplace-btn:hover:not(:disabled),
-.wplace-theme-classic .wplace-btn:hover:not(:disabled) {
- box-shadow: 0 4px 12px rgb(0 0 0 / 40%);
-}
-
-:root .wplace-btn-overlay.active,
-.wplace-theme-classic .wplace-btn-overlay.active {
- background: linear-gradient(135deg, #29b6f6 0%, #8e2de2 100%);
- box-shadow: 0 0 15px #8e2de2;
-}
-
-/* Button variants - exact upstream main colors */
-:root .wplace-btn-primary,
-.wplace-theme-classic .wplace-btn-primary {
- background: linear-gradient(135deg, #222 0%, #6a5acd 100%);
- color: var(--wplace-text);
-}
-
-:root .wplace-btn-upload,
-.wplace-theme-classic .wplace-btn-upload {
- background: linear-gradient(135deg, #111 0%, #4a4a4a 100%);
- color: var(--wplace-text);
- border: 1px dashed var(--wplace-highlight) !important;
-}
-
-:root .wplace-btn-start,
-.wplace-theme-classic .wplace-btn-start {
- background: linear-gradient(135deg, var(--wplace-success) 0%, #228b22 100%);
- color: white;
-}
-
-:root .wplace-btn-stop,
-.wplace-theme-classic .wplace-btn-stop {
- background: linear-gradient(135deg, var(--wplace-error) 0%, #dc143c 100%);
- color: white;
-}
-
-:root .wplace-btn-select,
-.wplace-theme-classic .wplace-btn-select {
- background: linear-gradient(135deg, var(--wplace-highlight) 0%, #9370db 100%);
- color: white;
-}
-
-:root .wplace-btn-file,
-.wplace-theme-classic .wplace-btn-file {
- background: linear-gradient(135deg, #ff8c00 0%, #ff7f50 100%);
- color: white;
-}
-
-:root .wplace-btn.active,
-:root .wplace-btn[aria-pressed="true"],
-.wplace-theme-classic .wplace-btn.active,
-.wplace-theme-classic .wplace-btn[aria-pressed="true"] {
- background: var(--wplace-highlight) !important;
- color: #000 !important;
- border-color: var(--wplace-text) !important;
- box-shadow: 0 0 8px rgb(0 0 0 / 25%) inset, 0 0 6px rgb(0 0 0 / 20%) !important;
-}
-
-:root .wplace-btn.active i,
-:root .wplace-btn[aria-pressed="true"] i,
-.wplace-theme-classic .wplace-btn.active i,
-.wplace-theme-classic .wplace-btn[aria-pressed="true"] i {
- filter: drop-shadow(0 0 3px #000);
-}
-
-:root .mask-mode-group .wplace-btn.active,
-:root .mask-mode-group .wplace-btn[aria-pressed="true"],
-.wplace-theme-classic .mask-mode-group .wplace-btn.active,
-.wplace-theme-classic .mask-mode-group .wplace-btn[aria-pressed="true"] {
- background: var(--wplace-highlight);
- color: #000;
- border-color: var(--wplace-text);
- box-shadow: 0 0 8px rgb(0 0 0 / 25%) inset, 0 0 6px rgb(0 0 0 / 20%);
-}
-
-/* Status styling for classic theme */
-:root .status-default,
-.wplace-theme-classic .status-default {
- background: rgb(255 255 255 / 10%);
- border-color: var(--wplace-text);
- color: var(--wplace-text);
-}
-
-:root .status-success,
-.wplace-theme-classic .status-success {
- background: rgb(0 255 0 / 10%);
- border-color: var(--wplace-success);
- color: var(--wplace-success);
- box-shadow: 0 0 15px var(--wplace-success);
-}
-
-:root .status-error,
-.wplace-theme-classic .status-error {
- background: rgb(255 0 0 / 10%);
- border-color: var(--wplace-error);
- color: var(--wplace-error);
- box-shadow: 0 0 15px var(--wplace-error);
-}
-
-:root .status-warning,
-.wplace-theme-classic .status-warning {
- background: rgb(255 165 0 / 10%);
- border-color: var(--wplace-warning);
- color: var(--wplace-warning);
- box-shadow: 0 0 15px var(--wplace-warning);
-}
-
-:root .wplace-status.status-default,
-.wplace-theme-classic .wplace-status.status-default {
- color: var(--wplace-text);
- background: rgb(255 255 255 / 10%);
- border-color: var(--wplace-text);
-}
-
-/* Alert styling for classic theme */
-:root .wplace-alert-info,
-.wplace-theme-classic .wplace-alert-info {
- background: linear-gradient(135deg, #3498db, #2980b9);
- box-shadow: 0 4px 12px rgb(52 152 219 / 30%);
-}
-
-:root .wplace-alert-success,
-.wplace-theme-classic .wplace-alert-success {
- background: linear-gradient(135deg, #27ae60, #229954);
- box-shadow: 0 4px 12px rgb(39 174 96 / 30%);
-}
-
-:root .wplace-alert-warning,
-.wplace-theme-classic .wplace-alert-warning {
- background: linear-gradient(135deg, #f39c12, #e67e22);
- box-shadow: 0 4px 12px rgb(243 156 18 / 30%);
-}
-
-:root .wplace-alert-error,
-.wplace-theme-classic .wplace-alert-error {
- background: linear-gradient(135deg, #e74c3c, #c0392b);
- box-shadow: 0 4px 12px rgb(231 76 60 / 30%);
-}
-
-/* Progress bar styling for classic theme */
-:root .wplace-progress,
-.wplace-theme-classic .wplace-progress {
- background: rgb(0 0 0 / 30%);
- border: 1px solid rgb(255 255 255 / 10%);
- border-radius: var(--wplace-radius);
-}
-
-:root .wplace-progress-bar,
-.wplace-theme-classic .wplace-progress-bar {
- background: linear-gradient(135deg, var(--wplace-highlight) 0%, #9370db 100%);
-}
-
-:root .wplace-progress-bar::after,
-.wplace-theme-classic .wplace-progress-bar::after {
- background: linear-gradient(90deg, transparent, rgb(255 255 255 / 30%), transparent);
-}
-
-/* Header and sections styling for classic theme */
-:root .wplace-header-btn,
-.wplace-theme-classic .wplace-header-btn {
- background: rgb(255 255 255 / 10%);
- color: var(--wplace-highlight);
- border-radius: 4px;
- font-family: var(--wplace-font);
-}
-
-:root .wplace-header-btn:hover,
-.wplace-theme-classic .wplace-header-btn:hover {
- background: #222;
- color: var(--wplace-text);
-}
-
-/* Font and typography for classic theme */
-:root .wplace-turnstile-overlay,
-.wplace-theme-classic .wplace-turnstile-overlay {
- background: rgb(0 0 0 / 90%) !important;
- border-radius: var(--wplace-radius) !important;
- box-shadow: 0 8px 32px rgb(0 0 0 / 40%) !important;
- backdrop-filter: var(--wplace-backdrop) !important;
- border: 1px solid rgb(255 255 255 / 20%) !important;
- color: var(--wplace-text) !important;
- font-family: var(--wplace-font) !important;
-}
-
-/* Auto light/dark support for classic theme */
-@media (prefers-color-scheme: light) {
- :root .theme-auto,
- .wplace-theme-classic .theme-auto {
- --wplace-primary: #fff;
- --wplace-secondary: #f5f5f5;
- --wplace-accent: #007acc;
- --wplace-text: #203C5D;
- }
-}
-
-@media (prefers-color-scheme: dark) {
- :root .theme-auto,
- .wplace-theme-classic .theme-auto {
- --wplace-primary: #1e1e1e;
- --wplace-secondary: #203C5D;
- --wplace-accent: var(--wplace-highlight);
- --wplace-text: #fff;
- }
-}
-
-/* Border and color styling for classic theme */
-:root .wplace-color-item-name,
-.wplace-theme-classic .wplace-color-item-name {
- color: #ccc;
-}
-
-:root .wplace-color-swatch,
-.wplace-theme-classic .wplace-color-swatch {
- border: 1px solid rgb(255 255 255 / 20%);
- border-radius: 4px;
-}
-
-:root .wplace-stat-color-swatch,
-.wplace-theme-classic .wplace-stat-color-swatch {
- border-radius: 3px;
- border: 1px solid rgb(255 255 255 / 10%);
- box-shadow: inset 0 0 2px rgb(0 0 0 / 50%);
-}
-
-:root .resize-tools button,
-.wplace-theme-classic .resize-tools button {
- border-radius: 6px;
- border: 1px solid rgb(255 255 255 / 20%);
- background: rgb(255 255 255 / 6%);
- color: #fff;
-}
-
-:root .resize-slider,
-.wplace-theme-classic .resize-slider {
- background: #ccc;
- border-radius: var(--wplace-radius);
-}
-
-/* Text effects and filters for classic theme */
-:root .wplace-color-swatch:not(.active),
-.wplace-theme-classic .wplace-color-swatch:not(.active) {
- filter: grayscale(80%);
-}
-
-:root .wplace-color-swatch.unavailable,
-.wplace-theme-classic .wplace-color-swatch.unavailable {
- border-color: #666;
-}
-
-:root .wplace-color-swatch.unavailable:not(.active),
-.wplace-theme-classic .wplace-color-swatch.unavailable:not(.active) {
- filter: grayscale(90%);
-}
-
-:root .wplace-color-swatch.active::after,
-.wplace-theme-classic .wplace-color-swatch.active::after {
- text-shadow: 0 0 3px black;
-}
-
-/* Icon colors for classic theme */
-:root .wplace-icon-key,
-.wplace-theme-classic .wplace-icon-key {
- color: #4facfe;
-}
-
-:root .wplace-icon-robot,
-.wplace-theme-classic .wplace-icon-robot {
- color: #4facfe;
-}
-
-:root .wplace-icon-speed,
-.wplace-theme-classic .wplace-icon-speed {
- color: #4facfe;
-}
-
-:root .wplace-icon-bell,
-.wplace-theme-classic .wplace-icon-bell {
- color: #ffd166;
-}
-
-:root .wplace-icon-palette,
-.wplace-theme-classic .wplace-icon-palette {
- color: #f093fb;
-}
-
-:root .wplace-icon-globe,
-.wplace-theme-classic .wplace-icon-globe {
- color: #ffeaa7;
-}
-
-:root .wplace-icon-paint,
-.wplace-theme-classic .wplace-icon-paint {
- color: #4facfe;
-}
-
-:root .wplace-icon-eye,
-.wplace-theme-classic .wplace-icon-eye {
- color: var(--wplace-highlight);
-}
-
-/* Form controls and sliders for classic theme */
-:root .wplace-slider,
-.wplace-theme-classic .wplace-slider {
- background: linear-gradient(to right, #4facfe 0%, #00f2fe 100%);
- border-radius: 4px;
-}
-
-: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,
-.wplace-theme-classic .wplace-speed-slider {
- background: linear-gradient(to right, #4facfe 0%, #00f2fe 100%);
- border-radius: 4px;
-}
-
-:root .wplace-speed-slider::-webkit-slider-thumb,
-.wplace-theme-classic .wplace-speed-slider::-webkit-slider-thumb {
- border-radius: 50%;
- border: 2px solid;
-}
-
-:root .wplace-speed-slider::-moz-range-thumb,
-.wplace-theme-classic .wplace-speed-slider::-moz-range-thumb {
- border-radius: 50%;
- border: 2px solid;
-}
-
-:root .wplace-turnstile-title,
-.wplace-theme-classic .wplace-turnstile-title {
- color: var(--wplace-text) !important;
-}
-
-:root .wplace-turnstile-hide-btn,
-.wplace-theme-classic .wplace-turnstile-hide-btn {
- color: var(--wplace-text) !important;
- border: 1px solid rgb(255 255 255 / 20%) !important;
- border-radius: 6px !important;
-}
-
-:root .wplace-turnstile-hide-btn:hover,
-.wplace-theme-classic .wplace-turnstile-hide-btn:hover {
- background: rgb(255 255 255 / 10%) !important;
-}
-
-:root .wplace-settings-select,
-.wplace-theme-classic .wplace-settings-select {
- background: rgb(255 255 255 / 15%);
- color: white;
- border: 1px solid rgb(255 255 255 / 20%);
- border-radius: 8px;
- box-shadow: 0 3px 10px rgb(0 0 0 / 10%);
-}
-
-:root .wplace-settings-option,
-.wplace-theme-classic .wplace-settings-option {
- background: #2d3748;
- color: white;
-}
-
-:root .wplace-stat-colors-grid,
-.wplace-theme-classic .wplace-stat-colors-grid {
- background: rgb(0 0 0 / 20%);
- border-radius: 4px;
-}
-
-:root .resize-overlay,
-.wplace-theme-classic .resize-overlay {
- background: rgb(0 0 0 / 80%);
-}
-
-:root .wplace-overlay,
-.wplace-theme-classic .wplace-overlay {
- background: rgb(0 0 0 / 80%);
-}
-
-:root .wplace-settings-error,
-.wplace-theme-classic .wplace-settings-error {
- background: rgb(255 0 0 / 40%) !important;
-}
-
-/* Settings sections and batch controls for classic theme */
-:root .wplace-settings-section-wrapper,
-.wplace-theme-classic .wplace-settings-section-wrapper {
- background: rgb(255 255 255 / 10%);
- border-radius: var(--wplace-radius);
- border: 1px solid rgb(255 255 255 / 10%);
-}
-
-:root .wplace-batch-controls,
-.wplace-theme-classic .wplace-batch-controls {
- background: rgb(255 255 255 / 10%);
- border-radius: var(--wplace-radius);
- border: 1px solid rgb(255 255 255 / 10%);
-}
-
-:root .wplace-overlay-opacity-slider,
-.wplace-theme-classic .wplace-overlay-opacity-slider {
- background: linear-gradient(to right, #4facfe 0%, #00f2fe 100%);
- border-radius: 4px;
-}
-
-:root .wplace-speed-value,
-.wplace-theme-classic .wplace-speed-value {
- background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
- border-radius: 8px;
- color: white;
- box-shadow: 0 3px 10px rgb(79 172 254 / 30%);
- border: 1px solid rgb(255 255 255 / 20%);
-}
-
-/* Additional elements for classic theme */
-:root .wplace-color-divider,
-.wplace-theme-classic .wplace-color-divider {
- background: rgb(255 255 255 / 10%);
-}
-
-:root .wplace-settings-select option,
-.wplace-theme-classic .wplace-settings-select option {
- background: #2d3748;
- color: white;
-}
-
-:root .wplace-settings-description,
-.wplace-theme-classic .wplace-settings-description {
- color: rgb(255 255 255 / 70%);
-}
-
-:root .wplace-settings-apply-btn,
-.wplace-theme-classic .wplace-settings-apply-btn {
- background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
- color: white;
- border-radius: 8px;
-}
-
-/* Dual control layout styling for classic theme */
-:root .wplace-dual-control-compact,
-.wplace-theme-classic .wplace-dual-control-compact {
- display: flex;
- flex-direction: row;
- align-items: center;
- gap: 12px;
- margin: 8px 0;
- flex-wrap: wrap;
-}
-
-:root .wplace-slider-container-compact,
-.wplace-theme-classic .wplace-slider-container-compact {
- display: flex;
- align-items: center;
- gap: 8px;
- flex: 1;
- min-width: 160px;
-}
-
-:root .wplace-speed-slider-container-compact,
-.wplace-theme-classic .wplace-speed-slider-container-compact {
- display: flex;
- align-items: center;
- gap: 8px;
- flex: 1;
- min-width: 160px;
-}
-
-:root .wplace-input-group-compact,
-.wplace-theme-classic .wplace-input-group-compact {
- display: flex;
- align-items: center;
- gap: 4px;
- flex-shrink: 0;
-}
-
-:root .wplace-input-btn-compact,
-.wplace-theme-classic .wplace-input-btn-compact {
- background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
- color: white;
- border: none;
- border-radius: 4px;
- width: 22px;
- height: 22px;
- cursor: pointer;
- font-weight: bold;
- font-size: 12px;
- display: flex;
- align-items: center;
- justify-content: center;
- transition: all 0.2s ease;
- box-shadow: 0 2px 4px rgba(0,0,0,0.2);
-}
-
-:root .wplace-input-btn-compact:hover,
-.wplace-theme-classic .wplace-input-btn-compact:hover {
- transform: translateY(-1px);
- box-shadow: 0 4px 8px rgba(0,0,0,0.3);
- background: linear-gradient(135deg, #00f2fe 0%, #4facfe 100%);
-}
-
-:root .wplace-input-btn-compact:active,
-.wplace-theme-classic .wplace-input-btn-compact:active {
- transform: translateY(0);
- box-shadow: 0 2px 4px rgba(0,0,0,0.2);
-}
-
-:root .wplace-number-input-compact,
-.wplace-theme-classic .wplace-number-input-compact {
- background: rgba(255,255,255,0.1);
- border: 1px solid rgba(255,255,255,0.2);
- border-radius: 4px;
- color: white;
- padding: 4px 8px;
- font-size: 12px;
- width: 65px;
- text-align: center;
- transition: all 0.2s ease;
- height: 22px;
-}
-
-:root .wplace-number-input-compact:focus,
-.wplace-theme-classic .wplace-number-input-compact:focus {
- outline: none;
- border-color: #4facfe;
- box-shadow: 0 0 0 3px rgba(79, 172, 254, 0.3);
- background: rgba(255,255,255,0.15);
-}
-
-:root .wplace-input-label-compact,
-.wplace-theme-classic .wplace-input-label-compact {
- color: rgba(255,255,255,0.8);
- font-size: 11px;
- margin-left: 4px;
- white-space: nowrap;
-}
-
-:root .wplace-batch-size-header,
-.wplace-theme-classic .wplace-batch-size-header {
- margin-bottom: 8px;
-}
-
-:root .wplace-batch-size-label,
-.wplace-theme-classic .wplace-batch-size-label {
- color: var(--wplace-highlight);
- font-weight: 500;
- font-size: 13px;
-}
-
-/* Cooldown control styling for classic theme */
-:root .wplace-cooldown-control,
-.wplace-theme-classic .wplace-cooldown-control {
- margin: 12px 0;
-}
-
-:root .wplace-cooldown-control label,
-.wplace-theme-classic .wplace-cooldown-control label {
- color: var(--wplace-highlight);
- font-weight: 500;
- font-size: 13px;
- margin-bottom: 8px;
- display: block;
-}
-
-/* Resize dialog styling for classic theme */
-:root .resize-container,
-.wplace-theme-classic .resize-container {
- background: #000;
- border: 1px solid #fff;
- border-radius: 12px;
- box-shadow: 0 0 20px rgb(0 0 0 / 50%);
-}
-
-/* Account switching UI styles */
-:root .wplace-section,
-.wplace-theme-classic .wplace-section {
- margin: 15px 0;
- border-radius: var(--wplace-radius);
- background: rgba(255, 255, 255, 0.05);
- padding: 15px;
- border: 1px solid rgba(255, 255, 255, 0.1);
-}
-
-:root .wplace-section-title,
-.wplace-theme-classic .wplace-section-title {
- display: flex;
- align-items: center;
- gap: 8px;
- font-weight: 600;
- color: var(--wplace-text);
- margin-bottom: 10px;
- font-size: 14px;
-}
-
-/* Auto-swap toggle switch */
-:root .wplace-switch,
-.wplace-theme-classic .wplace-switch {
- position: relative;
- display: inline-block;
- width: 50px;
- height: 24px;
-}
-
-:root .wplace-switch input,
-.wplace-theme-classic .wplace-switch input {
- opacity: 0;
- width: 0;
- height: 0;
-}
-
-:root .wplace-slider-round,
-.wplace-theme-classic .wplace-slider-round {
- position: absolute;
- cursor: pointer;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background-color: #444;
- transition: 0.4s;
- border-radius: 24px;
-}
-
-:root .wplace-slider-round:before,
-.wplace-theme-classic .wplace-slider-round:before {
- position: absolute;
- content: "";
- height: 18px;
- width: 18px;
- left: 3px;
- bottom: 3px;
- background-color: white;
- transition: 0.4s;
- border-radius: 50%;
-}
-
-:root .wplace-switch input:checked + .wplace-slider-round,
-.wplace-theme-classic .wplace-switch input:checked + .wplace-slider-round {
- background: var(--wplace-slider-track-bg);
-}
-
-:root .wplace-switch input:checked + .wplace-slider-round:before,
-.wplace-theme-classic .wplace-switch input:checked + .wplace-slider-round:before {
- transform: translateX(26px);
-}
-
-/* Account list container */
-:root .accounts-list-container,
-.wplace-theme-classic .accounts-list-container {
- max-height: 200px;
- overflow-y: auto;
- margin-top: 10px;
- padding: 0 6px;
-}
-
-/* Individual account item */
-:root .wplace-account-item,
-.wplace-theme-classic .wplace-account-item {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 6px 8px;
- margin: 4px 0;
- background: rgba(255, 255, 255, 0.03);
- border-radius: 8px;
- border: 1px solid rgba(255, 255, 255, 0.1);
- transition: all 0.3s ease;
- position: relative;
-}
-
-/* Account ordering number */
-:root .wplace-account-number,
-.wplace-theme-classic .wplace-account-number {
- position: absolute;
- top: 3px;
- left: 3px;
- width: 16px;
- height: 16px;
- background: var(--wplace-highlight);
- color: white;
- border-radius: 50%;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 9px;
- font-weight: bold;
- z-index: 1;
-}
-
-:root .wplace-account-item:hover,
-.wplace-theme-classic .wplace-account-item:hover {
- background: rgba(255, 255, 255, 0.08);
- border-color: var(--wplace-highlight);
-}
-
-:root .wplace-account-item.is-current,
-.wplace-theme-classic .wplace-account-item.is-current {
- background: rgba(79, 172, 254, 0.2);
- border-color: #4facfe;
- box-shadow: 0 0 10px rgba(79, 172, 254, 0.3);
-}
-
-/* Account details */
-:root .wplace-account-details,
-.wplace-theme-classic .wplace-account-details {
- flex: 1;
- margin-left: 20px;
- min-width: 0;
-}
-
-:root .wplace-account-name,
-.wplace-theme-classic .wplace-account-name {
- font-weight: 600;
- color: var(--wplace-text);
- font-size: 12px;
- margin-bottom: 1px;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
-}
-
-/* Account statistics */
-:root .wplace-account-stats,
-.wplace-theme-classic .wplace-account-stats {
- display: flex;
- gap: 8px;
- align-items: center;
- font-size: 11px;
- color: var(--wplace-muted-text);
- flex-shrink: 0;
-}
-
-:root .wplace-account-stats i,
-.wplace-theme-classic .wplace-account-stats i {
- margin-right: 4px;
-}
-
-:root .wplace-account-stats .fas.fa-bolt,
-.wplace-theme-classic .wplace-account-stats .fas.fa-bolt {
- color: #ffd700;
-}
-
-:root .wplace-account-stats .fas.fa-tint,
-.wplace-theme-classic .wplace-account-stats .fas.fa-tint {
- color: #00bfff;
-}
-
-/* Move Artwork Panel Styles for Classic Theme */
-:root .wplace-move-panel,
-.wplace-theme-classic .wplace-move-panel {
- position: fixed;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- background: var(--wplace-primary);
- border: 2px solid rgb(255 255 255 / 20%);
- border-radius: var(--wplace-radius);
- padding: 20px;
- z-index: 10000;
- box-shadow: var(--wplace-shadow);
- min-width: 200px;
- text-align: center;
- backdrop-filter: var(--wplace-backdrop);
- font-family: var(--wplace-font);
-}
-
-:root .wplace-move-panel-title,
-.wplace-theme-classic .wplace-move-panel-title {
- color: var(--wplace-text);
- margin: 0 0 15px 0;
- font-size: 16px;
- text-shadow: 0 1px 2px rgb(0 0 0 / 50%);
-}
-
-:root .wplace-move-controls,
-.wplace-theme-classic .wplace-move-controls {
- display: grid;
- grid-template-columns: 1fr 1fr 1fr;
- gap: 8px;
- margin-bottom: 15px;
-}
-
-:root .wplace-move-btn,
-.wplace-theme-classic .wplace-move-btn {
- padding: 10px;
- background: linear-gradient(135deg, #4CAF50 0%, #45a049 100%);
- color: white;
- border: 1px solid rgb(255 255 255 / 20%);
- border-radius: var(--wplace-btn-radius);
- cursor: pointer;
- font-family: var(--wplace-font);
- font-size: 14px;
- transition: all 0.2s ease;
- box-shadow: 0 2px 4px rgb(0 0 0 / 20%);
-}
-
-:root .wplace-move-btn:hover,
-.wplace-theme-classic .wplace-move-btn:hover {
- background: linear-gradient(135deg, #45a049 0%, #4CAF50 100%);
- transform: translateY(-1px);
- box-shadow: 0 4px 8px rgb(0 0 0 / 30%);
-}
-
-:root .wplace-move-btn:active,
-.wplace-theme-classic .wplace-move-btn:active {
- transform: translateY(0);
- box-shadow: 0 2px 4px rgb(0 0 0 / 20%);
-}
-
-:root .wplace-move-center,
-.wplace-theme-classic .wplace-move-center {
- color: var(--wplace-highlight);
- padding: 10px;
- font-size: 12px;
- font-weight: bold;
- display: flex;
- align-items: center;
- justify-content: center;
-}
-
-:root .wplace-move-close-btn,
-.wplace-theme-classic .wplace-move-close-btn {
- background: linear-gradient(135deg, #f44336 0%, #d32f2f 100%);
- color: white;
- border: 1px solid rgb(255 255 255 / 20%);
- padding: 10px 16px;
- border-radius: var(--wplace-btn-radius);
- cursor: pointer;
- font-family: var(--wplace-font);
- transition: all 0.2s ease;
- box-shadow: 0 2px 4px rgb(0 0 0 / 20%);
-}
-
-:root .wplace-move-close-btn:hover,
-.wplace-theme-classic .wplace-move-close-btn:hover {
- background: linear-gradient(135deg, #d32f2f 0%, #f44336 100%);
- transform: translateY(-1px);
- box-shadow: 0 4px 8px rgb(0 0 0 / 30%);
-}
-
-:root .wplace-move-overlay,
-.wplace-theme-classic .wplace-move-overlay {
- position: fixed;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- background: rgba(0, 0, 0, 0.8);
- z-index: 9999;
- backdrop-filter: blur(4px);
-}
-
diff --git a/Extensions/themes/neon-cyan.css b/Extensions/themes/neon-cyan.css
deleted file mode 100644
index 6f6a155..0000000
--- a/Extensions/themes/neon-cyan.css
+++ /dev/null
@@ -1,815 +0,0 @@
-/* WPlace Auto-Image Bot - Neon Cyan Theme */
-/* Retro cyberpunk aesthetic with cyan neon glow effects */
-
-.wplace-theme-neon-cyan {
- /* Neon theme colors - matching upstream main neon theme */
- --wplace-primary: #1959A1;
- --wplace-secondary: #3C74AF;
- --wplace-accent: #538CC0;
- --wplace-text: #81DCF7;
- --wplace-highlight: #EA9C00;
- --wplace-success: #39ff14;
- --wplace-error: #ff073a;
- --wplace-warning: #ff0;
-
- /* Neon UI properties */
- --wplace-radius: 0;
- --wplace-border-style: solid;
- --wplace-border-width: 2px;
- --wplace-border-color: #81DCF7;
- --wplace-shadow: 0 0 20px rgb(234 156 0 / 40%), inset 0 0 20px rgb(234 156 0 / 10%);
- --wplace-backdrop: none;
- --wplace-font: 'Press Start 2P', monospace, 'Courier New';
-
- /* Z-index layers */
- --wplace-z-overlay: 10000;
- --wplace-z-alert: 10002;
- --wplace-z-settings: 10002;
-
- /* Feature toggles */
- --wplace-scanline: 1;
- --wplace-pixel-blink: 1;
-
- /* Icon colors - neon variants */
- --wplace-icon-primary: #81DCF7;
- --wplace-icon-secondary: #39ff14;
- --wplace-icon-palette: #EA9C00;
-
- /* Additional UI colors - neon variants */
- --wplace-danger: #ff073a;
- --wplace-danger-dark: #cc0531;
- --wplace-muted-text: #00ff4180;
- --wplace-highlight-secondary: #fa0;
-
- /* Text variants - neon style */
- --wplace-text-secondary: #3C74AF;
- --wplace-text-muted: #00ff41;
- --wplace-text-dim: #00ff41;
- --wplace-text-faded: #00ff41cc;
-
- /* Background variants - neon style */
- --wplace-bg-input: rgb(234 156 0 / 15%);
- --wplace-bg-subtle: rgb(234 156 0 / 8%);
- --wplace-bg-faint: rgb(234 156 0 / 5%);
- --wplace-bg-ghost: rgb(234 156 0 / 3%);
- --wplace-bg-whisper: rgb(234 156 0 / 2%);
-
- /* Border variants - neon style */
- --wplace-border-subtle: rgb(234 156 0 / 40%);
- --wplace-border-faint: rgb(234 156 0 / 25%);
- --wplace-border-ghost: rgb(234 156 0 / 15%);
- --wplace-border-ultra-faint: rgb(234 156 0 / 8%);
-
- /* Shadow variants - neon style */
- --wplace-shadow-drag: 0 12px 40px rgb(234 156 0 / 60%), 0 0 0 2px rgb(234 156 0 / 80%), 0 0 20px rgb(234 156 0 / 30%);
- --wplace-shadow-notification: 0 4px 12px rgb(234 156 0 / 40%), 0 0 15px rgb(234 156 0 / 20%);
- --wplace-shadow-slider-thumb: 0 3px 6px rgb(234 156 0 / 50%), 0 0 0 2px var(--wplace-icon-primary), 0 0 10px rgb(234 156 0 / 30%);
- --wplace-shadow-slider-hover: 0 4px 8px rgb(234 156 0 / 60%), 0 0 0 3px var(--wplace-icon-primary), 0 0 15px rgb(234 156 0 / 40%);
-
- /* Animation colors - neon style */
- --wplace-pulse-start: rgb(234 156 0 / 80%);
- --wplace-pulse-mid: rgb(234 156 0 / 0%);
- --wplace-pulse-end: rgb(234 156 0 / 0%);
-
- /* Slider colors - neon style */
- --wplace-slider-thumb-bg: #81DCF7;
-}
-
-/* Neon-specific styling overrides */
-.wplace-theme-neon-cyan #wplace-image-bot-container {
- background: var(--wplace-primary) !important;
- border: 2px solid var(--wplace-text) !important;
- border-radius: 0 !important;
- box-shadow: 0 0 20px var(--wplace-text), inset 0 0 20px rgb(234 156 0 / 10%) !important;
- font-family: var(--wplace-font) !important;
-}
-
-.wplace-theme-neon-cyan #wplace-stats-container {
- background: var(--wplace-primary) !important;
- border: 2px solid var(--wplace-text) !important;
- border-radius: 0 !important;
- box-shadow: 0 0 20px var(--wplace-text), inset 0 0 20px rgb(234 156 0 / 10%) !important;
- font-family: var(--wplace-font) !important;
- position: fixed !important;
- overflow: hidden !important;
- z-index: 9998 !important;
-}
-
-.wplace-theme-neon-cyan .wplace-header {
- background: var(--wplace-secondary) !important;
- border-bottom: 1px solid var(--wplace-text) !important;
- color: var(--wplace-text) !important;
- text-shadow: 0 0 10px var(--wplace-text) !important;
- font-family: var(--wplace-font) !important;
-}
-
-.wplace-theme-neon-cyan .wplace-section {
- background: rgb(22 33 62 / 50%) !important;
- border: 1px solid var(--wplace-text) !important;
- border-radius: 0 !important;
-}
-
-.wplace-theme-neon-cyan .wplace-status-section {
- background: rgb(22 33 62 / 50%) !important;
- border: 1px solid var(--wplace-text) !important;
- border-radius: 0 !important;
-}
-
-.wplace-theme-neon-cyan .wplace-section-title {
- color: var(--wplace-text) !important;
- text-shadow: 0 0 8px var(--wplace-text) !important;
- text-transform: uppercase !important;
- font-family: var(--wplace-font) !important;
- font-size: 10px !important;
-}
-
-.wplace-theme-neon-cyan .wplace-btn {
- background: var(--wplace-secondary) !important;
- border: 1px solid var(--wplace-text) !important;
- border-radius: 0 !important;
- color: var(--wplace-text) !important;
- text-shadow: 0 0 5px var(--wplace-text) !important;
- font-family: var(--wplace-font) !important;
- font-size: 9px !important;
- text-transform: uppercase !important;
-}
-
-/* Pixel blinking and neon glow animation for buttons */
-.wplace-theme-neon-cyan .wplace-btn:hover:not(:disabled) {
- box-shadow: 0 0 15px var(--wplace-text), inset 0 0 15px rgb(234 156 0 / 10%) !important;
- animation: pixel-blink 0.5s infinite, neon-glow 1s ease-in-out infinite alternate !important;
-}
-
-.wplace-theme-neon-cyan .wplace-btn-start {
- background: var(--wplace-secondary) !important;
- border-color: var(--wplace-success) !important;
- color: var(--wplace-success) !important;
- text-shadow: 0 0 8px var(--wplace-success) !important;
-}
-
-.wplace-theme-neon-cyan .wplace-btn-stop {
- background: var(--wplace-secondary) !important;
- border-color: var(--wplace-error) !important;
- color: var(--wplace-error) !important;
- text-shadow: 0 0 8px var(--wplace-error) !important;
-}
-
-.wplace-theme-neon-cyan .wplace-btn-upload {
- background: var(--wplace-secondary) !important;
- border: 1px dashed var(--wplace-highlight) !important;
- color: var(--wplace-highlight) !important;
- text-shadow: 0 0 8px var(--wplace-highlight) !important;
-}
-
-.wplace-theme-neon-cyan .wplace-btn-select {
- background: var(--wplace-secondary) !important;
- border-color: var(--wplace-highlight) !important;
- color: var(--wplace-highlight) !important;
- text-shadow: 0 0 8px var(--wplace-highlight) !important;
-}
-
-.wplace-theme-neon-cyan .wplace-btn-file {
- background: var(--wplace-secondary) !important;
- border-color: var(--wplace-warning) !important;
- color: var(--wplace-warning) !important;
- text-shadow: 0 0 8px var(--wplace-warning) !important;
-}
-
-.wplace-theme-neon-cyan .wplace-progress {
- background: rgb(0 0 0 / 80%) !important;
- border: 1px solid var(--wplace-text) !important;
- border-radius: 0 !important;
-}
-
-.wplace-theme-neon-cyan .wplace-progress-bar {
- background: linear-gradient(90deg, var(--wplace-success) 0%, var(--wplace-text) 100%) !important;
- box-shadow: 0 0 10px var(--wplace-success) !important;
-}
-
-.wplace-theme-neon-cyan .wplace-stat-value {
- color: var(--wplace-text) !important;
- text-shadow: 0 0 5px var(--wplace-text) !important;
-}
-
-.wplace-theme-neon-cyan .status-success {
- background: rgb(57 255 20 / 10%) !important;
- border-color: var(--wplace-success) !important;
- color: var(--wplace-success) !important;
- box-shadow: 0 0 15px var(--wplace-success) !important;
- text-shadow: 0 0 8px var(--wplace-success) !important;
-}
-
-.wplace-theme-neon-cyan .status-error {
- background: rgb(255 7 58 / 10%) !important;
- border-color: var(--wplace-error) !important;
- color: var(--wplace-error) !important;
- box-shadow: 0 0 15px var(--wplace-error) !important;
- text-shadow: 0 0 8px var(--wplace-error) !important;
-}
-
-.wplace-theme-neon-cyan .status-default {
- background: rgb(234 156 0 / 10%) !important;
- border-color: var(--wplace-text) !important;
- color: var(--wplace-text) !important;
- text-shadow: 0 0 5px var(--wplace-text) !important;
-}
-
-/* Settings dialog neon styling */
-.wplace-theme-neon-cyan #wplace-settings-container {
- background: var(--wplace-primary) !important;
- border: 2px solid var(--wplace-text) !important;
- border-radius: 0 !important;
- box-shadow: 0 0 30px var(--wplace-text), inset 0 0 30px rgb(234 156 0 / 10%) !important;
- font-family: var(--wplace-font) !important;
-}
-
-.wplace-theme-neon-cyan .wplace-settings-header {
- background: var(--wplace-secondary) !important;
- border-bottom: 1px solid var(--wplace-text) !important;
-}
-
-.wplace-theme-neon-cyan .wplace-settings-header h3 {
- color: var(--wplace-text) !important;
- text-shadow: 0 0 10px var(--wplace-text) !important;
- font-family: var(--wplace-font) !important;
- font-size: 16px !important;
- text-transform: uppercase !important;
-}
-
-.wplace-theme-neon-cyan .wplace-settings-close-btn {
- background: var(--wplace-secondary) !important;
- border: 1px solid var(--wplace-text) !important;
- border-radius: 0 !important;
- color: var(--wplace-text) !important;
-}
-
-.wplace-theme-neon-cyan .wplace-settings-close-btn:hover {
- background: var(--wplace-error) !important;
- box-shadow: 0 0 15px var(--wplace-error) !important;
-}
-
-.wplace-theme-neon-cyan .wplace-settings-section-wrapper {
- background: rgb(22 33 62 / 30%) !important;
- border: 1px solid var(--wplace-text) !important;
- border-radius: 0 !important;
-}
-
-.wplace-theme-neon-cyan .wplace-settings-select {
- background: var(--wplace-secondary) !important;
- border: 1px solid var(--wplace-text) !important;
- border-radius: 0 !important;
- color: var(--wplace-text) !important;
- font-family: var(--wplace-font) !important;
- font-size: 11px !important;
-}
-
-.wplace-theme-neon-cyan .wplace-settings-section-label {
- color: var(--wplace-text) !important;
- text-shadow: 0 0 8px var(--wplace-text) !important;
- font-family: var(--wplace-font) !important;
- font-size: 12px !important;
- text-transform: uppercase !important;
-}
-
-.wplace-theme-neon-cyan .wplace-speed-value {
- background: var(--wplace-secondary) !important;
- border: 1px solid var(--wplace-text) !important;
- border-radius: 0 !important;
- color: var(--wplace-text) !important;
- text-shadow: 0 0 8px var(--wplace-text) !important;
- font-family: var(--wplace-font) !important;
- box-shadow: 0 0 10px rgb(234 156 0 / 30%) !important;
-}
-
-.wplace-theme-neon-cyan .wplace-overlay-opacity-value {
- background: var(--wplace-secondary) !important;
- border: 1px solid var(--wplace-text) !important;
- border-radius: 0 !important;
- color: var(--wplace-text) !important;
- text-shadow: 0 0 5px var(--wplace-text) !important;
-}
-
-/* Neon slider styling */
-.wplace-theme-neon-cyan .wplace-slider,
-.wplace-theme-neon-cyan .wplace-speed-slider,
-.wplace-theme-neon-cyan .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 rgb(234 156 0 / 30%) !important;
-}
-
-.wplace-theme-neon-cyan .wplace-slider::-webkit-slider-thumb,
-.wplace-theme-neon-cyan .wplace-speed-slider::-webkit-slider-thumb,
-.wplace-theme-neon-cyan .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 rgb(234 156 0 / 50%) !important;
-}
-
-.wplace-theme-neon-cyan .wplace-slider::-webkit-slider-thumb:hover,
-.wplace-theme-neon-cyan .wplace-speed-slider::-webkit-slider-thumb:hover,
-.wplace-theme-neon-cyan .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-cyan #wplace-image-bot-container::before {
- content: '';
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- height: 2px;
- background: linear-gradient(90deg, transparent, var(--wplace-text), transparent);
- z-index: 1;
- pointer-events: none;
- animation: scanline 3s linear infinite;
- opacity: 0.7;
-}
-
-
-.wplace-theme-neon-cyan #wplace-stats-container::before {
- content: '';
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- height: 2px;
- background: linear-gradient(90deg, transparent, var(--wplace-text), transparent);
- z-index: 1;
- pointer-events: none;
- animation: scanline-stats 4s linear infinite;
- opacity: 0.7;
-}
-
-
-/* Text glow animations */
-@keyframes neon-glow {
- 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 pixel-blink {
- 0%, 50% {
- opacity: 1;
- }
-
- 51%, 100% {
- opacity: 0.7;
- }
-}
-
-@keyframes scanline {
- 0% {
- transform: translateY(-100%);
- }
-
- 100% {
- transform: translateY(400px);
- }
-}
-
-@keyframes scanline-stats {
- 0% {
- transform: translateY(-100%);
- }
-
- 100% {
- transform: translateY(300px);
- }
-}
-
-/* Dual control layout styling for neon theme */
-.wplace-theme-neon-cyan .wplace-dual-control-compact {
- display: flex;
- flex-direction: row;
- align-items: center;
- gap: 8px;
- margin: 6px 0;
- flex-wrap: wrap;
-}
-
-.wplace-theme-neon-cyan .wplace-slider-container-compact {
- display: flex;
- align-items: center;
- gap: 6px;
- flex: 1;
- min-width: 160px;
-}
-
-.wplace-theme-neon-cyan .wplace-speed-slider-container-compact {
- display: flex;
- align-items: center;
- gap: 6px;
- flex: 1;
- min-width: 160px;
-}
-
-.wplace-theme-neon-cyan .wplace-input-group-compact {
- display: flex;
- align-items: center;
- gap: 3px;
- flex-shrink: 0;
-}
-
-.wplace-theme-neon-cyan .wplace-input-btn-compact {
- background: var(--wplace-secondary) !important;
- border: 1px solid var(--wplace-text) !important;
- border-radius: 0 !important;
- color: var(--wplace-text) !important;
- width: 18px;
- height: 18px;
- cursor: pointer;
- font-weight: bold;
- font-size: 10px;
- display: flex;
- align-items: center;
- justify-content: center;
- transition: all 0.2s ease;
- text-shadow: 0 0 5px var(--wplace-text) !important;
- font-family: var(--wplace-font) !important;
-}
-
-.wplace-theme-neon-cyan .wplace-input-btn-compact:hover {
- box-shadow: 0 0 10px var(--wplace-text) !important;
- animation: pixel-blink 0.5s infinite !important;
-}
-
-.wplace-theme-neon-cyan .wplace-number-input-compact {
- background: var(--wplace-secondary) !important;
- border: 1px solid var(--wplace-text) !important;
- border-radius: 0 !important;
- color: var(--wplace-text) !important;
- padding: 2px 4px;
- font-size: 10px;
- width: 65px;
- text-align: center;
- transition: all 0.2s ease;
- height: 18px;
- font-family: var(--wplace-font) !important;
- text-shadow: 0 0 5px var(--wplace-text) !important;
-}
-
-.wplace-theme-neon-cyan .wplace-number-input-compact:focus {
- outline: none;
- box-shadow: 0 0 10px var(--wplace-text) !important;
- background: rgb(234 156 0 / 15%) !important;
-}
-
-.wplace-theme-neon-cyan .wplace-input-label-compact {
- color: var(--wplace-text) !important;
- font-size: 9px;
- margin-left: 3px;
- white-space: nowrap;
- text-shadow: 0 0 5px var(--wplace-text) !important;
- font-family: var(--wplace-font) !important;
- text-transform: uppercase;
-}
-
-.wplace-theme-neon-cyan .wplace-batch-size-header {
- margin-bottom: 6px;
-}
-
-.wplace-theme-neon-cyan .wplace-batch-size-label {
- color: var(--wplace-text) !important;
- font-weight: 500;
- font-size: 11px;
- text-shadow: 0 0 8px var(--wplace-text) !important;
- font-family: var(--wplace-font) !important;
- text-transform: uppercase;
-}
-
-/* Cooldown control styling for neon theme */
-.wplace-theme-neon-cyan .wplace-cooldown-control {
- margin: 8px 0;
-}
-
-.wplace-theme-neon-cyan .wplace-cooldown-control label {
- color: var(--wplace-text) !important;
- font-weight: 500;
- font-size: 11px;
- text-shadow: 0 0 8px var(--wplace-text) !important;
- font-family: var(--wplace-font) !important;
- text-transform: uppercase;
- margin-bottom: 6px;
- display: block;
-}
-
-/* Resize dialog styling for neon theme */
-.wplace-theme-neon-cyan .resize-container {
- background-color: #1959A1 !important; /* Solid neon blue */
- opacity: 1 !important; /* Fully opaque */
- backdrop-filter: none !important; /* Remove blur effects */
- box-shadow: none !important; /* Optional: remove glows */
- font-family: 'Press Start 2P', monospace !important;
-}
-
-/* Account switching UI styles */
-.wplace-theme-neon-cyan .wplace-section {
- margin: 15px 0;
- border-radius: 0;
- background: rgba(0, 255, 255, 0.05);
- padding: 15px;
- border: 2px solid #00ffff;
- box-shadow: 0 0 20px rgba(0, 255, 255, 0.3);
- font-family: var(--wplace-font);
-}
-
-.wplace-theme-neon-cyan .wplace-section-title {
- display: flex;
- align-items: center;
- gap: 8px;
- font-weight: 600;
- color: var(--wplace-text);
- margin-bottom: 10px;
- font-size: 12px;
- text-transform: uppercase;
- text-shadow: 0 0 8px var(--wplace-text);
- font-family: var(--wplace-font);
-}
-
-/* Auto-swap toggle switch */
-.wplace-theme-neon-cyan .wplace-switch {
- position: relative;
- display: inline-block;
- width: 50px;
- height: 24px;
-}
-
-.wplace-theme-neon-cyan .wplace-switch input {
- opacity: 0;
- width: 0;
- height: 0;
-}
-
-.wplace-theme-neon-cyan .wplace-slider-round {
- position: absolute;
- cursor: pointer;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background-color: #1a1a2e;
- transition: 0.4s;
- border-radius: 0;
- border: 2px solid #333;
- box-shadow: 0 0 10px rgba(0, 255, 255, 0.2);
-}
-
-.wplace-theme-neon-cyan .wplace-slider-round:before {
- position: absolute;
- content: "";
- height: 14px;
- width: 14px;
- left: 3px;
- bottom: 3px;
- background-color: #fff;
- transition: 0.4s;
- border-radius: 0;
- box-shadow: 0 0 8px rgba(255, 255, 255, 0.5);
-}
-
-.wplace-theme-neon-cyan .wplace-switch input:checked + .wplace-slider-round {
- background-color: #00ffff;
- box-shadow: 0 0 20px rgba(0, 255, 255, 0.6);
-}
-
-.wplace-theme-neon-cyan .wplace-switch input:checked + .wplace-slider-round:before {
- transform: translateX(26px);
- box-shadow: 0 0 12px rgba(0, 255, 255, 0.8);
-}
-
-/* Account list container */
-.wplace-theme-neon-cyan .accounts-list-container {
- max-height: 200px;
- overflow-y: auto;
- margin-top: 10px;
- padding: 0 6px;
-}
-
-/* Individual account item */
-.wplace-theme-neon-cyan .wplace-account-item {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 6px 8px;
- margin: 4px 0;
- background: rgba(0, 255, 255, 0.03);
- border-radius: 0;
- border: 1px solid #00ffff;
- transition: all 0.3s ease;
- box-shadow: 0 0 5px rgba(0, 255, 255, 0.2);
- position: relative;
-}
-
-/* Account ordering number */
-.wplace-theme-neon-cyan .wplace-account-number {
- position: absolute;
- top: 3px;
- left: 3px;
- width: 8x;
- height: 8px;
- background: #00ffff;
- color: #1a1a2e;
- border-radius: 0;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 8px;
- font-weight: bold;
- z-index: 1;
- border: 1px solid #00ffff;
- box-shadow: 0 0 8px rgba(0, 255, 255, 0.6);
- font-family: var(--wplace-font);
-}
-
-.wplace-theme-neon-cyan .wplace-account-item:hover {
- background: rgba(0, 255, 255, 0.08);
- border-color: #00ffff;
- box-shadow: 0 0 15px rgba(0, 255, 255, 0.4);
- transform: translateX(2px);
-}
-
-.wplace-theme-neon-cyan .wplace-account-item.is-current {
- background: rgba(0, 255, 255, 0.15);
- border-color: #00ffff;
- box-shadow: 0 0 25px rgba(0, 255, 255, 0.6);
- animation: neon-cyan-pulse 2s infinite;
-}
-
-@keyframes neon-cyan-pulse {
- 0%, 100% { box-shadow: 0 0 25px rgba(0, 255, 255, 0.6); }
- 50% { box-shadow: 0 0 35px rgba(0, 255, 255, 0.8); }
-}
-
-/* Account details */
-.wplace-theme-neon-cyan .wplace-account-details {
- flex: 1;
- margin-left: 20px;
- min-width: 0;
-}
-
-.wplace-theme-neon-cyan .wplace-account-name {
- font-weight: 600;
- color: var(--wplace-text);
- font-size: 8px;
- margin-bottom: 1px;
- text-transform: uppercase;
- text-shadow: 0 0 5px var(--wplace-text);
- font-family: var(--wplace-font);
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
-}
-
-/* Account statistics */
-.wplace-theme-neon-cyan .wplace-account-stats {
- display: flex;
- gap: 8px;
- align-items: center;
- font-size: 8px;
- color: rgba(0, 255, 255, 0.8);
- text-transform: uppercase;
- font-family: var(--wplace-font);
- flex-shrink: 0;
-}
-
-.wplace-theme-neon-cyan .wplace-account-stats i {
- margin-right: 4px;
- text-shadow: 0 0 5px currentColor;
-}
-
-.wplace-theme-neon-cyan .wplace-account-stats .fas.fa-bolt {
- color: #ffff00;
- text-shadow: 0 0 8px #ffff00;
-}
-
-.wplace-theme-neon-cyan .wplace-account-stats .fas.fa-tint {
- color: #00ffff;
- text-shadow: 0 0 8px #00ffff;
-}
-
-/* Move Artwork Panel Styles for Neon Cyan Theme */
-.wplace-theme-neon-cyan .wplace-move-panel {
- position: fixed;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- background: var(--wplace-primary) !important;
- border: 2px solid var(--wplace-text) !important;
- border-radius: 0 !important;
- padding: 20px;
- z-index: 10000;
- box-shadow: 0 0 20px var(--wplace-text), inset 0 0 20px rgba(129, 220, 247, 0.1) !important;
- min-width: 200px;
- text-align: center;
- font-family: var(--wplace-font) !important;
- color: var(--wplace-text) !important;
-}
-
-.wplace-theme-neon-cyan .wplace-move-panel-title {
- color: var(--wplace-text) !important;
- margin: 0 0 15px 0;
- font-size: 12px !important;
- text-transform: uppercase;
- text-shadow: 0 0 10px var(--wplace-text) !important;
- font-family: var(--wplace-font) !important;
- animation: cyan-pulse 2s infinite;
-}
-
-.wplace-theme-neon-cyan .wplace-move-controls {
- display: grid;
- grid-template-columns: 1fr 1fr 1fr;
- gap: 4px;
- margin-bottom: 15px;
-}
-
-.wplace-theme-neon-cyan .wplace-move-btn {
- padding: 8px !important;
- background: var(--wplace-secondary) !important;
- color: var(--wplace-text) !important;
- border: 1px solid var(--wplace-text) !important;
- border-radius: 0 !important;
- cursor: pointer;
- font-family: var(--wplace-font) !important;
- font-size: 10px !important;
- text-shadow: 0 0 5px var(--wplace-text) !important;
- transition: all 0.2s ease;
- text-transform: uppercase;
-}
-
-.wplace-theme-neon-cyan .wplace-move-btn:hover {
- background: var(--wplace-accent) !important;
- box-shadow: 0 0 15px var(--wplace-text) !important;
- animation: cyan-flicker 0.5s infinite;
-}
-
-.wplace-theme-neon-cyan .wplace-move-center {
- color: var(--wplace-highlight) !important;
- padding: 8px;
- font-size: 9px !important;
- font-weight: bold;
- display: flex;
- align-items: center;
- justify-content: center;
- text-shadow: 0 0 5px var(--wplace-highlight) !important;
- text-transform: uppercase;
- font-family: var(--wplace-font) !important;
-}
-
-.wplace-theme-neon-cyan .wplace-move-close-btn {
- background: var(--wplace-error) !important;
- color: white !important;
- border: 1px solid var(--wplace-error) !important;
- padding: 8px 12px;
- border-radius: 0 !important;
- cursor: pointer;
- font-family: var(--wplace-font) !important;
- font-size: 9px !important;
- text-transform: uppercase;
- text-shadow: 0 0 5px white !important;
- transition: all 0.2s ease;
-}
-
-.wplace-theme-neon-cyan .wplace-move-close-btn:hover {
- box-shadow: 0 0 15px var(--wplace-error) !important;
- animation: error-pulse 0.5s infinite;
-}
-
-.wplace-theme-neon-cyan .wplace-move-overlay {
- position: fixed;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- background: rgba(25, 89, 161, 0.9) !important;
- z-index: 9999;
-}
-
-@keyframes cyan-pulse {
- 0%, 100% { text-shadow: 0 0 5px var(--wplace-text), 0 0 10px var(--wplace-text); }
- 50% { text-shadow: 0 0 10px var(--wplace-text), 0 0 20px var(--wplace-text); }
-}
-
-@keyframes cyan-flicker {
- 0%, 50% { opacity: 1; }
- 51%, 100% { opacity: 0.8; }
-}
-
-@keyframes error-pulse {
- 0%, 100% { box-shadow: 0 0 5px var(--wplace-error); }
- 50% { box-shadow: 0 0 15px var(--wplace-error); }
-}
diff --git a/Extensions/themes/neon-light.css b/Extensions/themes/neon-light.css
deleted file mode 100644
index fbc2316..0000000
--- a/Extensions/themes/neon-light.css
+++ /dev/null
@@ -1,881 +0,0 @@
-/* WPlace Auto-Image Bot - Neon Light Theme */
-/* Retro cyberpunk aesthetic with light neon glow effects */
-
-.wplace-theme-neon-light {
- /* Neon theme colors - matching upstream main neon theme */
- --wplace-primary: #E0E0E1;
- --wplace-secondary: #FBFBFB;
- --wplace-accent: #203C5D;
- --wplace-text: #203C5D;
- --wplace-highlight: #203C5D;
- --wplace-success: #39ff14;
- --wplace-error: #ff073a;
- --wplace-warning: #ff0;
-
- /* Neon UI properties */
- --wplace-radius: 0;
- --wplace-border-style: solid;
- --wplace-border-width: 2px;
- --wplace-border-color: #203C5D;
- --wplace-shadow: 0 0 20px rgb(234 156 0 / 40%), inset 0 0 20px rgb(234 156 0 / 10%);
- --wplace-backdrop: none;
- --wplace-font: 'Press Start 2P', monospace, 'Courier New';
-
- /* Z-index layers */
- --wplace-z-overlay: 10000;
- --wplace-z-alert: 10002;
- --wplace-z-settings: 10002;
-
- /* Feature toggles */
- --wplace-scanline: 1;
- --wplace-pixel-blink: 1;
-
- /* Icon colors - neon variants */
- --wplace-icon-primary: #203C5D;
- --wplace-icon-secondary: #39ff14;
- --wplace-icon-palette: #203C5D;
-
- /* Additional UI colors - neon variants */
- --wplace-danger: #ff073a;
- --wplace-danger-dark: #cc0531;
- --wplace-muted-text: #00ff4180;
- --wplace-highlight-secondary: #fa0;
-
- /* Text variants - neon style */
- --wplace-text-secondary: #3C74AF;
- --wplace-text-muted: #00ff41;
- --wplace-text-dim: #00ff41;
- --wplace-text-faded: #00ff41cc;
-
- /* Background variants - neon style */
- --wplace-bg-input: rgb(234 156 0 / 15%);
- --wplace-bg-subtle: rgb(234 156 0 / 8%);
- --wplace-bg-faint: rgb(234 156 0 / 5%);
- --wplace-bg-ghost: rgb(234 156 0 / 3%);
- --wplace-bg-whisper: rgb(234 156 0 / 2%);
-
- /* Border variants - neon style */
- --wplace-border-subtle: rgb(234 156 0 / 40%);
- --wplace-border-faint: rgb(234 156 0 / 25%);
- --wplace-border-ghost: rgb(234 156 0 / 15%);
- --wplace-border-ultra-faint: rgb(234 156 0 / 8%);
-
- /* Shadow variants - neon style */
- --wplace-shadow-drag: 0 12px 40px rgb(234 156 0 / 60%), 0 0 0 2px rgb(234 156 0 / 80%), 0 0 20px rgb(234 156 0 / 30%);
- --wplace-shadow-notification: 0 4px 12px rgb(234 156 0 / 40%), 0 0 15px rgb(234 156 0 / 20%);
- --wplace-shadow-slider-thumb: 0 3px 6px rgb(234 156 0 / 50%), 0 0 0 2px var(--wplace-icon-primary), 0 0 10px rgb(234 156 0 / 30%);
- --wplace-shadow-slider-hover: 0 4px 8px rgb(234 156 0 / 60%), 0 0 0 3px var(--wplace-icon-primary), 0 0 15px rgb(234 156 0 / 40%);
-
- /* Animation colors - neon style */
- --wplace-pulse-start: rgb(234 156 0 / 80%);
- --wplace-pulse-mid: rgb(234 156 0 / 0%);
- --wplace-pulse-end: rgb(234 156 0 / 0%);
-
- /* Slider colors - neon style */
- --wplace-slider-thumb-bg: #203C5D;
-}
-
-/* Neon-specific styling overrides */
-.wplace-theme-neon-light #wplace-image-bot-container {
- background: var(--wplace-primary) !important;
- border: 2px solid var(--wplace-text) !important;
- border-radius: 0 !important;
- box-shadow: 0 0 20px var(--wplace-text), inset 0 0 20px rgb(234 156 0 / 10%) !important;
- font-family: var(--wplace-font) !important;
-}
-
-.wplace-theme-neon-light #wplace-stats-container {
- background: var(--wplace-primary) !important;
- border: 2px solid var(--wplace-text) !important;
- border-radius: 0 !important;
- box-shadow: 0 0 20px var(--wplace-text), inset 0 0 20px rgb(234 156 0 / 10%) !important;
- font-family: var(--wplace-font) !important;
- position: fixed !important;
- overflow: hidden !important;
- z-index: 9998 !important;
-}
-
-.wplace-theme-neon-light .wplace-header {
- background: var(--wplace-secondary) !important;
- border-bottom: 1px solid var(--wplace-text) !important;
- color: var(--wplace-text) !important;
- text-shadow: 0 0 10px var(--wplace-text) !important;
- font-family: var(--wplace-font) !important;
-}
-
-.wplace-theme-neon-light .wplace-section {
- background: rgb(22 33 62 / 30%) !important;
- border: 1px solid var(--wplace-text) !important;
- border-radius: 0 !important;
-}
-
-.wplace-theme-neon-light .wplace-status-section {
- background: rgb(22 33 62 / 30%) !important;
- border: 1px solid var(--wplace-text) !important;
- border-radius: 0 !important;
-}
-
-.wplace-theme-neon-light .wplace-section-title {
- color: var(--wplace-text) !important;
- text-shadow: 0 0 8px var(--wplace-text) !important;
- text-transform: uppercase !important;
- font-family: var(--wplace-font) !important;
- font-size: 10px !important;
-}
-
-.wplace-theme-neon-light .wplace-btn {
- background: var(--wplace-secondary) !important;
- border: 1px solid var(--wplace-text) !important;
- border-radius: 0 !important;
- color: var(--wplace-text) !important;
- text-shadow: 0 0 5px var(--wplace-text) !important;
- font-family: var(--wplace-font) !important;
- font-size: 9px !important;
- text-transform: uppercase !important;
-}
-
-/* Pixel blinking and neon glow animation for buttons */
-.wplace-theme-neon-light .wplace-btn:hover:not(:disabled) {
- box-shadow: 0 0 15px var(--wplace-text), inset 0 0 15px rgb(234 156 0 / 10%) !important;
- animation: pixel-blink 0.5s infinite, neon-glow 1s ease-in-out infinite alternate !important;
-}
-
-.wplace-theme-neon-light .wplace-btn-start {
- background: var(--wplace-secondary) !important;
- border-color: var(--wplace-success) !important;
- color: var(--wplace-success) !important;
- text-shadow: 0 0 8px var(--wplace-success) !important;
-}
-
-.wplace-theme-neon-light .wplace-btn-stop {
- background: var(--wplace-secondary) !important;
- border-color: var(--wplace-error) !important;
- color: var(--wplace-error) !important;
- text-shadow: 0 0 8px var(--wplace-error) !important;
-}
-
-.wplace-theme-neon-light .wplace-btn-upload {
- background: var(--wplace-secondary) !important;
- border: 1px dashed var(--wplace-highlight) !important;
- color: var(--wplace-highlight) !important;
- text-shadow: 0 0 8px var(--wplace-highlight) !important;
-}
-
-.wplace-theme-neon-light .wplace-btn-select {
- background: var(--wplace-secondary) !important;
- border-color: var(--wplace-highlight) !important;
- color: var(--wplace-highlight) !important;
- text-shadow: 0 0 8px var(--wplace-highlight) !important;
-}
-
-.wplace-theme-neon-light .wplace-btn-file {
- background: var(--wplace-secondary) !important;
- border-color: var(--wplace-warning) !important;
- color: var(--wplace-warning) !important;
- text-shadow: 0 0 8px var(--wplace-warning) !important;
-}
-
-.wplace-theme-neon-light .wplace-progress {
- background: rgb(0 0 0 / 80%) !important;
- border: 1px solid var(--wplace-text) !important;
- border-radius: 0 !important;
-}
-
-.wplace-theme-neon-light .wplace-progress-bar {
- background: linear-gradient(90deg, var(--wplace-success) 0%, var(--wplace-text) 100%) !important;
- box-shadow: 0 0 10px var(--wplace-success) !important;
-}
-
-.wplace-theme-neon-light .wplace-stat-value {
- color: var(--wplace-text) !important;
- text-shadow: 0 0 5px var(--wplace-text) !important;
-}
-
-.wplace-theme-neon-light .status-success {
- background: rgb(57 255 20 / 10%) !important;
- border-color: var(--wplace-success) !important;
- color: var(--wplace-success) !important;
- box-shadow: 0 0 15px var(--wplace-success) !important;
- text-shadow: 0 0 8px var(--wplace-success) !important;
-}
-
-.wplace-theme-neon-light .status-error {
- background: rgb(255 7 58 / 10%) !important;
- border-color: var(--wplace-error) !important;
- color: var(--wplace-error) !important;
- box-shadow: 0 0 15px var(--wplace-error) !important;
- text-shadow: 0 0 8px var(--wplace-error) !important;
-}
-
-.wplace-theme-neon-light .status-default {
- background: rgb(234 156 0 / 10%) !important;
- border-color: var(--wplace-text) !important;
- color: var(--wplace-text) !important;
- text-shadow: 0 0 5px var(--wplace-text) !important;
-}
-
-/* Settings dialog neon styling */
-.wplace-theme-neon-light #wplace-settings-container {
- background: var(--wplace-primary) !important;
- border: 2px solid var(--wplace-text) !important;
- border-radius: 0 !important;
- box-shadow: 0 0 30px var(--wplace-text), inset 0 0 30px rgb(234 156 0 / 10%) !important;
- font-family: var(--wplace-font) !important;
-}
-
-.wplace-theme-neon-light .wplace-settings-header {
- background: var(--wplace-secondary) !important;
- border-bottom: 1px solid var(--wplace-text) !important;
-}
-
-.wplace-theme-neon-light .wplace-settings-header h3 {
- color: var(--wplace-text) !important;
- text-shadow: 0 0 10px var(--wplace-text) !important;
- font-family: var(--wplace-font) !important;
- font-size: 16px !important;
- text-transform: uppercase !important;
-}
-
-.wplace-theme-neon-light .wplace-settings-close-btn {
- background: var(--wplace-secondary) !important;
- border: 1px solid var(--wplace-text) !important;
- border-radius: 0 !important;
- color: var(--wplace-text) !important;
-}
-
-.wplace-theme-neon-light .wplace-settings-close-btn:hover {
- background: var(--wplace-error) !important;
- box-shadow: 0 0 15px var(--wplace-error) !important;
-}
-
-.wplace-theme-neon-light .wplace-settings-section-wrapper {
- background: rgb(22 33 62 / 10%) !important;
- border: 1px solid var(--wplace-text) !important;
- border-radius: 0 !important;
-}
-
-.wplace-theme-neon-light .wplace-settings-select {
- background: var(--wplace-secondary) !important;
- border: 1px solid var(--wplace-text) !important;
- border-radius: 0 !important;
- color: var(--wplace-text) !important;
- font-family: var(--wplace-font) !important;
- font-size: 11px !important;
-}
-
-.wplace-theme-neon-light .wplace-settings-section-label {
- color: var(--wplace-text) !important;
- text-shadow: 0 0 8px var(--wplace-text) !important;
- font-family: var(--wplace-font) !important;
- font-size: 12px !important;
- text-transform: uppercase !important;
-}
-
-.wplace-theme-neon-light .wplace-speed-value {
- background: var(--wplace-secondary) !important;
- border: 1px solid var(--wplace-text) !important;
- border-radius: 0 !important;
- color: var(--wplace-text) !important;
- text-shadow: 0 0 8px var(--wplace-text) !important;
- font-family: var(--wplace-font) !important;
- box-shadow: 0 0 10px rgb(234 156 0 / 30%) !important;
-}
-
-.wplace-theme-neon-light .wplace-overlay-opacity-value {
- background: var(--wplace-secondary) !important;
- border: 1px solid var(--wplace-text) !important;
- border-radius: 0 !important;
- color: var(--wplace-text) !important;
- text-shadow: 0 0 5px var(--wplace-text) !important;
-}
-
-/* Neon slider styling */
-.wplace-theme-neon-light .wplace-slider,
-.wplace-theme-neon-light .wplace-speed-slider,
-.wplace-theme-neon-light .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 rgb(234 156 0 / 30%) !important;
-}
-
-.wplace-theme-neon-light .wplace-slider::-webkit-slider-thumb,
-.wplace-theme-neon-light .wplace-speed-slider::-webkit-slider-thumb,
-.wplace-theme-neon-light .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 rgb(234 156 0 / 50%) !important;
-}
-
-.wplace-theme-neon-light .wplace-slider::-webkit-slider-thumb:hover,
-.wplace-theme-neon-light .wplace-speed-slider::-webkit-slider-thumb:hover,
-.wplace-theme-neon-light .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-light #wplace-image-bot-container::before {
- content: '';
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- height: 2px;
- background: linear-gradient(90deg, transparent, var(--wplace-text), transparent);
- z-index: 1;
- pointer-events: none;
- animation: scanline 3s linear infinite;
- opacity: 0.7;
-}
-
-
-.wplace-theme-neon-light #wplace-stats-container::before {
- content: '';
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- height: 2px;
- background: linear-gradient(90deg, transparent, var(--wplace-text), transparent);
- z-index: 1;
- pointer-events: none;
- animation: scanline-stats 4s linear infinite;
- opacity: 0.7;
-}
-
-
-/* Text glow animations */
-@keyframes neon-glow {
- 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 pixel-blink {
- 0%, 50% {
- opacity: 1;
- }
-
- 51%, 100% {
- opacity: 0.7;
- }
-}
-
-@keyframes scanline {
- 0% {
- transform: translateY(-100%);
- }
-
- 100% {
- transform: translateY(400px);
- }
-}
-
-@keyframes scanline-stats {
- 0% {
- transform: translateY(-100%);
- }
-
- 100% {
- transform: translateY(300px);
- }
-}
-
-/* Dual control layout styling for neon theme */
-.wplace-theme-neon-light .wplace-dual-control-compact {
- display: flex;
- flex-direction: row;
- align-items: center;
- gap: 8px;
- margin: 6px 0;
- flex-wrap: wrap;
-}
-
-.wplace-theme-neon-light .wplace-slider-container-compact {
- display: flex;
- align-items: center;
- gap: 6px;
- flex: 1;
- min-width: 160px;
-}
-
-.wplace-theme-neon-light .wplace-speed-slider-container-compact {
- display: flex;
- align-items: center;
- gap: 6px;
- flex: 1;
- min-width: 160px;
-}
-
-.wplace-theme-neon-light .wplace-input-group-compact {
- display: flex;
- align-items: center;
- gap: 3px;
- flex-shrink: 0;
-}
-
-.wplace-theme-neon-light .wplace-input-btn-compact {
- background: var(--wplace-secondary) !important;
- border: 1px solid var(--wplace-text) !important;
- border-radius: 0 !important;
- color: var(--wplace-text) !important;
- width: 18px;
- height: 18px;
- cursor: pointer;
- font-weight: bold;
- font-size: 10px;
- display: flex;
- align-items: center;
- justify-content: center;
- transition: all 0.2s ease;
- text-shadow: 0 0 5px var(--wplace-text) !important;
- font-family: var(--wplace-font) !important;
-}
-
-.wplace-theme-neon-light .wplace-input-btn-compact:hover {
- box-shadow: 0 0 10px var(--wplace-text) !important;
- animation: pixel-blink 0.5s infinite !important;
-}
-
-.wplace-theme-neon-light .wplace-number-input-compact {
- background: var(--wplace-secondary) !important;
- border: 1px solid var(--wplace-text) !important;
- border-radius: 0 !important;
- color: var(--wplace-text) !important;
- padding: 2px 4px;
- font-size: 10px;
- width: 65px;
- text-align: center;
- transition: all 0.2s ease;
- height: 18px;
- font-family: var(--wplace-font) !important;
- text-shadow: 0 0 5px var(--wplace-text) !important;
-}
-
-.wplace-theme-neon-light .wplace-number-input-compact:focus {
- outline: none;
- box-shadow: 0 0 10px var(--wplace-text) !important;
- background: rgb(234 156 0 / 15%) !important;
-}
-
-.wplace-theme-neon-light .wplace-input-label-compact {
- color: var(--wplace-text) !important;
- font-size: 9px;
- margin-left: 3px;
- white-space: nowrap;
- text-shadow: 0 0 5px var(--wplace-text) !important;
- font-family: var(--wplace-font) !important;
- text-transform: uppercase;
-}
-
-.wplace-theme-neon-light .wplace-batch-size-header {
- margin-bottom: 6px;
-}
-
-.wplace-theme-neon-light .wplace-batch-size-label {
- color: var(--wplace-text) !important;
- font-weight: 500;
- font-size: 11px;
- text-shadow: 0 0 8px var(--wplace-text) !important;
- font-family: var(--wplace-font) !important;
- text-transform: uppercase;
-}
-
-/* Cooldown control styling for neon theme */
-.wplace-theme-neon-light .wplace-cooldown-control {
- margin: 8px 0;
-}
-
-.wplace-theme-neon-light .wplace-cooldown-control label {
- color: var(--wplace-text) !important;
- font-weight: 500;
- font-size: 11px;
- text-shadow: 0 0 8px var(--wplace-text) !important;
- font-family: var(--wplace-font) !important;
- text-transform: uppercase;
- margin-bottom: 6px;
- display: block;
-}
-
-/* Resize dialog styling for neon theme */
-.wplace-theme-neon-light .resize-container {
- background-color: #E0E0E1 !important; /* Solid neon white */
- border: 3px solid #203C5D !important;
- border-radius: 0 !important;
- backdrop-filter: none !important; /* Remove blur effects */
- box-shadow: none !important; /* Optional: remove glows */
- font-family: 'Press Start 2P', monospace !important;
-}
-
-/* Account switching UI styles */
-.wplace-theme-neon-light .wplace-section {
- margin: 15px 0;
- border-radius: 0;
- background: rgba(32, 60, 93, 0.05);
- padding: 15px;
- border: 2px solid #203C5D;
- box-shadow: 0 0 20px rgba(32, 60, 93, 0.2);
- font-family: var(--wplace-font);
-}
-
-.wplace-theme-neon-light .wplace-section-title {
- display: flex;
- align-items: center;
- gap: 8px;
- font-weight: 600;
- color: var(--wplace-text);
- margin-bottom: 10px;
- font-size: 12px;
- text-transform: uppercase;
- text-shadow: 0 0 5px var(--wplace-text);
- font-family: var(--wplace-font);
-}
-
-/* Auto-swap toggle switch */
-.wplace-theme-neon-light .wplace-switch {
- position: relative;
- display: inline-block;
- width: 50px;
- height: 24px;
-}
-
-.wplace-theme-neon-light .wplace-switch input {
- opacity: 0;
- width: 0;
- height: 0;
-}
-
-.wplace-theme-neon-light .wplace-slider-round {
- position: absolute;
- cursor: pointer;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background-color: #f0f0f0;
- transition: 0.4s;
- border-radius: 0;
- border: 2px solid #ccc;
- box-shadow: 0 0 8px rgba(32, 60, 93, 0.15);
-}
-
-.wplace-theme-neon-light .wplace-slider-round:before {
- position: absolute;
- content: "";
- height: 14px;
- width: 14px;
- left: 3px;
- bottom: 3px;
- background-color: #203C5D;
- transition: 0.4s;
- border-radius: 0;
- box-shadow: 0 0 8px rgba(32, 60, 93, 0.3);
-}
-
-.wplace-theme-neon-light .wplace-switch input:checked + .wplace-slider-round {
- background-color: #203C5D;
- box-shadow: 0 0 15px rgba(32, 60, 93, 0.4);
-}
-
-.wplace-theme-neon-light .wplace-switch input:checked + .wplace-slider-round:before {
- transform: translateX(26px);
- background-color: #fff;
- box-shadow: 0 0 10px rgba(32, 60, 93, 0.5);
-}
-
-/* Account list container */
-.wplace-theme-neon-light .accounts-list-container {
- max-height: 200px;
- overflow-y: auto;
- margin-top: 10px;
- padding: 0 6px;
-}
-
-/* Individual account item */
-.wplace-theme-neon-light .wplace-account-item {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 6px 8px;
- margin: 4px 0;
- background: rgba(32, 60, 93, 0.03);
- border-radius: 0;
- border: 1px solid #203C5D;
- transition: all 0.3s ease;
- box-shadow: 0 0 5px rgba(32, 60, 93, 0.1);
- position: relative;
-}
-
-/* Account ordering number */
-.wplace-theme-neon-light .wplace-account-number {
- position: absolute;
- top: 3px;
- left: 3px;
- width: 8px;
- height: 8px;
- background: #203C5D;
- color: white;
- border-radius: 0;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 8px;
- font-weight: bold;
- z-index: 1;
- border: 1px solid #203C5D;
- box-shadow: 0 0 6px rgba(32, 60, 93, 0.4);
- font-family: var(--wplace-font);
-}
-
-.wplace-theme-neon-light .wplace-account-item:hover {
- background: rgba(32, 60, 93, 0.08);
- border-color: #203C5D;
- box-shadow: 0 0 12px rgba(32, 60, 93, 0.2);
- transform: translateX(2px);
-}
-
-.wplace-theme-neon-light .wplace-account-item.is-current {
- background: rgba(32, 60, 93, 0.15);
- border-color: #203C5D;
- box-shadow: 0 0 20px rgba(32, 60, 93, 0.3);
- animation: neon-light-pulse 2s infinite;
-}
-
-@keyframes neon-light-pulse {
- 0%, 100% { box-shadow: 0 0 20px rgba(32, 60, 93, 0.3); }
- 50% { box-shadow: 0 0 25px rgba(32, 60, 93, 0.4); }
-}
-
-/* Account details */
-.wplace-theme-neon-light .wplace-account-details {
- flex: 1;
- margin-left: 20px;
- min-width: 0;
-}
-
-.wplace-theme-neon-light .wplace-account-name {
- font-weight: 600;
- color: var(--wplace-text);
- font-size: 8px;
- margin-bottom: 1px;
- text-transform: uppercase;
- text-shadow: 0 0 3px var(--wplace-text);
- font-family: var(--wplace-font);
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
-}
-
-/* Account statistics */
-.wplace-theme-neon-light .wplace-account-stats {
- display: flex;
- gap: 8px;
- align-items: center;
- font-size: 8px;
- color: rgba(32, 60, 93, 0.8);
- text-transform: uppercase;
- font-family: var(--wplace-font);
- flex-shrink: 0;
-}
-
-.wplace-theme-neon-light .wplace-account-stats i {
- margin-right: 4px;
- text-shadow: 0 0 3px currentColor;
-}
-
-.wplace-theme-neon-light .wplace-account-stats .fas.fa-bolt {
- color: #ff6b35;
- text-shadow: 0 0 5px #ff6b35;
-}
-
-.wplace-theme-neon-light .wplace-account-stats .fas.fa-tint {
- color: #203C5D;
- text-shadow: 0 0 5px #203C5D;
-}
-
-/* Move Artwork Panel */
-.wplace-theme-neon-light .wplace-move-overlay {
- position: fixed;
- top: 0;
- left: 0;
- width: 100vw;
- height: 100vh;
- background: rgba(32, 60, 93, 0.3);
- backdrop-filter: blur(4px);
- z-index: 10000;
- display: flex;
- align-items: center;
- justify-content: center;
- animation: light-fade-in 0.3s ease-out;
-}
-
-.wplace-theme-neon-light .wplace-move-panel {
- background: var(--wplace-secondary);
- border: 2px solid var(--wplace-accent);
- border-radius: var(--wplace-radius);
- padding: 20px;
- box-shadow: var(--wplace-shadow),
- 0 0 25px rgba(234, 156, 0, 0.6),
- inset 0 0 15px rgba(234, 156, 0, 0.1);
- color: var(--wplace-text);
- font-family: var(--wplace-font);
- text-align: center;
- min-width: 200px;
- animation: light-panel-pulse 2s ease-in-out infinite alternate;
-}
-
-.wplace-theme-neon-light .wplace-move-panel h3 {
- margin: 0 0 15px 0;
- color: var(--wplace-accent);
- text-shadow: 0 0 8px var(--wplace-accent);
- font-size: 14px;
- font-weight: bold;
- letter-spacing: 1px;
- text-transform: uppercase;
-}
-
-.wplace-theme-neon-light .wplace-move-controls {
- display: grid;
- grid-template-columns: 1fr 1fr 1fr;
- grid-template-rows: 1fr 1fr 1fr;
- gap: 8px;
- width: 150px;
- height: 150px;
- margin: 0 auto 15px;
-}
-
-.wplace-theme-neon-light .wplace-move-btn {
- background: linear-gradient(135deg, var(--wplace-primary) 0%, var(--wplace-secondary) 100%);
- border: 2px solid var(--wplace-accent);
- border-radius: var(--wplace-radius);
- color: var(--wplace-accent);
- font-size: 16px;
- font-weight: bold;
- cursor: pointer;
- transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
- display: flex;
- align-items: center;
- justify-content: center;
- text-shadow: 0 0 5px currentColor;
- box-shadow: 0 0 10px rgba(234, 156, 0, 0.3);
- font-family: var(--wplace-font);
-}
-
-.wplace-theme-neon-light .wplace-move-btn:hover {
- background: linear-gradient(135deg, var(--wplace-accent) 0%, var(--wplace-highlight) 100%);
- color: var(--wplace-secondary);
- text-shadow: 0 0 10px currentColor;
- box-shadow: 0 0 20px rgba(234, 156, 0, 0.7),
- inset 0 0 15px rgba(234, 156, 0, 0.2);
- transform: scale(1.05);
- animation: light-glow 0.5s ease-in-out;
-}
-
-.wplace-theme-neon-light .wplace-move-btn:active {
- transform: scale(0.95);
- box-shadow: 0 0 15px rgba(234, 156, 0, 0.9),
- inset 0 0 20px rgba(234, 156, 0, 0.3);
-}
-
-.wplace-theme-neon-light .wplace-move-btn[data-direction="up"] {
- grid-column: 2;
- grid-row: 1;
-}
-
-.wplace-theme-neon-light .wplace-move-btn[data-direction="left"] {
- grid-column: 1;
- grid-row: 2;
-}
-
-.wplace-theme-neon-light .wplace-move-btn[data-direction="right"] {
- grid-column: 3;
- grid-row: 2;
-}
-
-.wplace-theme-neon-light .wplace-move-btn[data-direction="down"] {
- grid-column: 2;
- grid-row: 3;
-}
-
-.wplace-theme-neon-light .wplace-move-close {
- background: linear-gradient(135deg, var(--wplace-error) 0%, #ff6b6b 100%);
- border: 2px solid var(--wplace-error);
- border-radius: var(--wplace-radius);
- color: var(--wplace-secondary);
- padding: 8px 16px;
- font-size: 12px;
- font-weight: bold;
- cursor: pointer;
- transition: all 0.2s ease;
- text-shadow: 0 0 5px currentColor;
- box-shadow: 0 0 10px rgba(255, 7, 58, 0.3);
- font-family: var(--wplace-font);
- text-transform: uppercase;
- letter-spacing: 0.5px;
-}
-
-.wplace-theme-neon-light .wplace-move-close:hover {
- background: linear-gradient(135deg, #ff6b6b 0%, var(--wplace-error) 100%);
- box-shadow: 0 0 20px rgba(255, 7, 58, 0.7),
- inset 0 0 10px rgba(255, 7, 58, 0.2);
- transform: scale(1.05);
- animation: light-error-pulse 0.5s ease-in-out;
-}
-
-/* Neon Light Theme Animations */
-@keyframes light-fade-in {
- from {
- opacity: 0;
- backdrop-filter: blur(0px);
- }
- to {
- opacity: 1;
- backdrop-filter: blur(4px);
- }
-}
-
-@keyframes light-panel-pulse {
- 0% {
- box-shadow: var(--wplace-shadow),
- 0 0 25px rgba(234, 156, 0, 0.6),
- inset 0 0 15px rgba(234, 156, 0, 0.1);
- }
- 100% {
- box-shadow: var(--wplace-shadow),
- 0 0 35px rgba(234, 156, 0, 0.8),
- inset 0 0 20px rgba(234, 156, 0, 0.15);
- }
-}
-
-@keyframes light-glow {
- 0%, 100% {
- box-shadow: 0 0 20px rgba(234, 156, 0, 0.7),
- inset 0 0 15px rgba(234, 156, 0, 0.2);
- }
- 50% {
- box-shadow: 0 0 30px rgba(234, 156, 0, 0.9),
- inset 0 0 25px rgba(234, 156, 0, 0.3);
- }
-}
-
-@keyframes light-error-pulse {
- 0%, 100% {
- box-shadow: 0 0 20px rgba(255, 7, 58, 0.7),
- inset 0 0 10px rgba(255, 7, 58, 0.2);
- }
- 50% {
- box-shadow: 0 0 30px rgba(255, 7, 58, 0.9),
- inset 0 0 15px rgba(255, 7, 58, 0.3);
- }
-}
diff --git a/Extensions/themes/neon.css b/Extensions/themes/neon.css
deleted file mode 100644
index cad6dea..0000000
--- a/Extensions/themes/neon.css
+++ /dev/null
@@ -1,929 +0,0 @@
-/* WPlace Auto-Image Bot - Neon Theme */
-/* Retro cyberpunk aesthetic with green neon glow effects */
-
-.wplace-theme-neon {
- /* Neon theme colors - matching upstream main neon theme */
- --wplace-primary: #1a1a2e;
- --wplace-secondary: #16213e;
- --wplace-accent: #0f3460;
- --wplace-text: #00ff41;
- --wplace-highlight: #ff6b35;
- --wplace-success: #39ff14;
- --wplace-error: #ff073a;
- --wplace-warning: #ff0;
-
- /* Neon UI properties */
- --wplace-radius: 0;
- --wplace-border-style: solid;
- --wplace-border-width: 2px;
- --wplace-border-color: #00ff41;
- --wplace-shadow: 0 0 20px rgb(0 255 65 / 30%), inset 0 0 20px rgb(0 255 65 / 10%);
- --wplace-backdrop: none;
- --wplace-font: 'Press Start 2P', monospace, 'Courier New';
-
- /* Z-index layers */
- --wplace-z-overlay: 10000;
- --wplace-z-alert: 10002;
- --wplace-z-settings: 10002;
-
- /* Feature toggles */
- --wplace-scanline: 1;
- --wplace-pixel-blink: 1;
-
- /* Icon colors - neon variants */
- --wplace-icon-primary: #00ff41;
- --wplace-icon-secondary: #39ff14;
- --wplace-icon-palette: #ff6b35;
-
- /* Additional UI colors - neon variants */
- --wplace-danger: #ff073a;
- --wplace-danger-dark: #cc0531;
- --wplace-muted-text: #00ff4180;
- --wplace-highlight-secondary: #fa0;
-
- /* Text variants - neon style */
- --wplace-text-secondary: #00ff41dd;
- --wplace-text-muted: #00ff41bb;
- --wplace-text-dim: #00ff4199;
- --wplace-text-faded: #00ff41cc;
-
- /* Background variants - neon style */
- --wplace-bg-input: rgb(0 255 65 / 15%);
- --wplace-bg-subtle: rgb(0 255 65 / 8%);
- --wplace-bg-faint: rgb(0 255 65 / 5%);
- --wplace-bg-ghost: rgb(0 255 65 / 3%);
- --wplace-bg-whisper: rgb(0 255 65 / 2%);
-
- /* Border variants - neon style */
- --wplace-border-subtle: rgb(0 255 65 / 40%);
- --wplace-border-faint: rgb(0 255 65 / 25%);
- --wplace-border-ghost: rgb(0 255 65 / 15%);
- --wplace-border-ultra-faint: rgb(0 255 65 / 8%);
-
- /* Shadow variants - neon style */
- --wplace-shadow-drag: 0 12px 40px rgb(0 255 65 / 60%), 0 0 0 2px rgb(0 255 65 / 80%), 0 0 20px rgb(0 255 65 / 30%);
- --wplace-shadow-notification: 0 4px 12px rgb(0 255 65 / 40%), 0 0 15px rgb(0 255 65 / 20%);
- --wplace-shadow-slider-thumb: 0 3px 6px rgb(0 255 65 / 50%), 0 0 0 2px var(--wplace-icon-primary), 0 0 10px rgb(0 255 65 / 30%);
- --wplace-shadow-slider-hover: 0 4px 8px rgb(0 255 65 / 60%), 0 0 0 3px var(--wplace-icon-primary), 0 0 15px rgb(0 255 65 / 40%);
-
- /* Animation colors - neon style */
- --wplace-pulse-start: rgb(0 255 65 / 80%);
- --wplace-pulse-mid: rgb(0 255 65 / 0%);
- --wplace-pulse-end: rgb(0 255 65 / 0%);
-
- /* Slider colors - neon style */
- --wplace-slider-thumb-bg: #00ff41;
-}
-
-/* Neon-specific styling overrides */
-.wplace-theme-neon #wplace-image-bot-container {
- background: var(--wplace-primary) !important;
- border: 2px solid var(--wplace-text) !important;
- border-radius: 0 !important;
- box-shadow: 0 0 20px var(--wplace-text), inset 0 0 20px rgb(0 255 65 / 10%) !important;
- font-family: var(--wplace-font) !important;
-}
-
-/* Multi-Account Panel Styles */
-.wplace-theme-neon #wplace-multi-account-container {
- position: fixed !important;
- top: 180px !important;
- right: 20px !important;
- width: 320px !important;
- background: rgb(22 33 62 / 50%) !important;
- border: 2px solid var(--wplace-text) !important;
- border-radius: 0 !important;
- box-shadow: 0 0 20px var(--wplace-text), inset 0 0 20px rgb(0 255 65 / 10%) !important;
- font-family: var(--wplace-font) !important;
- z-index: 9997 !important;
- color: var(--wplace-text) !important;
- text-shadow: 0 0 5px var(--wplace-text) !important;
-}
-
-.wplace-theme-neon #wplace-multi-account-container .wplace-panel-header {
- display: flex !important;
- justify-content: space-between !important;
- align-items: center !important;
- padding: 10px !important;
- background: var(--wplace-secondary) !important;
- border-bottom: 1px solid var(--wplace-text) !important;
- text-transform: uppercase !important;
- font-size: 9px !important;
-}
-
-.wplace-theme-neon #wplace-multi-account-container .wplace-header-btn {
- background: transparent !important;
- border: 1px solid var(--wplace-text) !important;
- color: var(--wplace-text) !important;
- font-size: 8px !important;
- padding: 4px 8px !important;
- cursor: pointer !important;
- text-shadow: 0 0 5px var(--wplace-text) !important;
- font-family: var(--wplace-font) !important;
- text-transform: uppercase !important;
-}
-
-.wplace-theme-neon #wplace-multi-account-container .wplace-header-btn:hover {
- box-shadow: 0 0 15px var(--wplace-text) !important;
- animation: pixel-blink 0.5s infinite !important;
-}
-
-.wplace-theme-neon .wplace-multi-account-content {
- padding: 10px !important;
-}
-
-.wplace-theme-neon .wplace-multi-account-info {
- font-size: 9px !important;
- text-transform: uppercase !important;
- margin-bottom: 10px !important;
- color: var(--wplace-text) !important;
- text-shadow: 0 0 5px var(--wplace-text) !important;
- font-family: var(--wplace-font) !important;
-}
-
-.wplace-theme-neon .wplace-multi-account-current,
-.wplace-theme-neon .wplace-multi-account-stats {
- margin-bottom: 8px !important;
- font-size: 8px !important;
-}
-
-.wplace-theme-neon .wplace-multi-account-toggle {
- margin-bottom: 12px !important;
- display: flex !important;
- align-items: center !important;
-}
-
-.wplace-theme-neon .wplace-multi-account-switch {
- margin-right: 8px !important;
-}
-
-.wplace-theme-neon .wplace-multi-account-list {
- max-height: 200px !important;
- overflow-y: auto !important;
-}
-
-.wplace-theme-neon .wplace-multi-account-item {
- display: flex !important;
- justify-content: space-between !important;
- align-items: center !important;
- padding: 8px !important;
- margin-bottom: 4px !important;
- background: rgb(22 33 62 / 50%) !important;
- border: 1px solid var(--wplace-text) !important;
- font-size: 8px !important;
- text-transform: uppercase !important;
- font-family: var(--wplace-font) !important;
-}
-
-.wplace-theme-neon .wplace-multi-account-item.active {
- background: rgb(0 255 65 / 10%) !important;
- border-color: var(--wplace-success) !important;
- box-shadow: 0 0 15px var(--wplace-success) !important;
-}
-
-.wplace-theme-neon .wplace-multi-account-list::-webkit-scrollbar {
- width: 6px !important;
-}
-
-.wplace-theme-neon .wplace-multi-account-list::-webkit-scrollbar-track {
- background: rgb(22 33 62 / 50%) !important;
-}
-
-.wplace-theme-neon .wplace-multi-account-list::-webkit-scrollbar-thumb {
- background: var(--wplace-text) !important;
- border-radius: 0 !important;
- box-shadow: 0 0 10px var(--wplace-text) !important;
-}
-
-.wplace-theme-neon .wplace-multi-account-list::-webkit-scrollbar-thumb:hover {
- background: var(--wplace-success) !important;
- box-shadow: 0 0 15px var(--wplace-success) !important;
-}
-
-/* Move Artwork Panel Styles for Neon Theme */
-.wplace-theme-neon .wplace-move-panel {
- position: fixed;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- background: var(--wplace-primary) !important;
- border: 2px solid var(--wplace-text) !important;
- border-radius: 0 !important;
- padding: 20px;
- z-index: 10000;
- box-shadow: 0 0 20px var(--wplace-text), inset 0 0 20px rgb(0 255 65 / 10%) !important;
- min-width: 200px;
- text-align: center;
- font-family: var(--wplace-font) !important;
- color: var(--wplace-text) !important;
-}
-
-.wplace-theme-neon .wplace-move-panel-title {
- color: var(--wplace-text) !important;
- margin: 0 0 15px 0;
- font-size: 12px !important;
- text-transform: uppercase;
- text-shadow: 0 0 5px var(--wplace-text) !important;
- font-family: var(--wplace-font) !important;
- animation: pixel-blink 2s infinite;
-}
-
-.wplace-theme-neon .wplace-move-controls {
- display: grid;
- grid-template-columns: 1fr 1fr 1fr;
- gap: 4px;
- margin-bottom: 15px;
-}
-
-.wplace-theme-neon .wplace-move-btn {
- padding: 8px !important;
- background: var(--wplace-secondary) !important;
- color: var(--wplace-text) !important;
- border: 1px solid var(--wplace-text) !important;
- border-radius: 0 !important;
- cursor: pointer;
- font-family: var(--wplace-font) !important;
- font-size: 10px !important;
- text-shadow: 0 0 5px var(--wplace-text) !important;
- transition: all 0.2s ease;
- text-transform: uppercase;
-}
-
-.wplace-theme-neon .wplace-move-btn:hover {
- background: var(--wplace-accent) !important;
- box-shadow: 0 0 15px var(--wplace-text) !important;
- animation: pixel-blink 0.5s infinite;
-}
-
-.wplace-theme-neon .wplace-move-btn:active {
- background: var(--wplace-text) !important;
- color: var(--wplace-primary) !important;
- box-shadow: 0 0 20px var(--wplace-text) !important;
-}
-
-.wplace-theme-neon .wplace-move-center {
- color: var(--wplace-highlight) !important;
- padding: 8px;
- font-size: 9px !important;
- font-weight: bold;
- display: flex;
- align-items: center;
- justify-content: center;
- text-shadow: 0 0 5px var(--wplace-highlight) !important;
- text-transform: uppercase;
- font-family: var(--wplace-font) !important;
-}
-
-.wplace-theme-neon .wplace-move-close-btn {
- background: var(--wplace-error) !important;
- color: white !important;
- border: 1px solid var(--wplace-error) !important;
- padding: 8px 12px;
- border-radius: 0 !important;
- cursor: pointer;
- font-family: var(--wplace-font) !important;
- font-size: 9px !important;
- text-transform: uppercase;
- text-shadow: 0 0 5px white !important;
- transition: all 0.2s ease;
-}
-
-.wplace-theme-neon .wplace-move-close-btn:hover {
- box-shadow: 0 0 15px var(--wplace-error) !important;
- animation: pixel-blink 0.5s infinite;
-}
-
-.wplace-theme-neon .wplace-move-overlay {
- position: fixed;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- background: rgba(26, 26, 46, 0.9) !important;
- z-index: 9999;
-}
-
-/* Neon theme animations */
-@keyframes pixel-blink {
- 0%, 50% { opacity: 1; }
- 51%, 100% { opacity: 0.7; }
-}
-
-.wplace-theme-neon #wplace-stats-container {
- background: var(--wplace-primary) !important;
- border: 2px solid var(--wplace-text) !important;
- border-radius: 0 !important;
- box-shadow: 0 0 20px var(--wplace-text), inset 0 0 20px rgb(0 255 65 / 10%) !important;
- font-family: var(--wplace-font) !important;
- position: fixed !important;
- overflow: hidden !important;
- z-index: 9998 !important;
-}
-
-.wplace-theme-neon .wplace-header {
- background: var(--wplace-secondary) !important;
- border-bottom: 1px solid var(--wplace-text) !important;
- color: var(--wplace-text) !important;
- text-shadow: 0 0 10px var(--wplace-text) !important;
- font-family: var(--wplace-font) !important;
-}
-
-.wplace-theme-neon .wplace-section {
- background: rgb(22 33 62 / 50%) !important;
- border: 1px solid var(--wplace-text) !important;
- border-radius: 0 !important;
-}
-
-.wplace-theme-neon .wplace-status-section {
- background: rgb(22 33 62 / 50%) !important;
- border: 1px solid var(--wplace-text) !important;
- border-radius: 0 !important;
-}
-
-.wplace-theme-neon .wplace-section-title {
- color: var(--wplace-text) !important;
- text-shadow: 0 0 8px var(--wplace-text) !important;
- text-transform: uppercase !important;
- font-family: var(--wplace-font) !important;
- font-size: 10px !important;
-}
-
-.wplace-theme-neon .wplace-btn {
- background: var(--wplace-secondary) !important;
- border: 1px solid var(--wplace-text) !important;
- border-radius: 0 !important;
- color: var(--wplace-text) !important;
- text-shadow: 0 0 5px var(--wplace-text) !important;
- font-family: var(--wplace-font) !important;
- font-size: 9px !important;
- text-transform: uppercase !important;
-}
-
-/* Pixel blinking and neon glow animation for buttons */
-.wplace-theme-neon .wplace-btn:hover:not(:disabled) {
- box-shadow: 0 0 15px var(--wplace-text), inset 0 0 15px rgb(0 255 65 / 10%) !important;
- animation: pixel-blink 0.5s infinite, neon-glow 1s ease-in-out infinite alternate !important;
-}
-
-.wplace-theme-neon .wplace-btn-start {
- background: var(--wplace-secondary) !important;
- border-color: var(--wplace-success) !important;
- color: var(--wplace-success) !important;
- text-shadow: 0 0 8px var(--wplace-success) !important;
-}
-
-.wplace-theme-neon .wplace-btn-stop {
- background: var(--wplace-secondary) !important;
- border-color: var(--wplace-error) !important;
- color: var(--wplace-error) !important;
- text-shadow: 0 0 8px var(--wplace-error) !important;
-}
-
-.wplace-theme-neon .wplace-btn-upload {
- background: var(--wplace-secondary) !important;
- border: 1px dashed var(--wplace-highlight) !important;
- color: var(--wplace-highlight) !important;
- text-shadow: 0 0 8px var(--wplace-highlight) !important;
-}
-
-.wplace-theme-neon .wplace-btn-select {
- background: var(--wplace-secondary) !important;
- border-color: var(--wplace-highlight) !important;
- color: var(--wplace-highlight) !important;
- text-shadow: 0 0 8px var(--wplace-highlight) !important;
-}
-
-.wplace-theme-neon .wplace-btn-file {
- background: var(--wplace-secondary) !important;
- border-color: var(--wplace-warning) !important;
- color: var(--wplace-warning) !important;
- text-shadow: 0 0 8px var(--wplace-warning) !important;
-}
-
-.wplace-theme-neon .wplace-progress {
- background: rgb(0 0 0 / 80%) !important;
- border: 1px solid var(--wplace-text) !important;
- border-radius: 0 !important;
-}
-
-.wplace-theme-neon .wplace-progress-bar {
- background: linear-gradient(90deg, var(--wplace-success) 0%, var(--wplace-text) 100%) !important;
- box-shadow: 0 0 10px var(--wplace-success) !important;
-}
-
-.wplace-theme-neon .wplace-stat-value {
- color: var(--wplace-text) !important;
- text-shadow: 0 0 5px var(--wplace-text) !important;
-}
-
-.wplace-theme-neon .status-success {
- background: rgb(57 255 20 / 10%) !important;
- border-color: var(--wplace-success) !important;
- color: var(--wplace-success) !important;
- box-shadow: 0 0 15px var(--wplace-success) !important;
- text-shadow: 0 0 8px var(--wplace-success) !important;
-}
-
-.wplace-theme-neon .status-error {
- background: rgb(255 7 58 / 10%) !important;
- border-color: var(--wplace-error) !important;
- color: var(--wplace-error) !important;
- box-shadow: 0 0 15px var(--wplace-error) !important;
- text-shadow: 0 0 8px var(--wplace-error) !important;
-}
-
-.wplace-theme-neon .status-default {
- background: rgb(0 255 65 / 10%) !important;
- border-color: var(--wplace-text) !important;
- color: var(--wplace-text) !important;
- text-shadow: 0 0 5px var(--wplace-text) !important;
-}
-
-/* Settings dialog neon styling */
-.wplace-theme-neon #wplace-settings-container {
- background: var(--wplace-primary) !important;
- border: 2px solid var(--wplace-text) !important;
- border-radius: 0 !important;
- box-shadow: 0 0 30px var(--wplace-text), inset 0 0 30px rgb(0 255 65 / 10%) !important;
- font-family: var(--wplace-font) !important;
-}
-
-.wplace-theme-neon .wplace-settings-header {
- background: var(--wplace-secondary) !important;
- border-bottom: 1px solid var(--wplace-text) !important;
-}
-
-.wplace-theme-neon .wplace-settings-header h3 {
- color: var(--wplace-text) !important;
- text-shadow: 0 0 10px var(--wplace-text) !important;
- font-family: var(--wplace-font) !important;
- font-size: 16px !important;
- text-transform: uppercase !important;
-}
-
-.wplace-theme-neon .wplace-settings-close-btn {
- background: var(--wplace-secondary) !important;
- border: 1px solid var(--wplace-text) !important;
- border-radius: 0 !important;
- color: var(--wplace-text) !important;
-}
-
-.wplace-theme-neon .wplace-settings-close-btn:hover {
- background: var(--wplace-error) !important;
- box-shadow: 0 0 15px var(--wplace-error) !important;
-}
-
-.wplace-theme-neon .wplace-settings-section-wrapper {
- background: rgb(22 33 62 / 30%) !important;
- border: 1px solid var(--wplace-text) !important;
- border-radius: 0 !important;
-}
-
-.wplace-theme-neon .wplace-settings-select {
- background: var(--wplace-secondary) !important;
- border: 1px solid var(--wplace-text) !important;
- border-radius: 0 !important;
- color: var(--wplace-text) !important;
- font-family: var(--wplace-font) !important;
- font-size: 11px !important;
-}
-
-.wplace-theme-neon .wplace-settings-section-label {
- color: var(--wplace-text) !important;
- text-shadow: 0 0 8px var(--wplace-text) !important;
- font-family: var(--wplace-font) !important;
- font-size: 12px !important;
- text-transform: uppercase !important;
-}
-
-.wplace-theme-neon .wplace-speed-value {
- background: var(--wplace-secondary) !important;
- border: 1px solid var(--wplace-text) !important;
- border-radius: 0 !important;
- color: var(--wplace-text) !important;
- text-shadow: 0 0 8px var(--wplace-text) !important;
- font-family: var(--wplace-font) !important;
- box-shadow: 0 0 10px rgb(0 255 65 / 30%) !important;
-}
-
-.wplace-theme-neon .wplace-overlay-opacity-value {
- background: var(--wplace-secondary) !important;
- border: 1px solid var(--wplace-text) !important;
- border-radius: 0 !important;
- color: var(--wplace-text) !important;
- 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 rgb(0 255 65 / 30%) !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 rgb(0 255 65 / 50%) !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: '';
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- height: 2px;
- background: linear-gradient(90deg, transparent, var(--wplace-text), transparent);
- z-index: 1;
- pointer-events: none;
- animation: scanline 3s linear infinite;
- opacity: 0.7;
-}
-
-
-.wplace-theme-neon #wplace-stats-container::before {
- content: '';
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- height: 2px;
- background: linear-gradient(90deg, transparent, var(--wplace-text), transparent);
- z-index: 1;
- pointer-events: none;
- animation: scanline-stats 4s linear infinite;
- opacity: 0.7;
-}
-
-
-/* Text glow animations */
-@keyframes neon-glow {
- 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 pixel-blink {
- 0%, 50% {
- opacity: 1;
- }
-
- 51%, 100% {
- opacity: 0.7;
- }
-}
-
-@keyframes scanline {
- 0% {
- transform: translateY(-100%);
- }
-
- 100% {
- transform: translateY(400px);
- }
-}
-
-@keyframes scanline-stats {
- 0% {
- transform: translateY(-100%);
- }
-
- 100% {
- transform: translateY(300px);
- }
-}
-
-/* Dual control layout styling for neon theme */
-.wplace-theme-neon .wplace-dual-control-compact {
- display: flex;
- flex-direction: row;
- align-items: center;
- gap: 8px;
- margin: 6px 0;
- flex-wrap: wrap;
-}
-
-.wplace-theme-neon .wplace-slider-container-compact {
- display: flex;
- align-items: center;
- gap: 6px;
- flex: 1;
- min-width: 160px;
-}
-
-.wplace-theme-neon .wplace-speed-slider-container-compact {
- display: flex;
- align-items: center;
- gap: 6px;
- flex: 1;
- min-width: 160px;
-}
-
-.wplace-theme-neon .wplace-input-group-compact {
- display: flex;
- align-items: center;
- gap: 3px;
- flex-shrink: 0;
-}
-
-.wplace-theme-neon .wplace-input-btn-compact {
- background: var(--wplace-secondary) !important;
- border: 1px solid var(--wplace-text) !important;
- border-radius: 0 !important;
- color: var(--wplace-text) !important;
- width: 18px;
- height: 18px;
- cursor: pointer;
- font-weight: bold;
- font-size: 10px;
- display: flex;
- align-items: center;
- justify-content: center;
- transition: all 0.2s ease;
- text-shadow: 0 0 5px var(--wplace-text) !important;
- font-family: var(--wplace-font) !important;
-}
-
-.wplace-theme-neon .wplace-input-btn-compact:hover {
- box-shadow: 0 0 10px var(--wplace-text) !important;
- animation: pixel-blink 0.5s infinite !important;
-}
-
-.wplace-theme-neon .wplace-number-input-compact {
- background: var(--wplace-secondary) !important;
- border: 1px solid var(--wplace-text) !important;
- border-radius: 0 !important;
- color: var(--wplace-text) !important;
- padding: 2px 4px;
- font-size: 10px;
- width: 65px;
- text-align: center;
- transition: all 0.2s ease;
- height: 18px;
- font-family: var(--wplace-font) !important;
- text-shadow: 0 0 5px var(--wplace-text) !important;
-}
-
-.wplace-theme-neon .wplace-number-input-compact:focus {
- outline: none;
- box-shadow: 0 0 10px var(--wplace-text) !important;
- background: rgb(0 255 65 / 15%) !important;
-}
-
-.wplace-theme-neon .wplace-input-label-compact {
- color: var(--wplace-text) !important;
- font-size: 9px;
- margin-left: 3px;
- white-space: nowrap;
- text-shadow: 0 0 5px var(--wplace-text) !important;
- font-family: var(--wplace-font) !important;
- text-transform: uppercase;
-}
-
-.wplace-theme-neon .wplace-batch-size-header {
- margin-bottom: 6px;
-}
-
-.wplace-theme-neon .wplace-batch-size-label {
- color: var(--wplace-text) !important;
- font-weight: 500;
- font-size: 11px;
- text-shadow: 0 0 8px var(--wplace-text) !important;
- font-family: var(--wplace-font) !important;
- text-transform: uppercase;
-}
-
-/* Cooldown control styling for neon theme */
-.wplace-theme-neon .wplace-cooldown-control {
- margin: 8px 0;
-}
-
-.wplace-theme-neon .wplace-cooldown-control label {
- color: var(--wplace-text) !important;
- font-weight: 500;
- font-size: 11px;
- text-shadow: 0 0 8px var(--wplace-text) !important;
- font-family: var(--wplace-font) !important;
- text-transform: uppercase;
- margin-bottom: 6px;
- display: block;
-}
-
-/* Resize dialog styling for neon theme */
-.wplace-theme-neon .resize-container {
- background: #1a1a2e !important;
- border: 3px solid #00ff41 !important;
- border-radius: 0 !important;
- box-shadow: 0 0 30px rgb(0 255 65 / 50%) !important;
- font-family: 'Press Start 2P', monospace !important;
-}
-
-/* Account switching UI styles */
-.wplace-theme-neon .wplace-section {
- margin: 15px 0;
- border-radius: 0;
- background: rgba(0, 255, 65, 0.05);
- padding: 15px;
- border: 2px solid #00ff41;
- box-shadow: 0 0 20px rgba(0, 255, 65, 0.3);
- font-family: var(--wplace-font);
-}
-
-.wplace-theme-neon .wplace-section-title {
- display: flex;
- align-items: center;
- gap: 8px;
- font-weight: 600;
- color: var(--wplace-text);
- margin-bottom: 10px;
- font-size: 12px;
- text-transform: uppercase;
- text-shadow: 0 0 8px var(--wplace-text);
- font-family: var(--wplace-font);
-}
-
-/* Auto-swap toggle switch */
-.wplace-theme-neon .wplace-switch {
- position: relative;
- display: inline-block;
- width: 50px;
- height: 24px;
-}
-
-.wplace-theme-neon .wplace-switch input {
- opacity: 0;
- width: 0;
- height: 0;
-}
-
-.wplace-theme-neon .wplace-slider-round {
- position: absolute;
- cursor: pointer;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background-color: #1a1a2e;
- transition: 0.4s;
- border-radius: 0;
- border: 2px solid #333;
- box-shadow: 0 0 10px rgba(0, 255, 65, 0.2);
-}
-
-.wplace-theme-neon .wplace-slider-round:before {
- position: absolute;
- content: "";
- height: 14px;
- width: 14px;
- left: 3px;
- bottom: 3px;
- background-color: #fff;
- transition: 0.4s;
- border-radius: 0;
- box-shadow: 0 0 8px rgba(255, 255, 255, 0.5);
-}
-
-.wplace-theme-neon .wplace-switch input:checked + .wplace-slider-round {
- background-color: #00ff41;
- box-shadow: 0 0 20px rgba(0, 255, 65, 0.6);
-}
-
-.wplace-theme-neon .wplace-switch input:checked + .wplace-slider-round:before {
- transform: translateX(26px);
- box-shadow: 0 0 12px rgba(0, 255, 65, 0.8);
-}
-
-/* Account list container */
-.wplace-theme-neon .accounts-list-container {
- max-height: 200px;
- overflow-y: auto;
- margin-top: 10px;
- padding: 0 6px;
-}
-
-/* Individual account item */
-.wplace-theme-neon .wplace-account-item {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 6px 8px;
- margin: 4px 0;
- background: rgba(0, 255, 65, 0.03);
- border-radius: 0;
- border: 1px solid #00ff41;
- transition: all 0.3s ease;
- box-shadow: 0 0 5px rgba(0, 255, 65, 0.2);
- position: relative;
-}
-
-/* Account ordering number */
-.wplace-theme-neon .wplace-account-number {
- position: absolute;
- top: 3px;
- left: 3px;
- width: 8px;
- height: 8px;
- background: #00ff41;
- color: #1a1a2e;
- border-radius: 0;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 8px;
- font-weight: bold;
- z-index: 1;
- border: 1px solid #00ff41;
- box-shadow: 0 0 8px rgba(0, 255, 65, 0.6);
- font-family: var(--wplace-font);
-}
-
-.wplace-theme-neon .wplace-account-item:hover {
- background: rgba(0, 255, 65, 0.08);
- border-color: #00ff41;
- box-shadow: 0 0 15px rgba(0, 255, 65, 0.4);
- transform: translateX(2px);
-}
-
-.wplace-theme-neon .wplace-account-item.is-current {
- background: rgba(0, 255, 65, 0.15);
- border-color: #00ff41;
- box-shadow: 0 0 25px rgba(0, 255, 65, 0.6);
- animation: neon-pulse 2s infinite;
-}
-
-@keyframes neon-pulse {
- 0%, 100% { box-shadow: 0 0 25px rgba(0, 255, 65, 0.6); }
- 50% { box-shadow: 0 0 35px rgba(0, 255, 65, 0.8); }
-}
-
-/* Account details */
-.wplace-theme-neon .wplace-account-details {
- flex: 1;
- margin-left: 20px;
- min-width: 0;
-}
-
-.wplace-theme-neon .wplace-account-name {
- font-weight: 600;
- color: var(--wplace-text);
- font-size: 8px;
- margin-bottom: 1px;
- text-transform: uppercase;
- text-shadow: 0 0 5px var(--wplace-text);
- font-family: var(--wplace-font);
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
-}
-
-/* Account statistics */
-.wplace-theme-neon .wplace-account-stats {
- display: flex;
- gap: 8px;
- align-items: center;
- font-size: 8px;
- color: rgba(0, 255, 65, 0.8);
- text-transform: uppercase;
- font-family: var(--wplace-font);
- flex-shrink: 0;
-}
-
-.wplace-theme-neon .wplace-account-stats i {
- margin-right: 4px;
- text-shadow: 0 0 5px currentColor;
-}
-
-.wplace-theme-neon .wplace-account-stats .fas.fa-bolt {
- color: #ffff00;
- text-shadow: 0 0 8px #ffff00;
-}
-
-.wplace-theme-neon .wplace-account-stats .fas.fa-tint {
- color: #00bfff;
- text-shadow: 0 0 8px #00bfff;
-}
-