import { getItems, spinWheel, getBgColor, getTitleSettings } from './api.js'; const canvas = document.getElementById('wheel'); const ctx = canvas.getContext('2d'); const btn = document.getElementById('spin-btn'); const result = document.getElementById('result'); const DARK = { edge: '#6D0025', main: '#cb183d', highlight: '#E8005E', text: '#fff' }; const LIGHT = { edge: '#D4889A', main: '#FAD4DC', highlight: '#FFF0F3', text: '#7B0030' }; let audioCtx = null; function getAudioCtx() { if (!audioCtx || audioCtx.state === 'closed') { audioCtx = new (window.AudioContext || window.webkitAudioContext)(); } if (audioCtx.state === 'suspended') audioCtx.resume(); return audioCtx; } let items = []; let angle = 0; let spinning = false; let lampOn = true; function applyBg() { const color = getBgColor(); document.getElementById('main-body').style.background = `radial-gradient(ellipse at center, ${color}DD 0%, ${color} 55%, ${color}BB 100%)`; } function applyTitle() { const settings = getTitleSettings(); const h1 = document.querySelector('.page h1'); if (h1) { h1.textContent = settings.text; h1.style.color = settings.color; h1.style.textShadow = `0 0 20px ${settings.color}80`; } } async function init() { applyBg(); applyTitle(); window.addEventListener('storage', (e) => { if (e.key === 'wheelBgColor') applyBg(); if (e.key === 'wheelTitleSettings') applyTitle(); }); items = await getItems(); draw(angle); setInterval(() => { lampOn = !lampOn; draw(angle); }, 600); } function getTheme(i, total) { if (total % 2 === 0) { return i % 2 === 0 ? DARK : LIGHT; } else { if (i === total - 1) return DARK; return i % 2 === 0 ? DARK : LIGHT; } } function drawSegment(cx, cy, r, start, end, theme) { ctx.beginPath(); ctx.moveTo(cx, cy); ctx.arc(cx, cy, r, start, end); ctx.closePath(); ctx.fillStyle = theme.edge; ctx.fill(); const mid = (start + end) / 2; const gx = cx + r * 0.5 * Math.cos(mid); const gy = cy + r * 0.5 * Math.sin(mid); const grad = ctx.createRadialGradient(gx, gy, 0, cx, cy, r); grad.addColorStop(0, theme.highlight); grad.addColorStop(0.45, theme.main); grad.addColorStop(1, theme.edge); ctx.beginPath(); ctx.moveTo(cx, cy); ctx.arc(cx, cy, r - 2, start, end); ctx.closePath(); ctx.fillStyle = grad; ctx.fill(); const hGrad = ctx.createRadialGradient(gx, gy, 0, gx, gy, r * 0.35); hGrad.addColorStop(0, 'rgba(255,255,255,0.28)'); hGrad.addColorStop(1, 'rgba(255,255,255,0)'); ctx.beginPath(); ctx.moveTo(cx, cy); ctx.arc(cx, cy, r - 2, start, end); ctx.closePath(); ctx.fillStyle = hGrad; ctx.fill(); } function draw(rotation) { if (items.length === 0) return; const cx = canvas.width / 2; const cy = canvas.height / 2; const r = cx - 40; const slice = (2 * Math.PI) / items.length; const total = items.length; ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.save(); ctx.shadowColor = 'rgba(0,0,0,0.6)'; ctx.shadowBlur = 30; ctx.shadowOffsetX = 5; ctx.shadowOffsetY = 8; ctx.beginPath(); ctx.arc(cx, cy, r + 40, 0, Math.PI * 2); ctx.fillStyle = '#1A0008'; ctx.fill(); ctx.restore(); const rimGrad = ctx.createRadialGradient(cx - 28, cy - 28, r, cx, cy, r + 40); rimGrad.addColorStop(0, '#A0003A'); rimGrad.addColorStop(0.3, '#6D0025'); rimGrad.addColorStop(0.7, '#3D0015'); rimGrad.addColorStop(1, '#1A0008'); ctx.beginPath(); ctx.arc(cx, cy, r + 40, 0, Math.PI * 2); ctx.fillStyle = rimGrad; ctx.fill(); items.forEach((item, i) => { const start = rotation + i * slice; const end = start + slice; drawSegment(cx, cy, r, start, end, getTheme(i, total)); }); items.forEach((_, i) => { const lineAngle = rotation + i * slice; const lGrad = ctx.createLinearGradient( cx, cy, cx + r * Math.cos(lineAngle), cy + r * Math.sin(lineAngle) ); lGrad.addColorStop(0, 'rgba(255,215,0,0.3)'); lGrad.addColorStop(0.4, '#a2922e'); lGrad.addColorStop(1, '#c8b43b'); ctx.beginPath(); ctx.moveTo(cx, cy); ctx.lineTo(cx + r * Math.cos(lineAngle), cy + r * Math.sin(lineAngle)); ctx.strokeStyle = lGrad; ctx.lineWidth = 5; ctx.stroke(); }); items.forEach((item, i) => { const start = rotation + i * slice; const theme = getTheme(i, total); ctx.save(); ctx.translate(cx, cy); ctx.rotate(start + slice / 2); ctx.textAlign = 'center'; ctx.fillStyle = theme.text; ctx.font = 'bold 18px Dana'; ctx.shadowColor = 'rgba(0,0,0,0.6)'; ctx.shadowBlur = 4; const text = item.title; const maxWidth = r - 52; if (ctx.measureText(text).width > maxWidth) { const mid = Math.ceil(text.length / 2); ctx.fillText(text.slice(0, mid), (r - 20) / 1.4, -9); ctx.fillText(text.slice(mid), (r - 20) / 1.4, 9); } else { ctx.fillText(text, (r - 20) / 1.4, 5); } ctx.restore(); }); ctx.beginPath(); ctx.arc(cx, cy, r + 2, 0, Math.PI * 2); ctx.strokeStyle = '#dfd498'; ctx.lineWidth = 4.5; ctx.shadowColor = 'rgba(255,215,0,0.9)'; ctx.shadowBlur = 10; ctx.stroke(); ctx.shadowBlur = 0; const lampCount = 24; const lampR = r + 23; for (let i = 0; i < lampCount; i++) { const lampAngle = (i / lampCount) * Math.PI * 2; const lx = cx + lampR * Math.cos(lampAngle); const ly = cy + lampR * Math.sin(lampAngle); const isOn = lampOn ? i % 2 === 0 : i % 2 !== 0; if (isOn) { const glowGrad = ctx.createRadialGradient(lx, ly, 0, lx, ly, 14); glowGrad.addColorStop(0, 'rgba(255,220,80,0.55)'); glowGrad.addColorStop(1, 'rgba(255,220,80,0)'); ctx.beginPath(); ctx.arc(lx, ly, 14, 0, Math.PI * 2); ctx.fillStyle = glowGrad; ctx.fill(); } const lGrad = ctx.createRadialGradient(lx - 2.5, ly - 2.5, 0.5, lx, ly, 7.5); if (isOn) { lGrad.addColorStop(0, '#FFFFF5'); lGrad.addColorStop(0.2, '#FFE566'); lGrad.addColorStop(0.6, '#FFB300'); lGrad.addColorStop(1, '#E65100'); ctx.shadowColor = 'rgba(255,180,0,1)'; ctx.shadowBlur = 14; } else { lGrad.addColorStop(0, '#B8954A'); lGrad.addColorStop(1, '#4A3010'); } ctx.beginPath(); ctx.arc(lx, ly, 7.5, 0, Math.PI * 2); ctx.fillStyle = lGrad; ctx.fill(); ctx.shadowBlur = 0; ctx.beginPath(); ctx.arc(lx, ly, 7.5, 0, Math.PI * 2); ctx.strokeStyle = isOn ? '#C8960C' : '#6A4A18'; ctx.lineWidth = 1.2; ctx.stroke(); ctx.beginPath(); ctx.arc(lx - 2.5, ly - 2.5, 2.5, 0, Math.PI * 2); ctx.fillStyle = isOn ? 'rgba(255,255,255,0.6)' : 'rgba(255,255,255,0.2)'; ctx.fill(); } ctx.beginPath(); ctx.arc(cx, cy, r + 35, 0, Math.PI * 2); ctx.strokeStyle = '#C8960C'; ctx.lineWidth = 4.5; ctx.shadowColor = 'rgba(150,100,12,0.5)'; ctx.shadowBlur = 6; ctx.stroke(); ctx.shadowBlur = 0; const cGrad = ctx.createRadialGradient(cx - 8, cy - 8, 1, cx, cy, 22); cGrad.addColorStop(0, '#FFFFF0'); cGrad.addColorStop(0.15, '#FFE566'); cGrad.addColorStop(0.45, '#FFD700'); cGrad.addColorStop(0.75, '#ff962a'); cGrad.addColorStop(1, '#b37900'); ctx.beginPath(); ctx.arc(cx, cy, 22, 0, Math.PI * 2); ctx.fillStyle = cGrad; ctx.shadowColor = 'rgba(0,0,0,0.6)'; ctx.shadowBlur = 14; ctx.fill(); ctx.shadowBlur = 0; ctx.beginPath(); ctx.arc(cx, cy, 22, 0, Math.PI * 2); ctx.strokeStyle = '#8B6914'; ctx.lineWidth = 2; ctx.stroke(); ctx.beginPath(); ctx.arc(cx - 6, cy - 6, 7, 0, Math.PI * 2); ctx.fillStyle = 'rgba(255,255,255,0.3)'; ctx.fill(); drawPointer(cx); } function drawPointer(cx) { ctx.save(); ctx.shadowColor = 'rgba(0,0,0,0.4)'; ctx.shadowBlur = 8; ctx.shadowOffsetX = 3; ctx.shadowOffsetY = 4; ctx.beginPath(); ctx.arc(cx, 20, 17, 0, Math.PI * 2); ctx.fillStyle = 'rgba(0,0,0,0.1)'; ctx.fill(); ctx.restore(); const pGrad = ctx.createRadialGradient(cx - 6, 14, 1, cx, 20, 18); pGrad.addColorStop(0, '#FFFFF0'); pGrad.addColorStop(0.15, '#FFE566'); pGrad.addColorStop(0.45, '#FFD700'); pGrad.addColorStop(0.75, '#ff962a'); pGrad.addColorStop(1, '#b37900'); ctx.beginPath(); ctx.arc(cx, 20, 17, 0, Math.PI * 2); ctx.fillStyle = pGrad; ctx.shadowColor = 'rgba(200,150,0,0.8)'; ctx.shadowBlur = 12; ctx.fill(); ctx.shadowBlur = 0; ctx.strokeStyle = '#8B6914'; ctx.lineWidth = 2; ctx.stroke(); ctx.beginPath(); ctx.arc(cx - 5, 13, 6, 0, Math.PI * 2); ctx.fillStyle = 'rgba(255,255,255,0.35)'; ctx.fill(); const triGrad = ctx.createLinearGradient(cx, 34, cx, 62); triGrad.addColorStop(0, '#FFE566'); triGrad.addColorStop(0.35, '#FFD700'); triGrad.addColorStop(1, '#b37900'); ctx.beginPath(); ctx.moveTo(cx - 13, 35); ctx.lineTo(cx + 13, 35); ctx.lineTo(cx, 62); ctx.closePath(); ctx.fillStyle = triGrad; ctx.shadowColor = 'rgba(0,0,0,0.5)'; ctx.shadowBlur = 8; ctx.fill(); ctx.shadowBlur = 0; ctx.strokeStyle = '#8B6914'; ctx.lineWidth = 1.5; ctx.stroke(); } function playTickSound() { try { const actx = getAudioCtx(); const osc = actx.createOscillator(); const gain = actx.createGain(); osc.connect(gain); gain.connect(actx.destination); osc.frequency.value = 600; osc.type = 'sine'; gain.gain.setValueAtTime(0.15, actx.currentTime); gain.gain.exponentialRampToValueAtTime(0.001, actx.currentTime + 0.08); osc.start(actx.currentTime); osc.stop(actx.currentTime + 0.08); } catch(e) {} } function playWinSound() { try { const actx = getAudioCtx(); const notes = [523, 659, 784, 1047]; notes.forEach((freq, i) => { const osc = actx.createOscillator(); const gain = actx.createGain(); osc.connect(gain); gain.connect(actx.destination); osc.frequency.value = freq; osc.type = 'sine'; gain.gain.setValueAtTime(0.2, actx.currentTime + i * 0.15); gain.gain.exponentialRampToValueAtTime(0.001, actx.currentTime + i * 0.15 + 0.3); osc.start(actx.currentTime + i * 0.15); osc.stop(actx.currentTime + i * 0.15 + 0.3); }); } catch(e) {} } function showWinnerModal(winner) { const modal = document.getElementById('winner-modal'); const iconMap = { 'پوچ': '', 'کد تخفیف': '', 'لایوکوین': '' }; document.getElementById('modal-icon').textContent = iconMap[winner.prize_type] || '🎉'; document.getElementById('modal-prize-type').textContent = winner.prize_type; document.getElementById('modal-prize').textContent = winner.prize; document.getElementById('modal-description').textContent = winner.description || ''; modal.classList.remove('hidden'); document.getElementById('modal-close').onclick = () => modal.classList.add('hidden'); } btn.onclick = async () => { if (spinning || items.length === 0) return; spinning = true; btn.disabled = true; result.textContent = ''; const data = await spinWheel(); const winner = data.winner; const winnerIdx = items.findIndex(i => i.id === winner.id); const slice = (2 * Math.PI) / items.length; const targetAngle = angle + Math.PI * 2 * 8 + (-Math.PI / 2 - (winnerIdx * slice + slice / 2) - angle % (Math.PI * 2) + Math.PI * 2 * 3); const duration = 5500; const startTime = performance.now(); const from = angle; let lastTickIdx = 0; function animate(now) { const elapsed = now - startTime; const t = Math.min(elapsed / duration, 1); const ease = 1 - Math.pow(1 - t, 4); angle = from + (targetAngle - from) * ease; const currentIdx = Math.floor(Math.abs(angle) / slice); if (currentIdx !== lastTickIdx) { lastTickIdx = currentIdx; playTickSound(); } draw(angle); if (t < 1) { requestAnimationFrame(animate); } else { spinning = false; btn.disabled = false; draw(angle); playWinSound(); showWinnerModal(winner); } } requestAnimationFrame(animate); }; init();