apex-ui-kit 2.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/Licence.md +21 -0
  2. package/ReadMe.md +75 -0
  3. package/dist/ApexUI-Kit/templates/Avatar/Avatar.jsx +81 -0
  4. package/dist/ApexUI-Kit/templates/BeamBar/BeamBar.jsx +101 -0
  5. package/dist/ApexUI-Kit/templates/Cards/BasicCard.jsx +60 -0
  6. package/dist/ApexUI-Kit/templates/Carousel/Carousel.jsx +95 -0
  7. package/dist/ApexUI-Kit/templates/Carousel/DragCarousel.jsx +105 -0
  8. package/dist/ApexUI-Kit/templates/Carousel/ParallaxCarousel.jsx +161 -0
  9. package/dist/ApexUI-Kit/templates/DragCarousel/DragCarousel.jsx +105 -0
  10. package/dist/ApexUI-Kit/templates/EdgeGlowForm/EdgeGlowFrom.jsx +297 -0
  11. package/dist/ApexUI-Kit/templates/FlipProfile/FlipProfile.jsx +125 -0
  12. package/dist/ApexUI-Kit/templates/GlareCard/GlareCard.jsx +146 -0
  13. package/dist/ApexUI-Kit/templates/GlassCardStack/GlassCardStack.jsx +111 -0
  14. package/dist/ApexUI-Kit/templates/HoverText/HoverText.jsx +371 -0
  15. package/dist/ApexUI-Kit/templates/HyperCard/HyperCard.jsx +183 -0
  16. package/dist/ApexUI-Kit/templates/KineticThreadsBackground/KineticThreadBackground.jsx +249 -0
  17. package/dist/ApexUI-Kit/templates/LensFlareBackground/LensFlareBackground.jsx +268 -0
  18. package/dist/ApexUI-Kit/templates/LuminousParticleOcean/LuminousParticleOcean.jsx +155 -0
  19. package/dist/ApexUI-Kit/templates/NavDropDown/NavDropDown.jsx +114 -0
  20. package/dist/ApexUI-Kit/templates/NavMenu/NavMenu.jsx +153 -0
  21. package/dist/ApexUI-Kit/templates/OrbsBackground/FloatingOrbs.jsx +96 -0
  22. package/dist/ApexUI-Kit/templates/ParallaxCard/ParallaxCard.jsx +127 -0
  23. package/dist/ApexUI-Kit/templates/ParallaxCarousel/ParallaxCarousel.jsx +161 -0
  24. package/dist/ApexUI-Kit/templates/ParallaxProfile/ParallaxProfile.jsx +107 -0
  25. package/dist/ApexUI-Kit/templates/PointerFollower/PointerFollower.jsx +126 -0
  26. package/dist/ApexUI-Kit/templates/RainBackground/RainBackground.jsx +109 -0
  27. package/dist/ApexUI-Kit/templates/RippleBackground/RippleBackground.jsx +160 -0
  28. package/dist/ApexUI-Kit/templates/ScrollMarquee/ScrollMarquee.jsx +174 -0
  29. package/dist/ApexUI-Kit/templates/SmartGridCard/SmartGridCard.jsx +241 -0
  30. package/dist/ApexUI-Kit/templates/SmokeBackground/SmokeBackground.jsx +216 -0
  31. package/dist/ApexUI-Kit/templates/Testimonials/Testimonials.jsx +117 -0
  32. package/dist/ApexUI-Kit/templates/ThemeToggle/ThemeAnimation.js +252 -0
  33. package/dist/ApexUI-Kit/templates/ThemeToggle/ThemeConfig.js +8 -0
  34. package/dist/ApexUI-Kit/templates/ThemeToggle/ThemeToggle.css +560 -0
  35. package/dist/ApexUI-Kit/templates/ThemeToggle/ThemeToggle.jsx +118 -0
  36. package/dist/ApexUI-Kit/templates/ToolTip/ToolTip.jsx +85 -0
  37. package/dist/ApexUI-Kit/templates/WaterDropReveal/WaterDropReveal.jsx +151 -0
  38. package/dist/cli/index.d.ts +2 -0
  39. package/dist/cli/index.js +145 -0
  40. package/package.json +56 -0
