Docs
Sense Card

Sense Card

A card component that detects the direction from which a user approaches with their cursor and animates accordingly.

Premium Headphones
New

Premium Headphones

Noise-cancelling wireless headphones with premium sound quality

$299.99

Lightning Fast Performance

Experience blazing speeds with our optimized algorithms and cutting-edge technology. Perfect for demanding tasks and real-time processing.

Learn more
Blog post
Design
Feb 28, 2025

The Future of UI Design

Exploring emerging trends in user interface design and how they will shape the digital experiences of tomorrow.

Read article

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.

98%
Accuracy Rate
3x
Faster Processing

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.

JD
Jane Doe
CEO, TechCorp

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

PropTypeDefaultDescription
childrenReact.ReactNodeThe content to display inside the card.
classNamestringundefinedCustom class name for the container.
contentClassNamestringundefinedCustom class name for the content.
overlayColorstringrgba(0, 0, 0, 0.1)Optional background overlay color.
highlightColorstringrgba(255, 255, 255, 0.1)Optional highlight color for the directional effect.
effectIntensitynumber30Optional 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>