mirror of
https://github.com/tiennm99/WPlace-AutoBOT.git
synced 2026-10-11 12:08:55 +00:00
feat: move artwork feature
-move artwork feature -some bug fixes -extracted artwork reprocessing
This commit is contained in:
1 parent
3a2129ebb7
commit
23c3bb9747
8 files changed
+1524
-19
No files matched your search
+250
-17
@@ -1051,6 +1051,21 @@ function getText(key, params) {
|
||||
}
|
||||
},
|
||||
|
||||
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],
|
||||
@@ -2095,6 +2110,10 @@ function getText(key, params) {
|
||||
<i class="fas fa-expand"></i>
|
||||
<span>${Utils.t('resizeImage')}</span>
|
||||
</button>
|
||||
<button id="moveArtworkBtn" class="wplace-btn wplace-btn-primary" disabled>
|
||||
<i class="fas fa-arrows-alt"></i>
|
||||
<span>Move Artwork</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="wplace-row single">
|
||||
<button id="selectPosBtn" class="wplace-btn wplace-btn-select" disabled>
|
||||
@@ -2899,6 +2918,20 @@ function getText(key, params) {
|
||||
}>Perceptual (Lab)</option>
|
||||
<option value="legacy" ${state.colorMatchingAlgorithm === 'legacy' ? 'selected' : ''
|
||||
}>Legacy (RGB)</option>
|
||||
<option value="hsv" ${state.colorMatchingAlgorithm === 'hsv' ? 'selected' : ''
|
||||
}>HSV (Hue-Saturation-Value)</option>
|
||||
<option value="hsl" ${state.colorMatchingAlgorithm === 'hsl' ? 'selected' : ''
|
||||
}>HSL (Hue-Saturation-Lightness)</option>
|
||||
<option value="xyz" ${state.colorMatchingAlgorithm === 'xyz' ? 'selected' : ''
|
||||
}>XYZ (CIE Color Space)</option>
|
||||
<option value="luv" ${state.colorMatchingAlgorithm === 'luv' ? 'selected' : ''
|
||||
}>LUV (CIE L*u*v*)</option>
|
||||
<option value="yuv" ${state.colorMatchingAlgorithm === 'yuv' ? 'selected' : ''
|
||||
}>YUV (Luma-Chroma)</option>
|
||||
<option value="oklab" ${state.colorMatchingAlgorithm === 'oklab' ? 'selected' : ''
|
||||
}>Oklab (Perceptual Uniform)</option>
|
||||
<option value="lch" ${state.colorMatchingAlgorithm === 'lch' ? 'selected' : ''
|
||||
}>LCH (Lightness-Chroma-Hue)</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="resize-advanced-toggle">
|
||||
@@ -4157,6 +4190,110 @@ function getText(key, params) {
|
||||
|
||||
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 = `
|
||||
<div class="wplace-header">
|
||||
<div class="wplace-header-title">
|
||||
<i class="fas fa-arrows-alt"></i>
|
||||
Move Artwork
|
||||
</div>
|
||||
<div class="wplace-header-controls">
|
||||
<button id="closeMovePanel" class="wplace-header-btn" title="Close">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wplace-move-controls">
|
||||
<div></div>
|
||||
<button id="moveUp" class="wplace-move-btn" data-direction="up">▲</button>
|
||||
<div></div>
|
||||
<button id="moveLeft" class="wplace-move-btn" data-direction="left">◄</button>
|
||||
<div class="wplace-move-center">1px</div>
|
||||
<button id="moveRight" class="wplace-move-btn" data-direction="right">►</button>
|
||||
<div></div>
|
||||
<button id="moveDown" class="wplace-move-btn" data-direction="down">▼</button>
|
||||
<div></div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// 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;
|
||||
@@ -5325,7 +5462,8 @@ function getText(key, params) {
|
||||
state.totalPixels = totalValidPixels;
|
||||
state.paintedPixels = 0;
|
||||
state.imageLoaded = true;
|
||||
state.lastPosition = { x: 0, y: 0 };
|
||||
// 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);
|
||||
@@ -5432,6 +5570,46 @@ function getText(key, params) {
|
||||
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) {
|
||||
@@ -5457,14 +5635,20 @@ function getText(key, params) {
|
||||
// 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 to show position selection needed
|
||||
Utils.showAlert('Extracted artwork loaded! Click on the canvas to select where to place it.', 'success');
|
||||
// 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;
|
||||
if (state.colorsChecked) {
|
||||
resizeBtn.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
|
||||
@@ -5516,6 +5700,13 @@ function getText(key, params) {
|
||||
|
||||
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');
|
||||
}
|
||||
@@ -5540,10 +5731,34 @@ function getText(key, params) {
|
||||
|
||||
if (resizeBtn) {
|
||||
resizeBtn.addEventListener('click', () => {
|
||||
if (state.imageLoaded && state.imageData.processor && state.colorsChecked) {
|
||||
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 if (!state.colorsChecked) {
|
||||
Utils.showAlert(Utils.t('uploadImageFirstColors'), 'warning');
|
||||
} 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');
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -5556,6 +5771,12 @@ function getText(key, params) {
|
||||
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');
|
||||
@@ -5586,7 +5807,8 @@ function getText(key, params) {
|
||||
x: payload.coords[0],
|
||||
y: payload.coords[1],
|
||||
};
|
||||
state.lastPosition = { x: 0, y: 0 };
|
||||
// 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 {
|
||||
@@ -5602,6 +5824,12 @@ function getText(key, params) {
|
||||
|
||||
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;
|
||||
@@ -6296,7 +6524,12 @@ function getText(key, params) {
|
||||
}
|
||||
|
||||
// Only include pixels that haven't been marked as painted yet
|
||||
if (!Utils.isPixelPainted(x, y)) {
|
||||
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]);
|
||||
}
|
||||
}
|
||||
@@ -6422,7 +6655,7 @@ function getText(key, params) {
|
||||
|
||||
// Check initial charges to calculate wait time
|
||||
let chargeCheckCount = 0;
|
||||
const maxChargeChecks = 10; // Limit API calls during cooldown
|
||||
// const maxChargeChecks = 10; // REMOVED: No limit on API calls during cooldown
|
||||
|
||||
while (!state.stopFlag) {
|
||||
chargeCheckCount++;
|
||||
@@ -6469,11 +6702,11 @@ function getText(key, params) {
|
||||
console.log(`⏱️ Cooldown check ${chargeCheckCount}: ${state.displayCharges}/${state.cooldownChargeThreshold} charges, waiting 10s before next check`);
|
||||
await Utils.sleep(10000);
|
||||
|
||||
// Fail-safe: Don't exceed max checks
|
||||
if (chargeCheckCount >= maxChargeChecks) {
|
||||
console.warn('⚠️ Max charge checks reached during cooldown, continuing anyway');
|
||||
break;
|
||||
}
|
||||
// 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';
|
||||
@@ -6514,7 +6747,7 @@ function getText(key, params) {
|
||||
Utils.saveProgress();
|
||||
} else {
|
||||
updateUI('paintingComplete', 'success', { count: state.paintedPixels });
|
||||
state.lastPosition = { x: 0, y: 0 };
|
||||
state.lastPosition = { x: 0, y: 0 }; // Only reset when truly complete
|
||||
Utils.saveProgress(); // Save final complete state
|
||||
overlayManager.clear();
|
||||
const toggleOverlayBtn = document.getElementById('toggleOverlayBtn');
|
||||
|
||||
@@ -26,6 +26,13 @@ class ImageProcessor {
|
||||
// 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;
|
||||
@@ -196,6 +203,403 @@ class ImageProcessor {
|
||||
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
|
||||
@@ -235,7 +639,170 @@ class ImageProcessor {
|
||||
return cor;
|
||||
}
|
||||
|
||||
// LAB algorithm
|
||||
// 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;
|
||||
@@ -710,6 +1277,13 @@ class ImageProcessor {
|
||||
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;
|
||||
}
|
||||
@@ -738,4 +1312,4 @@ window.globalImageProcessor = new ImageProcessor();
|
||||
// Legacy compatibility - expose key methods globally for backward compatibility
|
||||
window.ImageProcessor = ImageProcessor;
|
||||
|
||||
console.log('✅ WPlace Image Processor loaded and ready');
|
||||
console.log('✅ WPlace Image Processor loaded and ready');
|
||||
@@ -779,4 +779,105 @@
|
||||
|
||||
.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);
|
||||
}
|
||||
@@ -775,6 +775,101 @@
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -921,3 +921,108 @@
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -701,3 +701,115 @@
|
||||
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); }
|
||||
}
|
||||
@@ -703,3 +703,179 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -200,6 +200,115 @@
|
||||
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;
|
||||
|
||||
Reference in new issue
Block a user