@@ -0,0 +1,114 @@
1
+ import React, { useState, useRef, useEffect } from 'react';
2
+ import { gsap } from 'gsap';
3
+ import { ChevronDown } from 'lucide-react';
4
+ const DropdownPanel = ({ item, isActive }) => {
5
+ const panelRef = useRef(null);
6
+
7
+ // GSAP animation for revealing the panel
8
+ useEffect(() => {
9
+ if (isActive) {
10
+ gsap.to(panelRef.current, {
11
+ autoAlpha: 1,
12
+ y: 0,
13
+ scale: 1,
14
+ duration: 0.2,
15
+ ease: 'power3.out'
16
+ });
17
+ } else {
18
+ gsap.to(panelRef.current, {
19
+ autoAlpha: 0,
20
+ y: -10,
21
+ scale: 0.95,
22
+ duration: 0.2,
23
+ ease: 'power3.in'
24
+ });
25
+ }
26
+ }, [isActive]);
27
+
28
+ return (
29
+ <div
30
+ ref={panelRef}
31
+ style={{ visibility: 'hidden', transformOrigin: 'top left' }}
32
+ className="absolute top-full left-1/2 -translate-x-1/2 mt-8 w-max max-w-4xl"
33
+ >
34
+ <div className="bg-gray-950 rounded-4xl shadow-xl p-6 z-50 border border-dashed border-gray-700/40">
35
+ {/* Links Dropdown */}
36
+ {item.type === "links" && (
37
+ <ul className="grid grid-cols-2 gap-x-8 gap-y-2 text-left">
38
+ {item.submenu.map((subItem, i) => (
39
+ <li key={i}>
40
+ <a href={subItem.href} className="block p-1.5 text-sm text-gray-400 rounded-md transition-colors duration-75 hover:text-lime-400 hover:bg-slate-800">
41
+ {subItem.label}
42
+ </a>
43
+ </li>
44
+ ))}
45
+ </ul>
46
+ )}
47
+ {/* Products Dropdown */}
48
+ {item.type === "products" && (
49
+ <div className=' border border-gray-700/40 bg-black/10 rounded-2xl p-5'>
50
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-4 z-50">
51
+ {item.submenu.map((subItem, i) => (
52
+ <a href={subItem.href} key={i} className={`flex items-center gap-4 bg-slate-800/50 p-3 rounded-lg hover:bg-slate-800 transition-all group w-80`}>
53
+ <div className="overflow-hidden rounded-md">
54
+ <img src={subItem.image} alt={subItem.label} className="w-20 h-20 object-cover transition-transform duration-300 group-hover:scale-110" />
55
+ </div>
56
+ <div className="text-left">
57
+ <h3 className="text-gray-200 font-medium text-sm">{subItem.label}</h3>
58
+ <p className="text-gray-500 text-xs">{subItem.subtitle}</p>
59
+ </div>
60
+ </a>
61
+ ))}
62
+ </div>
63
+ </div>
64
+ )}
65
+ {/* Services Dropdown */}
66
+ {item.type === "services" && (
67
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-3">
68
+ {item.submenu.map((subItem, i) => (
69
+ <a href={subItem.href} key={i} className="group block bg-slate-800/50 p-3 rounded-lg hover:bg-slate-800 transition-all w-64">
70
+ <h3 className="text-lime-400 font-medium transition-colors group-hover:text-lime-300">{subItem.label}</h3>
71
+ <p className="text-gray-500 text-sm">{subItem.subtitle}</p>
72
+ </a>
73
+ ))}
74
+ </div>
75
+ )}
76
+ </div>
77
+ </div>
78
+ );
79
+ };
80
+
81
+ const NavDropDown = ({ navData }) => {
82
+ const [openIndex, setOpenIndex] = useState(null);
83
+
84
+ return (
85
+ // New container panel with dashed border
86
+ <div className=" inline-block bg-stone-950 border border-dashed border-slate-300/20 rounded-xl p-4 fixed" style={{ zIndex: '4000' }}>
87
+ <nav className="relative">
88
+ <ul className="flex space-x-2 justify-center">
89
+ {navData.map((item, index) => (
90
+ <li
91
+ key={index}
92
+ className="relative"
93
+ onMouseEnter={() => item.submenu && setOpenIndex(index)}
94
+ onMouseLeave={() => item.submenu && setOpenIndex(null)}
95
+ >
96
+ <a
97
+ href={item.href || '#'}
98
+ className="flex items-center px-3 py-2 text-sm font-medium hover:text-lime-400 text-slate-300 transition-colors duration-300 cursor-pointer"
99
+ >
100
+ {item.label}
101
+ {item.submenu && (
102
+ <ChevronDown size={16} className={`ml-1 transition-transform duration-300 ${openIndex === index ? "rotate-180" : ""}`} />
103
+ )}
104
+ </a>
105
+ {item.submenu && <DropdownPanel item={item} isActive={openIndex === index} />}
106
+ </li>
107
+ ))}
108
+ </ul>
109
+ </nav>
110
+ </div>
111
+ );
112
+ };
113
+
114
+ export default NavDropDown;
@@ -0,0 +1,153 @@
1
+ import React, { useState, useRef, useEffect } from 'react';
2
+ import { gsap } from "gsap";
3
+ const NavMenu = ({
4
+ indicatorColor = "#a3e635",
5
+ backgroundColor = "rgba(17, 24, 39, 0.5)",
6
+ activeColor = "#ffffff",
7
+ indicatorAnimation = "elastic",
8
+ onNavItemClick
9
+ }) => {
10
+ const [activeIdx, setActiveIdx] = useState(0);
11
+ const navItemsRef = useRef([]);
12
+ const indicatorRef = useRef(null);
13
+ const navContainerRef = useRef(null);
14
+ useEffect(() => {
15
+ if (!navContainerRef.current) return;
16
+ gsap.fromTo(
17
+ navContainerRef.current,
18
+ { width: 0, opacity: 0 },
19
+ { width: "auto", opacity: 1, duration: 0.8, ease: "power2.out", delay: 0.5 }
20
+ );
21
+ }, []);
22
+
23
+ useEffect(() => {
24
+ const indicator = indicatorRef.current;
25
+ const el = navItemsRef.current[activeIdx];
26
+
27
+ if (!indicator || !el) return;
28
+
29
+ const getEase = () => {
30
+ switch (indicatorAnimation) {
31
+ case "spring": return "bounce.out";
32
+ case "power": return "power3.out";
33
+ case "elastic":
34
+ default: return "elastic.out(1, 0.6)";
35
+ }
36
+ };
37
+
38
+ const moveWithGSAP = () => {
39
+ const rect = el.getBoundingClientRect();
40
+ const parentRect = el.parentElement.getBoundingClientRect();
41
+ const left = rect.left - parentRect.left;
42
+ const width = rect.width;
43
+
44
+ gsap.to(indicator, {
45
+ x: left,
46
+ width,
47
+ duration: 0.5,
48
+ ease: getEase(),
49
+ });
50
+ };
51
+
52
+ const timeoutId = setTimeout(moveWithGSAP, 50);
53
+ return () => clearTimeout(timeoutId);
54
+
55
+ }, [activeIdx, indicatorAnimation]);
56
+
57
+ // Handle resize
58
+ useEffect(() => {
59
+ const handleResize = () => {
60
+ const el = navItemsRef.current[activeIdx];
61
+ if (el) {
62
+ const rect = el.getBoundingClientRect();
63
+ const parentRect = el.parentElement.getBoundingClientRect();
64
+ const left = rect.left - parentRect.left;
65
+ const width = rect.width;
66
+
67
+ gsap.set(indicatorRef.current, {
68
+ x: left,
69
+ width: width
70
+ });
71
+ }
72
+ };
73
+
74
+ window.addEventListener("resize", handleResize);
75
+ return () => window.removeEventListener("resize", handleResize);
76
+ }, [activeIdx]);
77
+
78
+ const navItems = ['Home', 'Docs', 'UI Kit', 'Contact'];
79
+
80
+ return (
81
+ <nav
82
+ ref={navContainerRef}
83
+ className="rounded px-3 py-2 shadow-lg border border-white/10 overflow-hidden"
84
+ style={{ backdropFilter: 'blur(16px)', WebkitBackdropFilter: 'blur(16px)', background: backgroundColor , }}
85
+ >
86
+ <div
87
+ ref={indicatorRef}
88
+ className="absolute top-1.5 h-[calc(100%-0.75rem)] rounded-lg z-0 "
89
+ style={{ background: indicatorColor, opacity: 0.3 }}
90
+ />
91
+ <div className="relative flex gap-2 z-10 whitespace-nowrap">
92
+ {navItems.map((label, i) => (
93
+ <button
94
+ key={label}
95
+ ref={(el) => (navItemsRef.current[i] = el)}
96
+ onClick={() => {
97
+ setActiveIdx(i);
98
+ if (onNavItemClick) onNavItemClick();
99
+ }}
100
+ className={`nav-item px-6 py-2 text-base font-semibold rounded-full relative z-10 transition-colors duration-300 ${i === activeIdx ? '' : 'text-gray-400 hover:text-white'}`}
101
+ style={{ color: i === activeIdx ? activeColor : undefined }}
102
+ >
103
+ {label}
104
+ </button>
105
+ ))}
106
+ </div>
107
+ </nav>
108
+ );
109
+ };
110
+
111
+
112
+ // Responsive Wrapper Component
113
+ const ResponsiveNavMenu = (props) => {
114
+ const [isOpen, setIsOpen] = useState(false);
115
+ const navItems = ['Home', 'Docs', 'UI Kit', 'Contact'];
116
+
117
+ return (
118
+ <nav className="flex items-center justify-center p-4 sticky top-0 left-0 w-full z-50 bg-transparent">
119
+ <div className="hidden md:flex w-full justify-center">
120
+ <NavMenu {...props} />
121
+ </div>
122
+
123
+ <div className="md:hidden flex w-full justify-end">
124
+ <button onClick={() => setIsOpen(!isOpen)} className="text-white focus:outline-none">
125
+ <svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
126
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d={isOpen ? "M6 18L18 6M6 6l12 12" : "M4 6h16M4 12h16m-7 6h7"} />
127
+ </svg>
128
+ </button>
129
+ </div>
130
+
131
+ {isOpen && (
132
+ <div className="md:hidden absolute top-20 left-4 right-4 rounded-xl shadow-lg border border-white/10"
133
+ style={{ backdropFilter: 'blur(16px)', WebkitBackdropFilter: 'blur(16px)', background: 'rgba(17, 24, 39, 0.7)' }}>
134
+ <div className="flex flex-col items-center gap-2 p-4">
135
+ {navItems.map((label, i) => (
136
+ <button
137
+ key={label}
138
+ onClick={() => {
139
+ console.log(`${label} clicked`);
140
+ setIsOpen(false);
141
+ }}
142
+ className="w-full text-center px-6 py-3 text-lg font-semibold rounded-full text-gray-300 hover:text-white hover:bg-white/10 transition-colors duration-300"
143
+ >
144
+ {label}
145
+ </button>
146
+ ))}
147
+ </div>
148
+ </div>
149
+ )}
150
+ </nav>
151
+ );
152
+ };
153
+ export default ResponsiveNavMenu;
@@ -0,0 +1,96 @@
1
+ /* eslint-disable no-unused-vars */
2
+ import React, { useState, useEffect } from 'react';
3
+ import { motion } from 'framer-motion';
4
+
5
+ // --- FloatingOrbsBackground Component (Optimized for Mobile) ---
6
+ const FloatingOrbsBackground = ({
7
+ children,
8
+ className,
9
+ containerClassName,
10
+ colors,
11
+ desktopOrbs = 20, // Orbs for desktop
12
+ mobileOrbs = 10, // Orbs for mobile
13
+ }) => {
14
+ const [numberOfOrbs, setNumberOfOrbs] = useState(desktopOrbs);
15
+ const [isMobile, setIsMobile] = useState(false);
16
+
17
+ // Screen size check karne ke liye
18
+ useEffect(() => {
19
+ const checkScreenSize = () => {
20
+ const isMobileDevice = window.innerWidth < 768;
21
+ setIsMobile(isMobileDevice);
22
+ setNumberOfOrbs(isMobileDevice ? mobileOrbs : desktopOrbs);
23
+ };
24
+
25
+ checkScreenSize(); // Pehli baar check karein
26
+ window.addEventListener('resize', checkScreenSize);
27
+
28
+ // Cleanup
29
+ return () => window.removeEventListener('resize', checkScreenSize);
30
+ }, [desktopOrbs, mobileOrbs]);
31
+
32
+ const orbColors = colors ?? [
33
+ "#10b981", "#06b6d4", "#6366f1", "#a855f7",
34
+ "#d946ef", "#ec4899", "#f43f5e", "#f97316",
35
+ ];
36
+
37
+ return (
38
+ <div
39
+ className={`relative h-[60vh] lg:h-[80vh] w-full flex flex-col items-center justify-center overflow-hidden bg-slate-950 ${containerClassName}`}
40
+ >
41
+ {/* Orbs Container */}
42
+ <div className="absolute inset-0 z-0 w-full h-full">
43
+ {Array.from({ length: numberOfOrbs }).map((_, index) => {
44
+ const size = Math.random() * 200 + 100;
45
+ const duration = Math.random() * 20 + 15;
46
+ // Mobile ke liye blur kam kar diya hai
47
+ const blur = isMobile ? '60px' : '80px';
48
+
49
+ return (
50
+ <motion.div
51
+ key={index}
52
+ className="absolute rounded-full"
53
+ style={{
54
+ width: size,
55
+ height: size,
56
+ backgroundColor: orbColors[index % orbColors.length],
57
+ filter: `blur(${blur})`,
58
+ opacity: 0.4,
59
+ // Performance behtar karne ke liye browser ko hint dena
60
+ willChange: 'transform, opacity',
61
+ }}
62
+ animate={{
63
+ x: [
64
+ // Movement range ko badha diya hai taaki yeh poori screen cover kare
65
+ `${Math.random() * 150 - 75}vw`,
66
+ `${Math.random() * 150 - 75}vw`,
67
+ `${Math.random() * 150 - 75}vw`,
68
+ ],
69
+ y: [
70
+ // Movement range ko badha diya hai taaki yeh poori screen cover kare
71
+ `${Math.random() * 150 - 75}vh`,
72
+ `${Math.random() * 150 - 75}vh`,
73
+ `${Math.random() * 150 - 75}vh`,
74
+ ],
75
+ scale: [1, 1.2, 1],
76
+ }}
77
+ transition={{
78
+ duration: duration,
79
+ repeat: Infinity,
80
+ ease: 'easeInOut',
81
+ repeatType: 'mirror',
82
+ }}
83
+ />
84
+ );
85
+ })}
86
+ </div>
87
+
88
+ <div className={`relative z-10 ${className}`}>
89
+ {children}
90
+ </div>
91
+ </div>
92
+ );
93
+ };
94
+
95
+
96
+ export default FloatingOrbsBackground;
@@ -0,0 +1,127 @@
1
+ import React, { useRef, useEffect } from 'react';
2
+ import { gsap } from 'gsap';
3
+ import { ArrowRight } from 'lucide-react';
4
+ import { User } from 'lucide-react';
5
+
6
+ // --- Multi-Layered 3D Parallax Card Component ---
7
+ const ParallaxCard = ({ cardData = [], tiltEnable, glareEnable, scale, perspective }) => {
8
+ // Helper for per-card refs
9
+ const cardRefs = useRef([]);
10
+ const titleRefs = useRef([]);
11
+ const imageRefs = useRef([]);
12
+ const descRefs = useRef([]);
13
+ const buttonRefs = useRef([]);
14
+ const linkRefs = useRef([]);
15
+
16
+ useEffect(() => {
17
+ cardData.forEach((_, idx) => {
18
+ const cardElement = cardRefs.current[idx];
19
+ const titleElement = titleRefs.current[idx];
20
+ const imageElement = imageRefs.current[idx];
21
+ const descElement = descRefs.current[idx];
22
+ const buttonElement = buttonRefs.current[idx];
23
+ const linkElement = linkRefs.current[idx];
24
+
25
+ if (!cardElement) return;
26
+
27
+ const handleMouseMove = (e) => {
28
+ if (!tiltEnable) return;
29
+ const { left, top, width, height } = cardElement.getBoundingClientRect();
30
+ const x = e.clientX - left;
31
+ const y = e.clientY - top;
32
+ const rotateX = (y / height - 0.5) * -30;
33
+ const rotateY = (x / width - 0.5) * 30;
34
+
35
+ gsap.to(cardElement, {
36
+ duration: 1,
37
+ rotationX: rotateX,
38
+ rotationY: rotateY,
39
+ scale: scale,
40
+ ease: "power3.out",
41
+ });
42
+
43
+ gsap.to(titleElement, { duration: 0.7, x: rotateY * 0.8, y: -rotateX * 0.8 });
44
+ gsap.to(imageElement, { duration: 0.8, x: rotateY * 1.5, y: -rotateX * 1.5 });
45
+ gsap.to(descElement, { duration: 0.9, x: rotateY * 1.2, y: -rotateX * 1.2 });
46
+ gsap.to(buttonElement, { duration: 1, x: rotateY * 1.1, y: -rotateX * 1.1 });
47
+ gsap.to(linkElement, { duration: 1, x: rotateY * 1.1, y: -rotateX * 1.1 });
48
+ };
49
+
50
+ const handleMouseLeave = () => {
51
+ if (tiltEnable) {
52
+ gsap.to(cardElement, {
53
+ duration: 1.2,
54
+ rotationX: 0,
55
+ rotationY: 0,
56
+ scale: 1,
57
+ ease: "elastic.out(1, 0.5)",
58
+ });
59
+ gsap.to([titleElement, imageElement, descElement, buttonElement, linkElement], {
60
+ duration: 1.2,
61
+ x: 0,
62
+ y: 0,
63
+ ease: "elastic.out(1, 0.5)",
64
+ });
65
+ }
66
+ };
67
+
68
+ cardElement.addEventListener("mousemove", handleMouseMove);
69
+ cardElement.addEventListener("mouseleave", handleMouseLeave);
70
+
71
+ return () => {
72
+ cardElement.removeEventListener("mousemove", handleMouseMove);
73
+ cardElement.removeEventListener("mouseleave", handleMouseLeave);
74
+ };
75
+ });
76
+ }, [cardData, tiltEnable, scale]);
77
+
78
+ return (
79
+ <div className="flex flex-wrap gap-8 justify-center">
80
+ {cardData.map((card, idx) => (
81
+ <div
82
+ key={idx}
83
+ ref={el => cardRefs.current[idx] = el}
84
+ className="w-full max-w-md rounded-2xl"
85
+ style={{ transformStyle: 'preserve-3d', perspective: `${perspective}px` }}
86
+ >
87
+ <div className="relative md:w-full w-70 h-[50vh] md:h-full bg-stone-950 rounded-2xl p-8 border border-lime-500/20 shadow-2 shadow-lime-900/40 overflow-hidden"
88
+ style={{ transformStyle: 'preserve-3d' }}
89
+ >
90
+ {glareEnable && (
91
+ <div className="absolute inset-0 w-full h-full bg-gradient-to-br from-white/10 to-transparent opacity-50 pointer-events-none z-0" style={{ transform: 'translateZ(100px)' }}></div>
92
+ )}
93
+
94
+ <div className="relative z-10 flex flex-col h-full">
95
+ <div ref={el => titleRefs.current[idx] = el} className="text-start" style={{ transform: 'translateZ(40px)' }}>
96
+ <div className='flex items-center gap-3'>
97
+ <User /> <h2 className="md:text-3xl font-bold text-white">{card.title}</h2>
98
+
99
+ </div>
100
+ <p className="text-lime-400 mt-1">{card.subtitle}</p>
101
+ </div>
102
+
103
+ <div ref={el => imageRefs.current[idx] = el} className="my-6" style={{ transform: 'translateZ(80px)' }}>
104
+ <img
105
+ src={card.imageUrl}
106
+ alt={card.title}
107
+ className="w-full md:h-56 object-cover rounded-lg shadow-lg shadow-black/40"
108
+ />
109
+ </div>
110
+
111
+ <div ref={el => descRefs.current[idx] = el} className="text-start" style={{ transform: 'translateZ(30px)' }}>
112
+ <p className="text-slate-400 md:text-sm text-xs mb-6">{card.description}</p>
113
+ </div>
114
+ <div ref={el => buttonRefs.current[idx] = el} className="text-center flex items-center justify-end" style={{ transform: 'translateZ(30px)' }}>
115
+ <a href={card.link} className="border border-lime-400 py-2 px-3 rounded-full flex items-center gap-2 font-medium text-white group hover:text-lime-400 transition-colors duration-300">
116
+ Learn More <ArrowRight size={16} className="transition-transform duration-300 group-hover:translate-x-1 translate-y-0.5" />
117
+ </a>
118
+ </div>
119
+ </div>
120
+ </div>
121
+ </div>
122
+ ))}
123
+ </div>
124
+ );
125
+ };
126
+
127
+ export default ParallaxCard;
@@ -0,0 +1,161 @@
1
+ /* eslint-disable no-unused-vars */
2
+ import React, { useState, useEffect, useRef } from 'react';
3
+ import { motion, useMotionValue, useTransform } from 'framer-motion';
4
+ import { ArrowLeft, ArrowRight } from 'lucide-react';
5
+
6
+ // --- Card Component with Hover Parallax Effect ---
7
+ const CarouselCard = ({ slide, isCenter }) => {
8
+ const cardRef = useRef(null);
9
+ const x = useMotionValue(0);
10
+ const y = useMotionValue(0);
11
+
12
+ // Mouse move ke hisab se rotation calculate karta hai (effect ko strong banaya gaya hai)
13
+ const rotateX = useTransform(y, [-200, 200], [15, -15]);
14
+ const rotateY = useTransform(x, [-200, 200], [-15, 15]);
15
+ const handleMouseMove = (event) => {
16
+ if (!cardRef.current || !isCenter) return;
17
+ const rect = cardRef.current.getBoundingClientRect();
18
+ x.set(event.clientX - rect.left - rect.width / 2);
19
+ y.set(event.clientY - rect.top - rect.height / 2);
20
+ };
21
+
22
+ const handleMouseLeave = () => {
23
+ x.set(0);
24
+ y.set(0);
25
+ };
26
+
27
+ return (
28
+ <motion.div
29
+ ref={cardRef}
30
+ className="w-full h-full bg-slate-900 rounded-2xl group"
31
+ style={{
32
+ transformStyle: 'preserve-3d',
33
+ perspective: '1500px',
34
+ rotateX: isCenter ? rotateX : 0,
35
+ rotateY: isCenter ? rotateY : 0,
36
+ }}
37
+ onMouseMove={handleMouseMove}
38
+ onMouseLeave={handleMouseLeave}
39
+
40
+ >
41
+ {/* Slide Content */}
42
+ <div className="absolute inset-0 w-full h-full"
43
+ style={{
44
+ transformStyle: 'preserve-3d',
45
+ perspective: '1500px',
46
+ }}
47
+ >
48
+ <img
49
+ src={slide.imageUrl}
50
+ alt={slide.title}
51
+ className="w-full h-full object-cover group-hover:opacity-30 transition-all duration-300 ease-in-out rounded-2xl"
52
+ />
53
+ <img
54
+ src={slide.pngUrl}
55
+ alt={slide.title}
56
+ className="absolute inset-0 w-full h-full object-cover group-hover:translate-z-15 transition-all duration-300 ease-in-out rounded-2xl"
57
+ />
58
+ </div>
59
+ <div
60
+ className="relative flex flex-col justify-end h-full text-white p-8"
61
+ style={{
62
+ transformStyle: 'preserve-3d',
63
+ perspective: '1500px',
64
+ }}
65
+ >
66
+ <h2 className="text-3xl font-bold group-hover:translate-z-18 transition-all duration-300 ease-in-out">{slide.title}</h2>
67
+ <p className="text-lime-400 mt-1 group-hover:translate-z-18 transition-all duration-300 ease-in-out">{slide.subtitle}</p>
68
+ <p className="text-slate-300 mt-4 text-sm leading-relaxed group-hover:translate-z-18 transition-all duration-300 ease-in-out">{slide.text}</p>
69
+ </div>
70
+ </motion.div>
71
+ );
72
+ };
73
+
74
+
75
+ // --- Custom Parallax Carousel Component ---
76
+ const ParallaxCarousel = ({ slides }) => {
77
+ const [activeIndex, setActiveIndex] = useState(0);
78
+ const [isHovered, setIsHovered] = useState(false);
79
+
80
+ const handleNext = () => {
81
+ setActiveIndex((prevIndex) => (prevIndex + 1) % slides.length);
82
+ };
83
+
84
+ const handlePrev = () => {
85
+ setActiveIndex((prevIndex) => (prevIndex - 1 + slides.length) % slides.length);
86
+ };
87
+
88
+ // Automatic sliding ke liye
89
+ useEffect(() => {
90
+ // Agar hovered hai to interval start hi mat karo
91
+ if (isHovered) {
92
+ return;
93
+ }
94
+
95
+ const interval = setInterval(() => {
96
+ handleNext();
97
+ }, 4000); // Har 4 second mein slide change hoga
98
+
99
+ return () => clearInterval(interval); // Cleanup
100
+ }, [activeIndex, isHovered]); // isHovered state change hone par effect ko firse chalayega
101
+
102
+ return (
103
+ <div
104
+ className="relative w-full flex flex-col items-center px-2"
105
+ onMouseEnter={() => setIsHovered(true)}
106
+ onMouseLeave={() => setIsHovered(false)}
107
+ >
108
+ {/* 3D Carousel Container */}
109
+ <div
110
+ className="relative w-full h-[480px] portrait:h-[480px] portrait:px-0"
111
+ style={{ perspective: '1200px', transformStyle: 'preserve-3d' }}
112
+ >
113
+ {slides.map((slide, index) => {
114
+ let positionOffset = index - activeIndex;
115
+ if (positionOffset > slides.length / 2) {
116
+ positionOffset -= slides.length;
117
+ } else if (positionOffset < -slides.length / 2) {
118
+ positionOffset += slides.length;
119
+ }
120
+
121
+ const isVisible = Math.abs(positionOffset) <= 2;
122
+
123
+ return (
124
+ <motion.div
125
+ key={index}
126
+ className={`absolute w-full h-[340px] portrait:w-full portrait:h-[480px] top-0 left-0 portrait:left-0 portrait:top-0 ${positionOffset === 0 ? '' : ' pointer-events-none'} md:w-[50%] md:h-[400px] md:top-[50px] md:left-1/2 lg:-translate-x-1/2`}
127
+ initial={false}
128
+ style={{ transformStyle: 'preserve-3d' }}
129
+ animate={{
130
+ transform: `translateX(${positionOffset * 50}%) translateZ(${-Math.abs(positionOffset) * 250}px) scale(${1 - Math.abs(positionOffset) * 0.15})`,
131
+ opacity: isVisible ? (1 - Math.abs(positionOffset) * 0.45) : 0,
132
+ zIndex: slides.length - Math.abs(positionOffset),
133
+ }}
134
+ transition={{ type: 'spring', stiffness: 290, damping: 30 }}
135
+ >
136
+ <CarouselCard slide={slide} isCenter={positionOffset === 0} />
137
+ </motion.div>
138
+ );
139
+ })}
140
+ </div>
141
+
142
+ {/* Side Navigation Buttons */}
143
+ <motion.button
144
+ onClick={handlePrev}
145
+ className="absolute -left-10 lg:left-0 top-1/2 -translate-y-1/2 z-30 p-3 rounded-full text-slate-400 hover:text-lime-400 transition-colors"
146
+ whileTap={{ scale: 0.9 }}
147
+ >
148
+ <ArrowLeft size={32} />
149
+ </motion.button>
150
+ <motion.button
151
+ onClick={handleNext}
152
+ className="absolute -right-10 lg:right-0 top-1/2 -translate-y-1/2 z-30 p-3 rounded-full text-slate-400 hover:text-lime-400 transition-colors"
153
+ whileTap={{ scale: 0.9 }}
154
+ >
155
+ <ArrowRight size={32} />
156
+ </motion.button>
157
+ </div>
158
+ );
159
+ };
160
+
161
+ export default ParallaxCarousel;