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,161 @@
|
|
|
1
|
+
/* eslint-disable no-unused-vars */
|
|
2
|
+
import React, { useState, useEffect, useRef } from 'react';
|
|
3
|
+
import { motion, useMotionValue, useTransform } from 'framer-motion';
|
|
4
|
+
import { ArrowLeft, ArrowRight } from 'lucide-react';
|
|
5
|
+
|
|
6
|
+
// --- Card Component with Hover Parallax Effect ---
|
|
7
|
+
const CarouselCard = ({ slide, isCenter }) => {
|
|
8
|
+
const cardRef = useRef(null);
|
|
9
|
+
const x = useMotionValue(0);
|
|
10
|
+
const y = useMotionValue(0);
|
|
11
|
+
|
|
12
|
+
// Mouse move ke hisab se rotation calculate karta hai (effect ko strong banaya gaya hai)
|
|
13
|
+
const rotateX = useTransform(y, [-200, 200], [15, -15]);
|
|
14
|
+
const rotateY = useTransform(x, [-200, 200], [-15, 15]);
|
|
15
|
+
const handleMouseMove = (event) => {
|
|
16
|
+
if (!cardRef.current || !isCenter) return;
|
|
17
|
+
const rect = cardRef.current.getBoundingClientRect();
|
|
18
|
+
x.set(event.clientX - rect.left - rect.width / 2);
|
|
19
|
+
y.set(event.clientY - rect.top - rect.height / 2);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const handleMouseLeave = () => {
|
|
23
|
+
x.set(0);
|
|
24
|
+
y.set(0);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<motion.div
|
|
29
|
+
ref={cardRef}
|
|
30
|
+
className="w-full h-full bg-slate-900 rounded-2xl group"
|
|
31
|
+
style={{
|
|
32
|
+
transformStyle: 'preserve-3d',
|
|
33
|
+
perspective: '1500px',
|
|
34
|
+
rotateX: isCenter ? rotateX : 0,
|
|
35
|
+
rotateY: isCenter ? rotateY : 0,
|
|
36
|
+
}}
|
|
37
|
+
onMouseMove={handleMouseMove}
|
|
38
|
+
onMouseLeave={handleMouseLeave}
|
|
39
|
+
|
|
40
|
+
>
|
|
41
|
+
{/* Slide Content */}
|
|
42
|
+
<div className="absolute inset-0 w-full h-full"
|
|
43
|
+
style={{
|
|
44
|
+
transformStyle: 'preserve-3d',
|
|
45
|
+
perspective: '1500px',
|
|
46
|
+
}}
|
|
47
|
+
>
|
|
48
|
+
<img
|
|
49
|
+
src={slide.imageUrl}
|
|
50
|
+
alt={slide.title}
|
|
51
|
+
className="w-full h-full object-cover group-hover:opacity-30 transition-all duration-300 ease-in-out rounded-2xl"
|
|
52
|
+
/>
|
|
53
|
+
<img
|
|
54
|
+
src={slide.pngUrl}
|
|
55
|
+
alt={slide.title}
|
|
56
|
+
className="absolute inset-0 w-full h-full object-cover group-hover:translate-z-15 transition-all duration-300 ease-in-out rounded-2xl"
|
|
57
|
+
/>
|
|
58
|
+
</div>
|
|
59
|
+
<div
|
|
60
|
+
className="relative flex flex-col justify-end h-full text-white p-8"
|
|
61
|
+
style={{
|
|
62
|
+
transformStyle: 'preserve-3d',
|
|
63
|
+
perspective: '1500px',
|
|
64
|
+
}}
|
|
65
|
+
>
|
|
66
|
+
<h2 className="text-3xl font-bold group-hover:translate-z-18 transition-all duration-300 ease-in-out">{slide.title}</h2>
|
|
67
|
+
<p className="text-lime-400 mt-1 group-hover:translate-z-18 transition-all duration-300 ease-in-out">{slide.subtitle}</p>
|
|
68
|
+
<p className="text-slate-300 mt-4 text-sm leading-relaxed group-hover:translate-z-18 transition-all duration-300 ease-in-out">{slide.text}</p>
|
|
69
|
+
</div>
|
|
70
|
+
</motion.div>
|
|
71
|
+
);
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
// --- Custom Parallax Carousel Component ---
|
|
76
|
+
const ParallaxCarousel = ({ slides }) => {
|
|
77
|
+
const [activeIndex, setActiveIndex] = useState(0);
|
|
78
|
+
const [isHovered, setIsHovered] = useState(false);
|
|
79
|
+
|
|
80
|
+
const handleNext = () => {
|
|
81
|
+
setActiveIndex((prevIndex) => (prevIndex + 1) % slides.length);
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const handlePrev = () => {
|
|
85
|
+
setActiveIndex((prevIndex) => (prevIndex - 1 + slides.length) % slides.length);
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
// Automatic sliding ke liye
|
|
89
|
+
useEffect(() => {
|
|
90
|
+
// Agar hovered hai to interval start hi mat karo
|
|
91
|
+
if (isHovered) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const interval = setInterval(() => {
|
|
96
|
+
handleNext();
|
|
97
|
+
}, 4000); // Har 4 second mein slide change hoga
|
|
98
|
+
|
|
99
|
+
return () => clearInterval(interval); // Cleanup
|
|
100
|
+
}, [activeIndex, isHovered]); // isHovered state change hone par effect ko firse chalayega
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<div
|
|
104
|
+
className="relative w-full flex flex-col items-center px-2"
|
|
105
|
+
onMouseEnter={() => setIsHovered(true)}
|
|
106
|
+
onMouseLeave={() => setIsHovered(false)}
|
|
107
|
+
>
|
|
108
|
+
{/* 3D Carousel Container */}
|
|
109
|
+
<div
|
|
110
|
+
className="relative w-full h-[480px] portrait:h-[480px] portrait:px-0"
|
|
111
|
+
style={{ perspective: '1200px', transformStyle: 'preserve-3d' }}
|
|
112
|
+
>
|
|
113
|
+
{slides.map((slide, index) => {
|
|
114
|
+
let positionOffset = index - activeIndex;
|
|
115
|
+
if (positionOffset > slides.length / 2) {
|
|
116
|
+
positionOffset -= slides.length;
|
|
117
|
+
} else if (positionOffset < -slides.length / 2) {
|
|
118
|
+
positionOffset += slides.length;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const isVisible = Math.abs(positionOffset) <= 2;
|
|
122
|
+
|
|
123
|
+
return (
|
|
124
|
+
<motion.div
|
|
125
|
+
key={index}
|
|
126
|
+
className={`absolute w-full h-[340px] portrait:w-full portrait:h-[480px] top-0 left-0 portrait:left-0 portrait:top-0 ${positionOffset === 0 ? '' : ' pointer-events-none'} md:w-[50%] md:h-[400px] md:top-[50px] md:left-1/2 lg:-translate-x-1/2`}
|
|
127
|
+
initial={false}
|
|
128
|
+
style={{ transformStyle: 'preserve-3d' }}
|
|
129
|
+
animate={{
|
|
130
|
+
transform: `translateX(${positionOffset * 50}%) translateZ(${-Math.abs(positionOffset) * 250}px) scale(${1 - Math.abs(positionOffset) * 0.15})`,
|
|
131
|
+
opacity: isVisible ? (1 - Math.abs(positionOffset) * 0.45) : 0,
|
|
132
|
+
zIndex: slides.length - Math.abs(positionOffset),
|
|
133
|
+
}}
|
|
134
|
+
transition={{ type: 'spring', stiffness: 290, damping: 30 }}
|
|
135
|
+
>
|
|
136
|
+
<CarouselCard slide={slide} isCenter={positionOffset === 0} />
|
|
137
|
+
</motion.div>
|
|
138
|
+
);
|
|
139
|
+
})}
|
|
140
|
+
</div>
|
|
141
|
+
|
|
142
|
+
{/* Side Navigation Buttons */}
|
|
143
|
+
<motion.button
|
|
144
|
+
onClick={handlePrev}
|
|
145
|
+
className="absolute -left-10 lg:left-0 top-1/2 -translate-y-1/2 z-30 p-3 rounded-full text-slate-400 hover:text-lime-400 transition-colors"
|
|
146
|
+
whileTap={{ scale: 0.9 }}
|
|
147
|
+
>
|
|
148
|
+
<ArrowLeft size={32} />
|
|
149
|
+
</motion.button>
|
|
150
|
+
<motion.button
|
|
151
|
+
onClick={handleNext}
|
|
152
|
+
className="absolute -right-10 lg:right-0 top-1/2 -translate-y-1/2 z-30 p-3 rounded-full text-slate-400 hover:text-lime-400 transition-colors"
|
|
153
|
+
whileTap={{ scale: 0.9 }}
|
|
154
|
+
>
|
|
155
|
+
<ArrowRight size={32} />
|
|
156
|
+
</motion.button>
|
|
157
|
+
</div>
|
|
158
|
+
);
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
export default ParallaxCarousel;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import React, { useState, useEffect, useRef } from 'react';
|
|
2
|
+
|
|
3
|
+
// To use GSAP in this environment, we'll import it from a CDN.
|
|
4
|
+
// In a local project: `npm install gsap`
|
|
5
|
+
import { gsap } from 'gsap';
|
|
6
|
+
import { Draggable } from 'gsap/Draggable';
|
|
7
|
+
|
|
8
|
+
// GSAP Draggable plugin ko register karna zaroori hai
|
|
9
|
+
gsap.registerPlugin(Draggable);
|
|
10
|
+
|
|
11
|
+
// --- GSAP Drag Carousel Component ---
|
|
12
|
+
const DragCarousel = ({ images }) => {
|
|
13
|
+
const [activeIndex, setActiveIndex] = useState(0);
|
|
14
|
+
const carouselRef = useRef(null);
|
|
15
|
+
const cardsRef = useRef([]);
|
|
16
|
+
const dragProxyRef = useRef(null);
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
const cards = cardsRef.current;
|
|
19
|
+
gsap.to(cards, {
|
|
20
|
+
duration: 0.4,
|
|
21
|
+
ease: 'power4.Out',
|
|
22
|
+
stagger: 0.05,
|
|
23
|
+
// Har card ke liye naya transform calculate karta hai
|
|
24
|
+
transform: (i) => {
|
|
25
|
+
let positionOffset = i - activeIndex;
|
|
26
|
+
if (positionOffset > images.length / 2) {
|
|
27
|
+
positionOffset -= images.length;
|
|
28
|
+
} else if (positionOffset < -images.length / 2) {
|
|
29
|
+
positionOffset += images.length;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const translateX = positionOffset * 50;
|
|
33
|
+
const translateZ = -Math.abs(positionOffset) * 250;
|
|
34
|
+
const scale = 1 - Math.abs(positionOffset) * 0.15;
|
|
35
|
+
|
|
36
|
+
return `translateX(${translateX}%) translateZ(${translateZ}px) scale(${scale})`;
|
|
37
|
+
},
|
|
38
|
+
zIndex: (i) => {
|
|
39
|
+
let positionOffset = i - activeIndex;
|
|
40
|
+
if (positionOffset > images.length / 2) positionOffset -= images.length;
|
|
41
|
+
if (positionOffset < -images.length / 2) positionOffset += images.length;
|
|
42
|
+
return images.length - Math.abs(positionOffset);
|
|
43
|
+
},
|
|
44
|
+
opacity: (i) => {
|
|
45
|
+
let positionOffset = i - activeIndex;
|
|
46
|
+
if (positionOffset > images.length / 2) positionOffset -= images.length;
|
|
47
|
+
if (positionOffset < -images.length / 2) positionOffset += images.length;
|
|
48
|
+
return Math.abs(positionOffset) > 2 ? 0 : 1;
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
}, [activeIndex, images.length]);
|
|
52
|
+
|
|
53
|
+
// GSAP Draggable ko setup karta hai
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
// BUG FIX: Draggable ab proxy element par lagaya gaya hai
|
|
56
|
+
const draggableInstance = Draggable.create(dragProxyRef.current, {
|
|
57
|
+
type: "x",
|
|
58
|
+
trigger: carouselRef.current,
|
|
59
|
+
inertia: true,
|
|
60
|
+
cursor: 'grab',
|
|
61
|
+
activeCursor: 'grabbing',
|
|
62
|
+
onDragEnd: function () {
|
|
63
|
+
gsap.to(this.target, { x: 0, duration: 0.3 });
|
|
64
|
+
if (this.getDirection("start") === "left") {
|
|
65
|
+
setActiveIndex(prev => (prev + 1) % images.length);
|
|
66
|
+
} else if (this.getDirection("start") === "right") {
|
|
67
|
+
setActiveIndex(prev => (prev - 1 + images.length) % images.length);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Cleanup function
|
|
73
|
+
return () => {
|
|
74
|
+
if (draggableInstance && draggableInstance[0]) {
|
|
75
|
+
draggableInstance[0].kill();
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
}, [images.length]);
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<div
|
|
82
|
+
ref={carouselRef}
|
|
83
|
+
className="relative w-full h-[490px] lg:h-[590px] cursor-grab"
|
|
84
|
+
style={{ perspective: '1200px', transformStyle: 'preserve-3d' }}
|
|
85
|
+
>
|
|
86
|
+
<div ref={dragProxyRef} className="absolute inset-0 z-20"></div>
|
|
87
|
+
|
|
88
|
+
{images.map((src, index) => (
|
|
89
|
+
<div
|
|
90
|
+
key={index}
|
|
91
|
+
ref={el => cardsRef.current[index] = el}
|
|
92
|
+
className="card-item absolute h-[480px] w-[300px] lg:h-[550px] lg:w-[350px] rounded-xl overflow-hidden shadow-2xl top-[25px] -left-4 md:left-36 lg:left-70"
|
|
93
|
+
>
|
|
94
|
+
<img
|
|
95
|
+
src={src}
|
|
96
|
+
alt={`carousel-item-${index}`}
|
|
97
|
+
className="w-full h-full object-cover pointer-events-none"
|
|
98
|
+
/>
|
|
99
|
+
</div>
|
|
100
|
+
))}
|
|
101
|
+
</div>
|
|
102
|
+
);
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
export default DragCarousel;
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import React, { useRef, useState, useEffect } from 'react';
|
|
2
|
+
import gsap from 'gsap';
|
|
3
|
+
|
|
4
|
+
const fields = [
|
|
5
|
+
{ name: 'firstName', label: 'First Name', type: 'text' },
|
|
6
|
+
{ name: 'lastName', label: 'Last Name', type: 'text' },
|
|
7
|
+
{ name: 'email', label: 'Email Address', type: 'email' },
|
|
8
|
+
{ name: 'mobile', label: 'Mobile Number', type: 'tel' },
|
|
9
|
+
];
|
|
10
|
+
|
|
11
|
+
const EdgeGlowForm = ({
|
|
12
|
+
GlowColor = 'violet',
|
|
13
|
+
borderGlowColor = '#a78bfa88',
|
|
14
|
+
borderGlowShadow = '#a78bfa33',
|
|
15
|
+
}) => {
|
|
16
|
+
const [values, setValues] = useState({ firstName: '', lastName: '', email: '', mobile: '' });
|
|
17
|
+
const [focused, setFocused] = useState('');
|
|
18
|
+
const [checked, setChecked] = useState(false);
|
|
19
|
+
const [btnState, setBtnState] = useState('default');
|
|
20
|
+
const [errors, setErrors] = useState({});
|
|
21
|
+
const [checkboxError, setCheckboxError] = useState(false);
|
|
22
|
+
const labelRefs = useRef({});
|
|
23
|
+
const borderRefs = useRef({});
|
|
24
|
+
const ballsRef = [useRef(), useRef(), useRef()];
|
|
25
|
+
const checkTick = useRef();
|
|
26
|
+
|
|
27
|
+
// Mouse movement for glassy background
|
|
28
|
+
const [radialPos, setRadialPos] = useState({ x: 50, y: 50 });
|
|
29
|
+
const bgRef = useRef();
|
|
30
|
+
|
|
31
|
+
const handleBgMouseMove = e => {
|
|
32
|
+
if (!bgRef.current) return;
|
|
33
|
+
const rect = bgRef.current.getBoundingClientRect();
|
|
34
|
+
const x = ((e.clientX - rect.left) / rect.width) * 100;
|
|
35
|
+
const y = ((e.clientY - rect.top) / rect.height) * 100;
|
|
36
|
+
setRadialPos({ x, y });
|
|
37
|
+
};
|
|
38
|
+
const handleBgMouseLeave = () => {
|
|
39
|
+
setRadialPos({ x: 50, y: 50 });
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const floatLabel = (name, up, color = null) => {
|
|
43
|
+
const label = labelRefs.current[name];
|
|
44
|
+
if (label) {
|
|
45
|
+
gsap.to(label, {
|
|
46
|
+
top: up ? '-2px' : '23%',
|
|
47
|
+
left: '5px',
|
|
48
|
+
scale: up ? 0.85 : 1,
|
|
49
|
+
color: color ?? (up ? '#a78bfa' : '#bdbdbd'),
|
|
50
|
+
transform: 'translateY(-4%)',
|
|
51
|
+
fontWeight: up ? 500 : 400,
|
|
52
|
+
duration: 0.2,
|
|
53
|
+
ease: 'easeInOut',
|
|
54
|
+
zIndex: 10,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const showBorder = (name, show, color = '#a78bfa88') => {
|
|
60
|
+
const [top, bottom] = borderRefs.current[name] || [];
|
|
61
|
+
[top, bottom].forEach(border => {
|
|
62
|
+
if (border) {
|
|
63
|
+
gsap.to(border, {
|
|
64
|
+
opacity: show ? 1 : 0,
|
|
65
|
+
scaleX: show ? 1 : 0.7,
|
|
66
|
+
background: `linear-gradient(90deg, transparent 0%, ${color} 50%, transparent 100%)`,
|
|
67
|
+
boxShadow: `0 0 8px 0 ${color.replace('88', '33')}`,
|
|
68
|
+
duration: 0.2,
|
|
69
|
+
ease: show ? 'ease.out' : 'ease.in',
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const handleFocus = name => {
|
|
76
|
+
setFocused(name);
|
|
77
|
+
floatLabel(name, true);
|
|
78
|
+
showBorder(name, true);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const handleBlur = name => {
|
|
82
|
+
setFocused('');
|
|
83
|
+
if (!values[name]) floatLabel(name, false);
|
|
84
|
+
showBorder(name, false);
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const handleChange = (e, name) => {
|
|
88
|
+
const v = e.target.value;
|
|
89
|
+
setValues(vals => ({ ...vals, [name]: v }));
|
|
90
|
+
if (v) floatLabel(name, true);
|
|
91
|
+
else if (focused !== name) floatLabel(name, false);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
useEffect(() => {
|
|
95
|
+
fields.forEach(f => {
|
|
96
|
+
const [top, bottom] = borderRefs.current[f.name] || [];
|
|
97
|
+
if (top) gsap.set(top, { opacity: 0, scaleX: 0.7 });
|
|
98
|
+
if (bottom) gsap.set(bottom, { opacity: 0, scaleX: 0.7 });
|
|
99
|
+
if (labelRefs.current[f.name]) gsap.set(labelRefs.current[f.name], {
|
|
100
|
+
top: '23%',
|
|
101
|
+
left: '5px',
|
|
102
|
+
scale: 1,
|
|
103
|
+
color: '#bdbdbd',
|
|
104
|
+
transform: 'translateY(-4%)',
|
|
105
|
+
fontWeight: 400,
|
|
106
|
+
position: 'absolute',
|
|
107
|
+
pointerEvents: 'none',
|
|
108
|
+
backgroundColor: '',
|
|
109
|
+
padding: '0 4px',
|
|
110
|
+
zIndex: 10,
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
}, []);
|
|
114
|
+
|
|
115
|
+
const handleCheckbox = () => {
|
|
116
|
+
setChecked(v => {
|
|
117
|
+
const next = !v;
|
|
118
|
+
if (checkTick.current) {
|
|
119
|
+
gsap.to(checkTick.current, {
|
|
120
|
+
scale: next ? 1 : 0,
|
|
121
|
+
opacity: next ? 1 : 0,
|
|
122
|
+
duration: 0.3,
|
|
123
|
+
ease: 'expo.out',
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
return next;
|
|
127
|
+
});
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
useEffect(() => {
|
|
131
|
+
if (checkTick.current) gsap.set(checkTick.current, { scale: checked ? 1 : 0, opacity: checked ? 1 : 0 });
|
|
132
|
+
}, [checked]);
|
|
133
|
+
|
|
134
|
+
const handleBtnClick = e => {
|
|
135
|
+
e.preventDefault();
|
|
136
|
+
const newErrors = {};
|
|
137
|
+
fields.forEach(f => {
|
|
138
|
+
if (!values[f.name]) newErrors[f.name] = true;
|
|
139
|
+
});
|
|
140
|
+
setErrors(newErrors);
|
|
141
|
+
Object.keys(newErrors).forEach(name => {
|
|
142
|
+
floatLabel(name, true, '#f87171');
|
|
143
|
+
showBorder(name, true, '#f87171cc');
|
|
144
|
+
});
|
|
145
|
+
if (!checked) {
|
|
146
|
+
setCheckboxError(true);
|
|
147
|
+
setTimeout(() => setCheckboxError(false), 2000);
|
|
148
|
+
}
|
|
149
|
+
if (Object.keys(newErrors).length > 0 || !checked) return;
|
|
150
|
+
if (btnState !== 'default') return;
|
|
151
|
+
|
|
152
|
+
setBtnState('loading');
|
|
153
|
+
ballsRef.forEach((ref, i) => {
|
|
154
|
+
gsap.fromTo(ref.current, { y: 0 }, {
|
|
155
|
+
y: -12,
|
|
156
|
+
repeat: -1,
|
|
157
|
+
yoyo: true,
|
|
158
|
+
delay: i * 0.12,
|
|
159
|
+
duration: 0.38,
|
|
160
|
+
ease: 'power1.inOut',
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
setTimeout(() => {
|
|
164
|
+
setBtnState('success');
|
|
165
|
+
ballsRef.forEach(ref => gsap.set(ref.current, { y: 0 }));
|
|
166
|
+
setTimeout(() => {
|
|
167
|
+
setBtnState('default');
|
|
168
|
+
}, 1200);
|
|
169
|
+
}, 1600);
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
return (
|
|
173
|
+
|
|
174
|
+
<div className=" rounded-xl w-full">
|
|
175
|
+
<div
|
|
176
|
+
className="w-full max-w-sm mx-auto rounded-xl shadow-2xl p-1 flex flex-col justify-center text-white relative overflow-hidden"
|
|
177
|
+
ref={bgRef}
|
|
178
|
+
onMouseMove={handleBgMouseMove}
|
|
179
|
+
onMouseLeave={handleBgMouseLeave}
|
|
180
|
+
>
|
|
181
|
+
{/* 🎯 Glassy Background */}
|
|
182
|
+
<div className="absolute inset-0 z-0 hidden md:block pointer-events-none rounded-xl">
|
|
183
|
+
<div
|
|
184
|
+
className="absolute inset-0 opacity-95"
|
|
185
|
+
style={{
|
|
186
|
+
background: `radial-gradient(circle at ${radialPos.x}% ${radialPos.y}%, ${GlowColor} 10%, transparent 60%)`
|
|
187
|
+
}}
|
|
188
|
+
></div>
|
|
189
|
+
</div>
|
|
190
|
+
<div className={`relative z-10 bg-black h-full w-full p-6 border border-purple-600 rounded-xl`}>
|
|
191
|
+
<h2 className="text-2xl font-bold mb-6 flex items-center gap-2">
|
|
192
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth="1.5" stroke="currentColor" className="w-6 h-6 text-purple-400">
|
|
193
|
+
<path strokeLinecap="round" strokeLinejoin="round" d="M21.75 6.75v10.5a2.25 2.25 0 01-2.25 2.25H4.5a2.25 2.25 0 01-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0019.5 4.5H4.5a2.25 2.25 0 00-2.25 2.25m19.5 0v.243a2.25 2.25 0 01-.659 1.591l-7.5 7.5a2.25 2.25 0 01-3.182 0l-7.5-7.5A2.25 2.25 0 012.25 6.993V6.75" />
|
|
194
|
+
</svg>
|
|
195
|
+
Get In Touch
|
|
196
|
+
</h2>
|
|
197
|
+
<form className="space-y-6 w-full" onSubmit={handleBtnClick}>
|
|
198
|
+
{fields.map(f => (
|
|
199
|
+
<div key={f.name} className="relative flex flex-col justify-center">
|
|
200
|
+
<label
|
|
201
|
+
ref={el => (labelRefs.current[f.name] = el)}
|
|
202
|
+
className="absolute text-white/60 text-sm transition-all pointer-events-none"
|
|
203
|
+
>{f.label}</label>
|
|
204
|
+
<span
|
|
205
|
+
ref={el => {
|
|
206
|
+
if (!borderRefs.current[f.name]) borderRefs.current[f.name] = [];
|
|
207
|
+
borderRefs.current[f.name][0] = el;
|
|
208
|
+
}}
|
|
209
|
+
className="pointer-events-none absolute left-1/2 -translate-x-1/2 top-0 w-[80%] h-[2px] rounded-full opacity-0 scale-x-70 origin-center z-20"
|
|
210
|
+
style={{
|
|
211
|
+
background: `linear-gradient(90deg, transparent 0%, yellow 20%, transparent 100%)`,
|
|
212
|
+
boxShadow: `0 0 8px 0 ${borderGlowShadow}`,
|
|
213
|
+
}}
|
|
214
|
+
/>
|
|
215
|
+
<input
|
|
216
|
+
type={f.type}
|
|
217
|
+
name={f.name}
|
|
218
|
+
value={values[f.name]}
|
|
219
|
+
onChange={e => handleChange(e, f.name)}
|
|
220
|
+
onFocus={() => handleFocus(f.name)}
|
|
221
|
+
onBlur={() => handleBlur(f.name)}
|
|
222
|
+
autoComplete="off"
|
|
223
|
+
className={`w-full px-4 pt-5 pb-2 rounded-lg bg-white/10 border ${errors[f.name] ? 'border-red-400' : 'border-white/20'} text-sm text-white focus:outline-none backdrop-blur-md transition font-light`}
|
|
224
|
+
/>
|
|
225
|
+
<span
|
|
226
|
+
ref={el => {
|
|
227
|
+
if (!borderRefs.current[f.name]) borderRefs.current[f.name] = [];
|
|
228
|
+
borderRefs.current[f.name][1] = el;
|
|
229
|
+
}}
|
|
230
|
+
className="pointer-events-none absolute left-1/2 -translate-x-1/2 bottom-0 w-[80%] h-[2px] rounded-full opacity-0 scale-x-70 origin-center"
|
|
231
|
+
style={{
|
|
232
|
+
background: `linear-gradient(90deg, transparent 0%, ${borderGlowColor} 20%, transparent 100%)`,
|
|
233
|
+
boxShadow: `0 0 8px 0 ${borderGlowShadow}`,
|
|
234
|
+
}}
|
|
235
|
+
/>
|
|
236
|
+
</div>
|
|
237
|
+
))}
|
|
238
|
+
|
|
239
|
+
<div className="flex items-center gap-3 mt-2 select-none relative">
|
|
240
|
+
<button
|
|
241
|
+
type="button"
|
|
242
|
+
aria-checked={checked}
|
|
243
|
+
tabIndex={0}
|
|
244
|
+
onClick={handleCheckbox}
|
|
245
|
+
className={`relative w-6 h-6 rounded-md border-2 border-white/30 bg-white/10 flex items-center justify-center focus:outline-none focus:ring-2 focus:ring-purple-400 transition`}
|
|
246
|
+
style={{ minWidth: 24 }}
|
|
247
|
+
>
|
|
248
|
+
<svg
|
|
249
|
+
ref={checkTick}
|
|
250
|
+
width="18" height="18" viewBox="0 0 18 18"
|
|
251
|
+
className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 text-purple-400"
|
|
252
|
+
style={{ scale: checked ? 1 : 0, opacity: checked ? 1 : 0, transition: 'all 0.2s' }}
|
|
253
|
+
>
|
|
254
|
+
<polyline points="3,10 7,14 15,6" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" />
|
|
255
|
+
</svg>
|
|
256
|
+
</button>
|
|
257
|
+
<span className="text-white/80 text-sm">I agree to the terms</span>
|
|
258
|
+
{checkboxError && (
|
|
259
|
+
<div className="absolute -left-0 -top-10 flex flex-col items-center " style={{ zIndex: 999 }}>
|
|
260
|
+
<div className="bg-red-500 text-white text-xs rounded px-3 py-1 shadow-lg font-medium">check this box</div>
|
|
261
|
+
<div className="w-3 h-3 bg-red-500 rotate-135 -mt-1" style={{ clipPath: 'polygon(0 0, 100% 0, 100% 100%)' }}></div>
|
|
262
|
+
</div>
|
|
263
|
+
)}
|
|
264
|
+
</div>
|
|
265
|
+
{/* Animated Submit Button */}
|
|
266
|
+
<button
|
|
267
|
+
type="submit"
|
|
268
|
+
className="mt-6 py-3 px-6 bg-gradient-to-r from-purple-700 via-purple-500 to-indigo-800 rounded-md hover:brightness-110 transition shadow-lg w-full font-semibold text-base flex items-center justify-center gap-2 relative overflow-hidden"
|
|
269
|
+
style={{ minHeight: 48 }}
|
|
270
|
+
onClick={handleBtnClick}
|
|
271
|
+
disabled={btnState !== 'default'}
|
|
272
|
+
>
|
|
273
|
+
{/* Bouncing Balls */}
|
|
274
|
+
<span className={`absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 flex gap-1 ${btnState === 'loading' ? 'opacity-100' : 'opacity-0'}`} style={{ transition: 'opacity 0.2s' }}>
|
|
275
|
+
<span ref={ballsRef[0]} className="w-2.5 h-2.5 rounded-full bg-white/80 inline-block" />
|
|
276
|
+
<span ref={ballsRef[1]} className="w-2.5 h-2.5 rounded-full bg-white/80 inline-block" />
|
|
277
|
+
<span ref={ballsRef[2]} className="w-2.5 h-2.5 rounded-full bg-white/80 inline-block" />
|
|
278
|
+
</span>
|
|
279
|
+
{/* Checkmark */}
|
|
280
|
+
<span
|
|
281
|
+
className={`absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 text-2xl text-green-400 ${btnState === 'success' ? 'opacity-100' : 'opacity-0'}`}
|
|
282
|
+
style={{ transition: 'opacity 0.2s' }}
|
|
283
|
+
>
|
|
284
|
+
<svg width="24" height="24" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round"><path d="M5 13l4 4L19 7" /></svg>
|
|
285
|
+
</span>
|
|
286
|
+
{/* Default text */}
|
|
287
|
+
<span className={`transition-opacity duration-200 ${btnState === 'default' ? 'opacity-100' : 'opacity-0'}`}>Send Message</span>
|
|
288
|
+
</button>
|
|
289
|
+
</form>
|
|
290
|
+
</div>
|
|
291
|
+
</div>
|
|
292
|
+
</div>
|
|
293
|
+
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
export default EdgeGlowForm;
|