function burst(x, y, color) { for (let i = 0; i < BURST_COUNT; i++) { const a = Math.random() * Math.PI * 2; const s = BURST_SPEED_MIN + Math.random() * (BURST_SPEED_MAX - BURST_SPEED_MIN); particles.push({ x, y, vx: Math.cos(a) * s, vy: Math.sin(a) * s, life: 1, decay: PARTICLE_DECAY_MIN + Math.random() * (PARTICLE_DECAY_MAX - PARTICLE_DECAY_MIN), r: PARTICLE_RADIUS_MIN + Math.random() * (PARTICLE_RADIUS_MAX - PARTICLE_RADIUS_MIN), color }); } } function drawBackground() { ctx.fillStyle = '#0a0a1a'; ctx.fillRect(0, 0, canvas.width, canvas.height); stars.forEach(s => { ctx.fillStyle = `rgba(255,255,255,${s.a})`; ctx.beginPath(); ctx.arc(s.x, s.y, s.r, 0, Math.PI * 2); ctx.fill(); }); } function drawCentralMolecule() { const t = Date.now() / 1000; const amb = ctx.createRadialGradient(CX, CY, 10, CX, CY, ANCHOR_RADIUS + 50); amb.addColorStop(0, 'rgba(0,180,255,0.12)'); amb.addColorStop(1, 'transparent'); ctx.fillStyle = amb; ctx.beginPath(); ctx.arc(CX, CY, ANCHOR_RADIUS + 50, 0, Math.PI * 2); ctx.fill(); anchorPoints.forEach(ap => { ctx.beginPath(); ctx.moveTo(CX, CY); ctx.lineTo(ap.x, ap.y); ctx.strokeStyle = ap.active ? 'rgba(255,80,80,0.45)' : 'rgba(0,200,255,0.18)'; ctx.lineWidth = ap.active ? 2 : 1; ctx.stroke(); }); const ng = ctx.createRadialGradient(CX, CY, 0, CX, CY, NUCLEUS_RADIUS); ng.addColorStop(0, '#00ffcc'); ng.addColorStop(0.55, '#0088ff'); ng.addColorStop(1, 'rgba(0,80,220,0.3)'); ctx.fillStyle = ng; ctx.beginPath(); ctx.arc(CX, CY, NUCLEUS_RADIUS, 0, Math.PI * 2); ctx.fill(); const pulse = Math.sin(t * 2) * 0.3 + 0.7; ctx.strokeStyle = `rgba(0,255,200,${pulse * 0.6})`; ctx.lineWidth = 2; ctx.beginPath(); ctx.arc(CX, CY, NUCLEUS_RADIUS + Math.sin(t * 2) * 5, 0, Math.PI * 2); ctx.stroke(); ctx.fillStyle = 'rgba(255,255,255,0.55)'; ctx.beginPath(); ctx.arc(CX - 16, CY - 16, 8, 0, Math.PI * 2); ctx.fill(); for (let i = 0; i < NUCLEUS_ORBITS; i++) { const orbitAngle = t * (0.45 + i * 0.3) + (i * Math.PI * 2 / 3); const rx = 72 + i * 10; const ry = 28 + i * 8; const tilt = i * Math.PI / 3; ctx.save(); ctx.translate(CX, CY); ctx.rotate(tilt); ctx.strokeStyle = 'rgba(0,200,255,0.13)'; ctx.lineWidth = 1; ctx.beginPath(); ctx.ellipse(0, 0, rx, ry, 0, 0, Math.PI * 2); ctx.stroke(); ctx.fillStyle = '#88eeff'; ctx.beginPath(); ctx.arc(Math.cos(orbitAngle) * rx, Math.sin(orbitAngle) * ry, 4, 0, Math.PI * 2); ctx.fill(); ctx.restore(); } } function drawAnchorPoints() { const t = Date.now() / 1000; anchorPoints.forEach(ap => { const pulse = Math.sin(t * 3 + ap.pulsePhase) * 0.5 + 0.5; if (ap.blockTimer > 0) { ap.blockTimer--; ctx.strokeStyle = `rgba(0,255,100,${ap.blockTimer / BLOCK_TIMER_FRAMES})`; ctx.lineWidth = 3; ctx.beginPath(); ctx.arc(ap.x, ap.y, 20 + (BLOCK_TIMER_FRAMES - ap.blockTimer), 0, Math.PI * 2); ctx.stroke(); ctx.fillStyle = 'rgba(0,255,100,0.55)'; ctx.beginPath(); ctx.arc(ap.x, ap.y, 13, 0, Math.PI * 2); ctx.fill(); } else if (ap.active) { ctx.strokeStyle = `rgba(255,70,70,${0.5 + pulse * 0.5})`; ctx.lineWidth = 2.5; ctx.beginPath(); ctx.arc(ap.x, ap.y, 18 + pulse * 6, 0, Math.PI * 2); ctx.stroke(); ctx.fillStyle = 'rgba(255,70,70,0.35)'; ctx.beginPath(); ctx.arc(ap.x, ap.y, 15, 0, Math.PI * 2); ctx.fill(); ctx.strokeStyle = 'rgba(255,200,200,0.85)'; ctx.lineWidth = 2; ctx.beginPath(); ctx.moveTo(ap.x - 9, ap.y); ctx.lineTo(ap.x + 9, ap.y); ctx.moveTo(ap.x, ap.y - 9); ctx.lineTo(ap.x, ap.y + 9); ctx.stroke(); } else { ctx.strokeStyle = `rgba(0,200,255,${0.18 + pulse * 0.28})`; ctx.lineWidth = 1.5; ctx.beginPath(); ctx.arc(ap.x, ap.y, 13, 0, Math.PI * 2); ctx.stroke(); ctx.fillStyle = 'rgba(0,200,255,0.08)'; ctx.beginPath(); ctx.arc(ap.x, ap.y, 13, 0, Math.PI * 2); ctx.fill(); } }); } function drawParticles() { particles = particles.filter(p => p.life > 0); particles.forEach(p => { p.x += p.vx; p.y += p.vy; p.vx *= 0.94; p.vy *= 0.94; p.life -= p.decay; ctx.globalAlpha = p.life; ctx.fillStyle = p.color; ctx.beginPath(); ctx.arc(p.x, p.y, p.r, 0, Math.PI * 2); ctx.fill(); ctx.globalAlpha = 1; }); } function drawClickEffects() { clickEffects = clickEffects.filter(e => e.life > 0); clickEffects.forEach(e => { e.radius += 3; e.life -= 0.06; ctx.globalAlpha = e.life; ctx.strokeStyle = '#00ff88'; ctx.lineWidth = 2; ctx.beginPath(); ctx.arc(e.x, e.y, e.radius, 0, Math.PI * 2); ctx.stroke(); ctx.globalAlpha = 1; }); } function updateCystinstein() { cysCtx.clearRect(0, 0, cysCanvas.width, cysCanvas.height); cysCtx.fillStyle = '#00aaff'; cysCtx.shadowColor = '#00aaff'; cysCtx.shadowBlur = docks > 0 ? 6 : 0; cysCtx.beginPath(); cysCtx.arc(CYS_CENTER_X, CYS_CENTER_Y, CYS_CORE_RADIUS, 0, Math.PI * 2); cysCtx.fill(); cysCtx.shadowBlur = 0; for (let i = 0; i < docks && i < MAX_DOCKS; i++) { const angle = (i / MAX_DOCKS) * Math.PI * 2 - Math.PI / 2; const ax = CYS_CENTER_X + Math.cos(angle) * CYS_DOCK_RADIUS; const ay = CYS_CENTER_Y + Math.sin(angle) * CYS_DOCK_RADIUS; const danger = i / (MAX_DOCKS - 1); cysCtx.strokeStyle = `rgba(255,${Math.floor(150 - danger * 150)},50,0.8)`; cysCtx.lineWidth = 2; cysCtx.beginPath(); cysCtx.moveTo(CYS_CENTER_X, CYS_CENTER_Y); cysCtx.lineTo(ax, ay); cysCtx.stroke(); cysCtx.fillStyle = `rgb(${Math.floor(200 + danger * 55)},${Math.floor(100 - danger * 100)},50)`; cysCtx.shadowColor = '#ff4444'; cysCtx.shadowBlur = danger > 0.7 ? 8 : 0; cysCtx.beginPath(); cysCtx.arc(ax, ay, CYS_ATOM_RADIUS, 0, Math.PI * 2); cysCtx.fill(); cysCtx.shadowBlur = 0; } if (docks >= MAX_DOCKS) { cysCtx.strokeStyle = 'rgba(255,0,0,0.9)'; cysCtx.lineWidth = 3; cysCtx.shadowColor = '#ff0000'; cysCtx.shadowBlur = 15; cysCtx.beginPath(); cysCtx.arc(CYS_CENTER_X, CYS_CENTER_Y, CYS_RING_RADIUS, 0, Math.PI * 2); cysCtx.stroke(); cysCtx.shadowBlur = 0; } }