first commit
ci / build (push) Failing after 23s
deploy / deploy (push) Failing after 10m12s

This commit is contained in:
soroush.asadi
2026-05-31 12:47:02 +03:30
commit add78d8460
100 changed files with 15221 additions and 0 deletions
+147
View File
@@ -0,0 +1,147 @@
'use client';
import { useEffect, useRef, useState } from 'react';
const HOVER_SELECTOR =
'a, button, [role="button"], input, textarea, select, summary, [data-cursor-hover]';
export function CustomCursor() {
const dotRef = useRef<HTMLDivElement>(null);
const ringRef = useRef<HTMLDivElement>(null);
const [enabled, setEnabled] = useState(false);
useEffect(() => {
// Only enable on desktop pointers (>= 900px and fine pointer)
const mq = window.matchMedia('(min-width: 900px) and (pointer: fine)');
const apply = () => {
const on = mq.matches;
setEnabled(on);
document.documentElement.classList.toggle('has-cursor', on);
};
apply();
mq.addEventListener('change', apply);
return () => mq.removeEventListener('change', apply);
}, []);
useEffect(() => {
if (!enabled) return;
let dotX = window.innerWidth / 2;
let dotY = window.innerHeight / 2;
let ringX = dotX;
let ringY = dotY;
let raf = 0;
const onMove = (e: MouseEvent) => {
dotX = e.clientX;
dotY = e.clientY;
};
const tick = () => {
// Ring lags the dot — trailing effect.
ringX += (dotX - ringX) * 0.18;
ringY += (dotY - ringY) * 0.18;
if (dotRef.current) {
dotRef.current.style.transform = `translate3d(${dotX}px, ${dotY}px, 0) translate(-50%, -50%)`;
}
if (ringRef.current) {
ringRef.current.style.transform = `translate3d(${ringX}px, ${ringY}px, 0) translate(-50%, -50%)`;
}
raf = requestAnimationFrame(tick);
};
const onOver = (e: MouseEvent) => {
const target = e.target as HTMLElement | null;
const isHover = !!target?.closest(HOVER_SELECTOR);
ringRef.current?.classList.toggle('cursor-ring--hover', isHover);
dotRef.current?.classList.toggle('cursor-dot--hover', isHover);
};
const onDown = () => ringRef.current?.classList.add('cursor-ring--down');
const onUp = () => ringRef.current?.classList.remove('cursor-ring--down');
const onLeave = () => {
ringRef.current?.classList.add('cursor--hidden');
dotRef.current?.classList.add('cursor--hidden');
};
const onEnter = () => {
ringRef.current?.classList.remove('cursor--hidden');
dotRef.current?.classList.remove('cursor--hidden');
};
window.addEventListener('mousemove', onMove);
window.addEventListener('mouseover', onOver);
window.addEventListener('mousedown', onDown);
window.addEventListener('mouseup', onUp);
document.addEventListener('mouseleave', onLeave);
document.addEventListener('mouseenter', onEnter);
raf = requestAnimationFrame(tick);
return () => {
window.removeEventListener('mousemove', onMove);
window.removeEventListener('mouseover', onOver);
window.removeEventListener('mousedown', onDown);
window.removeEventListener('mouseup', onUp);
document.removeEventListener('mouseleave', onLeave);
document.removeEventListener('mouseenter', onEnter);
cancelAnimationFrame(raf);
};
}, [enabled]);
if (!enabled) return null;
return (
<>
<style jsx global>{`
.cursor-dot,
.cursor-ring {
position: fixed;
left: 0;
top: 0;
pointer-events: none;
z-index: 9999;
will-change: transform;
transition:
width 0.25s ease,
height 0.25s ease,
background 0.25s ease,
border-color 0.25s ease,
opacity 0.25s ease;
}
.cursor-dot {
width: 6px;
height: 6px;
border-radius: 999px;
background: #38bdf8;
box-shadow: 0 0 14px rgba(56, 189, 248, 0.8);
mix-blend-mode: screen;
}
.cursor-ring {
width: 36px;
height: 36px;
border-radius: 999px;
border: 1.5px solid rgba(56, 189, 248, 0.55);
}
.cursor-dot--hover {
background: #e879f9;
box-shadow: 0 0 18px rgba(232, 121, 249, 0.85);
}
.cursor-ring--hover {
width: 56px;
height: 56px;
border-color: rgba(232, 121, 249, 0.7);
background: rgba(232, 121, 249, 0.05);
}
.cursor-ring--down {
width: 30px;
height: 30px;
border-color: rgba(255, 255, 255, 0.8);
}
.cursor--hidden {
opacity: 0;
}
`}</style>
<div ref={ringRef} className="cursor-ring" aria-hidden />
<div ref={dotRef} className="cursor-dot" aria-hidden />
</>
);
}