diff options
| author | schererleander <leander@schererleander.de> | 2025-06-06 11:34:42 +0200 |
|---|---|---|
| committer | schererleander <leander@schererleander.de> | 2025-06-06 11:34:42 +0200 |
| commit | 72f57ec3d80b1ad69b20eebc343a1ffe2c84bde6 (patch) | |
| tree | 0a0ab23fdfbb4a3586374c60e5ed548332d3f844 | |
| parent | 90abecf3cc94c7b7f809a3a4b67c88ac71d23b7a (diff) | |
add spotlight hover effect
| -rw-r--r-- | src/components/CardLink.tsx | 74 |
1 files changed, 62 insertions, 12 deletions
diff --git a/src/components/CardLink.tsx b/src/components/CardLink.tsx index 3492038..01d277b 100644 --- a/src/components/CardLink.tsx +++ b/src/components/CardLink.tsx @@ -1,3 +1,4 @@ +import { useRef, useState } from "react"; import ExternalLinkIcon from "./ExternalLink"; interface Props { @@ -8,22 +9,71 @@ interface Props { } export default function CardLink({ title, body, href, imgSrc }: Props) { - const Wrapper = href ? 'a' : 'div'; + const divRef = useRef<HTMLDivElement>(null); + const [position, setPosition] = useState({ x: 0, y: 0 }); + const [opacity, setOpacity] = useState(0); + + const handleMouseMove: React.MouseEventHandler<HTMLDivElement> = (e) => { + if (!divRef.current) return; + const rect = divRef.current.getBoundingClientRect(); + setPosition({ x: e.clientX - rect.left, y: e.clientY - rect.top }); + }; + + const handleMouseEnter = () => setOpacity(0.6); + const handleMouseLeave = () => setOpacity(0); + return ( - <Wrapper + <div + ref={divRef} + className={`relative flex items-center gap-4 py-2 px-2 rounded-lg border border-neutral-300 dark:border-neutral-800 bg-neutral-100 dark:bg-neutral-900 overflow-hidden ${ + href ? "cursor-pointer" : "" + } + [--spotlight-light:rgba(255,255,255,0.1)] + [--spotlight-dark:rgba(0,0,0,0.1)]`} + onMouseMove={handleMouseMove} + onMouseEnter={handleMouseEnter} + onMouseLeave={handleMouseLeave} {...(href && { - href, - target: '_blank', - rel: 'noopener noreferrer', + onClick: () => window.open(href, "_blank", "noopener,noreferrer"), + role: "link", + tabIndex: 0, })} - className="group flex items-center gap-4 py-2 px-2 rounded-lg" > - <img src={imgSrc || ''} className="w-20 h-20 object-cover rounded-lg transition-transform duration-200 group-hover:scale-105"/> - <div className="flex-1"> + {/* Spotlight overlay - light */} + <div + className="pointer-events-none absolute inset-0 opacity-0 transition-opacity duration-300 ease-in-out" + style={{ + opacity, + background: `radial-gradient(circle at ${position.x}px ${position.y}px, + var(--spotlight-light), + transparent 80%)`, + }} + /> + + {/* Spotlight overlay - dark mode */} + <div + className="pointer-events-none absolute inset-0 opacity-0 transition-opacity duration-300 ease-in-out dark:block" + style={{ + opacity, + background: `radial-gradient(circle at ${position.x}px ${position.y}px, + var(--spotlight-dark), + transparent 80%)`, + }} + /> + + {/* Content */} + {imgSrc && ( + <img + src={imgSrc} + className="w-20 h-20 object-cover rounded-lg transition-transform duration-200 hover:scale-105 z-10" + alt={title} + /> + )} + <div className="flex-1 z-10"> <h3 className="font-medium">{title}</h3> - <p className="text-sm text-gray-600 black:text-gray-300">{body}</p> + <p className="text-sm text-neutral-800 dark:text-neutral-400">{body}</p> </div> - {href && <ExternalLinkIcon />} - </Wrapper> + {href && <div className="z-10"><ExternalLinkIcon /></div>} + </div> ); -}
\ No newline at end of file +} |
