Magnetic Card

Efficiency Gain+820%
$142,850
Labor hours saved3,420 hrs

Operations ROI

Turn manual operational bottlenecks into measurable revenue gains and real cost savings.

Install the following dependencies

pnpm add motion

Code

components/magnetic-card.tsx

"use client"

import React, { useRef, useState, useCallback } from "react";
interface MagneticCardProps extends React.HTMLAttributes<HTMLDivElement> {
  children: React.ReactNode;
  className?: string;
  intensity?: number; // Max translation in pixels
  maxTilt?: number; // Max 3D tilt in degrees
}
export default function MagneticCard({
  children,
  className = "",
  intensity = 16,
  maxTilt = 5,
  ...props
}: MagneticCardProps) {
  const cardRef = useRef<HTMLDivElement>(null);
  const [transformStyle, setTransformStyle] = useState<string>(
    "translate3d(0px, 0px, 0px) rotateX(0deg) rotateY(0deg)",
  );
  const [isHovered, setIsHovered] = useState(false);

  const handleMouseMove = useCallback(
    (e: React.MouseEvent<HTMLDivElement>) => {
      if (!cardRef.current) return;

      const rect = cardRef.current.getBoundingClientRect();
      const cardCenterX = rect.left + rect.width / 2;
      const cardCenterY = rect.top + rect.height / 2;

      // Normalized coordinates from -1 to 1 based on center
      const offsetX = (e.clientX - cardCenterX) / (rect.width / 2);
      const offsetY = (e.clientY - cardCenterY) / (rect.height / 2);

      // Clamp values between -1 and 1
      const clampedX = Math.max(-1, Math.min(1, offsetX));
      const clampedY = Math.max(-1, Math.min(1, offsetY));

      // Magnetic pull translation & 3D tilt
      const translateX = clampedX * intensity;
      const translateY = clampedY * intensity;
      const rotateY = clampedX * maxTilt;
      const rotateX = -clampedY * maxTilt;

      setTransformStyle(
        `translate3d(${translateX.toFixed(2)}px, ${translateY.toFixed(
          2,
        )}px, 0px) rotateX(${rotateX.toFixed(2)}deg) rotateY(${rotateY.toFixed(
          2,
        )}deg)`,
      );
    },
    [intensity, maxTilt],
  );

  const handleMouseEnter = () => {
    setIsHovered(true);
  };

  const handleMouseLeave = () => {
    setIsHovered(false);
    // Smoothly spring back to origin
    setTransformStyle("translate3d(0px, 0px, 0px) rotateX(0deg) rotateY(0deg)");
  };

  return (
    <div
      ref={cardRef}
      onMouseMove={handleMouseMove}
      onMouseEnter={handleMouseEnter}
      onMouseLeave={handleMouseLeave}
      className={`will-change-transform ${className}`}
      style={{
        transform: transformStyle,
        transformStyle: "preserve-3d",
        perspective: 1000,
        transition: isHovered
          ? "transform 0.12s cubic-bezier(0.25, 1, 0.5, 1)"
          : "transform 0.65s cubic-bezier(0.16, 1, 0.3, 1)",
      }}
      {...props}
    >
      {children}
    </div>
  );
}

© Hitesh Suthar

Built with Chai & ❤

Coding