Files
2026-04-23 21:38:24 -04:00

421 lines
13 KiB
JavaScript

// 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 = '<div class="spinner-border spinner-border-sm text-primary" role="status"><span class="visually-hidden">Loading workers...</span></div>';
}
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 = `<small class="text-success"><i class="bi bi-check-circle me-1"></i>Loaded ${data.workers.length} active shop workers</small>`;
}
drawWheel();
} else {
if (resultDiv) {
resultDiv.innerHTML = '<small class="text-warning"><i class="bi bi-exclamation-triangle me-1"></i>No active shop workers found</small>';
}
}
} catch (error) {
console.error('Error loading shop workers:', error);
const resultDiv = document.getElementById('result');
if (resultDiv) {
resultDiv.innerHTML = '<small class="text-danger"><i class="bi bi-x-circle me-1"></i>Error loading shop workers</small>';
}
}
}
// 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 = '<div class="spinner-border text-primary" role="status"><span class="visually-hidden">Spinning...</span></div>';
// 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 = `
<div class="alert alert-success border-3" style="border-color: ${winnerColor} !important; animation: fadeInScale 0.5s ease;">
<h4 class="mb-2">
<i class="bi bi-trophy-fill text-warning me-2"></i>Winner!
</h4>
<h3 class="mb-0" style="color: ${winnerColor}; font-weight: bold; text-shadow: 2px 2px 4px rgba(0,0,0,0.1);">
${winner}
</h3>
</div>
`;
// 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);