apex-ui-kit 2.10.0 → 2.12.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/ReadMe.md +2 -3
- package/dist/ApexUI-Kit/templates/AnimatedTabs/AnimatedTabs.jsx +17 -3
- package/dist/ApexUI-Kit/templates/AuroraBackground/AuroraBackground.jsx +157 -35
- package/dist/ApexUI-Kit/templates/BluePrintBackground/BluePrintBackground.jsx +134 -145
- package/dist/ApexUI-Kit/templates/CardSwap3D/CardSwap3D.jsx +64 -40
- package/dist/ApexUI-Kit/templates/DotGlobe/DotGlobe.jsx +6 -3
- package/dist/ApexUI-Kit/templates/FlipClock/FlipClock.jsx +1 -1
- package/dist/ApexUI-Kit/templates/Folder/Folder.jsx +58 -36
- package/dist/ApexUI-Kit/templates/GooeyNav/GooeyNav.jsx +16 -2
- package/dist/ApexUI-Kit/templates/ImageAccordion/ImageAccordion.jsx +20 -10
- package/dist/ApexUI-Kit/templates/InfiniteCanvas/InfiniteCanvas.jsx +1 -1
- package/dist/ApexUI-Kit/templates/IsoStack/IsoStack.jsx +28 -10
- package/dist/ApexUI-Kit/templates/LuminousParticleOcean/LuminousParticleOcean.jsx +8 -6
- package/dist/ApexUI-Kit/templates/MeteorCard/MeteorCard.jsx +5 -5
- package/dist/ApexUI-Kit/templates/NavMenu/NavMenu.jsx +175 -158
- package/dist/ApexUI-Kit/templates/OrbitIcons/OrbitIcons.jsx +1 -1
- package/dist/ApexUI-Kit/templates/ParallaxCard/ParallaxCard.jsx +34 -33
- package/dist/ApexUI-Kit/templates/SmokeBackground/SmokeBackground.jsx +237 -235
- package/dist/ApexUI-Kit/templates/Testimonials/Testimonials.jsx +140 -120
- package/dist/cli/index.js +40 -21
- package/package.json +8 -2
- package/dist/ApexUI-Kit/templates/SmartGridCard/SmartGridCard.jsx +0 -243
|
@@ -1,159 +1,176 @@
|
|
|
1
|
-
import React, { useState, useRef, useEffect } from 'react';
|
|
2
|
-
import { gsap } from "gsap";
|
|
3
|
-
const NavMenu = ({
|
|
4
|
-
indicatorColor = "#a3e635",
|
|
5
|
-
backgroundColor = "rgba(17, 24, 39, 0.5)",
|
|
6
|
-
activeColor = "#ffffff",
|
|
7
|
-
indicatorAnimation = "elastic",
|
|
8
|
-
onNavItemClick
|
|
9
|
-
}) => {
|
|
10
|
-
const [activeIdx, setActiveIdx] = useState(0);
|
|
11
|
-
const navItemsRef = useRef([]);
|
|
12
|
-
const indicatorRef = useRef(null);
|
|
13
|
-
const navContainerRef = useRef(null);
|
|
14
|
-
useEffect(() => {
|
|
15
|
-
if (!navContainerRef.current) return;
|
|
16
|
-
gsap.fromTo(
|
|
17
|
-
navContainerRef.current,
|
|
18
|
-
{ width: 0, opacity: 0 },
|
|
19
|
-
{ width: "auto", opacity: 1, duration: 0.8, ease: "power2.out", delay: 0.5 }
|
|
20
|
-
);
|
|
21
|
-
}, []);
|
|
22
|
-
|
|
23
|
-
useEffect(() => {
|
|
24
|
-
const indicator = indicatorRef.current;
|
|
25
|
-
const el = navItemsRef.current[activeIdx];
|
|
26
|
-
|
|
27
|
-
if (!indicator || !el) return;
|
|
28
|
-
|
|
29
|
-
const getEase = () => {
|
|
30
|
-
switch (indicatorAnimation) {
|
|
31
|
-
case "spring": return "bounce.out";
|
|
32
|
-
case "power": return "power3.out";
|
|
33
|
-
case "elastic":
|
|
34
|
-
default: return "elastic.out(1, 0.6)";
|
|
35
|
-
}
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
const moveWithGSAP = () => {
|
|
39
|
-
const rect = el.getBoundingClientRect();
|
|
40
|
-
const parentRect = el.parentElement.getBoundingClientRect();
|
|
41
|
-
const left = rect.left - parentRect.left;
|
|
42
|
-
const width = rect.width;
|
|
43
|
-
|
|
44
|
-
gsap.to(indicator, {
|
|
45
|
-
x: left,
|
|
46
|
-
width,
|
|
47
|
-
duration: 0.5,
|
|
48
|
-
ease: getEase(),
|
|
49
|
-
});
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
const timeoutId = setTimeout(moveWithGSAP, 50);
|
|
53
|
-
return () => clearTimeout(timeoutId);
|
|
54
|
-
|
|
55
|
-
}, [activeIdx, indicatorAnimation]);
|
|
56
|
-
|
|
57
|
-
// Handle resize
|
|
58
|
-
useEffect(() => {
|
|
59
|
-
const handleResize = () => {
|
|
60
|
-
const el = navItemsRef.current[activeIdx];
|
|
61
|
-
if (el) {
|
|
62
|
-
const rect = el.getBoundingClientRect();
|
|
63
|
-
const parentRect = el.parentElement.getBoundingClientRect();
|
|
64
|
-
const left = rect.left - parentRect.left;
|
|
65
|
-
const width = rect.width;
|
|
66
|
-
|
|
67
|
-
gsap.set(indicatorRef.current, {
|
|
68
|
-
x: left,
|
|
69
|
-
width: width
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
window.addEventListener("resize", handleResize);
|
|
75
|
-
return () => window.removeEventListener("resize", handleResize);
|
|
76
|
-
}, [activeIdx]);
|
|
77
|
-
|
|
78
|
-
const navItems = ['Home', 'Docs', 'UI Kit', 'Contact'];
|
|
79
|
-
|
|
80
|
-
return (
|
|
81
|
-
<nav
|
|
82
|
-
ref={navContainerRef}
|
|
83
|
-
className="rounded px-3 py-2 shadow-lg border border-white/10 overflow-hidden"
|
|
84
|
-
style={{ backdropFilter: 'blur(16px)', WebkitBackdropFilter: 'blur(16px)', background: backgroundColor , }}
|
|
85
|
-
>
|
|
86
|
-
<div
|
|
87
|
-
ref={indicatorRef}
|
|
88
|
-
className="absolute top-1.5 h-[calc(100%-0.75rem)] rounded-lg z-0 "
|
|
89
|
-
style={{ background: indicatorColor, opacity: 0.3 }}
|
|
90
|
-
/>
|
|
91
|
-
<div className="relative flex gap-2 z-10 whitespace-nowrap">
|
|
92
|
-
{navItems.map((label, i) => (
|
|
93
|
-
<button
|
|
94
|
-
key={label}
|
|
95
|
-
ref={(el) => (navItemsRef.current[i] = el)}
|
|
96
|
-
onClick={() => {
|
|
97
|
-
setActiveIdx(i);
|
|
98
|
-
if (onNavItemClick) onNavItemClick();
|
|
99
|
-
}}
|
|
100
|
-
className={`nav-item px-6 py-2 text-base font-semibold rounded-full relative z-10 transition-colors duration-300 ${i === activeIdx ? '' : 'text-gray-400 hover:text-white'}`}
|
|
101
|
-
style={{ color: i === activeIdx ? activeColor : undefined }}
|
|
102
|
-
>
|
|
103
|
-
{label}
|
|
104
|
-
</button>
|
|
105
|
-
))}
|
|
106
|
-
</div>
|
|
107
|
-
</nav>
|
|
108
|
-
);
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
// Responsive Wrapper Component
|
|
113
|
-
const ResponsiveNavMenu = (props) => {
|
|
114
|
-
const [isOpen, setIsOpen] = useState(false);
|
|
115
|
-
const navItems = ['Home', 'Docs', 'UI Kit', 'Contact'];
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
1
|
+
import React, { useState, useRef, useEffect, useLayoutEffect } from 'react';
|
|
2
|
+
import { gsap } from "gsap";
|
|
3
|
+
const NavMenu = ({
|
|
4
|
+
indicatorColor = "#a3e635",
|
|
5
|
+
backgroundColor = "rgba(17, 24, 39, 0.5)",
|
|
6
|
+
activeColor = "#ffffff",
|
|
7
|
+
indicatorAnimation = "elastic",
|
|
8
|
+
onNavItemClick
|
|
9
|
+
}) => {
|
|
10
|
+
const [activeIdx, setActiveIdx] = useState(0);
|
|
11
|
+
const navItemsRef = useRef([]);
|
|
12
|
+
const indicatorRef = useRef(null);
|
|
13
|
+
const navContainerRef = useRef(null);
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
if (!navContainerRef.current) return;
|
|
16
|
+
gsap.fromTo(
|
|
17
|
+
navContainerRef.current,
|
|
18
|
+
{ width: 0, opacity: 0 },
|
|
19
|
+
{ width: "auto", opacity: 1, duration: 0.8, ease: "power2.out", delay: 0.5 }
|
|
20
|
+
);
|
|
21
|
+
}, []);
|
|
22
|
+
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
const indicator = indicatorRef.current;
|
|
25
|
+
const el = navItemsRef.current[activeIdx];
|
|
26
|
+
|
|
27
|
+
if (!indicator || !el) return;
|
|
28
|
+
|
|
29
|
+
const getEase = () => {
|
|
30
|
+
switch (indicatorAnimation) {
|
|
31
|
+
case "spring": return "bounce.out";
|
|
32
|
+
case "power": return "power3.out";
|
|
33
|
+
case "elastic":
|
|
34
|
+
default: return "elastic.out(1, 0.6)";
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const moveWithGSAP = () => {
|
|
39
|
+
const rect = el.getBoundingClientRect();
|
|
40
|
+
const parentRect = el.parentElement.getBoundingClientRect();
|
|
41
|
+
const left = rect.left - parentRect.left;
|
|
42
|
+
const width = rect.width;
|
|
43
|
+
|
|
44
|
+
gsap.to(indicator, {
|
|
45
|
+
x: left,
|
|
46
|
+
width,
|
|
47
|
+
duration: 0.5,
|
|
48
|
+
ease: getEase(),
|
|
49
|
+
});
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const timeoutId = setTimeout(moveWithGSAP, 50);
|
|
53
|
+
return () => clearTimeout(timeoutId);
|
|
54
|
+
|
|
55
|
+
}, [activeIdx, indicatorAnimation]);
|
|
56
|
+
|
|
57
|
+
// Handle resize
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
const handleResize = () => {
|
|
60
|
+
const el = navItemsRef.current[activeIdx];
|
|
61
|
+
if (el) {
|
|
62
|
+
const rect = el.getBoundingClientRect();
|
|
63
|
+
const parentRect = el.parentElement.getBoundingClientRect();
|
|
64
|
+
const left = rect.left - parentRect.left;
|
|
65
|
+
const width = rect.width;
|
|
66
|
+
|
|
67
|
+
gsap.set(indicatorRef.current, {
|
|
68
|
+
x: left,
|
|
69
|
+
width: width
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
window.addEventListener("resize", handleResize);
|
|
75
|
+
return () => window.removeEventListener("resize", handleResize);
|
|
76
|
+
}, [activeIdx]);
|
|
77
|
+
|
|
78
|
+
const navItems = ['Home', 'Docs', 'UI Kit', 'Contact'];
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<nav
|
|
82
|
+
ref={navContainerRef}
|
|
83
|
+
className="relative rounded px-3 py-2 shadow-lg border border-white/10 overflow-hidden"
|
|
84
|
+
style={{ backdropFilter: 'blur(16px)', WebkitBackdropFilter: 'blur(16px)', background: backgroundColor , }}
|
|
85
|
+
>
|
|
86
|
+
<div
|
|
87
|
+
ref={indicatorRef}
|
|
88
|
+
className="absolute top-1.5 h-[calc(100%-0.75rem)] rounded-lg z-0 "
|
|
89
|
+
style={{ background: indicatorColor, opacity: 0.3 }}
|
|
90
|
+
/>
|
|
91
|
+
<div className="relative flex gap-2 z-10 whitespace-nowrap">
|
|
92
|
+
{navItems.map((label, i) => (
|
|
93
|
+
<button
|
|
94
|
+
key={label}
|
|
95
|
+
ref={(el) => (navItemsRef.current[i] = el)}
|
|
96
|
+
onClick={() => {
|
|
97
|
+
setActiveIdx(i);
|
|
98
|
+
if (onNavItemClick) onNavItemClick();
|
|
99
|
+
}}
|
|
100
|
+
className={`nav-item px-6 py-2 text-base font-semibold rounded-full relative z-10 transition-colors duration-300 ${i === activeIdx ? '' : 'text-gray-400 hover:text-white'}`}
|
|
101
|
+
style={{ color: i === activeIdx ? activeColor : undefined }}
|
|
102
|
+
>
|
|
103
|
+
{label}
|
|
104
|
+
</button>
|
|
105
|
+
))}
|
|
106
|
+
</div>
|
|
107
|
+
</nav>
|
|
108
|
+
);
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
// Responsive Wrapper Component
|
|
113
|
+
const ResponsiveNavMenu = (props) => {
|
|
114
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
115
|
+
const navItems = ['Home', 'Docs', 'UI Kit', 'Contact'];
|
|
116
|
+
// Switch to the menu button when the space it sits in is narrow (not the
|
|
117
|
+
// screen), so it also fits sidebars, preview frames and small columns.
|
|
118
|
+
const wrap = useRef(null);
|
|
119
|
+
const [narrow, setNarrow] = useState(false);
|
|
120
|
+
|
|
121
|
+
useLayoutEffect(() => {
|
|
122
|
+
const el = wrap.current;
|
|
123
|
+
if (!el) return undefined;
|
|
124
|
+
const ro = new ResizeObserver(() => {
|
|
125
|
+
const rem = parseFloat(getComputedStyle(document.documentElement).fontSize) || 16;
|
|
126
|
+
setNarrow(el.clientWidth / rem < 34);
|
|
127
|
+
});
|
|
128
|
+
ro.observe(el);
|
|
129
|
+
return () => ro.disconnect();
|
|
130
|
+
}, []);
|
|
131
|
+
|
|
132
|
+
return (
|
|
133
|
+
<nav ref={wrap} className="flex items-center justify-center p-4 sticky top-0 left-0 w-full z-50 bg-transparent">
|
|
134
|
+
{!narrow ? (
|
|
135
|
+
<div className="flex w-full justify-center">
|
|
136
|
+
<NavMenu {...props} />
|
|
137
|
+
</div>
|
|
138
|
+
) : (
|
|
139
|
+
<div className="flex w-full justify-end">
|
|
140
|
+
{/* Same backdrop as the desktop bar, so the icon stays visible on light pages too. */}
|
|
141
|
+
<button
|
|
142
|
+
onClick={() => setIsOpen(!isOpen)}
|
|
143
|
+
aria-label={isOpen ? "Close menu" : "Open menu"}
|
|
144
|
+
className="rounded-full border border-white/10 p-2.5 text-white shadow-lg focus:outline-none"
|
|
145
|
+
style={{ backdropFilter: 'blur(16px)', WebkitBackdropFilter: 'blur(16px)', background: props.backgroundColor ?? 'rgba(17, 24, 39, 0.7)' }}
|
|
146
|
+
>
|
|
147
|
+
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
|
148
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d={isOpen ? "M6 18L18 6M6 6l12 12" : "M4 6h16M4 12h16m-7 6h7"} />
|
|
149
|
+
</svg>
|
|
150
|
+
</button>
|
|
151
|
+
</div>
|
|
152
|
+
)}
|
|
153
|
+
|
|
154
|
+
{narrow && isOpen && (
|
|
155
|
+
<div className="absolute top-20 left-4 right-4 rounded-xl shadow-lg border border-white/10"
|
|
156
|
+
style={{ backdropFilter: 'blur(16px)', WebkitBackdropFilter: 'blur(16px)', background: 'rgba(17, 24, 39, 0.7)' }}>
|
|
157
|
+
<div className="flex flex-col items-center gap-2 p-4">
|
|
158
|
+
{navItems.map((label) => (
|
|
159
|
+
<button
|
|
160
|
+
key={label}
|
|
161
|
+
onClick={() => {
|
|
162
|
+
setIsOpen(false);
|
|
163
|
+
props.onNavItemClick?.();
|
|
164
|
+
}}
|
|
165
|
+
className="w-full text-center px-6 py-3 text-lg font-semibold rounded-full text-gray-300 hover:text-white hover:bg-white/10 transition-colors duration-300"
|
|
166
|
+
>
|
|
167
|
+
{label}
|
|
168
|
+
</button>
|
|
169
|
+
))}
|
|
170
|
+
</div>
|
|
171
|
+
</div>
|
|
172
|
+
)}
|
|
173
|
+
</nav>
|
|
174
|
+
);
|
|
175
|
+
};
|
|
159
176
|
export default ResponsiveNavMenu;
|
|
@@ -148,7 +148,7 @@ const OrbitIcons = ({ rings = DEFAULT_RINGS, size = 28, tilt = 0.55, speed = 1,
|
|
|
148
148
|
return (
|
|
149
149
|
<div key={idx} ref={(e) => (nodes.current[idx] = e)} className="group absolute left-1/2 top-1/2 will-change-transform">
|
|
150
150
|
<div
|
|
151
|
-
className="grid place-items-center rounded-2xl border border-white/10 bg-neutral-900/90 text-white
|
|
151
|
+
className="grid place-items-center rounded-2xl border border-white/10 bg-neutral-900/90 text-white transition-colors duration-300 group-hover:border-[var(--orbit-accent)]"
|
|
152
152
|
style={{ width: "var(--orbit-item, 2.75rem)", aspectRatio: "1" }}
|
|
153
153
|
title={it.label}
|
|
154
154
|
>
|
|
@@ -25,53 +25,54 @@ const ParallaxCard = ({ cardData = [], tiltEnable, glareEnable, scale, perspecti
|
|
|
25
25
|
|
|
26
26
|
if (!cardElement) return;
|
|
27
27
|
|
|
28
|
+
// quickTo builds each tween once and just retargets it, instead of
|
|
29
|
+
// creating six new tweens on every mouse move.
|
|
30
|
+
const to = (el, prop, duration) => (el ? gsap.quickTo(el, prop, { duration, ease: "power3.out" }) : () => {});
|
|
31
|
+
const card = { rx: to(cardElement, "rotationX", 0.8), ry: to(cardElement, "rotationY", 0.8), s: to(cardElement, "scale", 0.8) };
|
|
32
|
+
const layers = [
|
|
33
|
+
[titleElement, 0.8, 0.6],
|
|
34
|
+
[imageElement, 1.5, 0.7],
|
|
35
|
+
[descElement, 1.2, 0.8],
|
|
36
|
+
[buttonElement, 1.1, 0.9],
|
|
37
|
+
[linkElement, 1.1, 0.9],
|
|
38
|
+
]
|
|
39
|
+
.filter(([el]) => el)
|
|
40
|
+
.map(([el, k, d]) => ({ k, x: to(el, "x", d), y: to(el, "y", d) }));
|
|
41
|
+
let rect = null;
|
|
42
|
+
|
|
43
|
+
const handleMouseEnter = () => {
|
|
44
|
+
rect = cardElement.getBoundingClientRect();
|
|
45
|
+
};
|
|
28
46
|
const handleMouseMove = (e) => {
|
|
29
47
|
if (!tiltEnable) return;
|
|
30
|
-
const
|
|
31
|
-
const
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
rotationY: rotateY,
|
|
40
|
-
scale: scale,
|
|
41
|
-
ease: "power3.out",
|
|
48
|
+
const r = rect ?? (rect = cardElement.getBoundingClientRect());
|
|
49
|
+
const rotateX = ((e.clientY - r.top) / r.height - 0.5) * -30;
|
|
50
|
+
const rotateY = ((e.clientX - r.left) / r.width - 0.5) * 30;
|
|
51
|
+
card.rx(rotateX);
|
|
52
|
+
card.ry(rotateY);
|
|
53
|
+
card.s(scale);
|
|
54
|
+
layers.forEach((l) => {
|
|
55
|
+
l.x(rotateY * l.k);
|
|
56
|
+
l.y(-rotateX * l.k);
|
|
42
57
|
});
|
|
43
|
-
|
|
44
|
-
if (titleElement) gsap.to(titleElement, { duration: 0.7, x: rotateY * 0.8, y: -rotateX * 0.8 });
|
|
45
|
-
if (imageElement) gsap.to(imageElement, { duration: 0.8, x: rotateY * 1.5, y: -rotateX * 1.5 });
|
|
46
|
-
if (descElement) gsap.to(descElement, { duration: 0.9, x: rotateY * 1.2, y: -rotateX * 1.2 });
|
|
47
|
-
if (buttonElement) gsap.to(buttonElement, { duration: 1, x: rotateY * 1.1, y: -rotateX * 1.1 });
|
|
48
|
-
if (linkElement) gsap.to(linkElement, { duration: 1, x: rotateY * 1.1, y: -rotateX * 1.1 });
|
|
49
58
|
};
|
|
50
59
|
|
|
51
60
|
const handleMouseLeave = () => {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
rotationY: 0,
|
|
57
|
-
scale: 1,
|
|
58
|
-
ease: "elastic.out(1, 0.5)",
|
|
59
|
-
});
|
|
60
|
-
gsap.to([titleElement, imageElement, descElement, buttonElement, linkElement].filter(Boolean), {
|
|
61
|
-
duration: 1.2,
|
|
62
|
-
x: 0,
|
|
63
|
-
y: 0,
|
|
64
|
-
ease: "elastic.out(1, 0.5)",
|
|
65
|
-
});
|
|
66
|
-
}
|
|
61
|
+
rect = null;
|
|
62
|
+
if (!tiltEnable) return;
|
|
63
|
+
gsap.to(cardElement, { duration: 1.2, rotationX: 0, rotationY: 0, scale: 1, ease: "elastic.out(1, 0.5)", overwrite: "auto" });
|
|
64
|
+
gsap.to([titleElement, imageElement, descElement, buttonElement, linkElement].filter(Boolean), { duration: 1.2, x: 0, y: 0, ease: "elastic.out(1, 0.5)", overwrite: "auto" });
|
|
67
65
|
};
|
|
68
66
|
|
|
67
|
+
cardElement.addEventListener("mouseenter", handleMouseEnter);
|
|
69
68
|
cardElement.addEventListener("mousemove", handleMouseMove, { passive: true });
|
|
70
69
|
cardElement.addEventListener("mouseleave", handleMouseLeave);
|
|
71
70
|
|
|
72
71
|
cleanups.push(() => {
|
|
72
|
+
cardElement.removeEventListener("mouseenter", handleMouseEnter);
|
|
73
73
|
cardElement.removeEventListener("mousemove", handleMouseMove);
|
|
74
74
|
cardElement.removeEventListener("mouseleave", handleMouseLeave);
|
|
75
|
+
gsap.killTweensOf([cardElement, titleElement, imageElement, descElement, buttonElement, linkElement].filter(Boolean));
|
|
75
76
|
});
|
|
76
77
|
});
|
|
77
78
|
return () => cleanups.forEach((fn) => fn());
|