apex-ui-kit 2.0.6 → 2.1.0
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.jsx +74 -53
- package/dist/ApexUI-Kit/templates/BeamBar/BeamBar.jsx +5 -3
- package/dist/ApexUI-Kit/templates/BluePrintBackground/BluePrintBackground.jsx +14 -2
- package/dist/ApexUI-Kit/templates/CardStack/CardStack.jsx +129 -0
- package/dist/ApexUI-Kit/templates/Cards/BasicCard.jsx +3 -3
- package/dist/ApexUI-Kit/templates/Carousel/ParallaxCarousel.jsx +0 -161
- package/dist/ApexUI-Kit/templates/DragCarousel/DragCarousel.jsx +39 -7
- package/dist/ApexUI-Kit/templates/EdgeGlowForm/EdgeGlowFrom.jsx +116 -98
- package/dist/ApexUI-Kit/templates/FlipProfile/FlipProfile.jsx +69 -37
- package/dist/ApexUI-Kit/templates/GlareCard/GlareCard.jsx +2 -2
- package/dist/ApexUI-Kit/templates/HoverText/HoverText.jsx +14 -13
- package/dist/ApexUI-Kit/templates/HyperCard/HyperCard.jsx +59 -42
- package/dist/ApexUI-Kit/templates/KineticThreadsBackground/KineticThreadBackground.jsx +17 -2
- package/dist/ApexUI-Kit/templates/LensFlareBackground/LensFlareBackground.jsx +18 -3
- package/dist/ApexUI-Kit/templates/LuminousParticleOcean/LuminousParticleOcean.jsx +15 -1
- package/dist/ApexUI-Kit/templates/MaskView/MaskView.jsx +345 -0
- package/dist/ApexUI-Kit/templates/MeteorCard/MeteorCard.jsx +34 -45
- package/dist/ApexUI-Kit/templates/NavDropDown/NavDropDown.jsx +272 -101
- package/dist/ApexUI-Kit/templates/NavMenu/NavMenu.jsx +8 -2
- package/dist/ApexUI-Kit/templates/ParallaxCard/ParallaxCard.jsx +13 -11
- package/dist/ApexUI-Kit/templates/ParallaxCarousel/ParallaxCarousel.jsx +60 -67
- package/dist/ApexUI-Kit/templates/ParallaxProfile/ParallaxProfile.jsx +69 -55
- package/dist/ApexUI-Kit/templates/PointerFollower/PointerFollower.jsx +34 -14
- package/dist/ApexUI-Kit/templates/ScrollMarquee/CardMarquee.jsx +0 -0
- package/dist/ApexUI-Kit/templates/SmartGridCard/SmartGridCard.jsx +4 -3
- package/dist/ApexUI-Kit/templates/SmokeBackground/SmokeBackground.jsx +35 -16
- package/dist/ApexUI-Kit/templates/Testimonials/Testimonials.jsx +11 -8
- package/dist/ApexUI-Kit/templates/ThemeToggle/ThemeAnimation.js +40 -69
- package/dist/ApexUI-Kit/templates/ThemeToggle/ThemeToggle.css +5 -1
- package/dist/ApexUI-Kit/templates/ThemeToggle/ThemeToggle.jsx +25 -7
- package/dist/ApexUI-Kit/templates/ToolTip/ToolTip.jsx +71 -52
- package/dist/cli/index.js +25 -14
- package/package.json +2 -2
- package/dist/ApexUI-Kit/templates/GlassCardStack/GlassCardStack.jsx +0 -111
- package/dist/ApexUI-Kit/templates/WaterDropReveal/WaterDropReveal.jsx +0 -151
|
@@ -2,18 +2,16 @@
|
|
|
2
2
|
import React, { useState, useEffect, useRef } from 'react';
|
|
3
3
|
import { motion, useMotionValue, useTransform } from 'framer-motion';
|
|
4
4
|
import { ArrowLeft, ArrowRight } from 'lucide-react';
|
|
5
|
-
|
|
6
|
-
// --- Card Component with Hover Parallax Effect ---
|
|
7
|
-
const CarouselCard = ({ slide, isCenter }) => {
|
|
5
|
+
const CarouselCard = ({ slide, isCenter, isScrolling }) => {
|
|
8
6
|
const cardRef = useRef(null);
|
|
9
7
|
const x = useMotionValue(0);
|
|
10
8
|
const y = useMotionValue(0);
|
|
11
9
|
|
|
12
|
-
// Mouse move ke hisab se rotation calculate karta hai (effect ko strong banaya gaya hai)
|
|
13
10
|
const rotateX = useTransform(y, [-200, 200], [15, -15]);
|
|
14
11
|
const rotateY = useTransform(x, [-200, 200], [-15, 15]);
|
|
12
|
+
|
|
15
13
|
const handleMouseMove = (event) => {
|
|
16
|
-
if (!cardRef.current || !isCenter) return;
|
|
14
|
+
if (!cardRef.current || !isCenter || isScrolling) return;
|
|
17
15
|
const rect = cardRef.current.getBoundingClientRect();
|
|
18
16
|
x.set(event.clientX - rect.left - rect.width / 2);
|
|
19
17
|
y.set(event.clientY - rect.top - rect.height / 2);
|
|
@@ -24,6 +22,13 @@ const CarouselCard = ({ slide, isCenter }) => {
|
|
|
24
22
|
y.set(0);
|
|
25
23
|
};
|
|
26
24
|
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
if (isScrolling) {
|
|
27
|
+
x.set(0);
|
|
28
|
+
y.set(0);
|
|
29
|
+
}
|
|
30
|
+
}, [isScrolling, x, y]);
|
|
31
|
+
|
|
27
32
|
return (
|
|
28
33
|
<motion.div
|
|
29
34
|
ref={cardRef}
|
|
@@ -36,36 +41,16 @@ const CarouselCard = ({ slide, isCenter }) => {
|
|
|
36
41
|
}}
|
|
37
42
|
onMouseMove={handleMouseMove}
|
|
38
43
|
onMouseLeave={handleMouseLeave}
|
|
39
|
-
|
|
40
44
|
>
|
|
41
45
|
{/* Slide Content */}
|
|
42
|
-
<div className="absolute inset-0 w-full h-full"
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
/>
|
|
46
|
+
<div className="absolute inset-0 w-full h-full" style={{ transformStyle: 'preserve-3d', perspective: '1500px' }}>
|
|
47
|
+
<img src={slide.imageUrl} alt={slide.title} className="w-full h-full object-cover group-hover:opacity-30 transition-all duration-300 ease-in-out rounded-2xl" />
|
|
48
|
+
<img src={slide.pngUrl} alt={slide.title} className="absolute inset-0 w-full h-full object-cover group-hover:translate-z-15 transition-all duration-300 ease-in-out rounded-2xl" />
|
|
58
49
|
</div>
|
|
59
|
-
<div
|
|
60
|
-
className="
|
|
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>
|
|
50
|
+
<div className="relative flex flex-col justify-end h-full text-white p-5 sm:p-8" style={{ transformStyle: 'preserve-3d', perspective: '1500px' }}>
|
|
51
|
+
<h2 className="text-2xl sm:text-3xl font-bold group-hover:translate-z-18 transition-all duration-300 ease-in-out">{slide.title}</h2>
|
|
67
52
|
<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>
|
|
53
|
+
<p className="text-slate-300 mt-3 sm:mt-4 text-xs sm:text-sm leading-relaxed line-clamp-3 sm:line-clamp-none group-hover:translate-z-18 transition-all duration-300 ease-in-out">{slide.text}</p>
|
|
69
54
|
</div>
|
|
70
55
|
</motion.div>
|
|
71
56
|
);
|
|
@@ -75,39 +60,56 @@ const CarouselCard = ({ slide, isCenter }) => {
|
|
|
75
60
|
// --- Custom Parallax Carousel Component ---
|
|
76
61
|
const ParallaxCarousel = ({ slides }) => {
|
|
77
62
|
const [activeIndex, setActiveIndex] = useState(0);
|
|
78
|
-
const
|
|
63
|
+
const animatingRef = useRef(false);
|
|
64
|
+
const animTimeoutRef = useRef(null);
|
|
79
65
|
|
|
66
|
+
const [isScrolling, setIsScrolling] = useState(false);
|
|
67
|
+
const scrollTimeoutRef = useRef(null);
|
|
68
|
+
|
|
69
|
+
// ✅ REMOVED: isHovered state hata diya gaya hai.
|
|
70
|
+
|
|
71
|
+
const NAV_ANIM_MS = 600;
|
|
80
72
|
const handleNext = () => {
|
|
73
|
+
if (animatingRef.current) return;
|
|
74
|
+
animatingRef.current = true;
|
|
75
|
+
clearTimeout(animTimeoutRef.current);
|
|
81
76
|
setActiveIndex((prevIndex) => (prevIndex + 1) % slides.length);
|
|
77
|
+
animTimeoutRef.current = setTimeout(() => {
|
|
78
|
+
animatingRef.current = false;
|
|
79
|
+
}, NAV_ANIM_MS);
|
|
82
80
|
};
|
|
83
81
|
|
|
84
82
|
const handlePrev = () => {
|
|
83
|
+
if (animatingRef.current) return;
|
|
84
|
+
animatingRef.current = true;
|
|
85
|
+
clearTimeout(animTimeoutRef.current);
|
|
85
86
|
setActiveIndex((prevIndex) => (prevIndex - 1 + slides.length) % slides.length);
|
|
87
|
+
animTimeoutRef.current = setTimeout(() => {
|
|
88
|
+
animatingRef.current = false;
|
|
89
|
+
}, NAV_ANIM_MS);
|
|
86
90
|
};
|
|
87
|
-
|
|
88
|
-
// Automatic sliding ke liye
|
|
89
91
|
useEffect(() => {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
92
|
+
const handleScroll = () => {
|
|
93
|
+
setIsScrolling(true);
|
|
94
|
+
clearTimeout(scrollTimeoutRef.current);
|
|
95
|
+
scrollTimeoutRef.current = setTimeout(() => {
|
|
96
|
+
setIsScrolling(false);
|
|
97
|
+
}, 150);
|
|
98
|
+
};
|
|
94
99
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
100
|
+
window.addEventListener('scroll', handleScroll, { passive: true });
|
|
101
|
+
|
|
102
|
+
return () => {
|
|
103
|
+
window.removeEventListener('scroll', handleScroll);
|
|
104
|
+
clearTimeout(scrollTimeoutRef.current);
|
|
105
|
+
};
|
|
106
|
+
}, []);
|
|
98
107
|
|
|
99
|
-
return () => clearInterval(interval); // Cleanup
|
|
100
|
-
}, [activeIndex, isHovered]); // isHovered state change hone par effect ko firse chalayega
|
|
101
108
|
|
|
102
109
|
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 */}
|
|
110
|
+
<div className="relative w-full flex flex-col items-center px-2">
|
|
109
111
|
<div
|
|
110
|
-
className="relative w-full h-[
|
|
112
|
+
className="relative w-full h-[380px] sm:h-[480px]"
|
|
111
113
|
style={{ perspective: '1200px', transformStyle: 'preserve-3d' }}
|
|
112
114
|
>
|
|
113
115
|
{slides.map((slide, index) => {
|
|
@@ -123,36 +125,27 @@ const ParallaxCarousel = ({ slides }) => {
|
|
|
123
125
|
return (
|
|
124
126
|
<motion.div
|
|
125
127
|
key={index}
|
|
126
|
-
className={`absolute
|
|
128
|
+
className={`absolute left-1/2 top-1/2 aspect-[25/26] w-[min(80%,25rem)] sm:w-[min(88%,25rem)] -translate-x-1/2 -translate-y-1/2 ${positionOffset === 0 ? '' : 'pointer-events-none'}`}
|
|
127
129
|
initial={false}
|
|
128
|
-
style={{ transformStyle: 'preserve-3d' }}
|
|
130
|
+
style={{ transformStyle: 'preserve-3d', willChange: 'transform, opacity' }}
|
|
129
131
|
animate={{
|
|
130
132
|
transform: `translateX(${positionOffset * 50}%) translateZ(${-Math.abs(positionOffset) * 250}px) scale(${1 - Math.abs(positionOffset) * 0.15})`,
|
|
131
133
|
opacity: isVisible ? (1 - Math.abs(positionOffset) * 0.45) : 0,
|
|
132
134
|
zIndex: slides.length - Math.abs(positionOffset),
|
|
133
135
|
}}
|
|
134
|
-
transition={{ type: 'spring', stiffness:
|
|
136
|
+
transition={{ type: 'spring', stiffness: 160, damping: 22 }}
|
|
135
137
|
>
|
|
136
|
-
<CarouselCard slide={slide} isCenter={positionOffset === 0} />
|
|
138
|
+
<CarouselCard slide={slide} isCenter={positionOffset === 0} isScrolling={isScrolling} />
|
|
137
139
|
</motion.div>
|
|
138
140
|
);
|
|
139
141
|
})}
|
|
140
142
|
</div>
|
|
141
143
|
|
|
142
|
-
{
|
|
143
|
-
|
|
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} />
|
|
144
|
+
<motion.button onClick={handlePrev} className="absolute left-0 top-1/2 -translate-y-1/2 z-30 p-1.5 sm:p-3 rounded-full text-slate-400 hover:text-lime-400 transition-colors" whileTap={{ scale: 0.9 }}>
|
|
145
|
+
<ArrowLeft size={32} className="h-5 w-5 sm:h-8 sm:w-8" />
|
|
149
146
|
</motion.button>
|
|
150
|
-
<motion.button
|
|
151
|
-
|
|
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} />
|
|
147
|
+
<motion.button onClick={handleNext} className="absolute right-0 top-1/2 -translate-y-1/2 z-30 p-1.5 sm:p-3 rounded-full text-slate-400 hover:text-lime-400 transition-colors" whileTap={{ scale: 0.9 }}>
|
|
148
|
+
<ArrowRight size={32} className="h-5 w-5 sm:h-8 sm:w-8" />
|
|
156
149
|
</motion.button>
|
|
157
150
|
</div>
|
|
158
151
|
);
|
|
@@ -6,25 +6,24 @@ const GitHubIcon = () => (<svg width="24" height="24" fill="none" stroke="curren
|
|
|
6
6
|
const LinkedInIcon = () => (<svg width="24" height="24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" viewBox="0 0 24 24"><path d="M16 8a6 6 0 0 1 6 6v7h-4v-7a2 2 0 0 0-2-2 2 2 0 0 0-2 2v7h-4v-7a6 6 0 0 1 6-6z" /><rect x="2" y="9" width="4" height="12" /><circle cx="4" cy="4" r="2" /></svg>);
|
|
7
7
|
const TwitterIcon = () => (<BsTwitterX size={24} />);
|
|
8
8
|
const InstagramIcon = () => (<svg width="24" height="24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" viewBox="0 0 24 24"><rect x="2" y="2" width="20" height="20" rx="5" ry="5" /><path d="M16 11.37A4 4 0 1112.63 8 4 4 0 0116 11.37z" /><line x1="17.5" y1="6.5" x2="17.5" y2="6.5" /></svg>);
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
onCvClick: () => alert("CV button clicked!"),
|
|
26
|
-
};
|
|
9
|
+
const defaultProfile = {
|
|
10
|
+
avatarUrl: "https://ik.imagekit.io/ny6yinyut/apexui/assets/spiderman.png?tr=f-auto,q-80,w-480",
|
|
11
|
+
name: "Spider Man",
|
|
12
|
+
username: "spider_man",
|
|
13
|
+
dob: "10-08-2001",
|
|
14
|
+
title: "PhotoGrapher",
|
|
15
|
+
status: "Available for Hire",
|
|
16
|
+
socialLinks: {
|
|
17
|
+
github: "https://github.com/",
|
|
18
|
+
linkedin: "https://www.linkedin.com/",
|
|
19
|
+
twitter: "https://twitter.com/",
|
|
20
|
+
instagram: "https://www.instagram.com/",
|
|
21
|
+
},
|
|
22
|
+
onHireClick: () => alert("Hire Me button clicked!"),
|
|
23
|
+
onCvClick: () => alert("CV button clicked!"),
|
|
24
|
+
};
|
|
27
25
|
|
|
26
|
+
const ParallaxProfileCard = ({ profile }) => {
|
|
28
27
|
const {
|
|
29
28
|
avatarUrl, name, username, dob, title, status, socialLinks, onHireClick, onCvClick
|
|
30
29
|
} = profile || defaultProfile;
|
|
@@ -45,7 +44,7 @@ const ParallaxProfileCard = ({ profile }) => {
|
|
|
45
44
|
gsap.to(card, { duration: 1, rotationX: 0, rotationY: 0, scale: 1, ease: "elastic.out(1, 0.3)" });
|
|
46
45
|
};
|
|
47
46
|
gsap.set(card, { transformPerspective: 1000 });
|
|
48
|
-
card.addEventListener("mousemove", handleMouseMove);
|
|
47
|
+
card.addEventListener("mousemove", handleMouseMove, { passive: true });
|
|
49
48
|
card.addEventListener("mouseleave", handleMouseLeave);
|
|
50
49
|
return () => {
|
|
51
50
|
card.removeEventListener("mousemove", handleMouseMove);
|
|
@@ -53,55 +52,70 @@ const ParallaxProfileCard = ({ profile }) => {
|
|
|
53
52
|
};
|
|
54
53
|
}, []);
|
|
55
54
|
|
|
55
|
+
const iconBtn = "flex h-9 w-9 items-center justify-center rounded-full border border-cyan-400/20 bg-cyan-400/5 text-cyan-300 transition-colors duration-200 hover:border-cyan-300/60 hover:text-white [&_svg]:h-[18px] [&_svg]:w-[18px]";
|
|
56
|
+
|
|
56
57
|
return (
|
|
57
58
|
<div
|
|
58
59
|
ref={cardRef}
|
|
59
|
-
className="relative w-[300px]
|
|
60
|
+
className="relative w-[300px] max-w-full bg-[#1D2B3A] rounded-3xl p-6 border border-cyan-400/30 shadow-2xl shadow-cyan-900/40"
|
|
60
61
|
style={{ transformStyle: "preserve-3d" }}
|
|
61
62
|
>
|
|
62
|
-
<div className="absolute inset-0 bg-gradient-to-br from-[#1D2B3A] to-[#
|
|
63
|
-
<div className="absolute
|
|
63
|
+
<div className="absolute inset-0 bg-gradient-to-br from-[#1D2B3A] to-[#0b1220] rounded-3xl"></div>
|
|
64
|
+
<div className="absolute inset-x-0 top-0 h-28 rounded-t-3xl" style={{ background: "radial-gradient(120% 100% at 50% 0%, rgba(34, 211, 238, 0.22), transparent 70%)", transform: "translateZ(20px)" }}></div>
|
|
64
65
|
|
|
65
|
-
<div className="relative z-10 flex flex-col items-center
|
|
66
|
-
{/*
|
|
67
|
-
<div className="
|
|
68
|
-
<img src={avatarUrl} alt="Profile" className="w-
|
|
69
|
-
<
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
66
|
+
<div className="relative z-10 flex flex-col items-center text-center" style={{ transform: "translateZ(50px)" }}>
|
|
67
|
+
{/* Avatar with status dot */}
|
|
68
|
+
<div className="relative">
|
|
69
|
+
<img src={avatarUrl} alt="Profile" className="w-24 h-24 rounded-full border-[3px] border-cyan-400 shadow-lg shadow-cyan-500/30 object-cover" onError={(e) => { e.target.onerror = null; e.target.src = "https://placehold.co/128x128/1D2B3A/A5F3FC?text=ERR"; }} />
|
|
70
|
+
{status && <span className="absolute bottom-1 right-1 h-4 w-4 rounded-full border-[3px] border-[#1D2B3A] bg-green-500" />}
|
|
71
|
+
</div>
|
|
72
|
+
<h2 className="mt-3 text-2xl font-bold text-cyan-300 tracking-wide">{name}</h2>
|
|
73
|
+
<p className="text-sm text-slate-400">{title}</p>
|
|
74
|
+
<div className="mt-3 flex flex-wrap items-center justify-center gap-1.5">
|
|
75
|
+
<span className="text-[11px] font-semibold text-slate-300 bg-slate-700/70 px-2.5 py-1 rounded-full">@{username}</span>
|
|
76
|
+
<span className="text-[11px] font-medium text-cyan-300 bg-slate-700/70 px-2.5 py-1 rounded-full">DOB: {dob}</span>
|
|
74
77
|
</div>
|
|
75
78
|
|
|
76
|
-
{
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
<div className="flex items-center justify-center gap-2 mt-2">
|
|
81
|
-
<span className="relative flex h-3 w-3"><span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75"></span><span className="relative inline-flex rounded-full h-3 w-3 bg-green-500"></span></span>
|
|
82
|
-
<span className="text-green-300 text-sm">{status}</span>
|
|
83
|
-
</div>
|
|
84
|
-
)}
|
|
85
|
-
<div className="flex justify-center gap-4 my-4">
|
|
86
|
-
{socialLinks?.github && (<a href={socialLinks.github} target="_blank" rel="noopener noreferrer" title="GitHub" className="text-cyan-400 hover:text-white transition-colors duration-200"><GitHubIcon /></a>)}
|
|
87
|
-
{socialLinks?.linkedin && (<a href={socialLinks.linkedin} target="_blank" rel="noopener noreferrer" title="LinkedIn" className="text-cyan-400 hover:text-white transition-colors duration-200"><LinkedInIcon /></a>)}
|
|
88
|
-
{socialLinks?.twitter && (<a href={socialLinks.twitter} target="_blank" rel="noopener noreferrer" title="Twitter" className="text-cyan-400 hover:text-white transition-colors duration-200"><TwitterIcon /></a>)}
|
|
89
|
-
{socialLinks?.instagram && (<a href={socialLinks.instagram} target="_blank" rel="noopener noreferrer" title="Instagram" className="text-cyan-400 hover:text-white transition-colors duration-200"><InstagramIcon /></a>)}
|
|
79
|
+
{status && (
|
|
80
|
+
<div className="mt-4 inline-flex items-center gap-2 rounded-full border border-green-400/20 bg-green-400/5 px-3 py-1">
|
|
81
|
+
<span className="relative flex h-2 w-2"><span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75"></span><span className="relative inline-flex rounded-full h-2 w-2 bg-green-500"></span></span>
|
|
82
|
+
<span className="text-green-300 text-xs font-medium">{status}</span>
|
|
90
83
|
</div>
|
|
84
|
+
)}
|
|
91
85
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
86
|
+
<div className="my-5 h-px w-full bg-gradient-to-r from-transparent via-cyan-400/25 to-transparent" />
|
|
87
|
+
|
|
88
|
+
<div className="flex justify-center gap-2.5">
|
|
89
|
+
{socialLinks?.github && (<a href={socialLinks.github} target="_blank" rel="noopener noreferrer" title="GitHub" className={iconBtn}><GitHubIcon /></a>)}
|
|
90
|
+
{socialLinks?.linkedin && (<a href={socialLinks.linkedin} target="_blank" rel="noopener noreferrer" title="LinkedIn" className={iconBtn}><LinkedInIcon /></a>)}
|
|
91
|
+
{socialLinks?.twitter && (<a href={socialLinks.twitter} target="_blank" rel="noopener noreferrer" title="Twitter" className={iconBtn}><TwitterIcon /></a>)}
|
|
92
|
+
{socialLinks?.instagram && (<a href={socialLinks.instagram} target="_blank" rel="noopener noreferrer" title="Instagram" className={iconBtn}><InstagramIcon /></a>)}
|
|
93
|
+
</div>
|
|
94
|
+
|
|
95
|
+
{/* Buttons Section */}
|
|
96
|
+
<div className="mt-5 flex w-full gap-3">
|
|
97
|
+
<button onClick={onHireClick} className="flex-1 whitespace-nowrap bg-cyan-500 text-slate-900 text-sm font-bold py-2.5 px-3 rounded-xl hover:bg-cyan-400 transition-colors duration-300">
|
|
98
|
+
Hire Me
|
|
99
|
+
</button>
|
|
100
|
+
<button onClick={onCvClick} className="flex-1 whitespace-nowrap bg-slate-700/80 text-cyan-300 text-sm font-bold py-2.5 px-3 rounded-xl hover:bg-slate-600 transition-colors duration-300">
|
|
101
|
+
Download CV
|
|
102
|
+
</button>
|
|
101
103
|
</div>
|
|
102
104
|
</div>
|
|
103
105
|
</div>
|
|
104
106
|
);
|
|
105
107
|
};
|
|
106
108
|
|
|
107
|
-
|
|
109
|
+
const ParallaxProfile = ({ profiles = [defaultProfile], className = '' }) => {
|
|
110
|
+
const safe = Array.isArray(profiles) && profiles.length > 0 ? profiles : [defaultProfile];
|
|
111
|
+
return (
|
|
112
|
+
<div className={`flex w-full flex-wrap gap-6 justify-center ${className}`}>
|
|
113
|
+
{safe.map((p, i) => (
|
|
114
|
+
<ParallaxProfileCard key={p.id || p.username || i} profile={p} />
|
|
115
|
+
))}
|
|
116
|
+
</div>
|
|
117
|
+
);
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
export { ParallaxProfileCard };
|
|
121
|
+
export default ParallaxProfile;
|
|
@@ -1,15 +1,21 @@
|
|
|
1
1
|
import React, { useRef, useEffect, useState } from "react";
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
{ id: 1, name: "Captain America", job: "Leader of the Avengers", img: "/assets/captainamerica.png" },
|
|
5
|
-
{ id: 2, name: "Doctor Strange", job: "Sorcerer Supreme", img: "/assets/doctorStrange.png" },
|
|
6
|
-
{ id: 3, name: "Iron Man", job: "Leader Of Stark Industries", img: "/assets/ironman.png" },
|
|
7
|
-
{ id: 4, name: "HULK", job: "Scientist", img: "/assets/hulk.png" },
|
|
8
|
-
{ id: 5, name: "Spider-Man", job: "Friendly Neighborhood Spider-Man", img: "/assets/spiderman.png" },
|
|
9
|
-
{ id: 6, name: "Thanos", job: "The Mad Titan", img: "/assets/thanos.png" },
|
|
3
|
+
const defaultPeople = [
|
|
4
|
+
{ id: 1, name: "Captain America", job: "Leader of the Avengers", img: "https://ik.imagekit.io/ny6yinyut/apexui/assets/captainamerica.png?tr=f-auto,q-80,w-480" },
|
|
5
|
+
{ id: 2, name: "Doctor Strange", job: "Sorcerer Supreme", img: "https://ik.imagekit.io/ny6yinyut/apexui/assets/doctorStrange.png?tr=f-auto,q-80,w-480" },
|
|
6
|
+
{ id: 3, name: "Iron Man", job: "Leader Of Stark Industries", img: "https://ik.imagekit.io/ny6yinyut/apexui/assets/ironman.png?tr=f-auto,q-80,w-480" },
|
|
7
|
+
{ id: 4, name: "HULK", job: "Scientist", img: "https://ik.imagekit.io/ny6yinyut/apexui/assets/hulk.png?tr=f-auto,q-80,w-480" },
|
|
8
|
+
{ id: 5, name: "Spider-Man", job: "Friendly Neighborhood Spider-Man", img: "https://ik.imagekit.io/ny6yinyut/apexui/assets/spiderman.png?tr=f-auto,q-80,w-480" },
|
|
9
|
+
{ id: 6, name: "Thanos", job: "The Mad Titan", img: "https://ik.imagekit.io/ny6yinyut/apexui/assets/thanos.png?tr=f-auto,q-80,w-480" },
|
|
10
10
|
];
|
|
11
11
|
|
|
12
|
-
const PointerFollower = ({
|
|
12
|
+
const PointerFollower = ({
|
|
13
|
+
cursorColor = "#fff",
|
|
14
|
+
interval = 3000,
|
|
15
|
+
badgeColor = "#fff",
|
|
16
|
+
badgeTextColor = "#212121",
|
|
17
|
+
people = defaultPeople,
|
|
18
|
+
}) => {
|
|
13
19
|
const containerRef = useRef(null);
|
|
14
20
|
const [pos, setPos] = useState({ x: 0, y: 0 });
|
|
15
21
|
const [hovered, setHovered] = useState(false);
|
|
@@ -20,11 +26,14 @@ const PointerFollower = ({ cursorColor = "#fff", interval = 3000, badgeColor = "
|
|
|
20
26
|
if (hovered) return;
|
|
21
27
|
|
|
22
28
|
const autoInterval = setInterval(() => {
|
|
23
|
-
setActiveIndex((prev) =>
|
|
29
|
+
setActiveIndex((prev) => {
|
|
30
|
+
if (!people || people.length === 0) return 0;
|
|
31
|
+
return (prev + 1) % people.length;
|
|
32
|
+
});
|
|
24
33
|
}, interval);
|
|
25
34
|
|
|
26
35
|
return () => clearInterval(autoInterval);
|
|
27
|
-
}, [hovered, interval]);
|
|
36
|
+
}, [hovered, interval, people]);
|
|
28
37
|
|
|
29
38
|
// 🖱️ Mouse tracking
|
|
30
39
|
useEffect(() => {
|
|
@@ -44,7 +53,7 @@ const PointerFollower = ({ cursorColor = "#fff", interval = 3000, badgeColor = "
|
|
|
44
53
|
setHovered(false);
|
|
45
54
|
};
|
|
46
55
|
|
|
47
|
-
container.addEventListener("pointermove", handleMove);
|
|
56
|
+
container.addEventListener("pointermove", handleMove, { passive: true });
|
|
48
57
|
container.addEventListener("pointerleave", handleLeave);
|
|
49
58
|
|
|
50
59
|
return () => {
|
|
@@ -53,13 +62,24 @@ const PointerFollower = ({ cursorColor = "#fff", interval = 3000, badgeColor = "
|
|
|
53
62
|
};
|
|
54
63
|
}, []);
|
|
55
64
|
|
|
56
|
-
|
|
65
|
+
// Guard against empty people array
|
|
66
|
+
const safePeople = Array.isArray(people) && people.length > 0 ? people : defaultPeople;
|
|
67
|
+
const person = safePeople[activeIndex % safePeople.length]; // ✅ SYNCED HERE
|
|
68
|
+
|
|
69
|
+
// If the provided people list changes, ensure activeIndex is valid
|
|
70
|
+
useEffect(() => {
|
|
71
|
+
if (!Array.isArray(people) || people.length === 0) {
|
|
72
|
+
setActiveIndex(0);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
setActiveIndex((idx) => idx % people.length);
|
|
76
|
+
}, [people]);
|
|
57
77
|
|
|
58
78
|
return (
|
|
59
79
|
<div className="relative w-full h-fit flex items-center justify-center">
|
|
60
80
|
<div ref={containerRef} className="absolute flex items-center justify-center w-64 h-66 cursor-none">
|
|
61
81
|
{/* 🔁 Image stack with auto fade */}
|
|
62
|
-
{
|
|
82
|
+
{safePeople.map((p, i) => (
|
|
63
83
|
<img
|
|
64
84
|
key={i}
|
|
65
85
|
src={p.img}
|
|
@@ -67,7 +87,7 @@ const PointerFollower = ({ cursorColor = "#fff", interval = 3000, badgeColor = "
|
|
|
67
87
|
className={`absolute w-full h-full object-cover rounded-xl transition-opacity duration-1000 ease-in-out border border-white/10`}
|
|
68
88
|
style={{
|
|
69
89
|
transform: `translate(${i * -2}px, ${i * -2}px)`,
|
|
70
|
-
zIndex:
|
|
90
|
+
zIndex: safePeople.length - i,
|
|
71
91
|
opacity: activeIndex === i ? 1 : 0,
|
|
72
92
|
}}
|
|
73
93
|
/>
|
|
File without changes
|
|
@@ -123,10 +123,11 @@ const SmartGridCard = ({
|
|
|
123
123
|
<div
|
|
124
124
|
className={`grid gap-2 justify-center p-4 w-full ${screenWidth >= 1024 ? 'grid-cols-3' : screenWidth >= 768 ? 'grid-cols-2' : 'grid-cols-1'}`}
|
|
125
125
|
style={screenWidth >= 1024 ? {
|
|
126
|
-
|
|
126
|
+
// minmax(0, …): columns share the container's width instead of overflowing it.
|
|
127
|
+
gridTemplateColumns: "repeat(3, minmax(0, 1fr))",
|
|
127
128
|
gridTemplateRows: `repeat(2, ${CARD_HEIGHT}px)`
|
|
128
129
|
} : screenWidth >= 768 ? {
|
|
129
|
-
gridTemplateColumns: "repeat(2, minmax(
|
|
130
|
+
gridTemplateColumns: "repeat(2, minmax(0, 1fr))",
|
|
130
131
|
gridTemplateRows: `repeat(3, ${CARD_HEIGHT}px)`
|
|
131
132
|
} : {}}
|
|
132
133
|
>
|
|
@@ -164,7 +165,7 @@ const SmartGridCard = ({
|
|
|
164
165
|
</div>
|
|
165
166
|
<button
|
|
166
167
|
type="button"
|
|
167
|
-
className="mt-auto relative overflow-hidden px-
|
|
168
|
+
className="mt-auto relative overflow-hidden whitespace-nowrap px-4 py-3 rounded-lg border border-purple-400/60 text-white transition hover:text-white bg-black/20 group flex items-center gap-2 text-xs md:text-base"
|
|
168
169
|
>
|
|
169
170
|
<span className="relative z-10 flex items-center gap-2">
|
|
170
171
|
{card.buttonText}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { useEffect, useRef } from "react";
|
|
2
|
-
import { Renderer, Program, Mesh, Triangle } from "
|
|
2
|
+
import { Renderer, Program, Mesh, Triangle } from "ogl";
|
|
3
3
|
|
|
4
4
|
const hexToRgb = (hex) => {
|
|
5
5
|
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
|
@@ -87,30 +87,37 @@ void main() {
|
|
|
87
87
|
|
|
88
88
|
|
|
89
89
|
export const SmokeBackground = ({
|
|
90
|
-
color = "#
|
|
90
|
+
color = "#00c950",
|
|
91
91
|
speed = 1,
|
|
92
92
|
direction = "forward",
|
|
93
93
|
scale = 1,
|
|
94
94
|
opacity = 1,
|
|
95
95
|
mouseInteractive = true,
|
|
96
96
|
className = "",
|
|
97
|
+
maxWidth = 'max-w-4xl',
|
|
97
98
|
}) => {
|
|
98
99
|
const containerRef = useRef(null);
|
|
99
100
|
const mousePos = useRef({ x: 0, y: 0 });
|
|
100
101
|
|
|
101
102
|
useEffect(() => {
|
|
102
|
-
|
|
103
|
+
const mountNode = containerRef.current;
|
|
104
|
+
if (!mountNode) return;
|
|
103
105
|
|
|
104
106
|
const useCustomColor = color ? 1.0 : 0.0;
|
|
105
107
|
const customColorRgb = color ? hexToRgb(color) : [1, 1, 1];
|
|
106
108
|
|
|
107
109
|
let directionMultiplier = direction === "backward" ? -1.0 : 1.0;
|
|
108
110
|
|
|
111
|
+
// cap DPR depending on mount width to avoid heavy pixel loads on small devices
|
|
112
|
+
const mountWidth = mountNode.clientWidth || window.innerWidth;
|
|
113
|
+
const dprCap = mountWidth > 1200 ? 1.8 : (mountWidth > 768 ? 1.4 : 1.0);
|
|
114
|
+
const dpr = Math.min(window.devicePixelRatio || 1, dprCap);
|
|
115
|
+
|
|
109
116
|
const renderer = new Renderer({
|
|
110
117
|
webgl: 2,
|
|
111
118
|
alpha: true,
|
|
112
119
|
antialias: false,
|
|
113
|
-
dpr
|
|
120
|
+
dpr,
|
|
114
121
|
});
|
|
115
122
|
|
|
116
123
|
const gl = renderer.gl;
|
|
@@ -118,7 +125,7 @@ export const SmokeBackground = ({
|
|
|
118
125
|
canvas.style.display = "block";
|
|
119
126
|
canvas.style.width = "100%";
|
|
120
127
|
canvas.style.height = "100%";
|
|
121
|
-
|
|
128
|
+
mountNode.appendChild(canvas);
|
|
122
129
|
|
|
123
130
|
const geometry = new Triangle(gl);
|
|
124
131
|
|
|
@@ -145,17 +152,17 @@ export const SmokeBackground = ({
|
|
|
145
152
|
let mouseNeedsUpdate = false;
|
|
146
153
|
const handleMouseMove = (e) => {
|
|
147
154
|
if (!mouseInteractive) return;
|
|
148
|
-
const rect =
|
|
155
|
+
const rect = mountNode.getBoundingClientRect();
|
|
149
156
|
mousePos.current.x = e.clientX - rect.left;
|
|
150
157
|
mousePos.current.y = rect.height - (e.clientY - rect.top);
|
|
151
158
|
mouseNeedsUpdate = true;
|
|
152
159
|
};
|
|
153
160
|
if (mouseInteractive) {
|
|
154
|
-
|
|
161
|
+
mountNode.addEventListener("mousemove", handleMouseMove, { passive: true });
|
|
155
162
|
}
|
|
156
163
|
|
|
157
164
|
const setSize = () => {
|
|
158
|
-
const rect =
|
|
165
|
+
const rect = mountNode.getBoundingClientRect();
|
|
159
166
|
const width = Math.max(1, Math.floor(rect.width));
|
|
160
167
|
const height = Math.max(1, Math.floor(rect.height));
|
|
161
168
|
renderer.setSize(width, height);
|
|
@@ -167,12 +174,16 @@ export const SmokeBackground = ({
|
|
|
167
174
|
const ro = new ResizeObserver(() => {
|
|
168
175
|
requestAnimationFrame(setSize);
|
|
169
176
|
});
|
|
170
|
-
ro.observe(
|
|
177
|
+
ro.observe(mountNode);
|
|
171
178
|
setSize();
|
|
172
179
|
|
|
173
180
|
let raf = 0;
|
|
181
|
+
// Only render while the background is on screen.
|
|
182
|
+
let visible = true;
|
|
174
183
|
const t0 = performance.now();
|
|
175
184
|
const loop = (t) => {
|
|
185
|
+
raf = 0;
|
|
186
|
+
if (!visible) return;
|
|
176
187
|
let timeValue = (t - t0) * 0.001;
|
|
177
188
|
|
|
178
189
|
if (direction === "funny") {
|
|
@@ -192,24 +203,32 @@ export const SmokeBackground = ({
|
|
|
192
203
|
raf = requestAnimationFrame(loop);
|
|
193
204
|
};
|
|
194
205
|
raf = requestAnimationFrame(loop);
|
|
206
|
+
const visibilityObserver = new IntersectionObserver(([entry]) => {
|
|
207
|
+
visible = entry.isIntersecting;
|
|
208
|
+
if (visible && !raf) raf = requestAnimationFrame(loop);
|
|
209
|
+
});
|
|
210
|
+
visibilityObserver.observe(mountNode);
|
|
195
211
|
|
|
196
212
|
return () => {
|
|
197
213
|
cancelAnimationFrame(raf);
|
|
214
|
+
visibilityObserver.disconnect();
|
|
198
215
|
ro.disconnect();
|
|
199
|
-
if (mouseInteractive &&
|
|
200
|
-
|
|
216
|
+
if (mouseInteractive && mountNode) {
|
|
217
|
+
mountNode.removeEventListener("mousemove", handleMouseMove);
|
|
201
218
|
}
|
|
202
219
|
try {
|
|
203
|
-
|
|
220
|
+
mountNode.removeChild(canvas);
|
|
204
221
|
} catch (error) { console.error("Error removing canvas:", error); }
|
|
222
|
+
gl.getExtension("WEBGL_lose_context")?.loseContext();
|
|
205
223
|
};
|
|
206
224
|
}, [color, speed, direction, scale, opacity, mouseInteractive, className]);
|
|
207
225
|
|
|
208
226
|
return (
|
|
209
|
-
<div
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
227
|
+
<div className={`w-full ${className}`}>
|
|
228
|
+
<div className={`mx-auto w-full ${maxWidth} h-[100vh] lg:h-[90vh] overflow-hidden relative`}>
|
|
229
|
+
<div ref={containerRef} className="absolute inset-0 w-full h-full" />
|
|
230
|
+
</div>
|
|
231
|
+
</div>
|
|
213
232
|
);
|
|
214
233
|
};
|
|
215
234
|
|