// Randomizer Wheel Easter Egg // A fun decision-making tool hidden in the Tools page let wheel = { canvas: null, ctx: null, spinning: false, rotation: 0, targetRotation: 0, spinSpeed: 0, spinStartTime: 0, options: [], colors: [], currentPreset: 'decisions' }; // Preset configurations const presets = { decisions: { name: 'Decisions', options: ['Yes!', 'No Way', 'Maybe', 'Ask Again', 'Definitely', 'Not Now', 'Go For It!', 'Wait'], colors: ['#FF6B6B', '#4ECDC4', '#45B7D1', '#FFA07A', '#98D8C8', '#F7DC6F', '#BB8FCE', '#85C1E2'] }, lunch: { name: 'Lunch Ideas', options: ['Pizza 🍕', 'Burgers 🍔', 'Sushi 🍣', 'Tacos 🌮', 'Salad 🥗', 'Pasta 🍝', 'Chinese 🥡', 'Sandwiches 🥪'], colors: ['#E74C3C', '#F39C12', '#3498DB', '#2ECC71', '#9B59B6', '#1ABC9C', '#E67E22', '#34495E'] }, tasks: { name: 'Task Priority', options: ['Do It Now!', 'Schedule It', 'Delegate', 'Skip It', 'High Priority', 'Low Priority', 'Break Time!', 'Focus Time'], colors: ['#FF4757', '#FFA502', '#2ED573', '#1E90FF', '#5F27CD', '#00D2D3', '#FF6348', '#C23616'] }, colors: { name: 'Powder Colors', options: ['Gloss Black', 'Candy Red', 'Chrome Silver', 'Matte White', 'Metallic Blue', 'Neon Green', 'Rose Gold', 'Deep Purple'], colors: ['#000000', '#DC143C', '#C0C0C0', '#F5F5F5', '#4169E1', '#39FF14', '#B76E79', '#663399'] } }; // Initialize the wheel when page loads document.addEventListener('DOMContentLoaded', function() { wheel.canvas = document.getElementById('wheelCanvas'); if (wheel.canvas) { wheel.ctx = wheel.canvas.getContext('2d'); setWheelPreset('decisions'); // Add click event to Tools header const header = document.getElementById('toolsHeader'); if (header) { header.addEventListener('click', function() { const modal = new bootstrap.Modal(document.getElementById('randomizerModal')); modal.show(); // Redraw wheel when modal opens (in case it was resized) setTimeout(() => drawWheel(), 100); }); } } }); // Set wheel to a preset configuration function setWheelPreset(presetName) { const preset = presets[presetName]; if (!preset) return; wheel.options = [...preset.options]; wheel.colors = [...preset.colors]; wheel.currentPreset = presetName; // Update button states const buttons = document.querySelectorAll('.btn-group .btn'); buttons.forEach((btn, index) => { btn.classList.remove('active'); // Set active based on preset name const btnText = btn.textContent.toLowerCase(); if (btnText.includes(presetName)) { btn.classList.add('active'); } }); // Clear result const resultDiv = document.getElementById('result'); if (resultDiv) { resultDiv.innerHTML = ''; } // Redraw wheel drawWheel(); } // Load shop workers from the server async function loadShopWorkers() { try { // Show loading state const resultDiv = document.getElementById('result'); if (resultDiv) { resultDiv.innerHTML = '
Loading workers...
'; } const response = await fetch('/Tools/GetShopWorkers'); const data = await response.json(); if (data.success && data.workers && data.workers.length > 0) { wheel.options = data.workers; wheel.colors = generateRainbowColors(data.workers.length); wheel.currentPreset = 'workers'; // Update button states const buttons = document.querySelectorAll('.btn-group .btn'); buttons.forEach(btn => { btn.classList.remove('active'); if (btn.textContent.includes('Shop Workers')) { btn.classList.add('active'); } }); // Clear result and redraw if (resultDiv) { resultDiv.innerHTML = `Loaded ${data.workers.length} active shop workers`; } drawWheel(); } else { if (resultDiv) { resultDiv.innerHTML = 'No active shop workers found'; } } } catch (error) { console.error('Error loading shop workers:', error); const resultDiv = document.getElementById('result'); if (resultDiv) { resultDiv.innerHTML = 'Error loading shop workers'; } } } // Set custom options from textarea function setCustomOptions() { const input = document.getElementById('customOptionsInput'); const options = input.value .split('\n') .map(opt => opt.trim()) .filter(opt => opt.length > 0); if (options.length < 2) { showWarning('Please enter at least 2 options!', 'Invalid Input'); return; } wheel.options = options; // Generate rainbow colors for custom options wheel.colors = generateRainbowColors(options.length); // Collapse the custom options panel const collapse = bootstrap.Collapse.getInstance(document.getElementById('customOptions')); if (collapse) collapse.hide(); // Clear result and redraw document.getElementById('result').innerHTML = ''; drawWheel(); } // Generate rainbow colors function generateRainbowColors(count) { const colors = []; for (let i = 0; i < count; i++) { const hue = (i * 360) / count; colors.push(`hsl(${hue}, 70%, 60%)`); } return colors; } // Draw the wheel function drawWheel() { if (!wheel.ctx) return; const canvas = wheel.canvas; const ctx = wheel.ctx; const centerX = canvas.width / 2; const centerY = canvas.height / 2; const radius = Math.min(centerX, centerY) - 10; // Clear canvas ctx.clearRect(0, 0, canvas.width, canvas.height); // Save context ctx.save(); // Rotate canvas ctx.translate(centerX, centerY); ctx.rotate(wheel.rotation); ctx.translate(-centerX, -centerY); const numOptions = wheel.options.length; const anglePerOption = (2 * Math.PI) / numOptions; // Draw each segment for (let i = 0; i < numOptions; i++) { const startAngle = i * anglePerOption - Math.PI / 2; const endAngle = (i + 1) * anglePerOption - Math.PI / 2; // Draw segment ctx.beginPath(); ctx.moveTo(centerX, centerY); ctx.arc(centerX, centerY, radius, startAngle, endAngle); ctx.closePath(); ctx.fillStyle = wheel.colors[i % wheel.colors.length]; ctx.fill(); // Draw border ctx.strokeStyle = '#ffffff'; ctx.lineWidth = 3; ctx.stroke(); // Draw text ctx.save(); ctx.translate(centerX, centerY); ctx.rotate(startAngle + anglePerOption / 2); ctx.textAlign = 'right'; ctx.fillStyle = '#ffffff'; ctx.font = 'bold 18px Arial'; ctx.shadowColor = 'rgba(0, 0, 0, 0.5)'; ctx.shadowBlur = 3; ctx.fillText(wheel.options[i], radius - 20, 8); ctx.restore(); } // Draw center circle ctx.beginPath(); ctx.arc(centerX, centerY, 30, 0, 2 * Math.PI); ctx.fillStyle = '#ffffff'; ctx.fill(); ctx.strokeStyle = '#333333'; ctx.lineWidth = 3; ctx.stroke(); // Draw center star ctx.fillStyle = '#FFD700'; ctx.font = 'bold 24px Arial'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText('★', centerX, centerY); // Restore context ctx.restore(); } // Spin the wheel function spinWheel() { if (wheel.spinning) return; wheel.spinning = true; wheel.spinStartTime = Date.now(); document.getElementById('spinBtn').disabled = true; document.getElementById('result').innerHTML = '
Spinning...
'; // Random spin: 5-8 full rotations + random position const fullRotations = 5 + Math.random() * 3; const randomAngle = Math.random() * 2 * Math.PI; wheel.targetRotation = wheel.rotation + (fullRotations * 2 * Math.PI) + randomAngle; wheel.spinSpeed = 0.3; animateSpin(); } // Animate the spin function animateSpin() { if (!wheel.spinning) return; // Safety timeout: stop after 10 seconds const elapsed = Date.now() - wheel.spinStartTime; if (elapsed > 10000) { console.warn('Spin animation timed out, forcing stop'); wheel.rotation = wheel.targetRotation; finishSpin(); return; } // Ease out const distance = wheel.targetRotation - wheel.rotation; // Stop if we're very close OR if the speed is too slow to make progress if (Math.abs(distance) < 0.01 || wheel.spinSpeed < 0.001) { wheel.rotation = wheel.targetRotation; finishSpin(); return; } wheel.rotation += distance * wheel.spinSpeed; wheel.spinSpeed *= 0.97; // Deceleration drawWheel(); requestAnimationFrame(animateSpin); } // Finish the spin and show result function finishSpin() { // Normalize rotation to 0-2π for next spin wheel.rotation = wheel.rotation % (2 * Math.PI); if (wheel.rotation < 0) wheel.rotation += 2 * Math.PI; wheel.spinning = false; document.getElementById('spinBtn').disabled = false; showResult(); } // Show the result function showResult() { const numOptions = wheel.options.length; const anglePerOption = (2 * Math.PI) / numOptions; // Normalize rotation to 0-2π let normalizedRotation = wheel.rotation % (2 * Math.PI); if (normalizedRotation < 0) normalizedRotation += 2 * Math.PI; // Calculate which segment is at the pointer (top of wheel) // Canvas rotation is counterclockwise, so we need to reverse the calculation const rawIndex = (2 * Math.PI - normalizedRotation) / anglePerOption; const selectedIndex = Math.floor(rawIndex) % numOptions; const winner = wheel.options[selectedIndex]; const winnerColor = wheel.colors[selectedIndex % wheel.colors.length]; // Display result with animation const resultDiv = document.getElementById('result'); resultDiv.innerHTML = `

