/* ============================================ NolHitam — Custom Cursor System Soft, minimal, premium cursor with magnetic hover ============================================ */ (function () { "use strict"; // Bail on touch devices if (window.matchMedia("(hover: none) and (pointer: coarse)").matches) { return; } // --- Create cursor elements --- const dot = document.createElement("div"); dot.className = "cursor-dot"; const glow = document.createElement("div"); glow.className = "cursor-glow"; document.body.appendChild(dot); document.body.appendChild(glow); // --- State --- let mouseX = 0; let mouseY = 0; let dotX = 0; let dotY = 0; let glowX = 0; let glowY = 0; // Easing factors (lower = more lag/smoother) const DOT_EASE = 0.15; const GLOW_EASE = 0.08; // --- Track mouse position --- document.addEventListener("mousemove", (e) => { mouseX = e.clientX; mouseY = e.clientY; }); // --- RAF animation loop --- function animate() { // Lerp dot position dotX += (mouseX - dotX) * DOT_EASE; dotY += (mouseY - dotY) * DOT_EASE; // Lerp glow position (more lag for premium feel) glowX += (mouseX - glowX) * GLOW_EASE; glowY += (mouseY - glowY) * GLOW_EASE; dot.style.transform = `translate(${dotX - 3}px, ${dotY - 3}px)`; glow.style.transform = `translate(${glowX - 16}px, ${glowY - 16}px)`; requestAnimationFrame(animate); } animate(); // --- Magnetic hover effect --- const magneticElements = new Set(); // Observe new .magnetic elements added to DOM const observer = new MutationObserver(() => { document.querySelectorAll(".magnetic").forEach((el) => { if (!magneticElements.has(el)) { magneticElements.add(el); attachMagnetic(el); } }); }); observer.observe(document.body, { childList: true, subtree: true }); // Attach magnetic behavior to an element function attachMagnetic(el) { let isHovering = false; let magnetX = 0; let magnetY = 0; let currentMagnetX = 0; let currentMagnetY = 0; let magnetAnimId = null; function onEnter() { isHovering = true; dot.classList.add("cursor-dot--magnetic"); glow.classList.add("cursor-glow--magnetic"); startMagnetAnim(); } function onMove(e) { if (!isHovering) return; const rect = el.getBoundingClientRect(); const centerX = rect.left + rect.width / 2; const centerY = rect.top + rect.height / 2; // Calculate offset from center (clamped to element bounds) const maxDist = Math.min(rect.width, rect.height) * 0.15; const dx = e.clientX - centerX; const dy = e.clientY - centerY; const dist = Math.sqrt(dx * dx + dy * dy); if (dist < maxDist * 3) { const factor = Math.min(1, dist / (maxDist * 3)); magnetX = dx * 0.12 * (1 - factor * 0.5); magnetY = dy * 0.12 * (1 - factor * 0.5); } else { magnetX = 0; magnetY = 0; } } function onLeave() { isHovering = false; magnetX = 0; magnetY = 0; dot.classList.remove("cursor-dot--magnetic"); glow.classList.remove("cursor-glow--magnetic"); if (magnetAnimId) { cancelAnimationFrame(magnetAnimId); magnetAnimId = null; } // Smoothly return element to original position gsap.to(el, { x: 0, y: 0, duration: 0.4, ease: "power2.out", overwrite: "auto", }); } function startMagnetAnim() { function step() { if (!isHovering) return; currentMagnetX += (magnetX - currentMagnetX) * 0.15; currentMagnetY += (magnetY - currentMagnetY) * 0.15; el.style.transform = `translate(${currentMagnetX}px, ${currentMagnetY}px)`; magnetAnimId = requestAnimationFrame(step); } step(); } el.addEventListener("mouseenter", onEnter); el.addEventListener("mousemove", onMove); el.addEventListener("mouseleave", onLeave); // Store cleanup el._cursorCleanup = () => { el.removeEventListener("mouseenter", onEnter); el.removeEventListener("mousemove", onMove); el.removeEventListener("mouseleave", onLeave); magneticElements.delete(el); }; } // Attach to existing .magnetic elements on load document.querySelectorAll(".magnetic").forEach((el) => { magneticElements.add(el); attachMagnetic(el); }); // --- Hide cursor when leaving window --- document.addEventListener("mouseleave", () => { dot.style.opacity = "0"; glow.style.opacity = "0"; }); document.addEventListener("mouseenter", () => { dot.style.opacity = "1"; glow.style.opacity = "1"; }); })();