Holo Card
Pokemon-style holographic card with cursor-driven 3D tilt, rainbow refraction, sparkle shift, and specular highlight
cardholographicpokemontilt3dhoverrainbowsparkle
Install dependencies
$npm install framer-motionPreview
Source Code
"use client";
import { useRef, useState } from "react";
import { motion, useMotionTemplate, useMotionValue, useSpring } from "framer-motion";
interface HoloCardProps {
children?: React.ReactNode;
className?: string;
/** Max tilt in degrees. Default 14. */
tilt?: number;
/** Holographic layer intensity 0–1. Default 0.7. */
intensity?: number;
/** Show sparkle texture overlay. Default true. */
sparkles?: boolean;
}
export default function HoloCard({
children,
className = "",
tilt = 14,
intensity = 0.7,
sparkles = true,
}: HoloCardProps) {
const ref = useRef<HTMLDivElement>(null);
const [hovered, setHovered] = useState(false);
const px = useMotionValue(50);
const py = useMotionValue(50);
const cx = useSpring(px, { stiffness: 220, damping: 30, mass: 0.5 });
const cy = useSpring(py, { stiffness: 220, damping: 30, mass: 0.5 });
const rotateX = useSpring(useMotionValue(0), { stiffness: 220, damping: 26 });
const rotateY = useSpring(useMotionValue(0), { stiffness: 220, damping: 26 });
const handleMove = (e: React.MouseEvent<HTMLDivElement>) => {
const el = ref.current;
if (!el) return;
const rect = el.getBoundingClientRect();
const x = ((e.clientX - rect.left) / rect.width) * 100;
const y = ((e.clientY - rect.top) / rect.height) * 100;
px.set(x);
py.set(y);
rotateY.set(((x - 50) / 50) * tilt);
rotateX.set(((50 - y) / 50) * tilt);
};
const handleLeave = () => {
setHovered(false);
px.set(50);
py.set(50);
rotateX.set(0);
rotateY.set(0);
};
const specular = useMotionTemplate`radial-gradient(circle at ${cx}% ${cy}%, rgba(255,255,255,0.55), rgba(255,255,255,0) 40%)`;
const holoConic = useMotionTemplate`conic-gradient(from ${cx}deg at ${cx}% ${cy}%, #ff6ec7, #ffd93d, #6dffa1, #4cc9ff, #b06bff, #ff6ec7)`;
const sparkleShift = useMotionTemplate`${cx}% ${cy}%`;
return (
<motion.div
ref={ref}
onMouseEnter={() => setHovered(true)}
onMouseMove={handleMove}
onMouseLeave={handleLeave}
style={{
rotateX,
rotateY,
transformPerspective: 1200,
transformStyle: "preserve-3d",
}}
className={`relative overflow-hidden rounded-2xl ${className}`}
>
<div className="relative z-10">{children}</div>
{/* Holographic rainbow layer */}
<motion.div
className="pointer-events-none absolute inset-0 transition-opacity duration-300"
style={{
background: holoConic,
mixBlendMode: "color-dodge",
opacity: hovered ? intensity : 0,
}}
/>
{/* Sparkle texture — tiny dots shifted by cursor */}
{sparkles && (
<motion.div
className="pointer-events-none absolute inset-0 transition-opacity duration-300"
style={{
opacity: hovered ? 0.55 : 0,
backgroundImage:
"radial-gradient(rgba(255,255,255,0.85) 1px, transparent 1px), radial-gradient(rgba(255,255,255,0.6) 1px, transparent 1px)",
backgroundSize: "14px 14px, 22px 22px",
backgroundPosition: sparkleShift,
mixBlendMode: "overlay",
filter: "blur(0.4px)",
}}
/>
)}
{/* Specular highlight */}
<motion.div
className="pointer-events-none absolute inset-0 transition-opacity duration-300"
style={{
background: specular,
opacity: hovered ? 0.9 : 0,
mixBlendMode: "soft-light",
}}
/>
{/* Edge glare on tilt */}
<div
className="pointer-events-none absolute inset-0 rounded-2xl ring-1 ring-inset ring-white/10"
aria-hidden="true"
/>
</motion.div>
);
}