From da7c67734f2b0cc51523cad4c7c6939403f2812d Mon Sep 17 00:00:00 2001 From: Nikita Egorov Date: Wed, 16 Sep 2026 23:15:46 +0500 Subject: [PATCH] update light effects --- src/components/hero/FlashlightText.tsx | 35 +++++++++++++++++++++++++- src/widgets/cursor/ui/Cursor.tsx | 20 ++++++++++----- 2 files changed, 48 insertions(+), 7 deletions(-) diff --git a/src/components/hero/FlashlightText.tsx b/src/components/hero/FlashlightText.tsx index c9c8307..957fc6c 100644 --- a/src/components/hero/FlashlightText.tsx +++ b/src/components/hero/FlashlightText.tsx @@ -1,6 +1,6 @@ "use client"; -import { useRef, type CSSProperties, type MouseEvent } from "react"; +import { useEffect, useRef, type CSSProperties, type MouseEvent } from "react"; import styles from "@/static/hero/flashlight.module.css"; @@ -13,6 +13,9 @@ type FlashlightTextProps = { const FlashlightText = ({ text }: FlashlightTextProps) => { const ref = useRef(null); + const mouseX = useRef(-1); + const mouseY = useRef(-1); + const setPosition = (x: number, y: number) => { const element = ref.current; if (!element) return; @@ -21,10 +24,40 @@ const FlashlightText = ({ text }: FlashlightTextProps) => { element.style.setProperty("--fy", `${y}px`); }; + const syncWithPointer = () => { + const element = ref.current; + if (!element) return; + + const point = document.elementFromPoint(mouseX.current, mouseY.current + ); + + if (point && element.contains(point)) { + const rect = element.getBoundingClientRect(); + setPosition(mouseX.current - rect.left, mouseY.current - rect.top); + element.classList.remove(styles.idle); + } else { + setPosition(OFFSCREEN, OFFSCREEN); + element.classList.add(styles.idle); + } + }; + + useEffect(() => { + const onScroll = () => syncWithPointer(); + + window.addEventListener("scroll", onScroll, { capture: true, passive: true }); + + return () => + window.removeEventListener("scroll", onScroll, { capture: true }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + const handleMove = (e: MouseEvent) => { const element = ref.current; if (!element) return; + mouseX.current = e.clientX; + mouseY.current = e.clientY; + const rect = element.getBoundingClientRect(); setPosition(e.clientX - rect.left, e.clientY - rect.top); diff --git a/src/widgets/cursor/ui/Cursor.tsx b/src/widgets/cursor/ui/Cursor.tsx index d7a8b55..c55034f 100644 --- a/src/widgets/cursor/ui/Cursor.tsx +++ b/src/widgets/cursor/ui/Cursor.tsx @@ -36,14 +36,20 @@ const Cursor = () => { setVisible(true); }; - const onPointerOver = (e: PointerEvent) => { - const target = e.target as HTMLElement | null; - - const interactive = target?.closest( - "a, button, [role='button'], input, select, textarea, [data-cursor-hover]" + const isInteractive = (target: Element | null) => + Boolean( + target?.closest( + "a, button, [role='button'], input, select, textarea, [data-cursor-hover]" + ) ); - setHovering(Boolean(interactive)); + const onPointerOver = (e: PointerEvent) => { + setHovering(isInteractive(e.target as Element | null)); + }; + + const onScroll = () => { + const target = document.elementFromPoint(mouseX, mouseY); + setHovering(isInteractive(target)); }; const onPointerDown = () => setPressed(true); @@ -62,6 +68,7 @@ const Cursor = () => { window.addEventListener("pointermove", onPointerMove, { passive: true }); window.addEventListener("pointerover", onPointerOver, { passive: true }); + window.addEventListener("scroll", onScroll, { capture: true, passive: true }); window.addEventListener("pointerdown", onPointerDown); window.addEventListener("pointerup", onPointerUp); @@ -71,6 +78,7 @@ const Cursor = () => { cancelAnimationFrame(raf); window.removeEventListener("pointermove", onPointerMove); window.removeEventListener("pointerover", onPointerOver); + window.removeEventListener("scroll", onScroll, { capture: true }); window.removeEventListener("pointerdown", onPointerDown); window.removeEventListener("pointerup", onPointerUp); document.documentElement.classList.remove("has-cursor");