diff --git a/Auto-Image.js b/Auto-Image.js index 8b35f6a..449bcad 100644 --- a/Auto-Image.js +++ b/Auto-Image.js @@ -194,7 +194,7 @@ currentTheme: 'Classic Autobot', PAINT_UNAVAILABLE: true, COORDINATE_MODE: 'rows', - COORDINATE_DIRECTION: 'bottom-left', + COORDINATE_DIRECTION: 'top-left', COORDINATE_SNAKE: true, COORDINATE_BLOCK_WIDTH: 6, COORDINATE_BLOCK_HEIGHT: 2, @@ -486,6 +486,7 @@ progress: 'Progress', pixels: 'Pixels', charges: 'Charges', + batchSize: 'Batch Size', initMessage: "Click 'Upload Image' to begin", }, }; @@ -1330,6 +1331,44 @@ return `${seconds}s`; }, + // Debounced scroll-to-adjust handler for sliders + createScrollToAdjust: (element, updateCallback, min, max, step = 1) => { + let debounceTimer = null; + + const handleWheel = (e) => { + // Only trigger when hovering over the slider + if (e.target !== element) return; + + e.preventDefault(); + e.stopPropagation(); + + // Clear existing debounce timer + if (debounceTimer) { + clearTimeout(debounceTimer); + } + + // Debounce the adjustment to make it precise + 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); // 50ms debounce for precise control + }; + + element.addEventListener('wheel', handleWheel, { passive: false }); + + // Return cleanup function + return () => { + if (debounceTimer) clearTimeout(debounceTimer); + element.removeEventListener('wheel', handleWheel); + }; + }, + /** * Calculate the range of tile coordinates (in region space) that cover a given image area. * @param {number} startRegionX - Base region X @@ -3429,9 +3468,17 @@
⏱️ ${Utils.t('cooldownSettings')}
-
- - ${state.cooldownChargeThreshold} +
+
+ + ${state.cooldownChargeThreshold} +
+
+ + + + ${Utils.t('charges')} +
@@ -3725,11 +3772,24 @@ - +
-
- -
${CONFIG.PAINTING_SPEED.DEFAULT} (batch size)
+
+ ${Utils.t('batchSize')} +
+
+
+ + ${CONFIG.PAINTING_SPEED.DEFAULT} +
+
+
+ + + + pixels +
+
${CONFIG.PAINTING_SPEED.MIN} @@ -3969,7 +4029,7 @@ } } - #speedSlider::-webkit-slider-thumb, #overlayOpacitySlider::-webkit-slider-thumb { + #speedSlider::-webkit-slider-thumb, #cooldownSlider::-webkit-slider-thumb, #overlayOpacitySlider::-webkit-slider-thumb { -webkit-appearance: none; width: 18px; height: 18px; @@ -3980,12 +4040,12 @@ transition: all 0.2s ease; } - #speedSlider::-webkit-slider-thumb:hover, #overlayOpacitySlider::-webkit-slider-thumb:hover { + #speedSlider::-webkit-slider-thumb:hover, #cooldownSlider::-webkit-slider-thumb:hover, #overlayOpacitySlider::-webkit-slider-thumb:hover { transform: scale(1.2); box-shadow: 0 4px 8px rgba(0,0,0,0.4), 0 0 0 3px #4facfe; } - #speedSlider::-moz-range-thumb, #overlayOpacitySlider::-moz-range-thumb { + #speedSlider::-moz-range-thumb, #cooldownSlider::-moz-range-thumb, #overlayOpacitySlider::-moz-range-thumb { width: 18px; height: 18px; border-radius: 50%; @@ -4032,6 +4092,25 @@ .wplace-settings-header:active { background: rgba(255,255,255,0.2) !important; } + + /* Custom Scrollbar for Content Area */ + .wplace-content::-webkit-scrollbar { + width: 6px; + } + + .wplace-content::-webkit-scrollbar-track { + background: rgba(255,255,255,0.1); + border-radius: 3px; + } + + .wplace-content::-webkit-scrollbar-thumb { + background: rgba(255,255,255,0.3); + border-radius: 3px; + } + + .wplace-content::-webkit-scrollbar-thumb:hover { + background: rgba(255,255,255,0.5); + } `; @@ -4283,6 +4362,10 @@ const closeStatsBtn = statsContainer.querySelector('#closeStatsBtn'); const refreshChargesBtn = statsContainer.querySelector('#refreshChargesBtn'); const cooldownSlider = container.querySelector('#cooldownSlider'); + const cooldownSliderValue = container.querySelector('#cooldownSliderValue'); + 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) { @@ -4609,11 +4692,19 @@ ); if (overlayOpacitySlider && overlayOpacityValue) { - overlayOpacitySlider.addEventListener('input', (e) => { - const opacity = parseFloat(e.target.value); + 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) { @@ -4644,16 +4735,49 @@ }); } - // Speed slider event listener + // Speed controls - both slider and input const speedSlider = settingsContainer.querySelector('#speedSlider'); + const speedSliderValue = settingsContainer.querySelector('#speedSliderValue'); + const speedInput = settingsContainer.querySelector('#speedInput'); + const speedDecrease = settingsContainer.querySelector('#speedDecrease'); + const speedIncrease = settingsContainer.querySelector('#speedIncrease'); const speedValue = settingsContainer.querySelector('#speedValue'); - if (speedSlider && speedValue) { - speedSlider.addEventListener('input', (e) => { - const speed = parseInt(e.target.value, 10); + + if (speedSlider && speedSliderValue && 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; - speedValue.textContent = `${speed} (batch size)`; + + // Update both controls without duplication + speedSlider.value = speed; + speedInput.value = speed; + speedSliderValue.textContent = `${speed}`; + 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) { @@ -5094,9 +5218,12 @@ intervalMs ); - if (cooldownSlider.max !== state.maxCharges) { + if (cooldownSlider && cooldownSlider.max !== state.maxCharges) { cooldownSlider.max = state.maxCharges; } + if (cooldownInput && cooldownInput.max !== state.maxCharges) { + cooldownInput.max = state.maxCharges; + } let imageStatsHTML = ''; if (state.imageLoaded) { @@ -6582,14 +6709,42 @@ setTimeout(checkSavedProgress, 1000); - if (cooldownSlider && cooldownValue) { - cooldownSlider.addEventListener('input', (e) => { - const threshold = parseInt(e.target.value); + if (cooldownSlider && cooldownSliderValue && cooldownInput && cooldownValue && cooldownDecrease && cooldownIncrease) { + const updateCooldown = (newValue) => { + const threshold = Math.max(1, Math.min(state.maxCharges || 999, parseInt(newValue))); state.cooldownChargeThreshold = threshold; - cooldownValue.textContent = threshold; + + // Update both controls + cooldownSlider.value = threshold; + cooldownInput.value = threshold; + cooldownSliderValue.textContent = threshold; + 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(); @@ -7473,9 +7628,13 @@ } const speedSlider = document.getElementById('speedSlider'); + const speedInput = document.getElementById('speedInput'); + const speedSliderValue = document.getElementById('speedSliderValue'); if (speedSlider) speedSlider.value = state.paintingSpeed; + if (speedInput) speedInput.value = state.paintingSpeed; + if (speedSliderValue) speedSliderValue.textContent = `${state.paintingSpeed} (batch size)`; const speedValue = document.getElementById('speedValue'); - if (speedValue) speedValue.textContent = `${state.paintingSpeed} (batch size)`; + if (speedValue) speedValue.textContent = Utils.t('batchSize'); const enableSpeedToggle = document.getElementById('enableSpeedToggle'); if (enableSpeedToggle) enableSpeedToggle.checked = CONFIG.PAINTING_SPEED_ENABLED; @@ -7507,9 +7666,13 @@ // AUTO_CAPTCHA_ENABLED is always true - no toggle to set const cooldownSlider = document.getElementById('cooldownSlider'); + const cooldownInput = document.getElementById('cooldownInput'); + const cooldownSliderValue = document.getElementById('cooldownSliderValue'); if (cooldownSlider) cooldownSlider.value = state.cooldownChargeThreshold; + if (cooldownInput) cooldownInput.value = state.cooldownChargeThreshold; + if (cooldownSliderValue) cooldownSliderValue.textContent = state.cooldownChargeThreshold; const cooldownValue = document.getElementById('cooldownValue'); - if (cooldownValue) cooldownValue.textContent = state.cooldownChargeThreshold; + if (cooldownValue) cooldownValue.textContent = Utils.t('charges'); const overlayOpacitySlider = document.getElementById('overlayOpacitySlider'); if (overlayOpacitySlider) overlayOpacitySlider.value = state.overlayOpacity; diff --git a/themes/classic-light.css b/themes/classic-light.css index 3761527..301f931 100644 --- a/themes/classic-light.css +++ b/themes/classic-light.css @@ -468,6 +468,104 @@ 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%, #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); +} + +.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, #00f2fe 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: #fff; + border: 1px solid rgb(0 0 0 / 20%); + border-radius: 4px; + color: var(--wplace-text); + padding: 4px 8px; + font-size: 12px; + width: 50px; + 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: #f8f9fa; +} + +.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; +} + /* Color swatches with light styling */ .wplace-theme-classic-light .wplace-color-swatch { border: 1px solid rgb(0 0 0 / 20%) !important; diff --git a/themes/classic.css b/themes/classic.css index f71efe3..4d9129f 100644 --- a/themes/classic.css +++ b/themes/classic.css @@ -590,6 +590,116 @@ 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: 50px; + 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; +} + /* Resize dialog styling for classic theme */ :root .resize-container, .wplace-theme-classic .resize-container { diff --git a/themes/neon.css b/themes/neon.css index 9ce3c5c..0a1b9b9 100644 --- a/themes/neon.css +++ b/themes/neon.css @@ -392,6 +392,106 @@ } } +/* 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: 140px; +} + +.wplace-theme-neon .wplace-speed-slider-container-compact { + display: flex; + align-items: center; + gap: 6px; + flex: 1; + min-width: 140px; +} + +.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: 40px; + 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; +} + /* Resize dialog styling for neon theme */ .wplace-theme-neon .resize-container { background: #1a1a2e !important;