From dbbc35040dce0d42155c1c27ceeeb9f47c445972 Mon Sep 17 00:00:00 2001 From: ashim-hq Date: Thu, 23 Apr 2026 18:15:17 +0800 Subject: [PATCH] feat: add mouse-responsive parallax to hero background, gradient separator on navbar --- apps/landing/src/components/hero.tsx | 51 +++++++++++++++++++++++--- apps/landing/src/components/navbar.tsx | 2 +- 2 files changed, 47 insertions(+), 6 deletions(-) diff --git a/apps/landing/src/components/hero.tsx b/apps/landing/src/components/hero.tsx index bad1fe70..79da59c3 100644 --- a/apps/landing/src/components/hero.tsx +++ b/apps/landing/src/components/hero.tsx @@ -1,4 +1,7 @@ +"use client"; + import { Github } from "lucide-react"; +import { useCallback, useEffect, useRef, useState } from "react"; import { FadeIn } from "./fade-in"; import { TypingCursor } from "./typing-cursor"; @@ -44,20 +47,58 @@ const wordCloud = [ { text: "Vectorize", x: 65, y: 92, size: 12, opacity: 0.08 }, ]; +function useMouseParallax(strength = 30) { + const [offset, setOffset] = useState({ x: 0, y: 0 }); + const rafRef = useRef(0); + + const handleMouseMove = useCallback( + (e: MouseEvent) => { + cancelAnimationFrame(rafRef.current); + rafRef.current = requestAnimationFrame(() => { + const cx = window.innerWidth / 2; + const cy = window.innerHeight / 2; + setOffset({ + x: ((e.clientX - cx) / cx) * strength, + y: ((e.clientY - cy) / cy) * strength, + }); + }); + }, + [strength], + ); + + useEffect(() => { + window.addEventListener("mousemove", handleMouseMove); + return () => { + window.removeEventListener("mousemove", handleMouseMove); + cancelAnimationFrame(rafRef.current); + }; + }, [handleMouseMove]); + + return offset; +} + export function Hero() { + const mouse = useMouseParallax(25); + return (
- {/* Gradient mesh background */} -
+ {/* Gradient mesh background - follows mouse */} +
-
+
- {/* Word cloud background */} -
+ {/* Word cloud background - follows mouse in opposite direction */} +
{wordCloud.map((word) => ( +