aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorschererleander <leander@schererleander.de>2025-06-06 11:34:42 +0200
committerschererleander <leander@schererleander.de>2025-06-06 11:34:42 +0200
commit72f57ec3d80b1ad69b20eebc343a1ffe2c84bde6 (patch)
tree0a0ab23fdfbb4a3586374c60e5ed548332d3f844 /src
parent90abecf3cc94c7b7f809a3a4b67c88ac71d23b7a (diff)
add spotlight hover effect
Diffstat (limited to 'src')
-rw-r--r--src/components/CardLink.tsx74
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
+}