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 = ` -
-
-
${message}
-
- `; - } - - 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.icon}
-

${script.displayName}

-
-

${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 = ` -
-
- ${iconUrl ? `AutoBOT` : ''} -

⚔ WPlace AutoBOT Script Manager ⚔

-
- -
-
- -
- - `; - - // 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.title} -
-
- -
-
-
-
- - -
- -
-
-
-
${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('title')} -
-
- - - - -
-
-
- -
-
- ${Utils.t('initMessage')} -
-
-
-
-
- - -
-
šŸ–¼ļø Image Management
-
-
- - -
-
- - -
-
- -
-
-
- - -
-
šŸŽ® Painting Control
-
-
- - -
-
- -
-
-
- - -
-
ā±ļø ${Utils.t('cooldownSettings')}
-
- -
-
- -
-
- - - - ${Utils.t('charges')} -
-
-
-
- - -
-
šŸ’¾ Data Management
-
-
- - -
-
- - -
-
-
-
- `; - - // Stats Window - Separate UI - const statsContainer = document.createElement('div'); - statsContainer.id = 'wplace-stats-container'; - statsContainer.style.display = 'none'; - statsContainer.innerHTML = ` -
-
- - ${Utils.t('paintingStats')} -
-
- - -
-
-
-
-
-
-
${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 = ` -
-
-

- - ${Utils.t('settings')} -

- -
-
- -
- - -
- -
- -

- Generator mode creates tokens automatically. Hybrid mode falls back to manual when generator fails. Manual mode only uses pixel placement. -

-
-
- - -
- - -
- - -
- -
- -
-
- Overlay Opacity -
${Math.round(state.overlayOpacity * 100)}%
-
- -
- - -
-
- - -
- - -
- - - - - - -
-
- - -
- - - -
- - -
- - -
-
- ${Utils.t('batchSize')} -
-
-
- -
-
-
- - - - pixels -
-
-
-
- ${CONFIG.PAINTING_SPEED.MIN} - ${CONFIG.PAINTING_SPEED.MAX} -
-
- - -
-
-
- - -
-
- - -
-
-

- šŸŽ² Random batch size between min and max values -

-
- - - -
- - -
- - - -
- - -
- - -
- - -
- - -
- -
- - -
-
-
- - -
-
- - -
-
-

- 🧱 Block dimensions for block-based generation modes -

-
-
- - -
- -
- - - -
- ${Utils.t('repeatEvery')} - - ${Utils.t('minutesPl')} -
-
- - -
-
-
- - -
- -
- -
-
- - -
- -
- -
-
-
- - - - - `; - - const resizeContainer = document.createElement('div'); - resizeContainer.className = 'resize-container'; - resizeContainer.innerHTML = ` -

${Utils.t('resizeImage')}

-
- - - - - -
- - - - - - - 100% -
- Drag to pan • Pinch to zoom • Double‑tap to zoom -
-
-
- -
-
-
- - -
-
-
-
-
-
-
- -
- - 1 -
-
-
- -
- - 1 -
-
-
-
- -
- - - -
-
- - - Shift = Row • Alt = Column -
-
- -
-
-  Color Palette -
-
-
- -
-
- - - -
-
-
-
- -
-
-  Advanced Color Matching -
-
- - -
-
- ${Utils.t('chromaWeight')} - ${state.chromaPenaltyWeight} -
- -
- -
- - -
- -
-
- -
- - - -
- `; - - 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 = ` -
-
- - Move Artwork -
-
- -
-
-
-
- -
- -
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 = ` -
- Attack -
-

- PIXEL ART ATTACKED! -

-

- Detected ${count} damaged pixels! -

- `; - } else if (type === 'repaired') { - notification.innerHTML = ` -
- Repaired -
-

- 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 = ` -
- Peaceful - 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 = ` -
-
-
šŸ”§
-

- WPlace Autonomous Repair Tool -

-
-
- - -
-
- -
-
-
- ${Utils.t('autonomousModeActive')} -
-
- Charges: 0/1 (cooldown: 31s) -
-
- Token: Initializing... -
-
- System: AUTONOMOUS | Token: GENERATOR | Auto-refresh: ON -
-
- Auto-Batch: ON | Size: 5 | Opt: ON -
-
- -
- - - - - -
- -
-
- -
- -
- - - seconds -
- -
- - -
- - -
-
- šŸŽÆ Pixel Batch Mode -
- - -
-
- -
- -
- -
-
- - -
- Batch Size: -
- - - -
- pixels -
-
- -
-
- -
-
Image: Not loaded
-
Position: Not set
-
Colors: 0 available
-
Batch Mode: Auto (${state.currentBatchSize} pixels)
-
-
- -
-
-

- šŸ“‹ ${Utils.t('debug')} (Enhanced System) -

- -
- -
-
[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; -} -