animation = {
const canvas = DOM.canvas(width, height);
const ctx = canvas.getContext('2d');
canvas.style.background = 'black';
canvas.style.cursor = 'pointer';
function drawDots(dots, color) {
ctx.clearRect(0, 0, width, height);
ctx.shadowBlur = 15;
ctx.shadowColor = color;
ctx.fillStyle = color;
ctx.globalAlpha = 0.3;
dots.forEach(d => {
ctx.beginPath();
ctx.arc(d.x, d.y, d.originalRadius * 2.5, 0, Math.PI * 2);
ctx.fill();
});
ctx.shadowBlur = 5;
ctx.globalAlpha = 0.3;
dots.forEach(d => {
ctx.beginPath();
ctx.arc(d.x, d.y, d.originalRadius * 1.5, 0, Math.PI * 2);
ctx.fill();
});
ctx.shadowBlur = 5;
ctx.globalAlpha = 1;
dots.forEach(d => {
ctx.beginPath();
ctx.arc(d.x, d.y, d.originalRadius, 0, Math.PI * 2);
ctx.fill();
});
}
drawDots(initialDots, '#74b5d6');
let isTransitioning = false;
let isForward = true;
function animateTransition(startPositions, endPositions, startColor, endColor, onComplete) {
const startTime = performance.now();
const duration = 1600;
function animate(currentTime) {
const elapsed = currentTime - startTime;
const progress = Math.min(1, elapsed / duration);
const easedProgress = d3.easeCubicInOut(progress);
const currentDots = startPositions.map((d, i) => ({
x: d.x + (endPositions[i].x - d.x) * easedProgress,
y: d.y + (endPositions[i].y - d.y) * easedProgress,
originalRadius: d.originalRadius
}));
const color = d3.interpolateRgb(startColor, endColor)(easedProgress);
drawDots(currentDots, color);
if (progress < 1) {
requestAnimationFrame(animate);
} else {
onComplete();
}
}
requestAnimationFrame(animate);
}
canvas.onclick = () => {
if (!isTransitioning) {
isTransitioning = true;
if (isForward) {
animateTransition(
initialDots,
positions.end,
'#74b5d6',
'#beffff',
() => {
isForward = false;
setTimeout(() => {
animateTransition(
positions.end,
initialDots,
'#beffff',
'#74b5d6',
() => {
isTransitioning = false;
isForward = true;
}
);
}, 500);
}
);
}
}
};
canvas.ontouchstart = (event) => {
event.preventDefault();
canvas.onclick();
};
return canvas;
}