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.
- package/dist/ApexUI-Kit/templates/AccordionMarquee/AccordionMarquee.css +1 -0
- package/dist/ApexUI-Kit/templates/AccordionMarquee/AccordionMarquee.jsx +140 -0
- package/dist/ApexUI-Kit/templates/BluePrintBackground/BluePrintBackground.jsx +133 -0
- package/dist/ApexUI-Kit/templates/CardStack/CardStack.jsx +101 -0
- package/dist/ApexUI-Kit/templates/Carousel/ParallaxCarousel.jsx +0 -161
- package/dist/ApexUI-Kit/templates/DragCarousel/DragCarousel.jsx +38 -6
- package/dist/ApexUI-Kit/templates/MeteorCard/MeteorCard.css +95 -0
- package/dist/ApexUI-Kit/templates/MeteorCard/MeteorCard.jsx +93 -0
- package/dist/ApexUI-Kit/templates/ParallaxCarousel/ParallaxCarousel.jsx +54 -61
- package/dist/ApexUI-Kit/templates/ParallaxProfile/ParallaxProfile.jsx +30 -19
- package/dist/ApexUI-Kit/templates/PointerFollower/PointerFollower.jsx +27 -7
- package/dist/ApexUI-Kit/templates/ScrollMarquee/CardMarquee.jsx +0 -0
- package/dist/ApexUI-Kit/templates/SmokeBackground/SmokeBackground.jsx +23 -15
- package/dist/ApexUI-Kit/templates/ThemeToggle/ThemeToggle.css +4 -0
- package/dist/cli/index.js +4 -1
- package/package.json +1 -1
- package/dist/ApexUI-Kit/templates/GlassCardStack/GlassCardStack.jsx +0 -111
|
@@ -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.
|
|
21
|
-
ease: '
|
|
22
|
-
stagger: 0.
|
|
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.
|
|
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
|
-
|
|
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
|