Files
personal_website/src/components/MatrixCursor.tsx
T
gpt-engineer-app[bot] 07a2fe0c17 Changes
2026-01-22 23:52:02 +00:00

106 lines
3.3 KiB
TypeScript

import { useState, useEffect } from 'react';
const MatrixCursor = () => {
const [position, setPosition] = useState({ x: 0, y: 0 });
const [isHovering, setIsHovering] = useState(false);
const [isVisible, setIsVisible] = useState(false);
const [isTouchDevice, setIsTouchDevice] = useState(false);
useEffect(() => {
// Detect touch device - only consider it a touch device if primary pointer is coarse (touch)
const checkTouchDevice = () => {
// Check if the device has a fine pointer (mouse) as primary input
const hasFinePrimary = window.matchMedia('(pointer: fine)').matches;
// Consider it a touch device only if it doesn't have a fine pointer as primary
setIsTouchDevice(!hasFinePrimary);
};
checkTouchDevice();
// Re-check on orientation change or resize (for hybrid devices)
window.addEventListener('resize', checkTouchDevice);
return () => {
window.removeEventListener('resize', checkTouchDevice);
};
}, []);
useEffect(() => {
// Don't set up mouse listeners on touch devices
if (isTouchDevice) {
document.body.classList.remove('matrix-cursor');
return;
}
const handleMouseMove = (e: MouseEvent) => {
setPosition({ x: e.clientX, y: e.clientY });
setIsVisible(true);
};
const handleMouseEnter = () => setIsVisible(true);
const handleMouseLeave = () => setIsVisible(false);
const handleHoverCheck = (e: MouseEvent) => {
const target = e.target as HTMLElement;
const isInteractive =
target.tagName === 'A' ||
target.tagName === 'BUTTON' ||
!!target.closest('a') ||
!!target.closest('button') ||
target.getAttribute('role') === 'button' ||
window.getComputedStyle(target).cursor === 'pointer';
setIsHovering(isInteractive);
};
// Hide cursor on any touch event (for hybrid devices)
const handleTouch = () => {
setIsVisible(false);
};
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mousemove', handleHoverCheck);
document.addEventListener('mouseenter', handleMouseEnter);
document.addEventListener('mouseleave', handleMouseLeave);
document.addEventListener('touchstart', handleTouch, { passive: true });
// Add cursor class to body
document.body.classList.add('matrix-cursor');
return () => {
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mousemove', handleHoverCheck);
document.removeEventListener('mouseenter', handleMouseEnter);
document.removeEventListener('mouseleave', handleMouseLeave);
document.removeEventListener('touchstart', handleTouch);
document.body.classList.remove('matrix-cursor');
};
}, [isTouchDevice]);
// Don't render anything on touch devices or when not visible
if (isTouchDevice || !isVisible) return null;
return (
<>
{/* Crosshair */}
<div
className="matrix-cursor-crosshair"
style={{
left: position.x,
top: position.y,
}}
/>
{/* Center dot */}
<div
className={`matrix-cursor-dot ${isHovering ? 'hovering' : ''}`}
style={{
left: position.x,
top: position.y,
}}
/>
</>
);
};
export default MatrixCursor;