672 lines
18 KiB
JavaScript
672 lines
18 KiB
JavaScript
/**
|
|
* Pixel Lock Plugin
|
|
*
|
|
* This plugin adds a pixel locking button to the FurryPlace interface.
|
|
* Users can click the button to enter lock mode, select pixels to lock,
|
|
* and protect them from being painted over for 24 hours.
|
|
*
|
|
* Cost: 5 droplets per pixel
|
|
* Duration: 24 hours
|
|
* Cooldown: 24 hours after unlock
|
|
*/
|
|
|
|
(function() {
|
|
'use strict';
|
|
|
|
// State
|
|
let lockMode = false;
|
|
let currentMode = 'rect'; // 'rect', 'brush', 'pixel'
|
|
let selectedPixels = new Set();
|
|
let currentTile = { x: 1024, y: 1024 };
|
|
let lockedPixelsCache = new Map();
|
|
let brushSize = 1;
|
|
|
|
// UI Elements
|
|
let lockModeUI = null;
|
|
let lockOverlay = null;
|
|
let lockCanvas = null;
|
|
|
|
// Icons
|
|
const lockIcon = `
|
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-6">
|
|
<path fill-rule="evenodd" d="M12 1.5a5.25 5.25 0 0 0-5.25 5.25v3a3 3 0 0 0-3 3v6.75a3 3 0 0 0 3 3h10.5a3 3 0 0 0 3-3v-6.75a3 3 0 0 0-3-3v-3c0-2.9-2.35-5.25-5.25-5.25Zm3.75 8.25v-3a3.75 3.75 0 1 0-7.5 0v3h7.5Z" clip-rule="evenodd" />
|
|
</svg>
|
|
`;
|
|
|
|
const unlockIcon = `
|
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-6">
|
|
<path d="M18 1.5c2.9 0 5.25 2.35 5.25 5.25v3.75a.75.75 0 0 1-1.5 0V6.75a3.75 3.75 0 1 0-7.5 0v3a3 3 0 0 1 3 3v6.75a3 3 0 0 1-3 3H3.75a3 3 0 0 1-3-3v-6.75a3 3 0 0 1 3-3h9v-3c0-2.9 2.35-5.25 5.25-5.25Z" />
|
|
</svg>
|
|
`;
|
|
|
|
// Create lock mode GUI
|
|
function createLockModeUI() {
|
|
const ui = document.createElement('div');
|
|
ui.id = 'pixel-lock-ui';
|
|
ui.innerHTML = `
|
|
<style>
|
|
#pixel-lock-ui {
|
|
position: fixed;
|
|
top: 20px;
|
|
right: 20px;
|
|
background: rgba(0, 0, 0, 0.9);
|
|
border: 2px solid #4ade80;
|
|
border-radius: 12px;
|
|
padding: 16px;
|
|
color: white;
|
|
font-family: system-ui, -apple-system, sans-serif;
|
|
z-index: 10001;
|
|
min-width: 280px;
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5);
|
|
}
|
|
|
|
#pixel-lock-ui h3 {
|
|
margin: 0 0 12px 0;
|
|
font-size: 18px;
|
|
font-weight: 600;
|
|
color: #4ade80;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
}
|
|
|
|
#pixel-lock-ui .status {
|
|
font-size: 14px;
|
|
margin-bottom: 16px;
|
|
padding: 8px;
|
|
background: rgba(74, 222, 128, 0.2);
|
|
border-radius: 6px;
|
|
border-left: 3px solid #4ade80;
|
|
}
|
|
|
|
#pixel-lock-ui .mode-selector {
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
#pixel-lock-ui .mode-selector label {
|
|
display: block;
|
|
margin-bottom: 8px;
|
|
font-size: 14px;
|
|
font-weight: 500;
|
|
}
|
|
|
|
#pixel-lock-ui .mode-buttons {
|
|
display: grid;
|
|
grid-template-columns: repeat(3, 1fr);
|
|
gap: 8px;
|
|
}
|
|
|
|
#pixel-lock-ui .mode-btn {
|
|
padding: 8px 12px;
|
|
background: rgba(255, 255, 255, 0.1);
|
|
border: 2px solid rgba(255, 255, 255, 0.3);
|
|
border-radius: 6px;
|
|
color: white;
|
|
cursor: pointer;
|
|
font-size: 12px;
|
|
transition: all 0.2s;
|
|
text-align: center;
|
|
}
|
|
|
|
#pixel-lock-ui .mode-btn:hover {
|
|
background: rgba(255, 255, 255, 0.2);
|
|
border-color: rgba(255, 255, 255, 0.5);
|
|
}
|
|
|
|
#pixel-lock-ui .mode-btn.active {
|
|
background: #4ade80;
|
|
border-color: #4ade80;
|
|
color: black;
|
|
font-weight: 600;
|
|
}
|
|
|
|
#pixel-lock-ui .brush-size {
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
#pixel-lock-ui .brush-size label {
|
|
display: block;
|
|
margin-bottom: 8px;
|
|
font-size: 14px;
|
|
font-weight: 500;
|
|
}
|
|
|
|
#pixel-lock-ui .brush-size input {
|
|
width: 100%;
|
|
accent-color: #4ade80;
|
|
}
|
|
|
|
#pixel-lock-ui .brush-size-value {
|
|
text-align: center;
|
|
margin-top: 4px;
|
|
font-size: 12px;
|
|
color: #4ade80;
|
|
}
|
|
|
|
#pixel-lock-ui .cost-preview {
|
|
margin-bottom: 16px;
|
|
padding: 12px;
|
|
background: rgba(96, 165, 250, 0.2);
|
|
border-radius: 6px;
|
|
border: 1px solid rgba(96, 165, 250, 0.5);
|
|
}
|
|
|
|
#pixel-lock-ui .cost-preview .row {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
margin-bottom: 6px;
|
|
font-size: 14px;
|
|
}
|
|
|
|
#pixel-lock-ui .cost-preview .row:last-child {
|
|
margin-bottom: 0;
|
|
padding-top: 6px;
|
|
border-top: 1px solid rgba(255, 255, 255, 0.2);
|
|
font-weight: 600;
|
|
color: #4ade80;
|
|
}
|
|
|
|
#pixel-lock-ui .actions {
|
|
display: flex;
|
|
gap: 8px;
|
|
}
|
|
|
|
#pixel-lock-ui .btn {
|
|
flex: 1;
|
|
padding: 10px;
|
|
border: none;
|
|
border-radius: 6px;
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
#pixel-lock-ui .btn-lock {
|
|
background: #4ade80;
|
|
color: black;
|
|
}
|
|
|
|
#pixel-lock-ui .btn-lock:hover:not(:disabled) {
|
|
background: #22c55e;
|
|
}
|
|
|
|
#pixel-lock-ui .btn-lock:disabled {
|
|
opacity: 0.5;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
#pixel-lock-ui .btn-clear {
|
|
background: rgba(239, 68, 68, 0.8);
|
|
color: white;
|
|
}
|
|
|
|
#pixel-lock-ui .btn-clear:hover {
|
|
background: rgba(239, 68, 68, 1);
|
|
}
|
|
|
|
#pixel-lock-ui .btn-exit {
|
|
background: rgba(255, 255, 255, 0.1);
|
|
color: white;
|
|
}
|
|
|
|
#pixel-lock-ui .btn-exit:hover {
|
|
background: rgba(255, 255, 255, 0.2);
|
|
}
|
|
|
|
#pixel-lock-ui .instructions {
|
|
margin-top: 12px;
|
|
font-size: 12px;
|
|
color: rgba(255, 255, 255, 0.7);
|
|
line-height: 1.5;
|
|
}
|
|
</style>
|
|
|
|
<h3>
|
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" style="width: 20px; height: 20px;">
|
|
<path fill-rule="evenodd" d="M12 1.5a5.25 5.25 0 0 0-5.25 5.25v3a3 3 0 0 0-3 3v6.75a3 3 0 0 0 3 3h10.5a3 3 0 0 0 3-3v-6.75a3 3 0 0 0-3-3v-3c0-2.9-2.35-5.25-5.25-5.25Zm3.75 8.25v-3a3.75 3.75 0 1 0-7.5 0v3h7.5Z" clip-rule="evenodd" />
|
|
</svg>
|
|
Lock Mode Active
|
|
</h3>
|
|
|
|
<div class="status">
|
|
🟢 Select pixels to lock (5 💧 per pixel)
|
|
</div>
|
|
|
|
<div class="mode-selector">
|
|
<label>Selection Mode:</label>
|
|
<div class="mode-buttons">
|
|
<button class="mode-btn active" data-mode="rect">
|
|
📐<br>Rect
|
|
</button>
|
|
<button class="mode-btn" data-mode="brush">
|
|
🖌️<br>Brush
|
|
</button>
|
|
<button class="mode-btn" data-mode="pixel">
|
|
⬜<br>Pixel
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="brush-size" id="brush-size-container" style="display: none;">
|
|
<label>Brush Size:</label>
|
|
<input type="range" id="brush-size-slider" min="1" max="10" value="1">
|
|
<div class="brush-size-value">Size: <span id="brush-size-display">1</span> pixels</div>
|
|
</div>
|
|
|
|
<div class="cost-preview">
|
|
<div class="row">
|
|
<span>Pixels selected:</span>
|
|
<span id="pixels-selected">0</span>
|
|
</div>
|
|
<div class="row">
|
|
<span>Cost per pixel:</span>
|
|
<span>5 💧</span>
|
|
</div>
|
|
<div class="row">
|
|
<span>Total cost:</span>
|
|
<span id="total-cost">0 💧</span>
|
|
</div>
|
|
<div class="row">
|
|
<span>Your droplets:</span>
|
|
<span id="user-droplets">0 💧</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="actions">
|
|
<button class="btn btn-lock" id="lock-confirm-btn" disabled>
|
|
🔒 Lock Pixels
|
|
</button>
|
|
<button class="btn btn-clear" id="lock-clear-btn">
|
|
Clear
|
|
</button>
|
|
</div>
|
|
|
|
<div class="actions" style="margin-top: 8px;">
|
|
<button class="btn btn-exit" id="lock-exit-btn">
|
|
Exit Lock Mode
|
|
</button>
|
|
</div>
|
|
|
|
<div class="instructions">
|
|
<strong>How to use:</strong><br>
|
|
• <strong>Rect:</strong> Drag to select rectangular area<br>
|
|
• <strong>Brush:</strong> Click and drag to paint selection<br>
|
|
• <strong>Pixel:</strong> Click individual pixels
|
|
</div>
|
|
`;
|
|
|
|
document.body.appendChild(ui);
|
|
lockModeUI = ui;
|
|
|
|
// Set up event listeners
|
|
setupUIEventListeners();
|
|
updateCostPreview();
|
|
}
|
|
|
|
function setupUIEventListeners() {
|
|
// Mode selection
|
|
const modeButtons = lockModeUI.querySelectorAll('.mode-btn');
|
|
modeButtons.forEach(btn => {
|
|
btn.addEventListener('click', () => {
|
|
modeButtons.forEach(b => b.classList.remove('active'));
|
|
btn.classList.add('active');
|
|
currentMode = btn.dataset.mode;
|
|
|
|
// Show/hide brush size control
|
|
const brushSizeContainer = document.getElementById('brush-size-container');
|
|
brushSizeContainer.style.display = currentMode === 'brush' ? 'block' : 'none';
|
|
|
|
updateCursor();
|
|
});
|
|
});
|
|
|
|
// Brush size
|
|
const brushSlider = document.getElementById('brush-size-slider');
|
|
const brushDisplay = document.getElementById('brush-size-display');
|
|
brushSlider.addEventListener('input', (e) => {
|
|
brushSize = parseInt(e.target.value);
|
|
brushDisplay.textContent = brushSize;
|
|
});
|
|
|
|
// Lock button
|
|
document.getElementById('lock-confirm-btn').addEventListener('click', confirmLock);
|
|
|
|
// Clear button
|
|
document.getElementById('lock-clear-btn').addEventListener('click', () => {
|
|
selectedPixels.clear();
|
|
updateCostPreview();
|
|
renderOverlay();
|
|
});
|
|
|
|
// Exit button
|
|
document.getElementById('lock-exit-btn').addEventListener('click', () => {
|
|
disableLockMode();
|
|
});
|
|
}
|
|
|
|
function updateCursor() {
|
|
if (!lockCanvas) return;
|
|
|
|
const cursors = {
|
|
rect: 'crosshair',
|
|
brush: 'cell',
|
|
pixel: 'pointer'
|
|
};
|
|
|
|
lockCanvas.style.cursor = cursors[currentMode] || 'crosshair';
|
|
}
|
|
|
|
function updateCostPreview() {
|
|
const pixelCount = selectedPixels.size;
|
|
const totalCost = pixelCount * 5;
|
|
const userDroplets = window.FurryPlaceSDK.getDroplets();
|
|
|
|
document.getElementById('pixels-selected').textContent = pixelCount;
|
|
document.getElementById('total-cost').textContent = `${totalCost} 💧`;
|
|
document.getElementById('user-droplets').textContent = `${userDroplets} 💧`;
|
|
|
|
const lockBtn = document.getElementById('lock-confirm-btn');
|
|
lockBtn.disabled = pixelCount === 0 || totalCost > userDroplets;
|
|
}
|
|
|
|
async function confirmLock() {
|
|
if (selectedPixels.size === 0) return;
|
|
|
|
const coords = Array.from(selectedPixels).flatMap(key => {
|
|
const [x, y] = key.split(',').map(Number);
|
|
return [x, y];
|
|
});
|
|
|
|
const cost = selectedPixels.size * 5;
|
|
const droplets = window.FurryPlaceSDK.getDroplets();
|
|
|
|
if (cost > droplets) {
|
|
alert(`Not enough droplets! Need ${cost} 💧, you have ${droplets} 💧.`);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const lockBtn = document.getElementById('lock-confirm-btn');
|
|
lockBtn.disabled = true;
|
|
lockBtn.textContent = '🔒 Locking...';
|
|
|
|
const result = await window.FurryPlaceSDK.lockPixels(
|
|
currentTile.x,
|
|
currentTile.y,
|
|
coords
|
|
);
|
|
|
|
alert(`✅ Successfully locked ${result.locked} pixel(s) for ${result.cost} 💧!`);
|
|
|
|
selectedPixels.clear();
|
|
await refreshLockedPixels();
|
|
updateCostPreview();
|
|
renderOverlay();
|
|
|
|
lockBtn.textContent = '🔒 Lock Pixels';
|
|
} catch (error) {
|
|
alert(`❌ Failed to lock pixels: ${error.message}`);
|
|
document.getElementById('lock-confirm-btn').disabled = false;
|
|
document.getElementById('lock-confirm-btn').textContent = '🔒 Lock Pixels';
|
|
}
|
|
}
|
|
|
|
function createLockOverlay() {
|
|
const overlay = document.createElement('div');
|
|
overlay.id = 'pixel-lock-overlay';
|
|
overlay.style.cssText = `
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
pointer-events: none;
|
|
z-index: 10000;
|
|
`;
|
|
|
|
const canvas = document.createElement('canvas');
|
|
canvas.id = 'pixel-lock-canvas';
|
|
canvas.width = window.innerWidth;
|
|
canvas.height = window.innerHeight;
|
|
canvas.style.cssText = `
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
pointer-events: auto;
|
|
cursor: crosshair;
|
|
`;
|
|
|
|
overlay.appendChild(canvas);
|
|
document.body.appendChild(overlay);
|
|
lockOverlay = overlay;
|
|
lockCanvas = canvas;
|
|
|
|
setupCanvasEventListeners();
|
|
return overlay;
|
|
}
|
|
|
|
function setupCanvasEventListeners() {
|
|
let isDragging = false;
|
|
let dragStart = null;
|
|
let lastBrushPos = null;
|
|
|
|
lockCanvas.addEventListener('mousedown', (e) => {
|
|
isDragging = true;
|
|
dragStart = { x: e.clientX, y: e.clientY };
|
|
lastBrushPos = { x: e.clientX, y: e.clientY };
|
|
|
|
if (currentMode === 'pixel') {
|
|
// Single pixel selection
|
|
const pixelKey = `${Math.floor(e.clientX)},${Math.floor(e.clientY)}`;
|
|
if (selectedPixels.has(pixelKey)) {
|
|
selectedPixels.delete(pixelKey);
|
|
} else {
|
|
selectedPixels.add(pixelKey);
|
|
}
|
|
updateCostPreview();
|
|
renderOverlay();
|
|
} else if (currentMode === 'brush') {
|
|
// Start brush stroke
|
|
addBrushPixels(e.clientX, e.clientY);
|
|
updateCostPreview();
|
|
renderOverlay();
|
|
}
|
|
});
|
|
|
|
lockCanvas.addEventListener('mousemove', (e) => {
|
|
if (!isDragging) return;
|
|
|
|
if (currentMode === 'rect') {
|
|
renderOverlay({ x1: dragStart.x, y1: dragStart.y, x2: e.clientX, y2: e.clientY });
|
|
} else if (currentMode === 'brush') {
|
|
addBrushPixels(e.clientX, e.clientY);
|
|
lastBrushPos = { x: e.clientX, y: e.clientY };
|
|
updateCostPreview();
|
|
renderOverlay();
|
|
}
|
|
});
|
|
|
|
lockCanvas.addEventListener('mouseup', (e) => {
|
|
if (!isDragging) return;
|
|
isDragging = false;
|
|
|
|
if (currentMode === 'rect') {
|
|
const x1 = Math.min(dragStart.x, e.clientX);
|
|
const y1 = Math.min(dragStart.y, e.clientY);
|
|
const x2 = Math.max(dragStart.x, e.clientX);
|
|
const y2 = Math.max(dragStart.y, e.clientY);
|
|
|
|
for (let x = Math.floor(x1); x <= Math.floor(x2); x++) {
|
|
for (let y = Math.floor(y1); y <= Math.floor(y2); y++) {
|
|
const key = `${x},${y}`;
|
|
selectedPixels.add(key);
|
|
}
|
|
}
|
|
|
|
updateCostPreview();
|
|
renderOverlay();
|
|
}
|
|
});
|
|
|
|
lockCanvas.addEventListener('mouseleave', () => {
|
|
isDragging = false;
|
|
renderOverlay();
|
|
});
|
|
}
|
|
|
|
function addBrushPixels(x, y) {
|
|
const centerX = Math.floor(x);
|
|
const centerY = Math.floor(y);
|
|
const radius = Math.floor(brushSize / 2);
|
|
|
|
for (let dx = -radius; dx <= radius; dx++) {
|
|
for (let dy = -radius; dy <= radius; dy++) {
|
|
const distance = Math.sqrt(dx * dx + dy * dy);
|
|
if (distance <= brushSize / 2) {
|
|
const key = `${centerX + dx},${centerY + dy}`;
|
|
selectedPixels.add(key);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function renderOverlay(dragRect = null) {
|
|
if (!lockCanvas) return;
|
|
|
|
const ctx = lockCanvas.getContext('2d');
|
|
ctx.clearRect(0, 0, lockCanvas.width, lockCanvas.height);
|
|
|
|
// Draw selected pixels (green)
|
|
ctx.fillStyle = 'rgba(74, 222, 128, 0.5)';
|
|
for (const key of selectedPixels) {
|
|
const [x, y] = key.split(',').map(Number);
|
|
ctx.fillRect(x, y, 1, 1);
|
|
}
|
|
|
|
// Draw drag rect preview
|
|
if (dragRect && currentMode === 'rect') {
|
|
const x = Math.min(dragRect.x1, dragRect.x2);
|
|
const y = Math.min(dragRect.y1, dragRect.y2);
|
|
const width = Math.abs(dragRect.x2 - dragRect.x1);
|
|
const height = Math.abs(dragRect.y2 - dragRect.y1);
|
|
|
|
ctx.fillStyle = 'rgba(74, 222, 128, 0.3)';
|
|
ctx.fillRect(x, y, width, height);
|
|
|
|
ctx.strokeStyle = 'rgba(74, 222, 128, 0.8)';
|
|
ctx.lineWidth = 2;
|
|
ctx.strokeRect(x, y, width, height);
|
|
}
|
|
}
|
|
|
|
async function refreshLockedPixels() {
|
|
try {
|
|
const result = await window.FurryPlaceSDK.getLockedPixels(
|
|
currentTile.x,
|
|
currentTile.y
|
|
);
|
|
lockedPixelsCache.set(`${currentTile.x},${currentTile.y}`, result.locked);
|
|
} catch (error) {
|
|
console.error('Failed to refresh locked pixels:', error);
|
|
}
|
|
}
|
|
|
|
function removeLockOverlay() {
|
|
if (lockOverlay) {
|
|
lockOverlay.remove();
|
|
lockOverlay = null;
|
|
lockCanvas = null;
|
|
}
|
|
}
|
|
|
|
function removeLockModeUI() {
|
|
if (lockModeUI) {
|
|
lockModeUI.remove();
|
|
lockModeUI = null;
|
|
}
|
|
}
|
|
|
|
async function enableLockMode() {
|
|
lockMode = true;
|
|
await refreshLockedPixels();
|
|
createLockOverlay();
|
|
createLockModeUI();
|
|
console.log('[Pixel Lock] Lock mode enabled');
|
|
}
|
|
|
|
function disableLockMode() {
|
|
lockMode = false;
|
|
removeLockOverlay();
|
|
removeLockModeUI();
|
|
selectedPixels.clear();
|
|
console.log('[Pixel Lock] Lock mode disabled');
|
|
|
|
// Update button
|
|
const button = document.querySelector('[data-furryplace-button="pixel-lock"] button');
|
|
if (button) {
|
|
button.classList.remove('btn-active');
|
|
button.innerHTML = lockIcon;
|
|
}
|
|
}
|
|
|
|
async function toggleLockMode() {
|
|
if (lockMode) {
|
|
disableLockMode();
|
|
} else {
|
|
enableLockMode();
|
|
|
|
// Update button
|
|
const button = document.querySelector('[data-furryplace-button="pixel-lock"] button');
|
|
if (button) {
|
|
button.classList.add('btn-active');
|
|
button.innerHTML = unlockIcon;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Initialize plugin
|
|
function init() {
|
|
if (!window.FurryPlaceSDK) {
|
|
console.error('[Pixel Lock] FurryPlace SDK not found');
|
|
return;
|
|
}
|
|
|
|
console.log('[Pixel Lock] Initializing pixel lock plugin...');
|
|
|
|
window.FurryPlaceSDK.registerButton({
|
|
id: 'pixel-lock',
|
|
title: 'Lock Pixels (5 droplets per pixel, 24h duration)',
|
|
icon: lockIcon,
|
|
position: 'before-leaderboard',
|
|
onClick: async (context) => {
|
|
if (!context.user.isLoggedIn) {
|
|
alert('Please log in to use pixel locking');
|
|
return;
|
|
}
|
|
|
|
await toggleLockMode();
|
|
},
|
|
condition: (context) => context.user?.isLoggedIn,
|
|
className: 'btn btn-square shadow-md'
|
|
});
|
|
|
|
console.log('[Pixel Lock] Plugin initialized successfully');
|
|
}
|
|
|
|
// Initialize when DOM is ready
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', init);
|
|
} else {
|
|
init();
|
|
}
|
|
|
|
// Refresh locked pixels periodically
|
|
setInterval(async () => {
|
|
if (lockMode) {
|
|
await refreshLockedPixels();
|
|
}
|
|
}, 30000);
|
|
|
|
})();
|