Winner!

${winner}

`; // Add confetti effect createConfetti(); } // Create confetti effect function createConfetti() { const duration = 2000; const animationEnd = Date.now() + duration; const colors = ['#FF6B6B', '#4ECDC4', '#45B7D1', '#FFA07A', '#98D8C8', '#F7DC6F']; (function frame() { const timeLeft = animationEnd - Date.now(); if (timeLeft <= 0) return; const particleCount = 2; for (let i = 0; i < particleCount; i++) { const particle = document.createElement('div'); particle.style.position = 'fixed'; particle.style.width = '10px'; particle.style.height = '10px'; particle.style.backgroundColor = colors[Math.floor(Math.random() * colors.length)]; particle.style.borderRadius = '50%'; particle.style.pointerEvents = 'none'; particle.style.zIndex = '9999'; const startX = Math.random() * window.innerWidth; const startY = -20; particle.style.left = startX + 'px'; particle.style.top = startY + 'px'; document.body.appendChild(particle); const angle = Math.random() * Math.PI * 2; const velocity = 2 + Math.random() * 2; const vx = Math.cos(angle) * velocity; const vy = Math.sin(angle) * velocity + 3; let x = startX; let y = startY; let opacity = 1; const animate = () => { y += vy; x += vx; opacity -= 0.02; particle.style.top = y + 'px'; particle.style.left = x + 'px'; particle.style.opacity = opacity; if (opacity > 0 && y < window.innerHeight) { requestAnimationFrame(animate); } else { particle.remove(); } }; animate(); } requestAnimationFrame(frame); })(); } // Add CSS animation for result const style = document.createElement('style'); style.textContent = ` @keyframes fadeInScale { from { opacity: 0; transform: scale(0.8); } to { opacity: 1; transform: scale(1); } } `; document.head.appendChild(style);