apex-ui-kit 2.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Licence.md +21 -0
- package/ReadMe.md +75 -0
- package/dist/ApexUI-Kit/templates/Avatar/Avatar.jsx +81 -0
- package/dist/ApexUI-Kit/templates/BeamBar/BeamBar.jsx +101 -0
- package/dist/ApexUI-Kit/templates/Cards/BasicCard.jsx +60 -0
- package/dist/ApexUI-Kit/templates/Carousel/Carousel.jsx +95 -0
- package/dist/ApexUI-Kit/templates/Carousel/DragCarousel.jsx +105 -0
- package/dist/ApexUI-Kit/templates/Carousel/ParallaxCarousel.jsx +161 -0
- package/dist/ApexUI-Kit/templates/DragCarousel/DragCarousel.jsx +105 -0
- package/dist/ApexUI-Kit/templates/EdgeGlowForm/EdgeGlowFrom.jsx +297 -0
- package/dist/ApexUI-Kit/templates/FlipProfile/FlipProfile.jsx +125 -0
- package/dist/ApexUI-Kit/templates/GlareCard/GlareCard.jsx +146 -0
- package/dist/ApexUI-Kit/templates/GlassCardStack/GlassCardStack.jsx +111 -0
- package/dist/ApexUI-Kit/templates/HoverText/HoverText.jsx +371 -0
- package/dist/ApexUI-Kit/templates/HyperCard/HyperCard.jsx +183 -0
- package/dist/ApexUI-Kit/templates/KineticThreadsBackground/KineticThreadBackground.jsx +249 -0
- package/dist/ApexUI-Kit/templates/LensFlareBackground/LensFlareBackground.jsx +268 -0
- package/dist/ApexUI-Kit/templates/LuminousParticleOcean/LuminousParticleOcean.jsx +155 -0
- package/dist/ApexUI-Kit/templates/NavDropDown/NavDropDown.jsx +114 -0
- package/dist/ApexUI-Kit/templates/NavMenu/NavMenu.jsx +153 -0
- package/dist/ApexUI-Kit/templates/OrbsBackground/FloatingOrbs.jsx +96 -0
- package/dist/ApexUI-Kit/templates/ParallaxCard/ParallaxCard.jsx +127 -0
- package/dist/ApexUI-Kit/templates/ParallaxCarousel/ParallaxCarousel.jsx +161 -0
- package/dist/ApexUI-Kit/templates/ParallaxProfile/ParallaxProfile.jsx +107 -0
- package/dist/ApexUI-Kit/templates/PointerFollower/PointerFollower.jsx +126 -0
- package/dist/ApexUI-Kit/templates/RainBackground/RainBackground.jsx +109 -0
- package/dist/ApexUI-Kit/templates/RippleBackground/RippleBackground.jsx +160 -0
- package/dist/ApexUI-Kit/templates/ScrollMarquee/ScrollMarquee.jsx +174 -0
- package/dist/ApexUI-Kit/templates/SmartGridCard/SmartGridCard.jsx +241 -0
- package/dist/ApexUI-Kit/templates/SmokeBackground/SmokeBackground.jsx +216 -0
- package/dist/ApexUI-Kit/templates/Testimonials/Testimonials.jsx +117 -0
- package/dist/ApexUI-Kit/templates/ThemeToggle/ThemeAnimation.js +252 -0
- package/dist/ApexUI-Kit/templates/ThemeToggle/ThemeConfig.js +8 -0
- package/dist/ApexUI-Kit/templates/ThemeToggle/ThemeToggle.css +560 -0
- package/dist/ApexUI-Kit/templates/ThemeToggle/ThemeToggle.jsx +118 -0
- package/dist/ApexUI-Kit/templates/ToolTip/ToolTip.jsx +85 -0
- package/dist/ApexUI-Kit/templates/WaterDropReveal/WaterDropReveal.jsx +151 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +145 -0
- package/package.json +56 -0
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
import React, { useRef } from "react";
|
|
2
|
+
import gsap from "gsap";
|
|
3
|
+
|
|
4
|
+
const HoverText = ({
|
|
5
|
+
text = "ApexUI",
|
|
6
|
+
fontSize = "2.5rem",
|
|
7
|
+
className = "",
|
|
8
|
+
effect, // if not passed, use 'defaultReveal'
|
|
9
|
+
effectColor = "#C27AFF"
|
|
10
|
+
}) => {
|
|
11
|
+
const charsRef = useRef([]);
|
|
12
|
+
const containerRef = useRef(null);
|
|
13
|
+
|
|
14
|
+
// Default animated reveal
|
|
15
|
+
function handleDefaultEnter(i) {
|
|
16
|
+
if (effect && effect !== "defaultReveal") return;
|
|
17
|
+
const el = charsRef.current[i];
|
|
18
|
+
if (!el) return;
|
|
19
|
+
gsap.to(el, {
|
|
20
|
+
scale: 1.4,
|
|
21
|
+
rotate: gsap.utils.random(-10, 10),
|
|
22
|
+
color: effectColor,
|
|
23
|
+
filter: `drop-shadow(0 4px 32px ${effectColor})`,
|
|
24
|
+
duration: 0.32,
|
|
25
|
+
ease: "elastic.out(1,0.5)",
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
function handleDefaultLeave(i) {
|
|
29
|
+
if (effect && effect !== "defaultReveal") return;
|
|
30
|
+
const el = charsRef.current[i];
|
|
31
|
+
if (!el) return;
|
|
32
|
+
gsap.to(el, {
|
|
33
|
+
scale: 1,
|
|
34
|
+
rotate: 0,
|
|
35
|
+
color: "#fff",
|
|
36
|
+
filter: `drop-shadow(0 0px 0px ${effectColor})`,
|
|
37
|
+
duration: 0.32,
|
|
38
|
+
ease: "expo.inOut",
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Party effect
|
|
43
|
+
function handlePartyEnter() {
|
|
44
|
+
if (effect !== "party") return;
|
|
45
|
+
charsRef.current.forEach((el, i) => {
|
|
46
|
+
if (!el) return;
|
|
47
|
+
gsap.to(el, {
|
|
48
|
+
scale: 1.25,
|
|
49
|
+
rotate: (i % 2 === 0 ? 1 : -1) * (18 + i * 4),
|
|
50
|
+
color: effectColor,
|
|
51
|
+
filter: `drop-shadow(0 4px 32px ${effectColor})`,
|
|
52
|
+
duration: 0.38,
|
|
53
|
+
ease: "elastic.out(1,0.5)",
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
function handlePartyLeave() {
|
|
58
|
+
if (effect !== "party") return;
|
|
59
|
+
charsRef.current.forEach((el) => {
|
|
60
|
+
if (!el) return;
|
|
61
|
+
gsap.to(el, {
|
|
62
|
+
scale: 1,
|
|
63
|
+
rotate: 0,
|
|
64
|
+
color: "#fff",
|
|
65
|
+
filter: `drop-shadow(0 0px 0px ${effectColor})`,
|
|
66
|
+
duration: 0.38,
|
|
67
|
+
ease: "expo.inOut",
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Reset all letters on mount or color change
|
|
73
|
+
React.useEffect(() => {
|
|
74
|
+
charsRef.current.forEach((el) => {
|
|
75
|
+
if (el) {
|
|
76
|
+
gsap.set(el, {
|
|
77
|
+
scale: 1,
|
|
78
|
+
rotate: 0,
|
|
79
|
+
x: 0,
|
|
80
|
+
y: 0,
|
|
81
|
+
filter: `drop-shadow(0 0px 0px ${effectColor})`,
|
|
82
|
+
color: "#fff",
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
}, [text, effectColor]);
|
|
87
|
+
|
|
88
|
+
// Magnetic effect
|
|
89
|
+
const handleMouseMove = (e) => {
|
|
90
|
+
if (effect !== "magnetic") return;
|
|
91
|
+
const rect = containerRef.current.getBoundingClientRect();
|
|
92
|
+
const mouseX = e.clientX - rect.left;
|
|
93
|
+
const mouseY = e.clientY - rect.top;
|
|
94
|
+
let minDist = Infinity;
|
|
95
|
+
let minIdx = -1;
|
|
96
|
+
charsRef.current.forEach((el, i) => {
|
|
97
|
+
if (!el) return;
|
|
98
|
+
const elRect = el.getBoundingClientRect();
|
|
99
|
+
const elCenterX = elRect.left - rect.left + elRect.width / 2;
|
|
100
|
+
const elCenterY = elRect.top - rect.top + elRect.height / 2;
|
|
101
|
+
const dx = (mouseX - elCenterX) / rect.width;
|
|
102
|
+
const dy = (mouseY - elCenterY) / rect.height;
|
|
103
|
+
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
104
|
+
if (dist < minDist) {
|
|
105
|
+
minDist = dist;
|
|
106
|
+
minIdx = i;
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
// Animate all letters
|
|
110
|
+
charsRef.current.forEach((el, i) => {
|
|
111
|
+
if (!el) return;
|
|
112
|
+
const elRect = el.getBoundingClientRect();
|
|
113
|
+
const elCenterX = elRect.left - rect.left + elRect.width / 2;
|
|
114
|
+
const elCenterY = elRect.top - rect.top + elRect.height / 2;
|
|
115
|
+
const dx = (mouseX - elCenterX) / rect.width;
|
|
116
|
+
const dy = (mouseY - elCenterY) / rect.height;
|
|
117
|
+
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
118
|
+
const maxDist = 0.35;
|
|
119
|
+
const mag = Math.max(0, 1 - dist / maxDist);
|
|
120
|
+
const isFocus = i === minIdx;
|
|
121
|
+
gsap.to(el, {
|
|
122
|
+
x: dx * 18 * mag,
|
|
123
|
+
y: dy * 18 * mag,
|
|
124
|
+
scale: isFocus ? 1.22 : 1 + 0.12 * mag,
|
|
125
|
+
rotate: dx * 10 * mag,
|
|
126
|
+
filter: isFocus
|
|
127
|
+
? `drop-shadow(0 4px 32px ${effectColor})`
|
|
128
|
+
: `drop-shadow(0 2px 16px ${effectColor}44)`,
|
|
129
|
+
color: isFocus ? effectColor : "#fff",
|
|
130
|
+
zIndex: isFocus ? 10 : 2,
|
|
131
|
+
duration: 0.35,
|
|
132
|
+
ease: "expo.out",
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
// Rubber effect
|
|
138
|
+
function handleRubberEnter(i) {
|
|
139
|
+
if (effect !== "rubber") return;
|
|
140
|
+
const el = charsRef.current[i];
|
|
141
|
+
if (!el) return;
|
|
142
|
+
gsap.to(el, {
|
|
143
|
+
scaleX: 1.5,
|
|
144
|
+
scaleY: 0.7,
|
|
145
|
+
color: effectColor,
|
|
146
|
+
filter: `drop-shadow(0 4px 32px ${effectColor})`,
|
|
147
|
+
duration: 0.22,
|
|
148
|
+
ease: "expo.out",
|
|
149
|
+
onComplete: () => {
|
|
150
|
+
gsap.to(el, {
|
|
151
|
+
scaleX: 0.85,
|
|
152
|
+
scaleY: 1.18,
|
|
153
|
+
duration: 0.18,
|
|
154
|
+
ease: "expo.inOut",
|
|
155
|
+
onComplete: () => {
|
|
156
|
+
gsap.to(el, {
|
|
157
|
+
scaleX: 1,
|
|
158
|
+
scaleY: 1,
|
|
159
|
+
color: "#fff",
|
|
160
|
+
filter: `drop-shadow(0 0px 0px ${effectColor})`,
|
|
161
|
+
duration: 0.18,
|
|
162
|
+
ease: "expo.inOut",
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Jump effect
|
|
171
|
+
function handleJumpEnter(i) {
|
|
172
|
+
if (effect !== "jump") return;
|
|
173
|
+
for (let j = -1; j <= 1; j++) {
|
|
174
|
+
const idx = i + j;
|
|
175
|
+
const el = charsRef.current[idx];
|
|
176
|
+
if (!el) continue;
|
|
177
|
+
if (j === 0) {
|
|
178
|
+
gsap.to(el, {
|
|
179
|
+
y: -28,
|
|
180
|
+
color: effectColor,
|
|
181
|
+
filter: `drop-shadow(0 4px 32px ${effectColor})`,
|
|
182
|
+
duration: 0.32,
|
|
183
|
+
ease: "expo.out",
|
|
184
|
+
});
|
|
185
|
+
} else {
|
|
186
|
+
gsap.to(el, {
|
|
187
|
+
x: j * 8,
|
|
188
|
+
rotate: j * 8,
|
|
189
|
+
duration: 0.22,
|
|
190
|
+
ease: "expo.out",
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
function handleJumpLeave(i) {
|
|
196
|
+
if (effect !== "jump") return;
|
|
197
|
+
for (let j = -1; j <= 1; j++) {
|
|
198
|
+
const idx = i + j;
|
|
199
|
+
const el = charsRef.current[idx];
|
|
200
|
+
if (!el) continue;
|
|
201
|
+
gsap.to(el, {
|
|
202
|
+
x: 0,
|
|
203
|
+
y: 0,
|
|
204
|
+
scale: 1,
|
|
205
|
+
rotate: 0,
|
|
206
|
+
color: "#fff",
|
|
207
|
+
filter: `drop-shadow(0 0px 0px ${effectColor})`,
|
|
208
|
+
duration: 0.32,
|
|
209
|
+
ease: "expo.inOut",
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Rotate effect
|
|
215
|
+
function handleRotateEnter(i) {
|
|
216
|
+
if (effect !== "rotate") return;
|
|
217
|
+
for (let j = -1; j <= 1; j++) {
|
|
218
|
+
const idx = i + j;
|
|
219
|
+
const el = charsRef.current[idx];
|
|
220
|
+
if (!el) continue;
|
|
221
|
+
if (j === 0) {
|
|
222
|
+
gsap.to(el, {
|
|
223
|
+
rotate: 24,
|
|
224
|
+
scale: 1.18,
|
|
225
|
+
color: effectColor,
|
|
226
|
+
filter: `drop-shadow(0 4px 32px ${effectColor})`,
|
|
227
|
+
duration: 0.32,
|
|
228
|
+
ease: "expo.out",
|
|
229
|
+
});
|
|
230
|
+
} else {
|
|
231
|
+
gsap.to(el, {
|
|
232
|
+
rotate: j * 12,
|
|
233
|
+
duration: 0.22,
|
|
234
|
+
ease: "expo.out",
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
function handleRotateLeave(i) {
|
|
240
|
+
if (effect !== "rotate") return;
|
|
241
|
+
for (let j = -1; j <= 1; j++) {
|
|
242
|
+
const idx = i + j;
|
|
243
|
+
const el = charsRef.current[idx];
|
|
244
|
+
if (!el) continue;
|
|
245
|
+
gsap.to(el, {
|
|
246
|
+
scale: 1,
|
|
247
|
+
rotate: 0,
|
|
248
|
+
color: "#fff",
|
|
249
|
+
filter: `drop-shadow(0 0px 0px ${effectColor})`,
|
|
250
|
+
duration: 0.32,
|
|
251
|
+
ease: "expo.inOut",
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Wave effect
|
|
257
|
+
function handleCharEnter(i) {
|
|
258
|
+
if (effect !== "wave") return;
|
|
259
|
+
for (let j = -2; j <= 2; j++) {
|
|
260
|
+
const idx = i + j;
|
|
261
|
+
const el = charsRef.current[idx];
|
|
262
|
+
if (!el) continue;
|
|
263
|
+
gsap.to(el, {
|
|
264
|
+
y: -18 + Math.abs(j) * 8,
|
|
265
|
+
scale: 1.12 - Math.abs(j) * 0.08,
|
|
266
|
+
color: effectColor,
|
|
267
|
+
filter: `drop-shadow(0 4px 32px ${effectColor})`,
|
|
268
|
+
duration: 0.38 + Math.abs(j) * 0.08,
|
|
269
|
+
ease: "elastic.out(1,0.5)",
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
function handleCharLeave(i) {
|
|
274
|
+
if (effect !== "wave") return;
|
|
275
|
+
for (let j = -2; j <= 2; j++) {
|
|
276
|
+
const idx = i + j;
|
|
277
|
+
const el = charsRef.current[idx];
|
|
278
|
+
if (!el) continue;
|
|
279
|
+
gsap.to(el, {
|
|
280
|
+
y: 0,
|
|
281
|
+
scale: 1,
|
|
282
|
+
color: "#fff",
|
|
283
|
+
filter: `drop-shadow(0 0px 0px ${effectColor})`,
|
|
284
|
+
duration: 0.38 + Math.abs(j) * 0.08,
|
|
285
|
+
ease: "expo.inOut",
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Reset on mouse leave
|
|
291
|
+
const handleMouseLeave = () => {
|
|
292
|
+
if (effect === "magnetic") {
|
|
293
|
+
charsRef.current.forEach((el) => {
|
|
294
|
+
if (el) {
|
|
295
|
+
gsap.to(el, {
|
|
296
|
+
x: 0,
|
|
297
|
+
y: 0,
|
|
298
|
+
scale: 1,
|
|
299
|
+
rotate: 0,
|
|
300
|
+
color: "#fff",
|
|
301
|
+
filter: `drop-shadow(0 0px 0px ${effectColor})`,
|
|
302
|
+
duration: 0.6,
|
|
303
|
+
ease: "expo.inOut",
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
} else if (effect === "flip") {
|
|
308
|
+
charsRef.current.forEach((el) => {
|
|
309
|
+
if (el) {
|
|
310
|
+
gsap.to(el, {
|
|
311
|
+
rotateY: 0,
|
|
312
|
+
color: "#fff",
|
|
313
|
+
filter: `drop-shadow(0 0px 0px ${effectColor})`,
|
|
314
|
+
duration: 0.6,
|
|
315
|
+
ease: "expo.inOut",
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
return (
|
|
323
|
+
<div
|
|
324
|
+
ref={containerRef}
|
|
325
|
+
className={`relative inline-flex items-center select-none cursor-pointer ${className}`}
|
|
326
|
+
style={{ fontSize, fontWeight: 900, letterSpacing: "0.08em", textTransform: "uppercase", background: "none", userSelect: "none" }}
|
|
327
|
+
onMouseMove={effect === "magnetic" ? handleMouseMove : undefined}
|
|
328
|
+
onMouseLeave={effect === "party" ? handlePartyLeave : handleMouseLeave}
|
|
329
|
+
onMouseEnter={effect === "party" ? handlePartyEnter : undefined}
|
|
330
|
+
>
|
|
331
|
+
{text.split("").map((char, i) => (
|
|
332
|
+
<span
|
|
333
|
+
key={i}
|
|
334
|
+
ref={el => charsRef.current[i] = el}
|
|
335
|
+
onMouseEnter={
|
|
336
|
+
!effect || effect === "defaultReveal" ? () => handleDefaultEnter(i)
|
|
337
|
+
: effect === "wave" ? () => handleCharEnter(i)
|
|
338
|
+
: effect === "rubber" ? () => handleRubberEnter(i)
|
|
339
|
+
: effect === "jump" ? () => handleJumpEnter(i)
|
|
340
|
+
: effect === "rotate" ? () => handleRotateEnter(i)
|
|
341
|
+
: undefined
|
|
342
|
+
}
|
|
343
|
+
onMouseLeave={
|
|
344
|
+
!effect || effect === "defaultReveal" ? () => handleDefaultLeave(i)
|
|
345
|
+
: effect === "wave" ? () => handleCharLeave(i)
|
|
346
|
+
: effect === "rubber" ? () => handleRubberEnter(i)
|
|
347
|
+
: effect === "jump" ? () => handleJumpLeave(i)
|
|
348
|
+
: effect === "rotate" ? () => handleRotateLeave(i)
|
|
349
|
+
: undefined
|
|
350
|
+
}
|
|
351
|
+
style={{
|
|
352
|
+
display: "inline-block",
|
|
353
|
+
marginRight: char === " " ? "0.18em" : 0,
|
|
354
|
+
padding: "0 0.04em",
|
|
355
|
+
willChange: "transform, color, filter, opacity",
|
|
356
|
+
borderRadius: "0.18em",
|
|
357
|
+
position: "relative",
|
|
358
|
+
zIndex: 2,
|
|
359
|
+
color: "#fff",
|
|
360
|
+
background: "none",
|
|
361
|
+
transition: "filter 0.3s, color 0.3s, background 0.3s, opacity 0.3s",
|
|
362
|
+
}}
|
|
363
|
+
>
|
|
364
|
+
{char === " " ? "\u00A0" : char}
|
|
365
|
+
</span>
|
|
366
|
+
))}
|
|
367
|
+
</div>
|
|
368
|
+
);
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
export default HoverText;
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import React, { useRef, useEffect, useState } from "react";
|
|
2
|
+
import { gsap } from "gsap";
|
|
3
|
+
import {
|
|
4
|
+
faCube,
|
|
5
|
+
faChevronRight, // Changed icon for a better look with the new button
|
|
6
|
+
} from "@fortawesome/free-solid-svg-icons";
|
|
7
|
+
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
8
|
+
|
|
9
|
+
const HyperCard = ({
|
|
10
|
+
// Content props remain unchanged
|
|
11
|
+
text = "Introducing Hyper Card",
|
|
12
|
+
LastText = "Effect",
|
|
13
|
+
LastTextColor = "#9AE600", // Green color from your original code
|
|
14
|
+
SubText = "Fully animated lightning effect card with customizable props, built with Apex UI.",
|
|
15
|
+
title = "Apex UI Hyper Card",
|
|
16
|
+
description = "Experience futuristic animations with customizable props like glow, beam, star count, and hover interactions.",
|
|
17
|
+
buttonText = "Get Started",
|
|
18
|
+
|
|
19
|
+
// Animation props remain unchanged
|
|
20
|
+
starColor = "#9AE600", // Green stars
|
|
21
|
+
starCount = 250,
|
|
22
|
+
glow = true,
|
|
23
|
+
baseSpeed = 0.5,
|
|
24
|
+
warpSpeed = 8.5,
|
|
25
|
+
circlePosition = "bottom",
|
|
26
|
+
}) => {
|
|
27
|
+
const canvasRef = useRef(null);
|
|
28
|
+
const contentRef = useRef(null);
|
|
29
|
+
const [hovered, setHovered] = useState(false);
|
|
30
|
+
const stars = useRef([]);
|
|
31
|
+
const speedRef = useRef({ value: baseSpeed });
|
|
32
|
+
|
|
33
|
+
// 🎇 Particle logic remains exactly the same
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
const canvas = canvasRef.current;
|
|
36
|
+
const ctx = canvas.getContext("2d");
|
|
37
|
+
let animationFrame;
|
|
38
|
+
let w, h;
|
|
39
|
+
|
|
40
|
+
const init = () => {
|
|
41
|
+
w = canvas.width = canvas.offsetWidth;
|
|
42
|
+
h = canvas.height = canvas.offsetHeight;
|
|
43
|
+
stars.current = Array.from({ length: starCount }, () => {
|
|
44
|
+
const z = Math.random() * w;
|
|
45
|
+
return {
|
|
46
|
+
x: Math.random() * w, y: Math.random() * h,
|
|
47
|
+
z, baseZ: z, brightness: Math.random() * 0.5 + 0.5,
|
|
48
|
+
};
|
|
49
|
+
});
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const draw = () => {
|
|
53
|
+
// Updated to draw on a transparent background
|
|
54
|
+
if (!ctx) return;
|
|
55
|
+
ctx.clearRect(0, 0, w, h);
|
|
56
|
+
ctx.fillStyle = starColor;
|
|
57
|
+
|
|
58
|
+
stars.current.forEach((star) => {
|
|
59
|
+
star.z -= speedRef.current.value;
|
|
60
|
+
if (star.z <= 1) star.z = star.baseZ;
|
|
61
|
+
const sx = (star.x - w / 2) * (w / star.z) + w / 2;
|
|
62
|
+
const sy = (star.y - h / 2) * (h / star.z) + h / 2;
|
|
63
|
+
const size = (1 - star.z / w) * 2;
|
|
64
|
+
ctx.beginPath();
|
|
65
|
+
ctx.globalAlpha = star.brightness;
|
|
66
|
+
if (speedRef.current.value > baseSpeed + 0.3) {
|
|
67
|
+
const tailLength = speedRef.current.value * 3;
|
|
68
|
+
const dx = sx - w / 2, dy = sy - h / 2;
|
|
69
|
+
const mag = Math.sqrt(dx * dx + dy * dy) || 1;
|
|
70
|
+
ctx.moveTo(sx, sy);
|
|
71
|
+
ctx.lineTo(sx + (dx / mag) * tailLength, sy + (dy / mag) * tailLength);
|
|
72
|
+
ctx.strokeStyle = starColor;
|
|
73
|
+
ctx.lineWidth = 1;
|
|
74
|
+
ctx.stroke();
|
|
75
|
+
} else {
|
|
76
|
+
ctx.arc(sx, sy, size > 0 ? size : 0.1, 0, Math.PI * 2);
|
|
77
|
+
ctx.fill();
|
|
78
|
+
}
|
|
79
|
+
ctx.globalAlpha = 1;
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
animationFrame = requestAnimationFrame(draw);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
init();
|
|
86
|
+
draw();
|
|
87
|
+
window.addEventListener("resize", init);
|
|
88
|
+
return () => { cancelAnimationFrame(animationFrame); window.removeEventListener("resize", init); };
|
|
89
|
+
}, [starColor, starCount, baseSpeed]);
|
|
90
|
+
|
|
91
|
+
// GSAP animations for speed and jiggle remain the same
|
|
92
|
+
useEffect(() => {
|
|
93
|
+
gsap.to(speedRef.current, { value: hovered ? warpSpeed : baseSpeed, duration: 1.5, ease: "power4.inOut" });
|
|
94
|
+
}, [hovered, warpSpeed, baseSpeed]);
|
|
95
|
+
|
|
96
|
+
useEffect(() => {
|
|
97
|
+
const el = contentRef.current;
|
|
98
|
+
if (hovered) {
|
|
99
|
+
gsap.to(el, { scale: 0.95, duration: 0.5, ease: "power1.out" });
|
|
100
|
+
gsap.to(el, { x: "+=1", y: "-=1", duration: 0.05, repeat: -1, yoyo: true, ease: "sine.inOut" });
|
|
101
|
+
} else {
|
|
102
|
+
gsap.killTweensOf(el);
|
|
103
|
+
gsap.to(el, { x: 0, y: 0, scale: 1, duration: 0.4, ease: "power2.out" });
|
|
104
|
+
}
|
|
105
|
+
}, [hovered]);
|
|
106
|
+
|
|
107
|
+
return (
|
|
108
|
+
<div
|
|
109
|
+
className="w-full max-w-xs mx-auto rounded-2xl overflow-hidden text-white
|
|
110
|
+
border border-slate-700 bg-slate-900 shadow-2xl shadow-black/50"
|
|
111
|
+
style={{
|
|
112
|
+
backgroundImage: 'linear-gradient(rgba(255, 255, 255, 0.03) 1px, transparent 1px), linear-gradient(90deg, rgba(255, 255, 255, 0.03) 1px, transparent 1px)',
|
|
113
|
+
backgroundSize: '2rem 2rem'
|
|
114
|
+
}}
|
|
115
|
+
>
|
|
116
|
+
{/* Top animated HyperCard */}
|
|
117
|
+
<div
|
|
118
|
+
className="relative w-full h-50 overflow-hidden group cursor-pointer"
|
|
119
|
+
onMouseEnter={() => setHovered(true)}
|
|
120
|
+
onMouseLeave={() => setHovered(false)}
|
|
121
|
+
>
|
|
122
|
+
<canvas ref={canvasRef} className="absolute inset-0 w-full h-full z-0" />
|
|
123
|
+
|
|
124
|
+
{/* Glow effect from your original code */}
|
|
125
|
+
{glow && (
|
|
126
|
+
<div
|
|
127
|
+
className="absolute inset-0 w-full h-full z-10 pointer-events-none"
|
|
128
|
+
style={{
|
|
129
|
+
mixBlendMode: "soft-light",
|
|
130
|
+
opacity: 0.8,
|
|
131
|
+
background: `radial-gradient(circle at ${circlePosition}, ${starColor} 0%, transparent 70%)`,
|
|
132
|
+
}}
|
|
133
|
+
/>
|
|
134
|
+
)}
|
|
135
|
+
|
|
136
|
+
{/* Glowing separator line */}
|
|
137
|
+
<div className="absolute bottom-0 w-full h-[2px] bg-purple-500/50 blur-sm"></div>
|
|
138
|
+
|
|
139
|
+
<div ref={contentRef} className="relative z-20 flex flex-col items-center justify-center w-full h-full px-4 py-2">
|
|
140
|
+
<h2 className="text-lg sm:text-xl font-normal text-center text-white">
|
|
141
|
+
{text}{" "}
|
|
142
|
+
{/* Fixed color issue by using inline style */}
|
|
143
|
+
<span className="font-bold ml-1" style={{ color: LastTextColor }}>
|
|
144
|
+
{LastText}
|
|
145
|
+
</span>
|
|
146
|
+
</h2>
|
|
147
|
+
<p className="text-sm text-gray-300 text-center max-w-sm mt-1">{SubText}</p>
|
|
148
|
+
</div>
|
|
149
|
+
</div>
|
|
150
|
+
|
|
151
|
+
{/* Bottom content section */}
|
|
152
|
+
<div className="px-6 pt-5 pb-6 w-full text-left flex flex-col items-start">
|
|
153
|
+
<div className="flex items-center gap-3 mb-2">
|
|
154
|
+
<FontAwesomeIcon icon={faCube} style={{ color: starColor }} className="text-2xl" />
|
|
155
|
+
<h3 className="text-2xl font-extrabold text-white tracking-tight">{title}</h3>
|
|
156
|
+
</div>
|
|
157
|
+
<p className="text-gray-400 text-base mb-5 max-w-xs leading-relaxed">{description}</p>
|
|
158
|
+
|
|
159
|
+
{/* --- NEW BUTTON WITH ANIMATION --- */}
|
|
160
|
+
<button
|
|
161
|
+
className="group relative flex items-center gap-2 px-6 py-2.5
|
|
162
|
+
border-2 border-[#9AE600] rounded-lg
|
|
163
|
+
font-semibold text-white overflow-hidden transition-all duration-300"
|
|
164
|
+
>
|
|
165
|
+
{/* Fill-up background effect */}
|
|
166
|
+
<span
|
|
167
|
+
className="absolute inset-0 h-full w-0 bg-[#4c6d0a]
|
|
168
|
+
transition-all duration-300 ease-in-out group-hover:w-full"
|
|
169
|
+
style={{ transformOrigin: 'bottom' }}
|
|
170
|
+
></span>
|
|
171
|
+
|
|
172
|
+
{/* Button text and icon */}
|
|
173
|
+
<span className="relative z-10 flex items-center gap-2">
|
|
174
|
+
{buttonText}
|
|
175
|
+
<FontAwesomeIcon icon={faChevronRight} className="transition-transform duration-300 group-hover:translate-x-1" />
|
|
176
|
+
</span>
|
|
177
|
+
</button>
|
|
178
|
+
</div>
|
|
179
|
+
</div>
|
|
180
|
+
);
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
export default HyperCard;
|