Introducing Nuvyx UI v1.0.0

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

Live
Revenue$24,892
Active Users8,245
Conversion Rate5.2%

Weather Forecast

Live
23°
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
Configuration
1import { 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
TypeScript
1"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

NameTypeDefaultDescription
variantstring"tilt"The animation variant. Possible values: "parallax", "tilt", "float", "magnetic", "layered", "morph", "breathe", "glow", "wave".
intensitynumber3The intensity of the effect (1-5).
themestring"light"The color theme. Possible values: "light", "dark", "glass", "gradient", "neon", "cosmic", "custom".
customColorsobjectundefinedCustom colors object with properties: background, and optionally border, shadow, and glow.
roundedstring"lg"Border radius. Possible values: "none", "sm", "md", "lg", "xl", "full", "pill".
shadowbooleantrueWhether to show a shadow effect.
shadowSizestring"md"Shadow size. Possible values: "sm", "md", "lg", "xl", "2xl".
shadowTypestring"standard"Shadow type. Possible values: "standard", "soft", "hard", "inner", "glow".
borderbooleanfalseWhether to show a border.
borderStylestring"solid"Border style. Possible values: "solid", "dashed", "dotted", "gradient", "glow".
hoverEffectbooleantrueWhether to enable the hover effect.
scrollEffectbooleanfalseWhether to enable scroll-based animations.
reduceMotionbooleanfalseReduce or disable animations for accessibility.
confettiEffectbooleanfalseAdds a confetti animation effect on hover.
speedstring"normal"Animation speed. Possible values: "slow", "normal", "fast".
blurBackgroundbooleanfalseApplies a backdrop blur effect to the background.
layerCountnumber3Number of layers for layered variant (1-5).
layerSeparationnumber2Distance between layers for layered variant (1-5).
floatPatternstring"simple"Float animation pattern. Possible values: "simple", "complex", "random", "sine", "circle".
classNamestringundefinedAdditional CSS classes to apply.
childrenReact.ReactNodeundefinedThe content to display inside the card.