// p5 Custom Cursor — Instance Mode
const cursorSketch = function(p) {
let trail = [];
let isHover = false;
let isPressed = false;
p.setup = function() {
p.createCanvas(p.windowWidth, p.windowHeight);
p.noCursor();
};
p.draw = function() {
p.clear();
// detect hover over clickable elements
let el = document.elementFromPoint(p.mouseX, p.mouseY);
isHover = el && (el.tagName === 'A' || el.tagName === 'BUTTON'
|| el.closest('a') || el.closest('button'));
trail.push({ x: p.mouseX, y: p.mouseY });
if (trail.length > 12) trail.shift();
let ballSize = isPressed ? 32 : (isHover ? 52 : 44);
let r = isHover ? 50 : 200;
let g = isHover ? 100 : 30;
let b = isHover ? 220 : 70;
for (let i = 0; i < trail.length; i++) {
let alpha = p.map(i, 0, trail.length, 30, 180);
let sz = p.map(i, 0, trail.length, 12, ballSize);
p.fill(r, g, b, alpha);
p.noStroke();
p.circle(trail[i].x, trail[i].y, sz);
}
p.fill(r, g, b);
p.circle(p.mouseX, p.mouseY, ballSize);
p.fill(r * 0.6, g * 0.4, b * 0.6);
let s = ballSize / 44;
p.circle(p.mouseX - 6*s, p.mouseY - 7*s, 8*s);
p.circle(p.mouseX + 6*s, p.mouseY - 7*s, 8*s);
p.circle(p.mouseX, p.mouseY + 5*s, 8*s);
if (isHover) {
p.noFill();
p.stroke(r, g, b, 100);
p.strokeWeight(2);
p.circle(p.mouseX, p.mouseY, ballSize + 16);
}
};
p.mousePressed = function() { isPressed = true; };
p.mouseReleased = function() { isPressed = false; };
p.windowResized = function() {
p.resizeCanvas(p.windowWidth, p.windowHeight);
};
};
new p5(cursorSketch, document.getElementById('cursor-canvas'));
// CSS
#cursor-canvas {
position: fixed;
top: 0; left: 0;
pointer-events: none;
z-index: 9999;
}
* { cursor: none; }