lukcy-draw/legacy/app.js
2025-12-12 09:15:19 +07:00

232 lines
7.9 KiB
JavaScript

document.addEventListener('DOMContentLoaded', () => {
// DOM Elements
const drawBtn = document.getElementById('draw-btn');
const resetBtn = document.getElementById('reset-btn');
const statusMsg = document.getElementById('status-message');
const historyList = document.getElementById('history-list');
const slots = [
document.getElementById('slot-1'),
document.getElementById('slot-2'),
document.getElementById('slot-3')
];
// Configuration
const SLOT_HEIGHT = 160; // px, must match CSS
const NUM_DUPLICATES = 3; // How many set of 0-9 to stack for spinning illusion
// State
let config = { maxNumber: 100 };
let appState = {
remainingNumbers: [],
drawHistory: []
};
// Initialize
async function init() {
try {
const response = await fetch('config.json');
const data = await response.json();
config = { ...config, ...data };
} catch (e) {
console.warn('Could not load config.json, using defaults.');
}
initializeSlotStrips();
loadState();
// If no state exists (first run), initialize numbers
if (appState.remainingNumbers.length === 0 && appState.drawHistory.length === 0) {
resetPool();
}
// Sync input
document.getElementById('max-number-input').value = config.maxNumber;
renderHistory();
updateUI();
drawBtn.addEventListener('click', handleDraw);
resetBtn.addEventListener('click', confirmReset);
}
function initializeSlotStrips() {
slots.forEach(slot => {
const strip = slot.querySelector('.digit-strip');
strip.innerHTML = '';
// Create a long strip: 0-9 repeated NUM_DUPLICATES times + final set for landing
let html = '';
for (let i = 0; i < NUM_DUPLICATES; i++) {
for (let d = 0; d <= 9; d++) {
html += `<div class="digit-box">${d}</div>`;
}
}
// Add one final set of 0-9 for the final target
for (let d = 0; d <= 9; d++) {
html += `<div class="digit-box">${d}</div>`;
}
strip.innerHTML = html;
// Set initial position to '0'
strip.style.transform = `translateY(0px)`;
});
}
// Core Logic
function resetPool() {
appState.remainingNumbers = Array.from({ length: config.maxNumber }, (_, i) => i + 1);
appState.drawHistory = [];
saveState();
renderHistory();
updateUI();
statusMsg.textContent = 'Ready to draw!';
// Reset slots visually to 000
slots.forEach(slot => {
const strip = slot.querySelector('.digit-strip');
strip.style.transition = 'none';
strip.style.transform = `translateY(0px)`;
strip.classList.remove('spinning');
});
}
async function handleDraw() {
if (appState.remainingNumbers.length === 0) {
statusMsg.textContent = 'No numbers remaining!';
return;
}
drawBtn.disabled = true;
statusMsg.textContent = 'Spinning...';
// 1. Pick winner
const randomIndex = Math.floor(Math.random() * appState.remainingNumbers.length);
const winner = appState.remainingNumbers[randomIndex];
// 2. Remove from pool
appState.remainingNumbers.splice(randomIndex, 1);
// 3. Add to history
appState.drawHistory.unshift(winner);
saveState(); // Save immediately in case of refresh
// 4. Animate
await animateSlots(winner);
// 5. Finalize
renderHistory();
updateUI();
drawBtn.disabled = false;
statusMsg.textContent = `Winner: ${winner}`;
}
function animateSlots(winnerNumber) {
return new Promise((resolve) => {
const strNum = winnerNumber.toString().padStart(3, '0');
const targetDigits = strNum.split('').map(Number);
slots.forEach((slot, index) => {
const strip = slot.querySelector('.digit-strip');
const targetDigit = targetDigits[index];
// Remove transition to reset position instantly if needed
// For a simple effect, we just assume we start from 0 or current position?
// To simplify: we reset to 0 (top) without transition, then spin down to target
strip.style.transition = 'none';
strip.style.transform = 'translateY(0px)';
// Force reflow
strip.offsetHeight;
// Calculate target Y
// We want to land on the LAST occurrence of this digit in our strip to mimic a long spin
// The strip has (NUM_DUPLICATES * 10) items before the final set.
// The final set starts at index (NUM_DUPLICATES * 10).
// Target index = (NUM_DUPLICATES * 10) + targetDigit
const targetIndex = (NUM_DUPLICATES * 10) + targetDigit;
const translateY = -(targetIndex * SLOT_HEIGHT);
// Add staggered delay for each slot (e.g., 0ms, 500ms, 1000ms)
const delay = index * 500;
setTimeout(() => {
strip.style.transition = `transform ${2 + (index * 0.5)}s cubic-bezier(0.1, 0.7, 0.1, 1)`;
strip.style.transform = `translateY(${translateY}px)`;
}, delay);
});
// Resolve after the last slot finishes (+ buffer)
const totalDuration = (2 * 1000) + (2 * 500) + 1000; // rough estimate
setTimeout(resolve, totalDuration);
});
}
function renderHistory() {
historyList.innerHTML = '';
appState.drawHistory.forEach((num) => {
const li = document.createElement('li');
li.textContent = num.toString().padStart(3, '0');
historyList.appendChild(li);
});
}
function updateUI() {
// Toggle disable state based on pool
if (appState.remainingNumbers.length === 0) {
drawBtn.disabled = true;
statusMsg.textContent = 'All numbers drawn!';
} else {
drawBtn.disabled = false;
}
}
// Persistence
function saveState() {
const stateToSave = {
...appState,
config: config // Save config too so maxNumber persists
};
localStorage.setItem('luckyDrawState', JSON.stringify(stateToSave));
}
function loadState() {
const saved = localStorage.getItem('luckyDrawState');
if (saved) {
try {
const parsed = JSON.parse(saved);
if (parsed.config) {
config = parsed.config;
}
appState = {
remainingNumbers: parsed.remainingNumbers || [],
drawHistory: parsed.drawHistory || []
};
} catch (e) {
console.error('Failed to parse saved state');
}
}
}
function confirmReset() {
const newMax = parseInt(document.getElementById('max-number-input').value, 10);
if (newMax && newMax !== config.maxNumber) {
if (confirm(`Change max number to ${newMax} and reset pool?`)) {
config.maxNumber = newMax;
resetPool();
} else {
// Revert input interaction if cancelled (optional, but good UX)
document.getElementById('max-number-input').value = config.maxNumber;
}
} else {
if (confirm('Are you sure you want to reset the pool and history?')) {
resetPool();
}
}
}
// Add input listener to warn/reset on change
document.getElementById('max-number-input').addEventListener('change', confirmReset);
init();
});