apex-ui-kit 2.0.5 → 2.0.7

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.
@@ -0,0 +1 @@
1
+ @import url("https://fonts.googleapis.com/css2?family=Righteous&display=swap");
@@ -0,0 +1,140 @@
1
+ import React, { useLayoutEffect, useRef } from 'react';
2
+ // GSAP ko seedhe CDN se import kiya gaya hai taaki yeh browser mein chale
3
+ import gsap from "gsap";
4
+ import './AccordionMarquee.css'
5
+ // Accordion ka data
6
+ const accordionData = [
7
+ {
8
+ title: "Cybernetic Dreams",
9
+ text: "Explore the Future",
10
+ },
11
+ {
12
+ title: "Quantum Leap",
13
+ text: "Journey Through Spacetime",
14
+ },
15
+ {
16
+ title: "Neural Networks",
17
+ text: "Unravel Complexity",
18
+ },
19
+ {
20
+ title: "Digital Odyssey",
21
+ text: "Voyage into the Digital World",
22
+ },
23
+ ];
24
+
25
+ const AccordionMarquee = ({bgColor='#bbf451', textColor='#27272a'}) => {
26
+ const containerRef = useRef(null);
27
+
28
+ useLayoutEffect(() => {
29
+ const items = gsap.utils.toArray(".accordion-item");
30
+ let activeItem = null;
31
+ let activeMarquee = null;
32
+
33
+ items.forEach((item) => {
34
+ const marqueeText = item.querySelector(".marquee-text");
35
+ const title = item.querySelector(".accordion-title");
36
+
37
+ // Marquee text ko 2 baar duplicate karte hain for seamless loop
38
+ const marqueeContent = marqueeText.innerText;
39
+ const sparkleIconSVG = `<svg xmlns="http://www.w3.org/2000/svg" width="100" height="70" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-sparkle-icon lucide-sparkle"><path d="M11.017 2.814a1 1 0 0 1 1.966 0l1.051 5.558a2 2 0 0 0 1.594 1.594l5.558 1.051a1 1 0 0 1 0 1.966l-5.558 1.051a2 2 0 0 0-1.594 1.594l-1.051 5.558a1 1 0 0 1-1.966 0l-1.051-5.558a2 2 0 0 0-1.594-1.594l-5.558-1.051a1 1 0 0 1 0-1.966l5.558-1.051a2 2 0 0 0 1.594-1.594z" class="sparkle-icon"/></svg>`;
40
+ marqueeText.innerHTML = [marqueeContent, sparkleIconSVG, marqueeContent, sparkleIconSVG, marqueeContent, sparkleIconSVG, marqueeContent, sparkleIconSVG].join("");
41
+
42
+ // Seamless Marquee animation
43
+ const marqueeWidth = marqueeText.scrollWidth / 2;
44
+ const marqueeTl = gsap.to(marqueeText, {
45
+ x: -marqueeWidth,
46
+ duration: marqueeWidth / 30, // Speed ko consistent rakhta hai
47
+ ease: "linear",
48
+ repeat: -1,
49
+ }).pause();
50
+
51
+ item.addEventListener("mouseenter", () => {
52
+ if (activeItem === item) return;
53
+
54
+ if (activeItem) {
55
+ const oldTitle = activeItem.querySelector(".accordion-title");
56
+ const oldMarqueeWrapper = activeItem.querySelector(".marquee-wrapper");
57
+ gsap.to(oldTitle, { autoAlpha: 1, duration: 0.2, ease: "power3.inOut" });
58
+ gsap.to(oldMarqueeWrapper, { autoAlpha: 0, duration: 0.2, ease: "power3.inOut" });
59
+ if (activeMarquee) activeMarquee.pause();
60
+ }
61
+
62
+ activeItem = item;
63
+ activeMarquee = marqueeTl;
64
+
65
+ const marqueeWrapper = item.querySelector(".marquee-wrapper");
66
+ gsap.to(title, { autoAlpha: 0, duration: 0.2, ease: "power3.inOut" });
67
+ gsap.to(marqueeWrapper, { autoAlpha: 1, duration: 0.2, delay: 0.1, ease: "power3.inOut" });
68
+
69
+ marqueeTl.play();
70
+ });
71
+ });
72
+
73
+ containerRef.current.addEventListener("mouseleave", () => {
74
+ if (!activeItem) return;
75
+
76
+ const activeTitle = activeItem.querySelector(".accordion-title");
77
+ const activeMarqueeWrapper = activeItem.querySelector(".marquee-wrapper");
78
+
79
+ gsap.to(activeTitle, { autoAlpha: 1, duration: 0.2, delay: 0.1, ease: "power3.inOut" });
80
+ gsap.to(activeMarqueeWrapper, { autoAlpha: 0, duration: 0.2, ease: "power3.inOut" });
81
+
82
+ if (activeMarquee) {
83
+ activeMarquee.pause();
84
+ }
85
+ activeItem = null;
86
+ activeMarquee = null;
87
+ });
88
+
89
+ }, []);
90
+
91
+ return (
92
+ <>
93
+ <style>
94
+ {`
95
+ @keyframes sparkle-pulse {
96
+ 0%, 100% {
97
+ stroke: #000;
98
+ stroke-width: 0.5;
99
+ }
100
+ 50% {
101
+ stroke: #444;
102
+ stroke-width: 1;
103
+ }
104
+ }
105
+ .sparkle-icon {
106
+ animation: sparkle-pulse 2.5s ease-in-out infinite;
107
+ }
108
+ `}
109
+ </style>
110
+ <div className="flex items-center justify-center w-screen bg-gray-900 text-white font-sans">
111
+ <div ref={containerRef} className="flex flex-col w-full h-[80vh] md:h-[70vh]">
112
+ {accordionData.map((item, index) => (
113
+ <React.Fragment key={index}>
114
+ <div className="accordion-item relative w-full h-40 overflow-hidden cursor-pointer flex items-center justify-center bg-gray-800">
115
+ <div className="relative z-10 w-full text-center px-4">
116
+ <h2 className="accordion-title text-2xl sm:text-3xl lg:text-5xl font-extrabold uppercase tracking-wider whitespace-nowrap" style={{ textShadow: '2px 2px 8px rgba(0,0,0,0.7)' }}>
117
+ {item.title}
118
+ </h2>
119
+ <div className="marquee-wrapper absolute inset-0 w-full h-20 flex items-center overflow-hidden opacity-0" style={{backgroundColor: bgColor}}>
120
+ <p className="marquee-text text-4xl sm:text-5xl md:text-6xl font-bold whitespace-nowrap flex items-center"
121
+ style={{ textShadow: '1px 1px 3px rgba(0,0,0,0.2)', fontFamily: "Righteous, sans-serif", color: textColor }}
122
+ >
123
+ {item.text}
124
+ </p>
125
+ </div>
126
+ </div>
127
+ </div>
128
+ {index < accordionData.length - 1 && (
129
+ <hr className="w-full border-t-2 border-gray-700 my-0" />
130
+ )}
131
+ </React.Fragment>
132
+ ))}
133
+ </div>
134
+ </div>
135
+ </>
136
+ );
137
+ };
138
+
139
+ export default AccordionMarquee;
140
+
@@ -0,0 +1,133 @@
1
+ import React, { useRef, useEffect, useState } from "react";
2
+ import { gsap } from "gsap";
3
+
4
+ function BluePrintBackground({ color = '#9ae600', borderColor = 'rgb(98, 116, 142, 0.1)', bgColor, circleColor = 'rgb(154, 230, 0, 0.2)'}) {
5
+ const gridRef = useRef(null);
6
+ const [grid, setGrid] = useState({ cols: 0, rows: 0 });
7
+ const boxSize = 40;
8
+ const lastHighlighted = useRef(null);
9
+ const colorRef = useRef(color);
10
+
11
+ // Effect 1: calculate grid size and keep it updated on resize
12
+ useEffect(() => {
13
+ const calculateGrid = () => {
14
+ const cols = Math.ceil(window.innerWidth / boxSize);
15
+ const rows = Math.ceil(window.innerHeight / boxSize) * 2; // double rows for infinite scroll
16
+ // kill any child tweens before we replace children to avoid visual flicker
17
+ const el = gridRef.current;
18
+ if (el) {
19
+ try { gsap.killTweensOf(el); } catch { /* ignore */ }
20
+ Array.from(el.children).forEach((c) => { try { gsap.killTweensOf(c); } catch { /* ignore */ } });
21
+ lastHighlighted.current = null;
22
+ }
23
+ setGrid({ cols, rows });
24
+ };
25
+
26
+ calculateGrid();
27
+ window.addEventListener("resize", calculateGrid);
28
+ return () => window.removeEventListener("resize", calculateGrid);
29
+ }, []);
30
+
31
+ // keep colorRef up-to-date without forcing effect re-run
32
+ useEffect(() => { colorRef.current = color; }, [color]);
33
+
34
+ // Effect 2: initialize GSAP infinite scroll and mouse handlers once (do not recreate on grid changes)
35
+ useEffect(() => {
36
+ const el = gridRef.current;
37
+ let scrollTween = null;
38
+
39
+ if (el) {
40
+ // make sure we start from current transform so GSAP doesn't jump
41
+ // create a looping tween on the container only once
42
+ scrollTween = gsap.to(el, {
43
+ y: "-50%",
44
+ duration: 15,
45
+ repeat: -1,
46
+ ease: "linear",
47
+ overwrite: false,
48
+ });
49
+ }
50
+
51
+ const handleMouseMove = (e) => {
52
+ const el2 = gridRef.current;
53
+ if (!el2) return;
54
+
55
+ const target = document.elementFromPoint(e.clientX, e.clientY);
56
+ if (!target || !el2.contains(target)) {
57
+ if (lastHighlighted.current !== null) {
58
+ const prev = el2.children[lastHighlighted.current];
59
+ if (prev) gsap.to(prev, { backgroundColor: "transparent", boxShadow: "none", duration: 0.18 });
60
+ lastHighlighted.current = null;
61
+ }
62
+ return;
63
+ }
64
+
65
+ const child = target.closest("div");
66
+ const index = Array.prototype.indexOf.call(el2.children, child);
67
+ if (index === -1) return;
68
+
69
+ if (lastHighlighted.current !== index) {
70
+ if (lastHighlighted.current !== null) {
71
+ const prevBox = el2.children[lastHighlighted.current];
72
+ if (prevBox) gsap.to(prevBox, { backgroundColor: "transparent", boxShadow: "none", duration: 0.18 });
73
+ }
74
+
75
+ const newBox = el2.children[index];
76
+ if (newBox) {
77
+ const c = colorRef.current || "#a3e635";
78
+ gsap.to(newBox, { backgroundColor: c, boxShadow: `0 0 12px ${c}`, duration: 0.12 });
79
+ }
80
+
81
+ lastHighlighted.current = index;
82
+ }
83
+ };
84
+
85
+ window.addEventListener("mousemove", handleMouseMove);
86
+
87
+ return () => {
88
+ window.removeEventListener("mousemove", handleMouseMove);
89
+ if (scrollTween && typeof scrollTween.kill === "function") {
90
+ try { scrollTween.kill(); } catch { /* ignore */ }
91
+ }
92
+ if (el) {
93
+ try { gsap.killTweensOf(el); } catch { /* ignore */ }
94
+ Array.from(el.children).forEach((c) => { try { gsap.killTweensOf(c); } catch { /* ignore */ } });
95
+ }
96
+ lastHighlighted.current = null;
97
+ };
98
+ }, []);
99
+
100
+ const total = grid.cols * grid.rows;
101
+
102
+ return (
103
+ <div className="absolute inset-0 z-0 w-full h-full overflow-hidden bg-slate-800" style={{ backgroundColor: `${bgColor}` }}>
104
+ <div
105
+ ref={gridRef}
106
+ className="grid"
107
+ style={{
108
+ gridTemplateColumns: `repeat(${grid.cols}, ${boxSize}px)`,
109
+ gridTemplateRows: `repeat(${grid.rows}, ${boxSize}px)`,
110
+ }}
111
+ >
112
+ {Array.from({ length: total }).map((_, i) => (
113
+ <div
114
+ key={i}
115
+ className={`border bg-transparent w-10 h-10`}
116
+ style={{
117
+ borderColor: borderColor
118
+ }}
119
+ />
120
+ ))}
121
+ </div>
122
+ {/* Spotlight overlay - positioned after grid to not interfere with scroll */}
123
+ <div
124
+ className="absolute inset-0 z-10 pointer-events-none blur-3xl"
125
+ style={{
126
+ background: `radial-gradient(circle, ${circleColor} 0%, rgba(0, 0, 255, 0.1) 100%)`
127
+ }}
128
+ />
129
+ </div >
130
+ );
131
+ }
132
+
133
+ export default BluePrintBackground;
@@ -0,0 +1,101 @@
1
+ /* eslint-disable no-unused-vars */
2
+ import React, { useState, useEffect, useRef } from "react";
3
+ import { gsap } from "gsap";
4
+ import { FaExternalLinkAlt, FaGithub, FaRegImage } from "react-icons/fa";
5
+ const CARD_STACK_OFFSET = 32;
6
+ const CARD_SCALE_STEP = 0.08;
7
+ const AUTO_CYCLE_INTERVAL = 5000;
8
+ export default function CardStack({
9
+ cards = [],
10
+ autoCycle = true,
11
+ cycleInterval = AUTO_CYCLE_INTERVAL,
12
+ }) {
13
+ const [active, setActive] = useState(0);
14
+ const [isHovered, setIsHovered] = useState(false);
15
+ const [isScrolling, setIsScrolling] = useState(false);
16
+ const timeoutRef = useRef(null);
17
+ const scrollTimeoutRef = useRef(null);
18
+ const containerRef = useRef(null);
19
+ useEffect(() => {
20
+ if (!autoCycle || isHovered || isScrolling) return;
21
+ timeoutRef.current = setTimeout(() => {
22
+ setActive((prev) => (prev + 1) % cards.length);
23
+ }, cycleInterval);
24
+ return () => clearTimeout(timeoutRef.current);
25
+ }, [active, isHovered, isScrolling, autoCycle, cycleInterval, cards.length]);
26
+
27
+ useEffect(() => {
28
+ const handleScroll = () => {
29
+ setIsScrolling(true);
30
+ clearTimeout(scrollTimeoutRef.current);
31
+ scrollTimeoutRef.current = setTimeout(() => setIsScrolling(false), 150);
32
+ };
33
+ window.addEventListener('scroll', handleScroll, { passive: true });
34
+ return () => {
35
+ window.removeEventListener('scroll', handleScroll);
36
+ clearTimeout(scrollTimeoutRef.current);
37
+ };
38
+ }, []);
39
+ useEffect(() => {
40
+ if (!containerRef.current) return;
41
+ const cardElements = gsap.utils.toArray(".card-item", containerRef.current);
42
+ const tl = gsap.timeline({
43
+ defaults: { duration: 0.6, ease: "elastic.out(1, 0.3)" },
44
+ });
45
+ cardElements.forEach((card, i) => {
46
+ const isTop = i === 0;
47
+ const z = cards.length - i;
48
+ const offset = i * CARD_STACK_OFFSET;
49
+ const scale = 1 - i * CARD_SCALE_STEP;
50
+
51
+ tl.to(card, {
52
+ y: -offset,
53
+ scale: scale,
54
+ filter: isTop ? "none" : "blur(1px)",
55
+ opacity: 1,
56
+ zIndex: z,
57
+ }, 0);
58
+ });
59
+ return () => {
60
+ tl.kill();
61
+ };
62
+ }, [active]);
63
+ const getCardIndex = (i) => (active + i) % cards.length;
64
+ return (
65
+ <div
66
+ ref={containerRef}
67
+ className="relative flex justify-center items-center h-[340px] md:h-[400px] w-full max-w-lg mx-auto"
68
+ onMouseEnter={() => setIsHovered(true)}
69
+ onMouseLeave={() => setIsHovered(false)}
70
+ >
71
+ {cards.map((_, i) => {
72
+ if (i >= 4) return null;
73
+
74
+ const idx = getCardIndex(i);
75
+ const isTop = i === 0;
76
+ const card = cards[idx];
77
+
78
+ return (
79
+ <div
80
+ key={idx}
81
+ className={`card-item absolute left-0 right-0 mx-auto w-11/12 md:w-96 h-48 md:h-60 rounded-3xl shadow-xl bg-gradient-to-br ${card.color} bg-slate-950 border border-white/20 flex flex-row items-center px-3 md:px-6 py-4 cursor-pointer select-none ${isTop ? "" : "pointer-events-none"}`}
82
+ style={{ opacity: 0 }}
83
+ >
84
+ <div className="flex-shrink-0 flex items-center justify-center w-16 h-16 md:w-24 md:h-24 lg:w-28 lg:h-28 rounded-xl overflow-hidden bg-white/10 border border-white/20 mr-3 md:mr-6">
85
+ {card.image ? (<img src={card.image} alt={card.title} className="object-cover w-full h-full" />) : (<FaRegImage className="text-3xl text-white/40" />)}
86
+ </div>
87
+ <div className="flex-1 flex flex-col justify-center gap-1 md:gap-2 overflow-hidden">
88
+ <div className="flex items-center gap-2">
89
+ <h2 className="text-base md:text-xl font-bold text-white drop-shadow truncate">{card.title}</h2>
90
+ {card.github && (<a href={card.github} target="_blank" rel="noopener noreferrer" className="text-white/70 hover:text-white ml-1 flex-shrink-0"><FaGithub className="inline-block align-middle text-lg md:text-xl" /></a>)}
91
+ </div>
92
+ <p className="text-xs md:text-sm text-white/60 mb-1 truncate">{card.subtitle}</p>
93
+ <p className="text-xs md:text-base text-white/80 mb-1 line-clamp-2">{card.desc}</p>
94
+ {card.link && (<a href={card.link} target="_blank" rel="noopener noreferrer" className="inline-flex items-center gap-1 text-xs md:text-sm px-2 py-1 rounded bg-blue-500/80 hover:bg-blue-600/90 text-white font-semibold shadow transition w-fit mt-1"><FaExternalLinkAlt className="inline-block mr-1 text-xs md:text-sm" />Visit</a>)}
95
+ </div>
96
+ </div>
97
+ );
98
+ })}
99
+ </div>
100
+ );
101
+ }
@@ -1,161 +0,0 @@
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;
@@ -16,10 +16,16 @@ const DragCarousel = ({ images }) => {
16
16
  const dragProxyRef = useRef(null);
17
17
  useEffect(() => {
18
18
  const cards = cardsRef.current;
19
+ // hint browser to use the GPU for transforms
20
+ cards.forEach((c) => {
21
+ if (c) c.style.willChange = 'transform, opacity';
22
+ });
23
+
19
24
  gsap.to(cards, {
20
- duration: 0.4,
21
- ease: 'power4.Out',
22
- stagger: 0.05,
25
+ duration: 0.45,
26
+ ease: 'power2.out',
27
+ stagger: 0.03,
28
+ overwrite: true,
23
29
  // Har card ke liye naya transform calculate karta hai
24
30
  transform: (i) => {
25
31
  let positionOffset = i - activeIndex;
@@ -31,7 +37,7 @@ const DragCarousel = ({ images }) => {
31
37
 
32
38
  const translateX = positionOffset * 50;
33
39
  const translateZ = -Math.abs(positionOffset) * 250;
34
- const scale = 1 - Math.abs(positionOffset) * 0.15;
40
+ const scale = 1 - Math.abs(positionOffset) * 0.12;
35
41
 
36
42
  return `translateX(${translateX}%) translateZ(${translateZ}px) scale(${scale})`;
37
43
  },
@@ -53,14 +59,39 @@ const DragCarousel = ({ images }) => {
53
59
  // GSAP Draggable ko setup karta hai
54
60
  useEffect(() => {
55
61
  // BUG FIX: Draggable ab proxy element par lagaya gaya hai
62
+ const cards = cardsRef.current;
63
+ const setters = cards.map((c) => (c ? gsap.quickSetter(c, 'css', 'transform') : null));
64
+
56
65
  const draggableInstance = Draggable.create(dragProxyRef.current, {
57
66
  type: "x",
58
67
  trigger: carouselRef.current,
59
68
  inertia: true,
60
69
  cursor: 'grab',
61
70
  activeCursor: 'grabbing',
71
+ onDrag: function () {
72
+ const w = carouselRef.current ? carouselRef.current.clientWidth : window.innerWidth;
73
+ const shiftPercent = (this.x / w) * 100; // percentage shift based on drag
74
+
75
+ cards.forEach((c, i) => {
76
+ if (!c || !setters[i]) return;
77
+ let positionOffset = i - activeIndex;
78
+ if (positionOffset > images.length / 2) positionOffset -= images.length;
79
+ if (positionOffset < -images.length / 2) positionOffset += images.length;
80
+
81
+ const baseX = positionOffset * 50;
82
+ const translateX = baseX + shiftPercent;
83
+ const translateZ = -Math.abs(positionOffset) * 250;
84
+ const scale = 1 - Math.abs(positionOffset) * 0.12;
85
+
86
+ setters[i](`translateX(${translateX}%) translateZ(${translateZ}px) scale(${scale})`);
87
+ c.style.opacity = Math.abs(positionOffset - (this.x / w)) > 2 ? 0 : 1;
88
+ c.style.zIndex = images.length - Math.abs(positionOffset);
89
+ });
90
+ },
62
91
  onDragEnd: function () {
63
- gsap.to(this.target, { x: 0, duration: 0.3 });
92
+ // Reset proxy position
93
+ gsap.to(this.target, { x: 0, duration: 0.35, ease: 'power2.out' });
94
+ // Direction based index change
64
95
  if (this.getDirection("start") === "left") {
65
96
  setActiveIndex(prev => (prev + 1) % images.length);
66
97
  } else if (this.getDirection("start") === "right") {
@@ -74,8 +105,9 @@ const DragCarousel = ({ images }) => {
74
105
  if (draggableInstance && draggableInstance[0]) {
75
106
  draggableInstance[0].kill();
76
107
  }
108
+ setters.forEach((s) => s && s.kill && s.kill());
77
109
  };
78
- }, [images.length]);
110
+ }, [images.length, activeIndex]);
79
111
 
80
112
  return (
81
113
  <div