Majestic Card
Cards that float in layers when scrolling. Tilt and rotate subtly based on mouse movement.
Majestic Cards
Elevate your UI with stunning interactive cards that captivate users and enhance engagement
Premium
Enterprise Plan
$99/ month
- All premium components
- Priority support
- Advanced analytics
- Custom branding
Dashboard
Revenue$24,892
Active Users8,245
Conversion Rate5.2%
Weather Forecast
Live23°
Thunderstorm in Indore
Mon
21°
Tue
24°
Wed
22°
Thu
25°
Installation Guide
1
Install Dependencies
Framer Motion
npm install framer-motion
Utility Functions
npm install clsx tailwind-merge
2
Setup Configuration
Create file:
/lib/utils.ts
/lib/utils.ts
Configuration1import { clsx, type ClassValue } from "clsx";
2import { twMerge } from "tailwind-merge";
3
4export function cn(...inputs: ClassValue[]) {
5 return twMerge(clsx(inputs));
6}
3
Copy Component Code
Majestic Card.tsx
TypeScript1"use client";
2
3import type React from "react";
4import { useRef, useEffect, useState, useCallback, useMemo } from "react";
5import { cn } from "@/lib/utils";
6import {
7 motion,
8 useMotionValue,
9 useSpring,
10 useTransform,
11 MotionProps,
12 HTMLMotionProps,
13} from "framer-motion";
14
15export interface MajesticCardProps
16 extends Omit<React.HTMLAttributes<HTMLDivElement>, keyof MotionProps> {
17 variant?:
18 | "parallax"
19 | "tilt"
20 | "float"
21 | "magnetic"
22 | "layered"
23 | "morph"
24 | "breathe"
25 | "glow"
26 | "wave";
27 intensity?: 1 | 2 | 3 | 4 | 5;
28 theme?:
29 | "light"
30 | "dark"
31 | "glass"
32 | "gradient"
33 | "neon"
34 | "cosmic"
35 | "custom";
36 customColors?: {
37 background: string;
38 border?: string;
39 shadow?: string;
40 glow?: string;
41 };
42 rounded?: "none" | "sm" | "md" | "lg" | "xl" | "full" | "pill";
43 shadow?: boolean;
44 shadowSize?: "sm" | "md" | "lg" | "xl" | "2xl";
45 shadowType?: "standard" | "soft" | "hard" | "inner" | "glow";
46 border?: boolean;
47 borderStyle?: "solid" | "dashed" | "dotted" | "gradient" | "glow";
48 hoverEffect?: boolean;
49 scrollEffect?: boolean;
50 reduceMotion?: boolean;
51 confettiEffect?: boolean;
52 speed?: "slow" | "normal" | "fast";
53 blurBackground?: boolean;
54 layerCount?: 1 | 2 | 3 | 4 | 5;
55 layerSeparation?: 1 | 2 | 3 | 4 | 5;
56 floatPattern?: "simple" | "complex" | "random" | "sine" | "circle";
57 children: React.ReactNode;
58}
59
60export function MajesticCard({
61 variant = "tilt",
62 intensity = 3,
63 theme = "light",
64 customColors,
65 rounded = "lg",
66 shadow = true,
67 shadowSize = "md",
68 shadowType = "standard",
69 border = false,
70 borderStyle = "solid",
71 hoverEffect = true,
72 scrollEffect = false,
73 reduceMotion = false,
74 confettiEffect = false,
75 speed = "normal",
76 blurBackground = false,
77 layerCount = 3,
78 layerSeparation = 2,
79 floatPattern = "simple",
80 className,
81 children,
82 ...props
83}: MajesticCardProps) {
84 const cardRef = useRef<HTMLDivElement>(null);
85 const [isHovered, setIsHovered] = useState(false);
86 const [layers, setLayers] = useState<HTMLElement[]>([]);
87 const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 });
88 const [showConfetti, setShowConfetti] = useState(false);
89 const [generatedLayers, setGeneratedLayers] = useState<React.ReactNode[]>([]);
90 const [floatPhase, setFloatPhase] = useState(0);
91 const [showRipple, setShowRipple] = useState(false);
92 const [ripplePosition, setRipplePosition] = useState({ x: 50, y: 50 });
93 const [waveTime, setWaveTime] = useState(0);
94
95 const x = useMotionValue(0);
96 const y = useMotionValue(0);
97 const rotateX = useMotionValue(0);
98 const rotateY = useMotionValue(0);
99 const scale = useMotionValue(1);
100 const floatY = useMotionValue(0);
101 const floatX = useMotionValue(0);
102 const rotate = useMotionValue(0);
103
104 const springConfig = {
105 stiffness: speed === "fast" ? 700 : speed === "slow" ? 200 : 400,
106 damping: speed === "fast" ? 20 : speed === "slow" ? 40 : 30,
107 };
108
109 const floatSpringConfig = {
110 stiffness: 50,
111 damping: 30,
112 mass: 1.5,
113 };
114
115 const springX = useSpring(x, springConfig);
116 const springY = useSpring(y, springConfig);
117 const springRotateX = useSpring(rotateX, springConfig);
118 const springRotateY = useSpring(rotateY, springConfig);
119 const springScale = useSpring(scale, springConfig);
120 const springFloatY = useSpring(floatY, floatSpringConfig);
121 const springFloatX = useSpring(floatX, floatSpringConfig);
122 const springRotate = useSpring(rotate, {
123 stiffness: 60,
124 damping: 20,
125 mass: 1,
126 });
127
128 const borderRadius = useTransform(
129 springScale,
130 [1, 1.05],
131 [
132 getBaseRadius(),
133 variant === "morph" && isHovered
134 ? "40% 60% 60% 40% / 60% 30% 70% 40%"
135 : getBaseRadius(),
136 ]
137 );
138
139 function getBaseRadius() {
140 switch (rounded) {
141 case "none":
142 return "0px";
143 case "sm":
144 return "0.125rem";
145 case "md":
146 return "0.375rem";
147 case "lg":
148 return "0.5rem";
149 case "xl":
150 return "0.75rem";
151 case "full":
152 return "9999px";
153 case "pill":
154 return "9999px";
155 default:
156 return "0.5rem";
157 }
158 }
159
160 const themeStyles = {
161 light: {
162 background: "bg-white dark:bg-gray-800",
163 border: "border-gray-200 dark:border-gray-700",
164 shadow: "shadow-gray-200/70 dark:shadow-gray-900/70",
165 glow: "shadow-white/20 dark:shadow-white/10",
166 },
167 dark: {
168 background: "bg-gray-900 dark:bg-gray-950 text-white",
169 border: "border-gray-800 dark:border-gray-900",
170 shadow: "shadow-gray-900/50 dark:shadow-black/50",
171 glow: "shadow-gray-800/30 dark:shadow-gray-800/20",
172 },
173 glass: {
174 background: "bg-white/70 dark:bg-gray-900/70 backdrop-blur-md",
175 border: "border-white/20 dark:border-gray-800/20",
176 shadow: "shadow-gray-200/50 dark:shadow-gray-900/50",
177 glow: "shadow-white/30 dark:shadow-gray-700/30",
178 },
179 gradient: {
180 background:
181 "bg-gradient-to-br from-blue-50 to-indigo-100 dark:from-gray-800 dark:to-gray-900",
182 border: "border-blue-100 dark:border-gray-700",
183 shadow: "shadow-blue-200/50 dark:shadow-gray-900/50",
184 glow: "shadow-blue-300/50 dark:shadow-blue-900/30",
185 },
186 neon: {
187 background: "bg-gray-950 text-white",
188 border: "border-indigo-500",
189 shadow: "shadow-indigo-500/50",
190 glow: "shadow-indigo-500/70",
191 },
192 cosmic: {
193 background:
194 "bg-gradient-to-br from-purple-900 via-violet-800 to-blue-900 text-white",
195 border: "border-purple-500/30",
196 shadow: "shadow-purple-700/50",
197 glow: "shadow-purple-500/70",
198 },
199 custom: {
200 background: customColors?.background
201 ? `bg-[${customColors.background}]`
202 : "bg-white",
203 border: customColors?.border
204 ? `border-[${customColors.border}]`
205 : "border-gray-200",
206 shadow: customColors?.shadow
207 ? `shadow-[${customColors.shadow}]`
208 : "shadow-gray-200",
209 glow: customColors?.glow
210 ? `shadow-[${customColors.glow}]`
211 : "shadow-white/20",
212 },
213 };
214
215 const currentTheme = themeStyles[theme];
216
217 const intensityFactors = useMemo(
218 () => ({
219 1: 0.2,
220 2: 0.4,
221 3: 0.6,
222 4: 0.8,
223 5: 1.0,
224 }),
225 []
226 );
227
228 const separationFactors = useMemo(
229 () => ({
230 1: 4,
231 2: 8,
232 3: 12,
233 4: 16,
234 5: 20,
235 }),
236 []
237 );
238
239 const roundedStyles = useMemo(
240 () => ({
241 none: "rounded-none",
242 sm: "rounded-sm",
243 md: "rounded-md",
244 lg: "rounded-lg",
245 xl: "rounded-xl",
246 full: "rounded-full",
247 pill: "rounded-full",
248 }),
249 []
250 );
251
252 const getBorderStyles = useCallback(() => {
253 if (!border) return "";
254
255 switch (borderStyle) {
256 case "gradient":
257 return "border-2 bg-gradient-to-r from-blue-500 to-purple-500 border-transparent bg-clip-border";
258 case "glow":
259 return `border ${
260 currentTheme.border
261 } shadow-[0_0_10px_3px_rgba(255,255,255,0.3)] shadow-${
262 theme === "neon" ? "indigo" : theme === "cosmic" ? "purple" : "blue"
263 }-500/30`;
264 case "dashed":
265 return `border-dashed border-2 ${currentTheme.border}`;
266 case "dotted":
267 return `border-dotted border-2 ${currentTheme.border}`;
268 default:
269 return `border ${currentTheme.border}`;
270 }
271 }, [border, borderStyle, currentTheme.border, theme]);
272
273 const getShadowStyles = useCallback(() => {
274 if (!shadow) return "";
275
276 const sizes = {
277 sm: "shadow-sm",
278 md: "shadow",
279 lg: "shadow-lg",
280 xl: "shadow-xl",
281 "2xl": "shadow-2xl",
282 };
283
284 switch (shadowType) {
285 case "soft":
286 return `${sizes[shadowSize]} ${currentTheme.shadow} blur-lg`;
287 case "hard":
288 return `${sizes[shadowSize]} ${currentTheme.shadow}`;
289 case "inner":
290 return `inner-shadow ${currentTheme.shadow}`;
291 case "glow":
292 return isHovered
293 ? `shadow-[0_0_20px_5px] ${currentTheme.glow}`
294 : `shadow-[0_0_10px_2px] ${currentTheme.glow}`;
295 default:
296 return `${sizes[shadowSize]} ${currentTheme.shadow}`;
297 }
298 }, [
299 shadow,
300 shadowSize,
301 shadowType,
302 currentTheme.shadow,
303 currentTheme.glow,
304 isHovered,
305 ]);
306
307 useEffect(() => {
308 if (variant === "layered") {
309 const newLayers = [];
310 const separationDistance = separationFactors[layerSeparation];
311 const layerOpacities = [0.1, 0.2, 0.3, 0.4];
312 const layerScales = [0.98, 0.96, 0.94, 0.92];
313
314 for (let i = 0; i < Math.min(layerCount, 4); i++) {
315 newLayers.push(
316 <motion.div
317 key={`layer-${i}`}
318 className={cn(
319 "absolute inset-0 pointer-events-none",
320 currentTheme.background,
321 i === 0 ? getBorderStyles() : "",
322 rounded !== "none" ? roundedStyles[rounded] : ""
323 )}
324 style={{
325 zIndex: -1 - i,
326 opacity: layerOpacities[i],
327 scale: layerScales[i],
328 y: i * separationDistance,
329 x: 0,
330 transformStyle: "preserve-3d",
331 }}
332 data-layer={i + 1}
333 />
334 );
335 }
336
337 setGeneratedLayers(newLayers);
338 }
339 }, [
340 variant,
341 layerCount,
342 layerSeparation,
343 theme,
344 border,
345 borderStyle,
346 rounded,
347 currentTheme.background,
348 getBorderStyles,
349 roundedStyles,
350 separationFactors,
351 ]);
352
353 useEffect(() => {
354 if (variant === "layered" && cardRef.current) {
355 const layerElements = Array.from(
356 cardRef.current.querySelectorAll("[data-layer]")
357 );
358 setLayers(layerElements as HTMLElement[]);
359 }
360 }, [variant, generatedLayers]);
361
362 useEffect(() => {
363 if (variant === "wave" && !reduceMotion) {
364 let animationFrame: number;
365 let lastTime = 0;
366 const animate = (time: number) => {
367 if (!lastTime) lastTime = time;
368 const delta = (time - lastTime) / 1000;
369 lastTime = time;
370
371 setWaveTime((prev) => prev + delta);
372
373 animationFrame = requestAnimationFrame(animate);
374 };
375
376 animationFrame = requestAnimationFrame(animate);
377
378 return () => {
379 cancelAnimationFrame(animationFrame);
380 };
381 }
382 }, [variant, reduceMotion]);
383
384 useEffect(() => {
385 if ((variant === "float" || variant === "wave") && !reduceMotion) {
386 let animationFrame: number;
387 let lastTime = 0;
388 const floatSpeed = speed === "slow" ? 0.5 : speed === "fast" ? 2 : 1;
389
390 const animate = (time: number) => {
391 if (!lastTime) lastTime = time;
392 const delta = (time - lastTime) / 1000;
393 lastTime = time;
394
395 setFloatPhase((prev) => (prev + delta * floatSpeed) % (Math.PI * 2));
396
397 animationFrame = requestAnimationFrame(animate);
398 };
399
400 animationFrame = requestAnimationFrame(animate);
401
402 return () => {
403 cancelAnimationFrame(animationFrame);
404 };
405 }
406 }, [variant, reduceMotion, speed]);
407
408 useEffect(() => {
409 if ((variant === "float" || variant === "wave") && !reduceMotion) {
410 const factor = intensityFactors[intensity];
411
412 switch (floatPattern) {
413 case "simple":
414 floatY.set(Math.sin(floatPhase) * 10 * factor);
415 break;
416 case "complex":
417 floatY.set(Math.sin(floatPhase) * 8 * factor);
418 floatX.set(Math.sin(floatPhase * 0.5) * 5 * factor);
419 rotate.set(Math.sin(floatPhase * 0.3) * 2 * factor);
420 break;
421 case "random":
422 if (floatPhase % 0.5 < 0.05) {
423 floatY.set(Math.sin(floatPhase) * (5 + Math.random() * 5) * factor);
424 floatX.set((Math.random() - 0.5) * 10 * factor);
425 }
426 break;
427 case "sine":
428 floatY.set(Math.sin(floatPhase) * 12 * factor);
429 break;
430 case "circle":
431 floatX.set(Math.sin(floatPhase) * 10 * factor);
432 floatY.set(Math.cos(floatPhase) * 10 * factor);
433 break;
434 default:
435 floatY.set(Math.sin(floatPhase) * 10 * factor);
436 }
437 }
438 }, [
439 floatPhase,
440 variant,
441 intensity,
442 floatPattern,
443 reduceMotion,
444 floatX,
445 floatY,
446 rotate,
447 intensityFactors,
448 ]);
449
450 const cleanupRef = useRef<(() => void) | null>(null);
451
452 useEffect(() => {
453 if (!cardRef.current || !hoverEffect || reduceMotion) return;
454
455 const card = cardRef.current;
456 const factor = intensityFactors[intensity];
457 const layersCopy = [...layers];
458
459 const handleMouseMove = (e: MouseEvent) => {
460 if (!card) return;
461
462 const rect = card.getBoundingClientRect();
463 const centerX = rect.left + rect.width / 2;
464 const centerY = rect.top + rect.height / 2;
465
466 const mouseX = e.clientX - centerX;
467 const mouseY = e.clientY - centerY;
468
469 setMousePosition({
470 x: (e.clientX - rect.left) / rect.width,
471 y: (e.clientY - rect.top) / rect.height,
472 });
473
474 if (variant === "tilt" || variant === "morph" || variant === "breathe") {
475 rotateX.set(-mouseY * 0.01 * factor);
476 rotateY.set(mouseX * 0.01 * factor);
477 } else if (variant === "magnetic") {
478 x.set(mouseX * 0.1 * factor);
479 y.set(mouseY * 0.1 * factor);
480 } else if (variant === "layered") {
481 layersCopy.forEach((layer) => {
482 const depth = Number.parseFloat(
483 layer.getAttribute("data-layer") || "1"
484 );
485 const moveX = mouseX * 0.02 * depth * factor;
486 const moveY = mouseY * 0.02 * depth * factor;
487
488 layer.style.transform = `translate3d(${moveX}px, ${moveY}px, 0) rotate(${
489 moveX * 0.02
490 }deg) scale(${1 - depth * 0.01 * factor})`;
491 });
492 } else if (variant === "wave") {
493 const dx = mouseX / rect.width;
494 const dy = mouseY / rect.height;
495 const distance = Math.sqrt(dx * dx + dy * dy);
496 const waveIntensity = Math.max(0, 1 - distance) * factor * 15;
497
498 rotateX.set(-dy * waveIntensity);
499 rotateY.set(dx * waveIntensity);
500 if (isHovered) {
501 const rippleX = ((e.clientX - rect.left) / rect.width) * 100;
502 const rippleY = ((e.clientY - rect.top) / rect.height) * 100;
503 setRipplePosition({ x: rippleX, y: rippleY });
504 setShowRipple(true);
505 }
506 }
507 };
508
509 const handleMouseLeave = () => {
510 x.set(0);
511 y.set(0);
512 rotateX.set(0);
513 rotateY.set(0);
514 scale.set(1);
515
516 if (variant === "layered") {
517 layersCopy.forEach((layer) => {
518 layer.style.transform = "";
519 });
520 }
521 };
522
523 document.addEventListener("mousemove", handleMouseMove);
524 card.addEventListener("mouseleave", handleMouseLeave);
525
526 cleanupRef.current = () => {
527 document.removeEventListener("mousemove", handleMouseMove);
528 card.removeEventListener("mouseleave", handleMouseLeave);
529 };
530
531 return () => {
532 if (cleanupRef.current) {
533 cleanupRef.current();
534 }
535 };
536 }, [
537 variant,
538 intensity,
539 hoverEffect,
540 layers,
541 reduceMotion,
542 x,
543 y,
544 rotateX,
545 rotateY,
546 scale,
547 intensityFactors,
548 isHovered,
549 ]);
550
551 useEffect(() => {
552 if (!scrollEffect || reduceMotion) return;
553
554 const handleScroll = () => {
555 const newScrollPosition = window.scrollY;
556
557 if (cardRef.current) {
558 const rect = cardRef.current.getBoundingClientRect();
559 const windowHeight = window.innerHeight;
560 const visiblePercentage = Math.min(
561 Math.max(0, (windowHeight - rect.top) / windowHeight),
562 Math.max(0, rect.bottom / windowHeight)
563 );
564
565 const factor = intensityFactors[intensity];
566
567 if (variant === "float" || variant === "parallax") {
568 y.set(-newScrollPosition * 0.05 * factor * visiblePercentage);
569 }
570
571 if (rect.top < windowHeight && rect.bottom > 0) {
572 const scrollScale = 1 + visiblePercentage * 0.05 * factor;
573 scale.set(Math.min(scrollScale, 1.1));
574 }
575 }
576 };
577
578 window.addEventListener("scroll", handleScroll);
579
580 return () => {
581 window.removeEventListener("scroll", handleScroll);
582 };
583 }, [
584 scrollEffect,
585 variant,
586 intensity,
587 reduceMotion,
588 y,
589 scale,
590 intensityFactors,
591 ]);
592
593 const handleMouseEnter = () => {
594 setIsHovered(true);
595 if (!reduceMotion) {
596 scale.set(1.02);
597
598 if (confettiEffect) {
599 setShowConfetti(true);
600 setTimeout(() => setShowConfetti(false), 1500);
601 }
602 }
603 };
604
605 const handleMouseLeave = () => {
606 setIsHovered(false);
607 if (!reduceMotion) {
608 scale.set(1);
609 }
610 };
611 const Confetti = () => {
612 return showConfetti ? (
613 <div className="absolute inset-0 overflow-hidden pointer-events-none">
614 {Array.from({ length: 30 }).map((_, i) => {
615 const size = Math.random() * 10 + 5;
616 const color = [
617 "bg-red-500",
618 "bg-blue-500",
619 "bg-green-500",
620 "bg-yellow-500",
621 "bg-purple-500",
622 "bg-pink-500",
623 ][Math.floor(Math.random() * 6)];
624 const left = Math.random() * 100;
625 const animationDuration = (Math.random() * 1 + 0.5).toFixed(1);
626
627 return (
628 <div
629 key={i}
630 className={`absolute ${color} rounded-full opacity-70`}
631 style={{
632 width: size,
633 height: size,
634 left: `${left}%`,
635 top: "-20px",
636 animation: `confetti ${animationDuration}s ease-out forwards`,
637 animationDelay: `${Math.random() * 0.3}s`,
638 }}
639 />
640 );
641 })}
642 </div>
643 ) : null;
644 };
645
646 const getAnimationClasses = () => {
647 if (reduceMotion) return "";
648
649 if (variant === "breathe") {
650 return "animate-pulse";
651 }
652 return "";
653 };
654
655 const getGlowGradient = () => {
656 if (variant !== "glow" || !isHovered) return "";
657
658 const x = mousePosition.x * 100;
659 const y = mousePosition.y * 100;
660
661 return (
662 <div
663 className="absolute inset-0 pointer-events-none transition-opacity duration-300 rounded-inherit"
664 style={{
665 background: `radial-gradient(circle at ${x}% ${y}%, ${
666 theme === "neon"
667 ? "rgba(99, 102, 241, 0.7)"
668 : theme === "cosmic"
669 ? "rgba(147, 51, 234, 0.7)"
670 : "rgba(147, 197, 253, 0.5)"
671 }, transparent 60%)`,
672 opacity: isHovered ? 1 : 0,
673 borderRadius: "inherit",
674 }}
675 />
676 );
677 };
678
679 useEffect(() => {
680 if (!confettiEffect && variant !== "float" && variant !== "wave") return;
681
682 const style = document.createElement("style");
683 style.innerHTML = `
684 @keyframes confetti {
685 0% { transform: translateY(0) rotate(0); opacity: 1; }
686 100% { transform: translateY(300px) rotate(720deg); opacity: 0; }
687 }
688
689 .inner-shadow {
690 box-shadow: inset 0 2px 4px 0 rgba(0, 0, 0, 0.06);
691 }
692
693 @keyframes float {
694 0% { transform: translateY(0px); }
695 50% { transform: translateY(-10px); }
696 100% { transform: translateY(0px); }
697 }
698
699 @keyframes floatComplex {
700 0% { transform: translate(0px, 0px) rotate(0deg); }
701 25% { transform: translate(5px, -10px) rotate(1deg); }
702 50% { transform: translate(0px, -15px) rotate(0deg); }
703 75% { transform: translate(-5px, -10px) rotate(-1deg); }
704 100% { transform: translate(0px, 0px) rotate(0deg); }
705 }
706
707 @keyframes floatWave {
708 0% { transform: translateX(0px); }
709 25% { transform: translateX(5px); }
710 75% { transform: translateX(-5px); }
711 100% { transform: translateX(0px); }
712 }
713 @keyframes ripple {
714 0% { transform: translate(-50%, -50%) scale(0); opacity: 0.7; }
715 100% { transform: translate(-50%, -50%) scale(3); opacity: 0; }
716 }
717
718 @keyframes waveFlow {
719 0% { transform: translateX(-50%) scaleY(1); }
720 50% { transform: translateX(0%) scaleY(1.2); }
721 100% { transform: translateX(50%) scaleY(1); }
722 }
723 `;
724 document.head.appendChild(style);
725
726 return () => {
727 document.head.removeChild(style);
728 };
729 }, [confettiEffect, variant]);
730
731 const motionProps: HTMLMotionProps<"div"> = {
732 ref: cardRef,
733 style: {
734 x:
735 variant === "float" || variant === "wave"
736 ? springFloatX
737 : scrollEffect
738 ? springY
739 : springX,
740 y: variant === "float" || variant === "wave" ? springFloatY : springY,
741 rotate: variant === "float" || variant === "wave" ? springRotate : 0,
742 rotateX: springRotateX,
743 rotateY: springRotateY,
744 scale: springScale,
745 borderRadius: borderRadius,
746 transformStyle: "preserve-3d",
747 perspective: "1000px",
748 },
749 onMouseEnter: handleMouseEnter,
750 onMouseLeave: handleMouseLeave,
751 };
752
753 return (
754 <motion.div
755 className={cn(
756 "relative overflow-hidden transition-colors duration-300",
757 currentTheme.background,
758 getBorderStyles(),
759 getShadowStyles(),
760 getAnimationClasses(),
761 blurBackground && "backdrop-blur-md",
762 className
763 )}
764 {...motionProps}
765 {...props}
766 >
767 {variant === "layered" && generatedLayers}
768
769 {variant === "glow" && getGlowGradient()}
770
771 {isHovered && (theme === "glass" || borderStyle === "gradient") && (
772 <div
773 className="absolute inset-0 bg-gradient-to-r from-transparent via-white to-transparent pointer-events-none"
774 style={{
775 opacity: 0.1,
776 transform: "rotate(30deg) translateX(-200%)",
777 width: "150%",
778 height: "200%",
779 top: "-50%",
780 left: 0,
781 animation: "shine 2s ease-in-out infinite",
782 }}
783 />
784 )}
785
786 {variant === "wave" && isHovered && (
787 <div className="absolute inset-0 pointer-events-none overflow-hidden">
788 {Array.from({ length: 5 }).map((_, i) => {
789 const speed = 2 + i * 0.5;
790 const height = 6 + i * 2;
791 const opacity = 0.3 - i * 0.05;
792 const delay = i * 0.4;
793
794 return (
795 <div
796 key={`wave-${i}`}
797 className="absolute bottom-0 left-0 right-0"
798 style={{
799 height: `${height}px`,
800 background: `linear-gradient(90deg,
801 transparent 0%,
802 ${
803 theme === "neon"
804 ? `rgba(99, 102, 241, ${opacity})`
805 : theme === "cosmic"
806 ? `rgba(147, 51, 234, ${opacity})`
807 : `rgba(147, 197, 253, ${opacity})`
808 } 50%,
809 transparent 100%)`,
810 transform: `translateY(${
811 Math.sin(waveTime * speed + delay) * 5
812 }px)
813 scaleY(${
814 1 + Math.sin(waveTime * speed * 0.5) * 0.2
815 })`,
816 bottom: `${i * 12}px`,
817 }}
818 />
819 );
820 })}
821
822 {showRipple && (
823 <div
824 className="absolute pointer-events-none"
825 style={{
826 left: `${ripplePosition.x}%`,
827 top: `${ripplePosition.y}%`,
828 width: "100px",
829 height: "100px",
830 borderRadius: "50%",
831 transform: "translate(-50%, -50%)",
832 background:
833 "radial-gradient(circle, rgba(255,255,255,0.3) 0%, transparent 70%)",
834 animation: "ripple 1.5s ease-out forwards",
835 }}
836 />
837 )}
838 </div>
839 )}
840
841 {confettiEffect && <Confetti />}
842 <div className="relative z-10">{children}</div>
843 </motion.div>
844 );
845}
846
4
Final Steps
Update Import Paths
Make sure to update the import paths in the component code to match your project structure. For example, change @/components/ui/button
to match your UI components location.
Props
Name | Type | Default | Description |
---|---|---|---|
variant | string | "tilt" | The animation variant. Possible values: "parallax", "tilt", "float", "magnetic", "layered", "morph", "breathe", "glow", "wave". |
intensity | number | 3 | The intensity of the effect (1-5). |
theme | string | "light" | The color theme. Possible values: "light", "dark", "glass", "gradient", "neon", "cosmic", "custom". |
customColors | object | undefined | Custom colors object with properties: background, and optionally border, shadow, and glow. |
rounded | string | "lg" | Border radius. Possible values: "none", "sm", "md", "lg", "xl", "full", "pill". |
shadow | boolean | true | Whether to show a shadow effect. |
shadowSize | string | "md" | Shadow size. Possible values: "sm", "md", "lg", "xl", "2xl". |
shadowType | string | "standard" | Shadow type. Possible values: "standard", "soft", "hard", "inner", "glow". |
border | boolean | false | Whether to show a border. |
borderStyle | string | "solid" | Border style. Possible values: "solid", "dashed", "dotted", "gradient", "glow". |
hoverEffect | boolean | true | Whether to enable the hover effect. |
scrollEffect | boolean | false | Whether to enable scroll-based animations. |
reduceMotion | boolean | false | Reduce or disable animations for accessibility. |
confettiEffect | boolean | false | Adds a confetti animation effect on hover. |
speed | string | "normal" | Animation speed. Possible values: "slow", "normal", "fast". |
blurBackground | boolean | false | Applies a backdrop blur effect to the background. |
layerCount | number | 3 | Number of layers for layered variant (1-5). |
layerSeparation | number | 2 | Distance between layers for layered variant (1-5). |
floatPattern | string | "simple" | Float animation pattern. Possible values: "simple", "complex", "random", "sine", "circle". |
className | string | undefined | Additional CSS classes to apply. |
children | React.ReactNode | undefined | The content to display inside the card. |