Premium Headphones
Noise-cancelling wireless headphones with premium sound quality
Lightning Fast Performance
Experience blazing speeds with our optimized algorithms and cutting-edge technology. Perfect for demanding tasks and real-time processing.
The Future of UI Design
Exploring emerging trends in user interface design and how they will shape the digital experiences of tomorrow.
Enterprise Security
Advanced protection for your business with end-to-end encryption, multi-factor authentication, and real-time threat monitoring.
- End-to-end encryption
- Multi-factor authentication
- Real-time threat monitoring
AI-Powered Analytics
Transform your data into actionable insights with our machine learning algorithms that adapt to your business needs.
This product exceeded all my expectations. The quality is outstanding, and the customer service team was incredibly helpful. I would highly recommend it to anyone looking for a reliable solution.
Installation
Copy and paste the following code into your project.
components/ui/sense-card.tsx
"use client";
import React, { useState, useRef, useEffect, CSSProperties } from "react";
import { cn } from "@/lib/utils";
export interface SenseCardProps {
children: React.ReactNode;
className?: string;
contentClassName?: string;
overlayColor?: string;
highlightColor?: string;
effectIntensity?: number;
animationStyle?: "lift" | "tilt" | "glow" | "scale";
}
export function SenseCard({
children,
className,
contentClassName,
overlayColor = "rgba(0, 0, 0, 0.05)",
highlightColor = "rgba(99, 102, 241, 0.1)",
effectIntensity = 50,
animationStyle = "lift",
}: SenseCardProps) {
const [isHovering, setIsHovering] = useState(false);
const [direction, setDirection] = useState<string | null>(null);
const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 });
const cardRef = useRef<HTMLDivElement>(null);
const contentRef = useRef<HTMLDivElement>(null);
const prevPosition = useRef<{ x: number; y: number } | null>(null);
const calculateDirection = (e: React.MouseEvent<HTMLDivElement>) => {
if (!cardRef.current || !prevPosition.current) return null;
const rect = cardRef.current.getBoundingClientRect();
const dx = e.clientX - prevPosition.current.x;
const dy = e.clientY - prevPosition.current.y;
const angle = Math.atan2(dy, dx) * (180 / Math.PI);
if (angle >= -45 && angle < 45) return "right";
if (angle >= 45 && angle < 135) return "bottom";
if (angle >= 135 || angle < -135) return "left";
return "top";
};
const handleMouseMove = (e: React.MouseEvent<HTMLDivElement>) => {
if (!cardRef.current) return;
const rect = cardRef.current.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
setMousePosition({
x: (x / rect.width) * 100,
y: (y / rect.height) * 100,
});
};
const handleMouseEnter = (e: React.MouseEvent<HTMLDivElement>) => {
if (!prevPosition.current) {
prevPosition.current = { x: e.clientX, y: e.clientY };
return;
}
const dir = calculateDirection(e);
setDirection(dir);
setIsHovering(true);
};
const handleMouseLeave = () => {
setIsHovering(false);
};
useEffect(() => {
const updatePrevPosition = (e: MouseEvent) => {
prevPosition.current = { x: e.clientX, y: e.clientY };
};
window.addEventListener("mousemove", updatePrevPosition, { passive: true });
return () => window.removeEventListener("mousemove", updatePrevPosition);
}, []);
const getCardStyles = (): CSSProperties => {
if (!isHovering) {
return {
transform: "translate(0, 0) scale(1)",
transition: "all 0.4s cubic-bezier(0.2, 0.8, 0.2, 1)",
};
}
// Different animation styles for the card container
switch (animationStyle) {
case "tilt":
// For tilt, we keep the card container stable
return {
boxShadow:
"0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)",
transition: "box-shadow 0.3s cubic-bezier(0.2, 0.8, 0.2, 1)",
};
case "glow":
return {
boxShadow:
"0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)",
transition: "all 0.3s cubic-bezier(0.2, 0.8, 0.2, 1)",
};
case "scale":
return {
transform: "scale(1.02)",
boxShadow:
"0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)",
transition: "all 0.3s cubic-bezier(0.2, 0.8, 0.2, 1)",
};
case "lift":
default:
return {
transform: "translate(0, -6px) scale(1.01)",
boxShadow:
"0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)",
transition: "all 0.3s cubic-bezier(0.2, 0.8, 0.2, 1)",
};
}
};
const getContentStyles = (): CSSProperties => {
if (!isHovering) {
return {
transform: "translate(0, 0) rotateX(0) rotateY(0)",
transition: "transform 0.3s cubic-bezier(0.2, 0.8, 0.2, 1)",
};
}
// Different animation styles for the content
switch (animationStyle) {
case "tilt":
const tiltX = (mousePosition.y - 50) / 15;
const tiltY = (mousePosition.x - 50) / -15;
return {
transform: `perspective(1000px) rotateX(${tiltX}deg) rotateY(${tiltY}deg)`,
transition: "transform 0.15s ease-out",
};
case "glow":
const glowX = mousePosition.x;
const glowY = mousePosition.y;
return {
background: `radial-gradient(circle at ${glowX}% ${glowY}%, ${highlightColor} 0%, transparent 50%)`,
filter: "brightness(1.05)",
transition: "all 0.1s cubic-bezier(0.2, 0.8, 0.2, 1)",
};
default:
return {};
}
};
const getHighlightStyles = (): CSSProperties => {
if (!isHovering || !direction) return { opacity: 0 };
const intensity = Math.min(Math.max(effectIntensity, 0), 100) / 100;
const gradientSize = `${50 * intensity}%`;
const baseStyles: CSSProperties = {
position: "absolute",
inset: 0,
opacity: 1,
pointerEvents: "none",
borderRadius: "inherit",
transition: "opacity 0.3s ease-in-out, background-position 0.3s ease-out",
};
switch (direction) {
case "top":
return {
...baseStyles,
background: `linear-gradient(to bottom, ${highlightColor} 0%, transparent ${gradientSize})`,
};
case "right":
return {
...baseStyles,
background: `linear-gradient(to left, ${highlightColor} 0%, transparent ${gradientSize})`,
};
case "bottom":
return {
...baseStyles,
background: `linear-gradient(to top, ${highlightColor} 0%, transparent ${gradientSize})`,
};
case "left":
return {
...baseStyles,
background: `linear-gradient(to right, ${highlightColor} 0%, transparent ${gradientSize})`,
};
default:
return { opacity: 0 };
}
};
const getOverlayStyles = (): CSSProperties => {
let initialClipPath = "inset(100% 100% 100% 100%)";
if (direction === "top") initialClipPath = "inset(100% 0 0 0)";
if (direction === "right") initialClipPath = "inset(0 100% 0 0)";
if (direction === "bottom") initialClipPath = "inset(0 0 100% 0)";
if (direction === "left") initialClipPath = "inset(0 0 0 100%)";
return {
position: "absolute",
inset: 0,
backgroundColor: overlayColor,
opacity: 1,
clipPath: isHovering ? "inset(0 0 0 0)" : initialClipPath,
transition:
"clip-path 0.3s cubic-bezier(0.2, 0.8, 0.2, 1), opacity 0.2s ease-in-out",
borderRadius: "inherit",
};
};
return (
<div
ref={cardRef}
className={cn(
"relative transition-all duration-300 will-change-transform",
className,
)}
style={getCardStyles()}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
onMouseMove={handleMouseMove}
>
<div style={getOverlayStyles()} />
<div style={getHighlightStyles()} />
<div
ref={contentRef}
className={cn("relative z-10 h-full w-full", contentClassName)}
style={getContentStyles()}
>
{children}
</div>
</div>
);
}
Update the import paths to match your project setup.
import { SenseCard } from "@/components/ui/sense-card";
Usage
<SenseCard
className="h-[380px] w-full cursor-pointer overflow-hidden"
highlightColor="rgba(255, 255, 255, 0.2)"
effectIntensity={60}
animationStyle="tilt"
>
<div className="flex h-full flex-col p-6">
<h3 className="text-xl font-semibold">Card Title</h3>
<p className="text-muted-foreground">Card content goes here...</p>
</div>
</SenseCard>
Props
Prop | Type | Default | Description |
---|---|---|---|
children | React.ReactNode | The content to display inside the card. | |
className | string | undefined | Custom class name for the container. |
contentClassName | string | undefined | Custom class name for the content. |
overlayColor | string | rgba(0, 0, 0, 0.1) | Optional background overlay color. |
highlightColor | string | rgba(255, 255, 255, 0.1) | Optional highlight color for the directional effect. |
effectIntensity | number | 30 | Optional intensity of the directional effect (0-100). |
animationStyle | "lift" | "tilt" | "glow" | "scale" | "lift" | Optional animation style to apply on hover. |
Examples
Product Card with Tilt Effect
<SenseCard
className="h-[380px] w-full cursor-pointer overflow-hidden"
highlightColor="rgba(255, 255, 255, 0.2)"
effectIntensity={80}
animationStyle="tilt"
>
<div className="flex h-full flex-col">
<div className="relative h-[220px] w-full overflow-hidden">
<Image
src="/product-image.jpg"
alt="Product"
fill
className="object-cover transition-transform duration-700 group-hover:scale-110"
/>
</div>
<div className="flex flex-1 flex-col p-6">
<h3 className="text-xl font-semibold">Product Name</h3>
<p className="text-muted-foreground">Product description...</p>
<div className="mt-auto flex justify-between">
<span className="font-semibold">$99.99</span>
<button className="rounded-full bg-primary px-4 py-2 text-primary-foreground">
Buy Now
</button>
</div>
</div>
</div>
</SenseCard>
Feature Card with Glow Effect
<SenseCard
className="h-[380px] w-full cursor-pointer overflow-hidden"
highlightColor="rgba(0, 100, 255, 0.15)"
effectIntensity={90}
animationStyle="glow"
>
<div className="flex h-full flex-col p-7">
<div className="mb-5 flex h-12 w-12 items-center justify-center rounded-full bg-primary/10">
<Zap className="h-6 w-6 text-primary" />
</div>
<h3 className="text-xl font-semibold">Feature Title</h3>
<p className="text-muted-foreground">Feature description...</p>
</div>
</SenseCard>
Blog Post Card with Scale Effect
<SenseCard
className="h-[380px] w-full cursor-pointer overflow-hidden"
highlightColor="rgba(255, 255, 255, 0.2)"
effectIntensity={60}
animationStyle="scale"
>
<div className="flex h-full flex-col">
<div className="relative h-[180px] w-full overflow-hidden">
<Image
src="/blog-image.jpg"
alt="Blog post"
fill
className="object-cover"
/>
</div>
<div className="flex flex-1 flex-col p-6">
<div className="text-xs text-muted-foreground">Feb 28, 2025</div>
<h3 className="text-xl font-semibold">Blog Post Title</h3>
<p className="text-muted-foreground">Blog post excerpt...</p>
</div>
</div>
</SenseCard>