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.
- package/Licence.md +21 -0
- package/ReadMe.md +75 -0
- package/dist/ApexUI-Kit/templates/Avatar/Avatar.jsx +81 -0
- package/dist/ApexUI-Kit/templates/BeamBar/BeamBar.jsx +101 -0
- package/dist/ApexUI-Kit/templates/Cards/BasicCard.jsx +60 -0
- package/dist/ApexUI-Kit/templates/Carousel/Carousel.jsx +95 -0
- package/dist/ApexUI-Kit/templates/Carousel/DragCarousel.jsx +105 -0
- package/dist/ApexUI-Kit/templates/Carousel/ParallaxCarousel.jsx +161 -0
- package/dist/ApexUI-Kit/templates/DragCarousel/DragCarousel.jsx +105 -0
- package/dist/ApexUI-Kit/templates/EdgeGlowForm/EdgeGlowFrom.jsx +297 -0
- package/dist/ApexUI-Kit/templates/FlipProfile/FlipProfile.jsx +125 -0
- package/dist/ApexUI-Kit/templates/GlareCard/GlareCard.jsx +146 -0
- package/dist/ApexUI-Kit/templates/GlassCardStack/GlassCardStack.jsx +111 -0
- package/dist/ApexUI-Kit/templates/HoverText/HoverText.jsx +371 -0
- package/dist/ApexUI-Kit/templates/HyperCard/HyperCard.jsx +183 -0
- package/dist/ApexUI-Kit/templates/KineticThreadsBackground/KineticThreadBackground.jsx +249 -0
- package/dist/ApexUI-Kit/templates/LensFlareBackground/LensFlareBackground.jsx +268 -0
- package/dist/ApexUI-Kit/templates/LuminousParticleOcean/LuminousParticleOcean.jsx +155 -0
- package/dist/ApexUI-Kit/templates/NavDropDown/NavDropDown.jsx +114 -0
- package/dist/ApexUI-Kit/templates/NavMenu/NavMenu.jsx +153 -0
- package/dist/ApexUI-Kit/templates/OrbsBackground/FloatingOrbs.jsx +96 -0
- package/dist/ApexUI-Kit/templates/ParallaxCard/ParallaxCard.jsx +127 -0
- package/dist/ApexUI-Kit/templates/ParallaxCarousel/ParallaxCarousel.jsx +161 -0
- package/dist/ApexUI-Kit/templates/ParallaxProfile/ParallaxProfile.jsx +107 -0
- package/dist/ApexUI-Kit/templates/PointerFollower/PointerFollower.jsx +126 -0
- package/dist/ApexUI-Kit/templates/RainBackground/RainBackground.jsx +109 -0
- package/dist/ApexUI-Kit/templates/RippleBackground/RippleBackground.jsx +160 -0
- package/dist/ApexUI-Kit/templates/ScrollMarquee/ScrollMarquee.jsx +174 -0
- package/dist/ApexUI-Kit/templates/SmartGridCard/SmartGridCard.jsx +241 -0
- package/dist/ApexUI-Kit/templates/SmokeBackground/SmokeBackground.jsx +216 -0
- package/dist/ApexUI-Kit/templates/Testimonials/Testimonials.jsx +117 -0
- package/dist/ApexUI-Kit/templates/ThemeToggle/ThemeAnimation.js +252 -0
- package/dist/ApexUI-Kit/templates/ThemeToggle/ThemeConfig.js +8 -0
- package/dist/ApexUI-Kit/templates/ThemeToggle/ThemeToggle.css +560 -0
- package/dist/ApexUI-Kit/templates/ThemeToggle/ThemeToggle.jsx +118 -0
- package/dist/ApexUI-Kit/templates/ToolTip/ToolTip.jsx +85 -0
- package/dist/ApexUI-Kit/templates/WaterDropReveal/WaterDropReveal.jsx +151 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +145 -0
- package/package.json +56 -0
package/Licence.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Mohan Kumar Dalei
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/ReadMe.md
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Apex UI Kit
|
|
2
|
+
|
|
3
|
+
Apex UI Kit is a modern, blazing-fast, beautifully animated React UI library built with Tailwind CSS, GSAP, and Framer Motion. Perfect for modern apps, portfolios, dashboards, and high-performance UIs.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 🌐 Website & Docs
|
|
8
|
+
|
|
9
|
+
- **Main Site:** [https://apex-ui.in/](https://apex-ui.in/)
|
|
10
|
+
- **Introduction:** [https://apex-ui.in/components/docs/getting-started/introduction](https://apex-ui.in/components/docs/getting-started/introduction)
|
|
11
|
+
- **Components:** [https://apex-ui.in/components/glass-navbar](https://apex-ui.in/components/glass-navbar)
|
|
12
|
+
|
|
13
|
+
## 🚀 Installation
|
|
14
|
+
|
|
15
|
+
### 1. Install the package
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install apex-ui-kit
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### 2. Add a component using the CLI
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npx apex-ui-kit add hyper-card
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## 🧩 Usage Example
|
|
30
|
+
|
|
31
|
+
```jsx
|
|
32
|
+
import HyperCard from "./ApexUI-Kit/HyperCard/HyperCard.jsx";
|
|
33
|
+
|
|
34
|
+
const App = () => {
|
|
35
|
+
return (
|
|
36
|
+
<div>
|
|
37
|
+
<HyperCard
|
|
38
|
+
starColor="#9f7aea"
|
|
39
|
+
glow={true}
|
|
40
|
+
beamCount={10}
|
|
41
|
+
starCount={250}
|
|
42
|
+
/>
|
|
43
|
+
</div>
|
|
44
|
+
);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export default App;
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## 📦 Features
|
|
53
|
+
|
|
54
|
+
- Modern, animated React components
|
|
55
|
+
- Easy CLI for adding components
|
|
56
|
+
- Built with Tailwind CSS, GSAP, Framer Motion
|
|
57
|
+
- Perfect for dashboards, portfolios, and modern web apps
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## 📚 Documentation
|
|
62
|
+
|
|
63
|
+
- More components and usage: [Coming Soon]
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## 🛠️ Contributing
|
|
68
|
+
|
|
69
|
+
Pull requests are welcome! For major changes, please open an issue first to discuss what you would like to change.
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## 📝 License
|
|
74
|
+
|
|
75
|
+
MIT
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { motion } from "framer-motion";
|
|
3
|
+
const Avatar = ({ users }) => {
|
|
4
|
+
const containerVariants = {
|
|
5
|
+
hidden: { opacity: 0 },
|
|
6
|
+
visible: {
|
|
7
|
+
opacity: 1,
|
|
8
|
+
transition: {
|
|
9
|
+
staggerChildren: 0.15,
|
|
10
|
+
},
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const itemVariants = {
|
|
15
|
+
hidden: { y: 20, opacity: 0 },
|
|
16
|
+
visible: {
|
|
17
|
+
y: 0,
|
|
18
|
+
opacity: 1,
|
|
19
|
+
transition: {
|
|
20
|
+
type: 'spring',
|
|
21
|
+
stiffness: 100,
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// Helper to get initials and fallback color
|
|
27
|
+
const getInitial = (name) => name ? name.charAt(0).toUpperCase() : "?";
|
|
28
|
+
const getBgColor = (color) => color || "#4a5568";
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<div className="font-sans flex flex-col items-center justify-center">
|
|
32
|
+
<motion.div
|
|
33
|
+
className="flex -space-x-6"
|
|
34
|
+
variants={containerVariants}
|
|
35
|
+
initial="hidden"
|
|
36
|
+
animate="visible"
|
|
37
|
+
>
|
|
38
|
+
{users.map((user, index) => (
|
|
39
|
+
<motion.div key={index} variants={itemVariants}>
|
|
40
|
+
<motion.div
|
|
41
|
+
className="relative"
|
|
42
|
+
whileHover={{ scale: 1.1, zIndex: 10 }}
|
|
43
|
+
transition={{ type: 'spring', stiffness: 300, damping: 15 }}
|
|
44
|
+
>
|
|
45
|
+
{user.imageUrl ? (
|
|
46
|
+
<img
|
|
47
|
+
src={user.imageUrl}
|
|
48
|
+
alt={user.name}
|
|
49
|
+
className="w-20 h-20 object-cover rounded-full border-2"
|
|
50
|
+
style={{ borderColor: getBgColor(user.color) }}
|
|
51
|
+
onError={(e) => { e.target.onerror = null; e.target.src = ""; }}
|
|
52
|
+
/>
|
|
53
|
+
) : (
|
|
54
|
+
<div
|
|
55
|
+
className="w-20 h-20 flex items-center justify-center rounded-full border-4 text-5xl font-bold text-white/40"
|
|
56
|
+
style={{ backgroundColor: getBgColor(user.color), borderColor: getBgColor(user.color) }}
|
|
57
|
+
>
|
|
58
|
+
{getInitial(user.name)}
|
|
59
|
+
</div>
|
|
60
|
+
)}
|
|
61
|
+
<motion.span
|
|
62
|
+
className="absolute bottom-1 right-1 w-4 h-4 bg-green-400 rounded-full border-2 border-slate-900"
|
|
63
|
+
animate={{
|
|
64
|
+
scale: [1, 1.2, 1],
|
|
65
|
+
opacity: [0.8, 1, 0.8],
|
|
66
|
+
}}
|
|
67
|
+
transition={{
|
|
68
|
+
duration: 1.5,
|
|
69
|
+
repeat: Infinity,
|
|
70
|
+
ease: "easeInOut",
|
|
71
|
+
}}
|
|
72
|
+
/>
|
|
73
|
+
</motion.div>
|
|
74
|
+
</motion.div>
|
|
75
|
+
))}
|
|
76
|
+
</motion.div>
|
|
77
|
+
</div>
|
|
78
|
+
);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export default Avatar;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/* eslint-disable no-unused-vars */
|
|
2
|
+
import React, { useEffect, useState } from 'react';
|
|
3
|
+
import { motion, useAnimation } from 'framer-motion';
|
|
4
|
+
|
|
5
|
+
const BeamBar = ({
|
|
6
|
+
fromColor = "#a855f7",
|
|
7
|
+
viaColor = "#38bdf8",
|
|
8
|
+
toColor = "#06b6d4",
|
|
9
|
+
barWidth: maxBarWidth = 320, // Prop ko maxBarWidth ki tarah use karenge
|
|
10
|
+
barHeight = 8,
|
|
11
|
+
duration = 1.2,
|
|
12
|
+
pulseDuration = 2.5
|
|
13
|
+
}) => {
|
|
14
|
+
const controls = useAnimation();
|
|
15
|
+
const [pulse, setPulse] = useState(false);
|
|
16
|
+
const [beamOpacity, setBeamOpacity] = useState(0.1);
|
|
17
|
+
|
|
18
|
+
// Responsive width ke liye state
|
|
19
|
+
const [dynamicBarWidth, setDynamicBarWidth] = useState(0);
|
|
20
|
+
|
|
21
|
+
// Screen size change ko handle karne ke liye useEffect
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
const updateWidth = () => {
|
|
24
|
+
// Screen width ka 80% ya maxBarWidth, jo bhi kam ho
|
|
25
|
+
const newWidth = Math.min(window.innerWidth * 0.8, maxBarWidth);
|
|
26
|
+
setDynamicBarWidth(newWidth);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
updateWidth(); // Pehli baar set karne ke liye
|
|
30
|
+
window.addEventListener('resize', updateWidth); // Resize par update karne ke liye
|
|
31
|
+
|
|
32
|
+
// Cleanup function
|
|
33
|
+
return () => window.removeEventListener('resize', updateWidth);
|
|
34
|
+
}, [maxBarWidth]);
|
|
35
|
+
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
// Jab dynamicBarWidth set ho jaye tab animation start karein
|
|
38
|
+
if (dynamicBarWidth > 0) {
|
|
39
|
+
controls.start({
|
|
40
|
+
width: dynamicBarWidth,
|
|
41
|
+
transition: { duration, ease: "easeInOut" }
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// Animate beam opacity gradually during width expansion
|
|
45
|
+
let start = null;
|
|
46
|
+
function animateBeam(ts) {
|
|
47
|
+
if (!start) start = ts;
|
|
48
|
+
const elapsed = (ts - start) / 1000;
|
|
49
|
+
const progress = Math.min(elapsed / duration, 1);
|
|
50
|
+
setBeamOpacity(0.2 + 0.8 * progress); // from 0.2 to 1.0
|
|
51
|
+
if (progress < 1) {
|
|
52
|
+
requestAnimationFrame(animateBeam);
|
|
53
|
+
} else {
|
|
54
|
+
setPulse(true);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
requestAnimationFrame(animateBeam);
|
|
58
|
+
|
|
59
|
+
// Cleanup
|
|
60
|
+
return () => {
|
|
61
|
+
setBeamOpacity(0.2);
|
|
62
|
+
setPulse(false);
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
}, [dynamicBarWidth, duration, controls]);
|
|
66
|
+
|
|
67
|
+
// Beam ka size ab bar ki width par depend karega
|
|
68
|
+
const beamWidth = dynamicBarWidth * 1.25;
|
|
69
|
+
const beamHeight = 800; // Ise bhi dynamic kar sakte hain agar zaroorat ho
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<div className="fixed top-10 left-0 w-full flex justify-center z-50 pointer-events-none">
|
|
73
|
+
<motion.div
|
|
74
|
+
initial={{ width: 0 }}
|
|
75
|
+
animate={controls}
|
|
76
|
+
// Class se 'hidden md:block' hataya gaya hai
|
|
77
|
+
className={`absolute top-0 left-1/2 -translate-x-1/2 rounded-md shadow-[0_0_10px_#fff,0_0_20px_#4f46e5,0_0_30px_#06b6d4] ${pulse ? 'animate-pulse' : ''}`}
|
|
78
|
+
style={{
|
|
79
|
+
height: barHeight,
|
|
80
|
+
background: `linear-gradient(to right, ${fromColor}, ${viaColor}, ${toColor})`,
|
|
81
|
+
animationDuration: pulse ? `${pulseDuration}s` : undefined,
|
|
82
|
+
}}
|
|
83
|
+
>
|
|
84
|
+
{/* Beam from Bar using pseudo-element */}
|
|
85
|
+
<div
|
|
86
|
+
className="absolute top-2 left-1/2 transform -translate-x-1/2 pointer-events-none z-0"
|
|
87
|
+
style={{
|
|
88
|
+
width: `${beamWidth}px`, // Dynamic width
|
|
89
|
+
height: `${beamHeight}px`, // Dynamic height (optional)
|
|
90
|
+
background: `linear-gradient(to bottom, rgba(111, 93, 245, 0.7), rgba(7, 7, 7, 0.3), transparent)`,
|
|
91
|
+
filter: "blur(100px)",
|
|
92
|
+
opacity: beamOpacity,
|
|
93
|
+
animation: pulse ? `glow ${pulseDuration}s infinite alternate` : undefined
|
|
94
|
+
}}
|
|
95
|
+
></div>
|
|
96
|
+
</motion.div>
|
|
97
|
+
</div>
|
|
98
|
+
);
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
export default BeamBar;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/* eslint-disable no-unused-vars */
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { motion } from "framer-motion";
|
|
4
|
+
import { SquareArrowOutUpRight, Wallpaper } from 'lucide-react';
|
|
5
|
+
import { NavLink } from "react-router-dom";
|
|
6
|
+
const ProfileCard = ({ title, summary, image, link }) => {
|
|
7
|
+
return (
|
|
8
|
+
<motion.div
|
|
9
|
+
initial={{ y: 20, opacity: 0 }}
|
|
10
|
+
animate={{ y: 0, opacity: 1 }}
|
|
11
|
+
transition={{ type: "spring", stiffness: 300, damping: 20 }}
|
|
12
|
+
className="w-72 rounded-2xl bg-slate-950 border border-gray-800 shadow-2xl overflow-hidden p-2 shadow-lime-700/20"
|
|
13
|
+
>
|
|
14
|
+
{/* Image */}
|
|
15
|
+
<div className="h-[30vh] w-full rounded-b-4xl rounded-t-lg overflow-hidden">
|
|
16
|
+
<motion.img
|
|
17
|
+
src={image}
|
|
18
|
+
alt={title}
|
|
19
|
+
className="w-full object-cover h-full"
|
|
20
|
+
whileHover={{ scale: 1.05 }}
|
|
21
|
+
transition={{ duration: 0.4 }}
|
|
22
|
+
/>
|
|
23
|
+
</div>
|
|
24
|
+
{/* Content */}
|
|
25
|
+
<div className="p-4 flex flex-col gap-2">
|
|
26
|
+
<h2 className="text-lg font-bold flex items-center gap-3 text-[var(--color-pages-side-active-text)]">
|
|
27
|
+
<Wallpaper size={18} /> {title}
|
|
28
|
+
</h2>
|
|
29
|
+
|
|
30
|
+
<p className="text-sm text-gray-400 leading-relaxed">
|
|
31
|
+
{summary}
|
|
32
|
+
</p>
|
|
33
|
+
|
|
34
|
+
{/* Stats + Button */}
|
|
35
|
+
<div className="flex items-center justify-center mt-3 transition-all duration-150 ease-in-out">
|
|
36
|
+
{/* Visit Button */}
|
|
37
|
+
<NavLink
|
|
38
|
+
to={link}
|
|
39
|
+
className="px-7 py-1.5 rounded-full bg-lime-600 text-white font-medium shadow hover:bg-lime-500/90 active:scale-95 transition-all duration-150 ease-in-out flex items-center justify-center gap-2 ring-4 ring-lime-600/30 active:ring-lime-600"
|
|
40
|
+
>
|
|
41
|
+
Visit <SquareArrowOutUpRight size={15} />
|
|
42
|
+
</NavLink>
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
</motion.div>
|
|
46
|
+
);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// Wrapper to map array of objects
|
|
50
|
+
const ProfileCardList = ({ data }) => {
|
|
51
|
+
return (
|
|
52
|
+
<div className="flex flex-wrap gap-3 justify-center items-center">
|
|
53
|
+
{data.map((item, index) => (
|
|
54
|
+
<ProfileCard key={index} {...item} />
|
|
55
|
+
))}
|
|
56
|
+
</div>
|
|
57
|
+
);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export default ProfileCardList;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
|
|
3
|
+
// To use Framer Motion in this environment, we'll import it from a CDN.
|
|
4
|
+
// In a local project, you would typically use: `npm install framer-motion`
|
|
5
|
+
import { motion } from 'framer-motion';
|
|
6
|
+
import { ArrowLeft, ArrowRight } from 'lucide-react';
|
|
7
|
+
|
|
8
|
+
// --- Custom Parallax Carousel Component ---
|
|
9
|
+
const ParallaxCarousel = ({ slides }) => {
|
|
10
|
+
const [activeIndex, setActiveIndex] = useState(0);
|
|
11
|
+
|
|
12
|
+
const handleNext = () => {
|
|
13
|
+
setActiveIndex((prevIndex) => (prevIndex + 1) % slides.length);
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const handlePrev = () => {
|
|
17
|
+
setActiveIndex((prevIndex) => (prevIndex - 1 + slides.length) % slides.length);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<div className="w-full flex flex-col items-center px-2">
|
|
23
|
+
{/* 3D Carousel Container without drag/grab */}
|
|
24
|
+
<motion.div
|
|
25
|
+
className="relative w-full h-[440px] portrait:h-[480px] mb-8"
|
|
26
|
+
style={{ perspective: '1200px', transformStyle: 'preserve-3d' }}
|
|
27
|
+
>
|
|
28
|
+
{slides.map((slide, index) => {
|
|
29
|
+
let positionOffset = index - activeIndex;
|
|
30
|
+
if (positionOffset > slides.length / 2) {
|
|
31
|
+
positionOffset -= slides.length;
|
|
32
|
+
} else if (positionOffset < -slides.length / 2) {
|
|
33
|
+
positionOffset += slides.length;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const isVisible = Math.abs(positionOffset) <= 2;
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<motion.div
|
|
40
|
+
key={index}
|
|
41
|
+
className={`absolute w-full h-[440px] portrait:w-full portrait:h-[480px] top-0 left-0 portrait:left-0 portrait:top-0 bg-slate-900 rounded-2xl overflow-hidden border-2 border-slate-800 md:w-[50%] md:h-[450px] md:top-[50px] md:left-[25%]`}
|
|
42
|
+
initial={false}
|
|
43
|
+
animate={{
|
|
44
|
+
translateX: `${positionOffset * 50}%`,
|
|
45
|
+
translateZ: -Math.abs(positionOffset) * 250,
|
|
46
|
+
scale: 1 - Math.abs(positionOffset) * 0.15,
|
|
47
|
+
opacity: isVisible ? (1 - Math.abs(positionOffset) * 0.35) : 0,
|
|
48
|
+
zIndex: slides.length - Math.abs(positionOffset),
|
|
49
|
+
}}
|
|
50
|
+
transition={{ type: 'spring', stiffness: 260, damping: 30 }}
|
|
51
|
+
style={{ transformStyle: 'preserve-3d' }}
|
|
52
|
+
>
|
|
53
|
+
{/* Slide Content */}
|
|
54
|
+
<div className="absolute inset-0 w-full h-full">
|
|
55
|
+
<img
|
|
56
|
+
src={slide.imageUrl}
|
|
57
|
+
alt={slide.title}
|
|
58
|
+
className="w-full h-full object-cover opacity-50 pointer-events-none"
|
|
59
|
+
/>
|
|
60
|
+
</div>
|
|
61
|
+
<div
|
|
62
|
+
className="relative z-10 flex flex-col justify-end h-full p-8 text-white"
|
|
63
|
+
style={{ transform: 'translateZ(60px)' }} // Content ko 3D space mein aage rakhta hai
|
|
64
|
+
>
|
|
65
|
+
<h2 className="text-3xl font-bold">{slide.title}</h2>
|
|
66
|
+
<p className="text-lime-400 mt-1">{slide.subtitle}</p>
|
|
67
|
+
<p className="text-slate-300 mt-4 text-sm leading-relaxed">{slide.text}</p>
|
|
68
|
+
</div>
|
|
69
|
+
</motion.div>
|
|
70
|
+
);
|
|
71
|
+
})}
|
|
72
|
+
</motion.div>
|
|
73
|
+
|
|
74
|
+
{/* Navigation Buttons at the bottom */}
|
|
75
|
+
<div className="flex items-center justify-center gap-4 mt-10">
|
|
76
|
+
<motion.button
|
|
77
|
+
onClick={handlePrev}
|
|
78
|
+
className="bg-slate-800/80 hover:bg-slate-800 rounded-full p-3 transition-colors text-slate-400 hover:text-lime-400"
|
|
79
|
+
whileTap={{ scale: 0.9 }}
|
|
80
|
+
>
|
|
81
|
+
<ArrowLeft size={24} />
|
|
82
|
+
</motion.button>
|
|
83
|
+
<motion.button
|
|
84
|
+
onClick={handleNext}
|
|
85
|
+
className="bg-slate-800/80 hover:bg-slate-800 rounded-full p-3 transition-colors text-slate-400 hover:text-lime-400"
|
|
86
|
+
whileTap={{ scale: 0.9 }}
|
|
87
|
+
>
|
|
88
|
+
<ArrowRight size={24} />
|
|
89
|
+
</motion.button>
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
);
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export default ParallaxCarousel;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import React, { useState, useEffect, useRef } from 'react';
|
|
2
|
+
|
|
3
|
+
// To use GSAP in this environment, we'll import it from a CDN.
|
|
4
|
+
// In a local project: `npm install gsap`
|
|
5
|
+
import { gsap } from 'gsap';
|
|
6
|
+
import { Draggable } from 'gsap/Draggable';
|
|
7
|
+
|
|
8
|
+
// GSAP Draggable plugin ko register karna zaroori hai
|
|
9
|
+
gsap.registerPlugin(Draggable);
|
|
10
|
+
|
|
11
|
+
// --- GSAP Drag Carousel Component ---
|
|
12
|
+
const DragCarousel = ({ images }) => {
|
|
13
|
+
const [activeIndex, setActiveIndex] = useState(0);
|
|
14
|
+
const carouselRef = useRef(null);
|
|
15
|
+
const cardsRef = useRef([]);
|
|
16
|
+
const dragProxyRef = useRef(null);
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
const cards = cardsRef.current;
|
|
19
|
+
gsap.to(cards, {
|
|
20
|
+
duration: 0.4,
|
|
21
|
+
ease: 'power4.Out',
|
|
22
|
+
stagger: 0.05,
|
|
23
|
+
// Har card ke liye naya transform calculate karta hai
|
|
24
|
+
transform: (i) => {
|
|
25
|
+
let positionOffset = i - activeIndex;
|
|
26
|
+
if (positionOffset > images.length / 2) {
|
|
27
|
+
positionOffset -= images.length;
|
|
28
|
+
} else if (positionOffset < -images.length / 2) {
|
|
29
|
+
positionOffset += images.length;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const translateX = positionOffset * 50;
|
|
33
|
+
const translateZ = -Math.abs(positionOffset) * 250;
|
|
34
|
+
const scale = 1 - Math.abs(positionOffset) * 0.15;
|
|
35
|
+
|
|
36
|
+
return `translateX(${translateX}%) translateZ(${translateZ}px) scale(${scale})`;
|
|
37
|
+
},
|
|
38
|
+
zIndex: (i) => {
|
|
39
|
+
let positionOffset = i - activeIndex;
|
|
40
|
+
if (positionOffset > images.length / 2) positionOffset -= images.length;
|
|
41
|
+
if (positionOffset < -images.length / 2) positionOffset += images.length;
|
|
42
|
+
return images.length - Math.abs(positionOffset);
|
|
43
|
+
},
|
|
44
|
+
opacity: (i) => {
|
|
45
|
+
let positionOffset = i - activeIndex;
|
|
46
|
+
if (positionOffset > images.length / 2) positionOffset -= images.length;
|
|
47
|
+
if (positionOffset < -images.length / 2) positionOffset += images.length;
|
|
48
|
+
return Math.abs(positionOffset) > 2 ? 0 : 1;
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
}, [activeIndex, images.length]);
|
|
52
|
+
|
|
53
|
+
// GSAP Draggable ko setup karta hai
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
// BUG FIX: Draggable ab proxy element par lagaya gaya hai
|
|
56
|
+
const draggableInstance = Draggable.create(dragProxyRef.current, {
|
|
57
|
+
type: "x",
|
|
58
|
+
trigger: carouselRef.current,
|
|
59
|
+
inertia: true,
|
|
60
|
+
cursor: 'grab',
|
|
61
|
+
activeCursor: 'grabbing',
|
|
62
|
+
onDragEnd: function () {
|
|
63
|
+
gsap.to(this.target, { x: 0, duration: 0.3 });
|
|
64
|
+
if (this.getDirection("start") === "left") {
|
|
65
|
+
setActiveIndex(prev => (prev + 1) % images.length);
|
|
66
|
+
} else if (this.getDirection("start") === "right") {
|
|
67
|
+
setActiveIndex(prev => (prev - 1 + images.length) % images.length);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Cleanup function
|
|
73
|
+
return () => {
|
|
74
|
+
if (draggableInstance && draggableInstance[0]) {
|
|
75
|
+
draggableInstance[0].kill();
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
}, [images.length]);
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<div
|
|
82
|
+
ref={carouselRef}
|
|
83
|
+
className="relative w-full h-[490px] lg:h-[590px] cursor-grab"
|
|
84
|
+
style={{ perspective: '1200px', transformStyle: 'preserve-3d' }}
|
|
85
|
+
>
|
|
86
|
+
<div ref={dragProxyRef} className="absolute inset-0 z-20"></div>
|
|
87
|
+
|
|
88
|
+
{images.map((src, index) => (
|
|
89
|
+
<div
|
|
90
|
+
key={index}
|
|
91
|
+
ref={el => cardsRef.current[index] = el}
|
|
92
|
+
className="card-item absolute h-[480px] w-[300px] lg:h-[550px] lg:w-[350px] rounded-xl overflow-hidden shadow-2xl top-[25px] -left-4 md:left-36 lg:left-70"
|
|
93
|
+
>
|
|
94
|
+
<img
|
|
95
|
+
src={src}
|
|
96
|
+
alt={`carousel-item-${index}`}
|
|
97
|
+
className="w-full h-full object-cover pointer-events-none"
|
|
98
|
+
/>
|
|
99
|
+
</div>
|
|
100
|
+
))}
|
|
101
|
+
</div>
|
|
102
|
+
);
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
export default DragCarousel;
|