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,107 @@
|
|
|
1
|
+
import React, { useRef, useEffect } from "react";
|
|
2
|
+
import { gsap } from "gsap";
|
|
3
|
+
import { BsTwitterX } from "react-icons/bs";
|
|
4
|
+
// --- SVG Icons (No changes here) --- //
|
|
5
|
+
const GitHubIcon = () => (<svg width="24" height="24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" viewBox="0 0 24 24"><path d="M12 2C6.48 2 2 6.48 2 12c0 4.42 2.87 8.17 6.84 9.5.5.09.66-.22.66-.48 0-.24-.01-.87-.01-1.7-2.78.6-3.37-1.34-3.37-1.34-.45-1.15-1.1-1.46-1.1-1.46-.9-.62.07-.6.07-.6 1 .07 1.53 1.03 1.53 1.03.89 1.52 2.34 1.08 2.91.83.09-.65.35-1.08.63-1.33-2.22-.25-4.56-1.11-4.56-4.95 0-1.09.39-1.98 1.03-2.68-.1-.25-.45-1.27.1-2.65 0 0 .84-.27 2.75 1.02A9.56 9.56 0 0112 6.8c.85.004 1.71.12 2.51.35 1.91-1.29 2.75-1.02 2.75-1.02.55 1.38.2 2.4.1 2.65.64.7 1.03 1.59 1.03 2.68 0 3.85-2.34 4.7-4.57 4.95.36.31.68.92.68 1.85 0 1.33-.01 2.4-.01 2.73 0 .27.16.58.67.48A10.01 10.01 0 0022 12c0-5.52-4.48-10-10-10z" /></svg>);
|
|
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
|
+
const TwitterIcon = () => (<BsTwitterX size={24} />);
|
|
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 ParallaxProfileCard = ({ profile }) => {
|
|
10
|
+
const defaultProfile = {
|
|
11
|
+
avatarUrl: "/assets/spiderman.png",
|
|
12
|
+
name: "Spider Man",
|
|
13
|
+
username: "spider_man",
|
|
14
|
+
dob: "10-08-2001",
|
|
15
|
+
title: "PhotoGrapher",
|
|
16
|
+
status: "Available for Hire",
|
|
17
|
+
socialLinks: {
|
|
18
|
+
github: "https://github.com/",
|
|
19
|
+
linkedin: "https://www.linkedin.com/",
|
|
20
|
+
twitter: "https://twitter.com/",
|
|
21
|
+
instagram: "https://www.instagram.com/",
|
|
22
|
+
},
|
|
23
|
+
// Button actions
|
|
24
|
+
onHireClick: () => alert("Hire Me button clicked!"),
|
|
25
|
+
onCvClick: () => alert("CV button clicked!"),
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const {
|
|
29
|
+
avatarUrl, name, username, dob, title, status, socialLinks, onHireClick, onCvClick
|
|
30
|
+
} = profile || defaultProfile;
|
|
31
|
+
|
|
32
|
+
const cardRef = useRef(null);
|
|
33
|
+
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
const card = cardRef.current;
|
|
36
|
+
if (!card) return;
|
|
37
|
+
const handleMouseMove = (e) => {
|
|
38
|
+
const { left, top, width, height } = card.getBoundingClientRect();
|
|
39
|
+
const x = e.clientX - left; const y = e.clientY - top;
|
|
40
|
+
const rotateX = gsap.utils.mapRange(0, height, -9, 9, y);
|
|
41
|
+
const rotateY = gsap.utils.mapRange(0, width, 9, -9, x);
|
|
42
|
+
gsap.to(card, { duration: 0.7, rotationX: rotateX, rotationY: rotateY, scale: 1.05, ease: "power3.out" });
|
|
43
|
+
};
|
|
44
|
+
const handleMouseLeave = () => {
|
|
45
|
+
gsap.to(card, { duration: 1, rotationX: 0, rotationY: 0, scale: 1, ease: "elastic.out(1, 0.3)" });
|
|
46
|
+
};
|
|
47
|
+
gsap.set(card, { transformPerspective: 1000 });
|
|
48
|
+
card.addEventListener("mousemove", handleMouseMove);
|
|
49
|
+
card.addEventListener("mouseleave", handleMouseLeave);
|
|
50
|
+
return () => {
|
|
51
|
+
card.removeEventListener("mousemove", handleMouseMove);
|
|
52
|
+
card.removeEventListener("mouseleave", handleMouseLeave);
|
|
53
|
+
};
|
|
54
|
+
}, []);
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<div
|
|
58
|
+
ref={cardRef}
|
|
59
|
+
className="relative w-[300px] h-[440px] bg-[#1D2B3A] rounded-3xl p-6 border border-cyan-400/30 shadow-2xl shadow-cyan-900/40 flex flex-col"
|
|
60
|
+
style={{ transformStyle: "preserve-3d" }}
|
|
61
|
+
>
|
|
62
|
+
<div className="absolute inset-0 bg-gradient-to-br from-[#1D2B3A] to-[#111827] rounded-3xl"></div>
|
|
63
|
+
<div className="absolute top-0 left-0 w-full h-full" style={{ background: "radial-gradient(circle at 50% 50%, rgba(34, 211, 238, 0.08), transparent 70%)", transform: "translateZ(20px)" }}></div>
|
|
64
|
+
|
|
65
|
+
<div className="relative z-10 flex flex-col items-center justify-between h-full" style={{ transform: "translateZ(50px)" }}>
|
|
66
|
+
{/* Top Part */}
|
|
67
|
+
<div className="text-center">
|
|
68
|
+
<img src={avatarUrl} alt="Profile" className="w-28 h-28 rounded-full mx-auto mb-3 border-4 border-cyan-400 shadow-lg object-cover" onError={(e) => { e.target.onerror = null; e.target.src = "https://placehold.co/128x128/1D2B3A/A5F3FC?text=ERR"; }} />
|
|
69
|
+
<h2 className="text-2xl font-bold text-cyan-300 tracking-wide">{name}</h2>
|
|
70
|
+
<div className="flex flex-col items-center mt-1">
|
|
71
|
+
<span className="text-xs font-semibold text-slate-300 bg-slate-700 px-2 py-0.5 rounded-full mb-1">@{username}</span>
|
|
72
|
+
<span className="text-xs font-medium text-cyan-400 bg-slate-700 px-2 py-0.5 rounded-full">DOB: {dob}</span>
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
|
|
76
|
+
{/* Bottom Part */}
|
|
77
|
+
<div className="w-full text-center">
|
|
78
|
+
<p className="text-slate-400 text-lg">{title}</p>
|
|
79
|
+
{status && (
|
|
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>)}
|
|
90
|
+
</div>
|
|
91
|
+
|
|
92
|
+
{/* Buttons Section */}
|
|
93
|
+
<div className="flex justify-center gap-3">
|
|
94
|
+
<button onClick={onHireClick} className="w-1/2 bg-cyan-500 text-slate-900 font-bold py-2 px-4 rounded-lg hover:bg-cyan-400 transition-colors duration-300">
|
|
95
|
+
Hire Me
|
|
96
|
+
</button>
|
|
97
|
+
<button onClick={onCvClick} className="w-1/2 bg-slate-700 text-cyan-300 font-bold py-2 px-4 rounded-lg hover:bg-slate-600 transition-colors duration-300">
|
|
98
|
+
CV
|
|
99
|
+
</button>
|
|
100
|
+
</div>
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
</div>
|
|
104
|
+
);
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
export default ParallaxProfileCard;
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import React, { useRef, useEffect, useState } from "react";
|
|
2
|
+
|
|
3
|
+
const people = [
|
|
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" },
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
const PointerFollower = ({ cursorColor = "#fff", interval = 3000, badgeColor = "#fff", badgeTextColor = "#212121" }) => {
|
|
13
|
+
const containerRef = useRef(null);
|
|
14
|
+
const [pos, setPos] = useState({ x: 0, y: 0 });
|
|
15
|
+
const [hovered, setHovered] = useState(false);
|
|
16
|
+
const [activeIndex, setActiveIndex] = useState(0);
|
|
17
|
+
|
|
18
|
+
// 🛠️ Auto cycle with sync fix
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
if (hovered) return;
|
|
21
|
+
|
|
22
|
+
const autoInterval = setInterval(() => {
|
|
23
|
+
setActiveIndex((prev) => (prev + 1) % people.length);
|
|
24
|
+
}, interval);
|
|
25
|
+
|
|
26
|
+
return () => clearInterval(autoInterval);
|
|
27
|
+
}, [hovered, interval]);
|
|
28
|
+
|
|
29
|
+
// 🖱️ Mouse tracking
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
const container = containerRef.current;
|
|
32
|
+
if (!container) return;
|
|
33
|
+
|
|
34
|
+
const handleMove = (e) => {
|
|
35
|
+
const rect = container.getBoundingClientRect();
|
|
36
|
+
setPos({
|
|
37
|
+
x: e.clientX - rect.left,
|
|
38
|
+
y: e.clientY - rect.top,
|
|
39
|
+
});
|
|
40
|
+
setHovered(true);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const handleLeave = () => {
|
|
44
|
+
setHovered(false);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
container.addEventListener("pointermove", handleMove);
|
|
48
|
+
container.addEventListener("pointerleave", handleLeave);
|
|
49
|
+
|
|
50
|
+
return () => {
|
|
51
|
+
container.removeEventListener("pointermove", handleMove);
|
|
52
|
+
container.removeEventListener("pointerleave", handleLeave);
|
|
53
|
+
};
|
|
54
|
+
}, []);
|
|
55
|
+
|
|
56
|
+
const person = people[activeIndex]; // ✅ SYNCED HERE
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<div className="relative w-full h-fit flex items-center justify-center">
|
|
60
|
+
<div ref={containerRef} className="absolute flex items-center justify-center w-64 h-66 cursor-none">
|
|
61
|
+
{/* 🔁 Image stack with auto fade */}
|
|
62
|
+
{people.map((p, i) => (
|
|
63
|
+
<img
|
|
64
|
+
key={i}
|
|
65
|
+
src={p.img}
|
|
66
|
+
alt={p.name}
|
|
67
|
+
className={`absolute w-full h-full object-cover rounded-xl transition-opacity duration-1000 ease-in-out border border-white/10`}
|
|
68
|
+
style={{
|
|
69
|
+
transform: `translate(${i * -2}px, ${i * -2}px)`,
|
|
70
|
+
zIndex: people.length - i,
|
|
71
|
+
opacity: activeIndex === i ? 1 : 0,
|
|
72
|
+
}}
|
|
73
|
+
/>
|
|
74
|
+
))}
|
|
75
|
+
{/* 🖱️ Custom SVG Cursor */}
|
|
76
|
+
{hovered && (
|
|
77
|
+
<>
|
|
78
|
+
<div
|
|
79
|
+
className="absolute z-50"
|
|
80
|
+
style={{
|
|
81
|
+
left: pos.x,
|
|
82
|
+
top: pos.y,
|
|
83
|
+
transform: "translate(-50%, -50%)",
|
|
84
|
+
pointerEvents: "none",
|
|
85
|
+
}}
|
|
86
|
+
>
|
|
87
|
+
<svg
|
|
88
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
89
|
+
width="28"
|
|
90
|
+
height="28"
|
|
91
|
+
viewBox="0 0 24 24"
|
|
92
|
+
>
|
|
93
|
+
<path
|
|
94
|
+
fill={cursorColor}
|
|
95
|
+
stroke="#000"
|
|
96
|
+
strokeWidth="1.8"
|
|
97
|
+
d="M5.5 3.21V20.8c0 .45.54.67.85.35l4.86-4.86a.5.5 0 0 1 .35-.15h6.87a.5.5 0 0 0 .35-.85L6.35 2.85a.5.5 0 0 0-.85.35Z"
|
|
98
|
+
/>
|
|
99
|
+
</svg>
|
|
100
|
+
</div>
|
|
101
|
+
|
|
102
|
+
{/* 🎯 Job Passion Label */}
|
|
103
|
+
<div
|
|
104
|
+
className="absolute z-40"
|
|
105
|
+
style={{
|
|
106
|
+
left: pos.x + 24,
|
|
107
|
+
top: pos.y + 36,
|
|
108
|
+
transform: "translate(-50%, -50%)",
|
|
109
|
+
pointerEvents: "none",
|
|
110
|
+
color: badgeTextColor,
|
|
111
|
+
}}
|
|
112
|
+
>
|
|
113
|
+
<div className="px-4 py-3 rounded-full text-[11px] overflow-hidden font-semibold shadow-[0_2px_8px_rgba(0,0,0,0.25)] border border-black/10" style={{ backgroundColor: badgeColor }}>
|
|
114
|
+
<span className="text-[12px] font-medium tracking-wide whitespace-nowrap drop-shadow" style={{ color: badgeTextColor }}>
|
|
115
|
+
{person.name} — {person.job}
|
|
116
|
+
</span>
|
|
117
|
+
</div>
|
|
118
|
+
</div>
|
|
119
|
+
</>
|
|
120
|
+
)}
|
|
121
|
+
</div>
|
|
122
|
+
</div>
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export default PointerFollower;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import React, { useEffect, useRef } from 'react';
|
|
2
|
+
import gsap from 'gsap';
|
|
3
|
+
function createRainEffectWithCollision(container, dropCount = 0, dropColor, collisionColor, dropGradient, collisionGradient) {
|
|
4
|
+
if (!container) return;
|
|
5
|
+
for (let i = 0; i < dropCount; i++) {
|
|
6
|
+
const drop = document.createElement('div');
|
|
7
|
+
drop.className = 'rain-drop pointer-events-none';
|
|
8
|
+
drop.style.position = 'absolute';
|
|
9
|
+
drop.style.left = Math.random() * 98 + '%';
|
|
10
|
+
drop.style.top = Math.random() * -100 - 20 + 'px';
|
|
11
|
+
drop.style.width = '2px';
|
|
12
|
+
drop.style.height = Math.random() * 32 + 16 + 'px';
|
|
13
|
+
drop.style.background = dropGradient ? dropGradient : `linear-gradient(to bottom, ${dropColor} 60%, ${collisionColor} 100%)`;
|
|
14
|
+
drop.style.opacity = Math.random() * 0.3 + 0.3;
|
|
15
|
+
drop.style.borderRadius = '1px';
|
|
16
|
+
drop.style.zIndex = 1;
|
|
17
|
+
container.appendChild(drop);
|
|
18
|
+
animateRainDropWithCollision(drop, container, collisionColor, dropGradient, collisionGradient, dropColor);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function animateRainDropWithCollision(drop, container, collisionColor, dropGradient, collisionGradient, dropColor) {
|
|
23
|
+
const duration = Math.random() * 1.2 + 0.8;
|
|
24
|
+
gsap.fromTo(drop, {
|
|
25
|
+
y: 0,
|
|
26
|
+
}, {
|
|
27
|
+
y: container.offsetHeight + 40,
|
|
28
|
+
|
|
29
|
+
duration,
|
|
30
|
+
ease: 'power1.in',
|
|
31
|
+
onUpdate: function () {
|
|
32
|
+
const rect = drop.getBoundingClientRect();
|
|
33
|
+
const parentRect = container.getBoundingClientRect();
|
|
34
|
+
if (!drop._collided && rect.bottom >= parentRect.bottom - 8) {
|
|
35
|
+
drop._collided = true;
|
|
36
|
+
drop.style.background = collisionGradient ? collisionGradient : collisionColor;
|
|
37
|
+
const relX = rect.left - parentRect.left + rect.width / 2;
|
|
38
|
+
createRainCollisionExplosion(relX, container.offsetHeight - 8, container, collisionColor, collisionGradient);
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
onStart: function () {
|
|
42
|
+
drop._collided = false;
|
|
43
|
+
drop.style.background = dropGradient ? dropGradient : `linear-gradient(to bottom, ${dropColor} 60%, ${collisionColor} 100%)`;
|
|
44
|
+
},
|
|
45
|
+
onComplete: () => {
|
|
46
|
+
drop.style.left = Math.random() * 98 + '%';
|
|
47
|
+
drop.style.height = Math.random() * 32 + 16 + 'px';
|
|
48
|
+
drop.style.opacity = Math.random() * 0.3 + 0.3;
|
|
49
|
+
drop.style.background = dropGradient ? dropGradient : `linear-gradient(to bottom, ${dropColor} 60%, ${collisionColor} 100%)`;
|
|
50
|
+
gsap.set(drop, { y: 0, opacity: drop.style.opacity });
|
|
51
|
+
animateRainDropWithCollision(drop, container, collisionColor, dropGradient, collisionGradient, dropColor);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function createRainCollisionExplosion(x, y, container, collisionColor = '#e0e7ff', collisionGradient) {
|
|
57
|
+
const explosion = document.createElement("div");
|
|
58
|
+
explosion.className = "absolute z-50 h-2 w-2 pointer-events-none";
|
|
59
|
+
explosion.style.left = `${x}px`;
|
|
60
|
+
explosion.style.top = `${y}px`;
|
|
61
|
+
explosion.style.transform = "translate(-50%, 0)";
|
|
62
|
+
const blurBar = document.createElement("div");
|
|
63
|
+
blurBar.style.position = "absolute";
|
|
64
|
+
blurBar.style.left = "-0.7rem";
|
|
65
|
+
blurBar.style.top = "0";
|
|
66
|
+
blurBar.style.width = "1.4rem";
|
|
67
|
+
blurBar.style.height = "0.18rem";
|
|
68
|
+
blurBar.style.borderRadius = "1rem";
|
|
69
|
+
blurBar.style.background = collisionGradient ? collisionGradient : `${collisionColor}`;
|
|
70
|
+
blurBar.style.filter = "blur(2px)";
|
|
71
|
+
explosion.appendChild(blurBar);
|
|
72
|
+
for (let i = 0; i < 6; i++) {
|
|
73
|
+
const span = document.createElement("span");
|
|
74
|
+
span.style.position = "absolute";
|
|
75
|
+
span.style.height = "0.18rem";
|
|
76
|
+
span.style.width = "0.18rem";
|
|
77
|
+
span.style.borderRadius = "50%";
|
|
78
|
+
span.style.background = collisionGradient ? collisionGradient : `${collisionColor}`;
|
|
79
|
+
explosion.appendChild(span);
|
|
80
|
+
const dx = Math.floor(Math.random() * 16 - 8);
|
|
81
|
+
const dy = Math.floor(Math.random() * -10 - 2);
|
|
82
|
+
gsap.fromTo(span, { x: 0, y: 0 }, {
|
|
83
|
+
x: dx,
|
|
84
|
+
y: dy,
|
|
85
|
+
opacity: 0,
|
|
86
|
+
duration: Math.random() * 0.5 + 0.2,
|
|
87
|
+
ease: "power2.out",
|
|
88
|
+
onComplete: () => { span.remove(); }
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
gsap.fromTo(blurBar, { opacity: 0 }, { opacity: 1, duration: 0.12, yoyo: true, repeat: 1, onComplete: () => blurBar.remove() });
|
|
92
|
+
setTimeout(() => explosion.remove(), 500);
|
|
93
|
+
container.appendChild(explosion);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const RainBackground = ({ dropCount = 32, dropColor = '#fff', collisionColor = '#e0e7ff', dropGradient, collisionGradient }) => {
|
|
97
|
+
const rainRef = useRef(null);
|
|
98
|
+
useEffect(() => {
|
|
99
|
+
const container = rainRef.current;
|
|
100
|
+
if (!container) return;
|
|
101
|
+
createRainEffectWithCollision(container, dropCount, dropColor, collisionColor, dropGradient, collisionGradient);
|
|
102
|
+
return () => { container.innerHTML = ''; };
|
|
103
|
+
}, [dropCount, dropColor, collisionColor, dropGradient, collisionGradient]);
|
|
104
|
+
return (
|
|
105
|
+
<div ref={rainRef} id="beams-collision-container" className="absolute inset-0 w-full h-full pointer-events-none z-0" style={{ overflow: 'hidden' }} />
|
|
106
|
+
);
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
export default RainBackground;
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import React, { useLayoutEffect, useRef, useState, useEffect } from 'react';
|
|
2
|
+
import { gsap } from 'gsap';
|
|
3
|
+
|
|
4
|
+
// --- RippleBackground Component (Optimized for Mobile) ---
|
|
5
|
+
const RippleBackground = ({
|
|
6
|
+
children,
|
|
7
|
+
className,
|
|
8
|
+
containerClassName,
|
|
9
|
+
colors,
|
|
10
|
+
desktopGridSize = 20, // Grid size for desktop
|
|
11
|
+
mobileGridSize = 10, // Grid size for mobile
|
|
12
|
+
}) => {
|
|
13
|
+
const containerRef = useRef(null);
|
|
14
|
+
const shapesRef = useRef([]);
|
|
15
|
+
const [gridSize, setGridSize] = useState(desktopGridSize);
|
|
16
|
+
const [isMobile, setIsMobile] = useState(false);
|
|
17
|
+
|
|
18
|
+
// Check screen size to determine if it's mobile
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
const checkScreenSize = () => {
|
|
21
|
+
const isMobileDevice = window.innerWidth < 768;
|
|
22
|
+
setIsMobile(isMobileDevice);
|
|
23
|
+
setGridSize(isMobileDevice ? mobileGridSize : desktopGridSize);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
checkScreenSize();
|
|
27
|
+
window.addEventListener('resize', checkScreenSize);
|
|
28
|
+
return () => window.removeEventListener('resize', checkScreenSize);
|
|
29
|
+
}, [mobileGridSize, desktopGridSize]);
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
const shapeColors = colors ?? [
|
|
33
|
+
"#10b981", // emerald-500
|
|
34
|
+
"#06b6d4", // cyan-500
|
|
35
|
+
"#6366f1", // indigo-500
|
|
36
|
+
"#a855f7", // purple-500
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
// Handles the ripple effect on click
|
|
40
|
+
const handleClick = (e) => {
|
|
41
|
+
const { clientX, clientY } = e;
|
|
42
|
+
const shapes = shapesRef.current;
|
|
43
|
+
|
|
44
|
+
shapes.forEach(shape => {
|
|
45
|
+
if (!shape) return;
|
|
46
|
+
const rect = shape.getBoundingClientRect();
|
|
47
|
+
const dx = rect.left + rect.width / 2 - clientX;
|
|
48
|
+
const dy = rect.top + rect.height / 2 - clientY;
|
|
49
|
+
const distance = Math.sqrt(dx * dx + dy * dy);
|
|
50
|
+
const delay = distance * 0.005; // Ripple delay based on distance
|
|
51
|
+
|
|
52
|
+
gsap.timeline({ defaults: { overwrite: 'auto' } })
|
|
53
|
+
.to(shape, {
|
|
54
|
+
scale: 2.5,
|
|
55
|
+
opacity: 1,
|
|
56
|
+
duration: 0.3,
|
|
57
|
+
ease: "power2.out",
|
|
58
|
+
delay: delay
|
|
59
|
+
})
|
|
60
|
+
.to(shape, {
|
|
61
|
+
scale: 1,
|
|
62
|
+
opacity: 0.5,
|
|
63
|
+
duration: 0.5,
|
|
64
|
+
ease: "power2.in"
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// Handles micro-interaction on hover
|
|
70
|
+
const handleMouseEnter = (shape) => {
|
|
71
|
+
gsap.to(shape, {
|
|
72
|
+
scale: 2,
|
|
73
|
+
opacity: 1,
|
|
74
|
+
duration: 0.3,
|
|
75
|
+
ease: 'power2.out',
|
|
76
|
+
overwrite: 'auto'
|
|
77
|
+
});
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const handleMouseLeave = (shape) => {
|
|
81
|
+
// Returns the shape to its normal blinking state
|
|
82
|
+
gsap.to(shape, {
|
|
83
|
+
scale: 1,
|
|
84
|
+
opacity: 0.3,
|
|
85
|
+
duration: 2,
|
|
86
|
+
ease: 'power2.out',
|
|
87
|
+
overwrite: 'auto'
|
|
88
|
+
});
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
// Continuous blinking animation
|
|
92
|
+
useLayoutEffect(() => {
|
|
93
|
+
const shapes = shapesRef.current.filter(s => s);
|
|
94
|
+
if (shapes.length === 0) return;
|
|
95
|
+
|
|
96
|
+
// On mobile, we disable the heavy continuous animation for better performance
|
|
97
|
+
if (isMobile) {
|
|
98
|
+
// Set a default static state for mobile
|
|
99
|
+
gsap.set(shapes, { scale: 1, opacity: 0.3 });
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const masterTl = gsap.timeline({ repeat: -1, yoyo: true });
|
|
104
|
+
|
|
105
|
+
shapes.forEach(shape => {
|
|
106
|
+
masterTl.to(shape, {
|
|
107
|
+
scale: () => Math.random() * 1.5 + 0.5,
|
|
108
|
+
opacity: () => Math.random() * 0.7 + 0.3,
|
|
109
|
+
duration: 2,
|
|
110
|
+
ease: "sine.inOut",
|
|
111
|
+
}, Math.random() * 2); // Random start time for each shape
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
return () => {
|
|
115
|
+
masterTl.kill(); // Cleanup the timeline
|
|
116
|
+
};
|
|
117
|
+
}, [gridSize, isMobile]); // Rerun effect if grid size or mobile status changes
|
|
118
|
+
|
|
119
|
+
return (
|
|
120
|
+
<div
|
|
121
|
+
className={`relative h-[60vh] lg:h-[80vh] w-screen flex flex-col items-center justify-center overflow-hidden ${containerClassName}`}
|
|
122
|
+
onClick={handleClick} // Click event listener on the main container
|
|
123
|
+
>
|
|
124
|
+
{/* Shapes Grid Container */}
|
|
125
|
+
<div
|
|
126
|
+
ref={containerRef}
|
|
127
|
+
className="absolute inset-0 z-0 w-full h-full grid"
|
|
128
|
+
style={{
|
|
129
|
+
gridTemplateColumns: `repeat(${gridSize}, 1fr)`,
|
|
130
|
+
gridTemplateRows: `repeat(${gridSize}, 1fr)`,
|
|
131
|
+
}}
|
|
132
|
+
>
|
|
133
|
+
{Array.from({ length: gridSize * gridSize }).map((_, index) => (
|
|
134
|
+
<div
|
|
135
|
+
key={index}
|
|
136
|
+
className="w-full h-full flex items-center justify-center"
|
|
137
|
+
onMouseEnter={() => handleMouseEnter(shapesRef.current[index])}
|
|
138
|
+
onMouseLeave={() => handleMouseLeave(shapesRef.current[index])}
|
|
139
|
+
>
|
|
140
|
+
<div
|
|
141
|
+
ref={el => shapesRef.current[index] = el}
|
|
142
|
+
className="w-2 h-2 rounded-full pointer-events-none"
|
|
143
|
+
style={{
|
|
144
|
+
backgroundColor: shapeColors[index % shapeColors.length],
|
|
145
|
+
// Performance optimization for animations
|
|
146
|
+
willChange: 'transform, opacity',
|
|
147
|
+
}}
|
|
148
|
+
/>
|
|
149
|
+
</div>
|
|
150
|
+
))}
|
|
151
|
+
</div>
|
|
152
|
+
|
|
153
|
+
<div className={`relative z-10 ${className}`}>
|
|
154
|
+
{children}
|
|
155
|
+
</div>
|
|
156
|
+
</div>
|
|
157
|
+
);
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
export default RippleBackground;
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/* eslint-disable no-unused-vars */
|
|
2
|
+
import { useRef, useLayoutEffect, useState } from "react";
|
|
3
|
+
import { motion, useTransform, useMotionValue, useAnimationFrame, useScroll, useVelocity, useSpring } from "framer-motion";
|
|
4
|
+
|
|
5
|
+
function useElementWidth(ref) {
|
|
6
|
+
const [width, setWidth] = useState(0);
|
|
7
|
+
|
|
8
|
+
useLayoutEffect(() => {
|
|
9
|
+
function updateWidth() {
|
|
10
|
+
if (ref.current) {
|
|
11
|
+
setWidth(ref.current.offsetWidth);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
updateWidth();
|
|
15
|
+
window.addEventListener("resize", updateWidth);
|
|
16
|
+
return () => window.removeEventListener("resize", updateWidth);
|
|
17
|
+
}, [ref]);
|
|
18
|
+
|
|
19
|
+
return width;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Utility for seamless wrap
|
|
23
|
+
function wrapValue(min, max, v) {
|
|
24
|
+
const range = max - min;
|
|
25
|
+
const mod = (((v - min) % range) + range) % range;
|
|
26
|
+
return mod + min;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Main Marquee Component
|
|
30
|
+
export function ScrollMarquee({
|
|
31
|
+
items = ['ApexUI', 'apexui'],
|
|
32
|
+
speed = 50,
|
|
33
|
+
direction,
|
|
34
|
+
className = "apexui-marquee-text",
|
|
35
|
+
repeat = 10,
|
|
36
|
+
containerClass = "apexui-marquee-container",
|
|
37
|
+
scrollerClass = "apexui-marquee-track",
|
|
38
|
+
containerStyle,
|
|
39
|
+
scrollerStyle,
|
|
40
|
+
textStroke = false,
|
|
41
|
+
textStrokeColor = '#C27AFF',
|
|
42
|
+
textFillColor = '#1E2637',
|
|
43
|
+
textColor = '#fff',
|
|
44
|
+
scrollRef,
|
|
45
|
+
}) {
|
|
46
|
+
// Single scrolling row
|
|
47
|
+
function MarqueeRow({ children, baseSpeed = speed, direction = "left" }) {
|
|
48
|
+
const baseX = useMotionValue(0);
|
|
49
|
+
const copyRef = useRef(null);
|
|
50
|
+
const copyWidth = useElementWidth(copyRef);
|
|
51
|
+
|
|
52
|
+
// Scroll tracking (window or custom container)
|
|
53
|
+
const scrollOptions = scrollRef ? { container: scrollRef } : {};
|
|
54
|
+
const { scrollY } = useScroll(scrollOptions);
|
|
55
|
+
const scrollVelocity = useVelocity(scrollY);
|
|
56
|
+
const smoothVelocity = useSpring(scrollVelocity, { damping: 50, stiffness: 400 });
|
|
57
|
+
const velocityFactor = useTransform(smoothVelocity, [0, 1000], [0, 2], { clamp: false });
|
|
58
|
+
|
|
59
|
+
const x = useTransform(baseX, (v) => {
|
|
60
|
+
if (copyWidth === 0) return "0px";
|
|
61
|
+
return `${wrapValue(-copyWidth, 0, v)}px`;
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// Unique scroll-based animation: gradient appears on scroll, white by default
|
|
65
|
+
// Animate gradient opacity and hue-rotate based on velocity
|
|
66
|
+
const hue = useTransform(velocityFactor, v => Math.max(-40, Math.min(40, v * 30)));
|
|
67
|
+
const gradientOpacity = useTransform(velocityFactor, v => Math.min(1, Math.abs(v) * 0.7));
|
|
68
|
+
const filter = useTransform(hue, h => `hue-rotate(${h}deg)`);
|
|
69
|
+
|
|
70
|
+
// Alternate direction, flip on scroll
|
|
71
|
+
const directionFactor = useRef(direction === "left" ? -1 : 1);
|
|
72
|
+
const lastVelocity = useRef(0);
|
|
73
|
+
useAnimationFrame((t, delta) => {
|
|
74
|
+
const v = velocityFactor.get();
|
|
75
|
+
if (v !== 0 && Math.sign(v) !== Math.sign(lastVelocity.current)) {
|
|
76
|
+
directionFactor.current *= -1;
|
|
77
|
+
}
|
|
78
|
+
lastVelocity.current = v;
|
|
79
|
+
let moveBy = directionFactor.current * (baseSpeed + Math.abs(v * 100)) * (delta / 1000);
|
|
80
|
+
baseX.set(baseX.get() + moveBy);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// Modern font and gradient color, with color/stroke props
|
|
84
|
+
// Use Google font 'Barlow Condensed', fallback to 'Montserrat', then sans-serif
|
|
85
|
+
const spans = [];
|
|
86
|
+
for (let i = 0; i < repeat; i++) {
|
|
87
|
+
spans.push(
|
|
88
|
+
<span
|
|
89
|
+
className={`flex-shrink-0 ${className} tracking-tighter font-semibold custom-marquee-span`}
|
|
90
|
+
key={i}
|
|
91
|
+
ref={i === 0 ? copyRef : null}
|
|
92
|
+
style={{
|
|
93
|
+
fontFamily: "helvetica, Arial, sans-serif",
|
|
94
|
+
cursor: "default",
|
|
95
|
+
fontOpticalSizing: 'auto',
|
|
96
|
+
color: !textStroke ? textColor : '#fff',
|
|
97
|
+
WebkitTextStroke: undefined,
|
|
98
|
+
WebkitTextFillColor: undefined,
|
|
99
|
+
fontSize: '6.8rem',
|
|
100
|
+
transition: 'all 0.5s cubic-bezier(.4,2,.6,1)',
|
|
101
|
+
position: 'relative',
|
|
102
|
+
display: 'inline-block',
|
|
103
|
+
}}
|
|
104
|
+
>
|
|
105
|
+
<span style={{ position: 'relative', zIndex: 1 }}>{children}</span>
|
|
106
|
+
<motion.span
|
|
107
|
+
className={"marquee-gradient-overlay absolute left-0 top-0 w-full h-full bg-gradient-to-r from-fuchsia-400 via-cyan-400 to-lime-400 bg-clip-text text-transparent drop-shadow-lg"}
|
|
108
|
+
style={{
|
|
109
|
+
filter,
|
|
110
|
+
opacity: gradientOpacity,
|
|
111
|
+
WebkitTextFillColor: 'transparent',
|
|
112
|
+
mixBlendMode: 'normal',
|
|
113
|
+
zIndex: 2,
|
|
114
|
+
}}
|
|
115
|
+
aria-hidden="true"
|
|
116
|
+
>
|
|
117
|
+
{children}
|
|
118
|
+
</motion.span>
|
|
119
|
+
</span>
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Add hover effect via inline style and a custom class
|
|
124
|
+
// The hover will show a stroke and fill color
|
|
125
|
+
// Add a <style> tag for the custom class
|
|
126
|
+
// This is safe in component scope in React
|
|
127
|
+
return (
|
|
128
|
+
<div className={`${containerClass} relative overflow-hidden`} style={containerStyle}>
|
|
129
|
+
<style>{`
|
|
130
|
+
.custom-marquee-span {
|
|
131
|
+
transition: all 0.5s cubic-bezier(.4,2,.6,1);
|
|
132
|
+
}
|
|
133
|
+
.custom-marquee-span:hover {
|
|
134
|
+
-webkit-text-stroke: 2px ${textStrokeColor};
|
|
135
|
+
-webkit-text-fill-color: ${textFillColor};
|
|
136
|
+
color: ${textFillColor};
|
|
137
|
+
filter: drop-shadow(0 2px 12px ${textStrokeColor}55);
|
|
138
|
+
transition: all 0.5s cubic-bezier(.4,2,.6,1);
|
|
139
|
+
}
|
|
140
|
+
.custom-marquee-span:hover .marquee-gradient-overlay {
|
|
141
|
+
opacity: 0 !important;
|
|
142
|
+
}
|
|
143
|
+
`}</style>
|
|
144
|
+
<motion.div
|
|
145
|
+
className={`${scrollerClass} flex h-28 whitespace-nowrap text-center font-barlow text-5xl md:text-[5.5rem] tracking-[0.08em] md:leading-[6rem]`}
|
|
146
|
+
style={{ x, ...scrollerStyle }}
|
|
147
|
+
>
|
|
148
|
+
{spans}
|
|
149
|
+
</motion.div>
|
|
150
|
+
</div>
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Render all marquee rows
|
|
155
|
+
return (
|
|
156
|
+
<section>
|
|
157
|
+
{items.map((item, idx) => {
|
|
158
|
+
// Alternate direction: even left, odd right
|
|
159
|
+
const rowDirection = direction ? direction : (idx % 2 === 0 ? "left" : "right");
|
|
160
|
+
return (
|
|
161
|
+
<MarqueeRow
|
|
162
|
+
key={idx}
|
|
163
|
+
baseSpeed={speed}
|
|
164
|
+
direction={rowDirection}
|
|
165
|
+
>
|
|
166
|
+
{item}
|
|
167
|
+
</MarqueeRow>
|
|
168
|
+
);
|
|
169
|
+
})}
|
|
170
|
+
</section>
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export default ScrollMarquee;
|