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();
});
}
// Initial state with first color (#74b5d6)
drawDots(initialDots, '#74b5d6');
let isTransitioning = false;
let isForward = true; // Track direction of animation
function animateTransition(startPositions, endPositions, startColor, endColor, onComplete) {
const startTime = performance.now();
const duration = 1600; // 1.6 seconds
function animate(currentTime) {
const elapsed = currentTime - startTime;
const progress = Math.min(1, elapsed / duration);
// Add easing for smoother transition
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
}));
// Color transition
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) {
// Forward animation: scattered to hiFive
animateTransition(
initialDots,
positions.end,
'#74b5d6',
'#beffff',
() => {
isForward = false;
// Wait a moment before reversing
setTimeout(() => {
// Reverse animation: hiFive to scattered
animateTransition(
positions.end,
initialDots,
'#beffff',
'#74b5d6',
() => {
isTransitioning = false;
isForward = true;
}
);
}, 500); // 500ms pause before reversing
}
);
}
}
};
canvas.ontouchstart = (event) => {
event.preventDefault();
canvas.onclick();
};
return canvas;
}