211 lines
6.9 KiB
JavaScript
211 lines
6.9 KiB
JavaScript
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;
|
|
}
|
|
}
|