apex-ui-kit 2.0.7 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ApexUI-Kit/templates/AccordionMarquee/AccordionMarquee.jsx +74 -53
- package/dist/ApexUI-Kit/templates/BeamBar/BeamBar.jsx +5 -3
- package/dist/ApexUI-Kit/templates/BluePrintBackground/BluePrintBackground.jsx +14 -2
- package/dist/ApexUI-Kit/templates/CardStack/CardStack.jsx +56 -28
- package/dist/ApexUI-Kit/templates/Cards/BasicCard.jsx +3 -3
- package/dist/ApexUI-Kit/templates/DragCarousel/DragCarousel.jsx +1 -1
- package/dist/ApexUI-Kit/templates/EdgeGlowForm/EdgeGlowFrom.jsx +116 -98
- package/dist/ApexUI-Kit/templates/FlipProfile/FlipProfile.jsx +70 -37
- package/dist/ApexUI-Kit/templates/GlareCard/GlareCard.jsx +2 -2
- package/dist/ApexUI-Kit/templates/HoverText/HoverText.jsx +14 -13
- package/dist/ApexUI-Kit/templates/HyperCard/HyperCard.jsx +59 -42
- package/dist/ApexUI-Kit/templates/KineticThreadsBackground/KineticThreadBackground.jsx +17 -2
- package/dist/ApexUI-Kit/templates/LensFlareBackground/LensFlareBackground.jsx +18 -3
- package/dist/ApexUI-Kit/templates/LuminousParticleOcean/LuminousParticleOcean.jsx +15 -1
- package/dist/ApexUI-Kit/templates/MaskView/MaskView.jsx +345 -0
- package/dist/ApexUI-Kit/templates/MeteorCard/MeteorCard.jsx +34 -45
- package/dist/ApexUI-Kit/templates/NavDropDown/NavDropDown.jsx +272 -101
- package/dist/ApexUI-Kit/templates/NavMenu/NavMenu.jsx +8 -2
- package/dist/ApexUI-Kit/templates/OrbsBackground/FloatingOrbs.jsx +59 -59
- package/dist/ApexUI-Kit/templates/ParallaxCard/ParallaxCard.jsx +13 -11
- package/dist/ApexUI-Kit/templates/ParallaxCarousel/ParallaxCarousel.jsx +9 -9
- package/dist/ApexUI-Kit/templates/ParallaxProfile/ParallaxProfile.jsx +41 -38
- package/dist/ApexUI-Kit/templates/PointerFollower/PointerFollower.jsx +7 -7
- package/dist/ApexUI-Kit/templates/SmartGridCard/SmartGridCard.jsx +13 -11
- package/dist/ApexUI-Kit/templates/SmokeBackground/SmokeBackground.jsx +13 -2
- package/dist/ApexUI-Kit/templates/Testimonials/Testimonials.jsx +11 -8
- package/dist/ApexUI-Kit/templates/ThemeToggle/ThemeAnimation.js +40 -69
- package/dist/ApexUI-Kit/templates/ThemeToggle/ThemeToggle.css +1 -1
- package/dist/ApexUI-Kit/templates/ThemeToggle/ThemeToggle.jsx +25 -7
- package/dist/ApexUI-Kit/templates/ToolTip/ToolTip.jsx +71 -52
- package/dist/cli/index.js +24 -13
- package/package.json +2 -2
- package/dist/ApexUI-Kit/templates/WaterDropReveal/WaterDropReveal.jsx +0 -151
|
@@ -5,81 +5,100 @@ const ToolTip = ({ items }) => {
|
|
|
5
5
|
const [hoveredIndex, setHoveredIndex] = useState(null);
|
|
6
6
|
const tooltipRefs = useRef([]);
|
|
7
7
|
const avatarRefs = useRef([]);
|
|
8
|
+
const last = items.length - 1;
|
|
8
9
|
|
|
9
10
|
useEffect(() => {
|
|
10
|
-
//
|
|
11
|
+
// Lift the active avatar, dim the others
|
|
11
12
|
items.forEach((_, idx) => {
|
|
12
13
|
const isHovered = hoveredIndex === idx;
|
|
13
|
-
|
|
14
|
-
// Animate the avatar image
|
|
15
14
|
gsap.to(avatarRefs.current[idx], {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
y: isHovered ? -6 : 0,
|
|
16
|
+
scale: isHovered ? 1.12 : 1,
|
|
17
|
+
opacity: hoveredIndex === null || isHovered ? 1 : 0.55,
|
|
18
|
+
duration: 0.3,
|
|
19
|
+
ease: "power3.out",
|
|
20
20
|
});
|
|
21
21
|
});
|
|
22
22
|
|
|
23
|
-
//
|
|
23
|
+
// Pop the tooltip in
|
|
24
24
|
if (hoveredIndex !== null && tooltipRefs.current[hoveredIndex]) {
|
|
25
25
|
gsap.fromTo(
|
|
26
26
|
tooltipRefs.current[hoveredIndex],
|
|
27
|
-
{ opacity: 0, y:
|
|
28
|
-
{
|
|
29
|
-
opacity: 1,
|
|
30
|
-
y: 0,
|
|
31
|
-
scale: 1,
|
|
32
|
-
duration: 0.4,
|
|
33
|
-
ease: "power3.out",
|
|
34
|
-
}
|
|
27
|
+
{ opacity: 0, y: 12, scale: 0.85, rotate: 0 },
|
|
28
|
+
{ opacity: 1, y: 0, scale: 1, duration: 0.45, ease: "back.out(1.8)" }
|
|
35
29
|
);
|
|
36
30
|
}
|
|
37
31
|
}, [hoveredIndex, items]);
|
|
38
32
|
|
|
33
|
+
// The tooltip swings toward the pointer while it moves across the avatar.
|
|
34
|
+
const handlePointerMove = (e, idx) => {
|
|
35
|
+
const tip = tooltipRefs.current[idx];
|
|
36
|
+
if (!tip || e.pointerType !== "mouse") return;
|
|
37
|
+
const rect = e.currentTarget.getBoundingClientRect();
|
|
38
|
+
const offset = ((e.clientX - rect.left) / rect.width - 0.5) * 2; // -1 … 1
|
|
39
|
+
gsap.to(tip, { rotate: offset * 10, x: offset * 12, duration: 0.5, ease: "power3.out", overwrite: "auto" });
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// Keep the end tooltips inside small screens: align them to their outer edge.
|
|
43
|
+
const placement = (idx) =>
|
|
44
|
+
idx === 0 && last > 0 ? { box: "left-0", arrow: "left-6" }
|
|
45
|
+
: idx === last && last > 0 ? { box: "right-0", arrow: "right-6" }
|
|
46
|
+
: { box: "left-1/2 -translate-x-1/2", arrow: "left-1/2 -translate-x-1/2" };
|
|
47
|
+
|
|
39
48
|
return (
|
|
40
|
-
<div className="flex items-end -space-x-4">
|
|
41
|
-
{items.map((item, idx) =>
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
<
|
|
56
|
-
{
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
49
|
+
<div className="flex items-end -space-x-3 sm:-space-x-4">
|
|
50
|
+
{items.map((item, idx) => {
|
|
51
|
+
const { box, arrow } = placement(idx);
|
|
52
|
+
return (
|
|
53
|
+
<div
|
|
54
|
+
key={item.id ?? item.name}
|
|
55
|
+
onPointerEnter={(e) => e.pointerType === "mouse" && setHoveredIndex(idx)}
|
|
56
|
+
onPointerLeave={(e) => e.pointerType === "mouse" && setHoveredIndex(null)}
|
|
57
|
+
onPointerMove={(e) => handlePointerMove(e, idx)}
|
|
58
|
+
// Tap to toggle on touch screens
|
|
59
|
+
onClick={() => setHoveredIndex((cur) => (cur === idx ? null : idx))}
|
|
60
|
+
className="relative cursor-pointer"
|
|
61
|
+
style={{ zIndex: hoveredIndex === idx ? 50 : 10 + idx }}
|
|
62
|
+
>
|
|
63
|
+
{hoveredIndex === idx && (
|
|
64
|
+
<div className={`pointer-events-none absolute bottom-full mb-4 z-50 ${box}`}>
|
|
65
|
+
{/* The card and its arrow animate together, so the arrow follows every swing. */}
|
|
66
|
+
<div ref={(el) => (tooltipRefs.current[idx] = el)} className="relative origin-bottom">
|
|
67
|
+
<div className="relative overflow-hidden rounded-xl border border-white/10 bg-zinc-950 px-3.5 py-2.5 shadow-[0_16px_40px_-12px_rgba(0,0,0,0.7)]">
|
|
68
|
+
<span className="absolute inset-x-3 top-0 h-px bg-gradient-to-r from-transparent via-lime-400 to-cyan-400" />
|
|
69
|
+
<span className="flex items-center gap-1.5 whitespace-nowrap text-sm font-semibold text-white">
|
|
70
|
+
<span className="h-1.5 w-1.5 rounded-full bg-lime-400 shadow-[0_0_8px_#a3e635]" />
|
|
71
|
+
{item.name}
|
|
72
|
+
</span>
|
|
73
|
+
<span className="mt-0.5 block whitespace-nowrap text-xs text-zinc-400">{item.designation}</span>
|
|
74
|
+
</div>
|
|
75
|
+
{/* Downward arrow: filled triangle that covers the card's bottom border, with the border on its two sides only */}
|
|
76
|
+
<svg width="16" height="8" viewBox="0 0 16 8" className={`absolute top-full -mt-px ${arrow}`} aria-hidden="true">
|
|
77
|
+
<path d="M0 0 L8 8 L16 0 Z" fill="#09090b" />
|
|
78
|
+
<path d="M0.5 0 L8 7.5 L15.5 0" fill="none" stroke="rgba(255,255,255,0.1)" />
|
|
79
|
+
</svg>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
)}
|
|
83
|
+
<div ref={(el) => (avatarRefs.current[idx] = el)} className="rounded-full bg-gradient-to-br from-lime-400 via-emerald-400 to-cyan-400 p-[2px] shadow-lg shadow-black/30">
|
|
84
|
+
<img
|
|
85
|
+
src={item.image}
|
|
86
|
+
alt={item.name}
|
|
87
|
+
draggable="false"
|
|
88
|
+
className="h-12 w-12 sm:h-14 sm:w-14 rounded-full border-2 border-zinc-950 object-cover object-top"
|
|
89
|
+
/>
|
|
61
90
|
</div>
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
src={item.image}
|
|
66
|
-
alt={item.name}
|
|
67
|
-
className="h-14 w-14 object-cover object-center rounded-full border-2 border-white shadow-lg transition-transform"
|
|
68
|
-
style={{
|
|
69
|
-
zIndex: hoveredIndex === idx ? 50 : 10,
|
|
70
|
-
transition: "all 0.25s ease-in-out",
|
|
71
|
-
}}
|
|
72
|
-
/>
|
|
73
|
-
</div>
|
|
74
|
-
))}
|
|
91
|
+
</div>
|
|
92
|
+
);
|
|
93
|
+
})}
|
|
75
94
|
</div>
|
|
76
95
|
);
|
|
77
96
|
};
|
|
78
97
|
|
|
79
98
|
export default function ToolTipInner({ items }) {
|
|
80
99
|
return (
|
|
81
|
-
<div className="flex flex-row items-center justify-center">
|
|
100
|
+
<div className="flex flex-row items-center justify-center pt-24">
|
|
82
101
|
<ToolTip items={items} />
|
|
83
102
|
</div>
|
|
84
103
|
);
|
|
85
|
-
}
|
|
104
|
+
}
|
package/dist/cli/index.js
CHANGED
|
@@ -9,7 +9,7 @@ import { execSync } from "child_process";
|
|
|
9
9
|
const __filename = fileURLToPath(import.meta.url);
|
|
10
10
|
const __dirname = path.dirname(__filename);
|
|
11
11
|
const componentRoot = path.join(__dirname, "..", "ApexUI-Kit", "templates");
|
|
12
|
-
// Component name map
|
|
12
|
+
// Component name map (CLI name -> folder in templates). Older names stay as aliases.
|
|
13
13
|
const componentMap = {
|
|
14
14
|
"avatar": "Avatar",
|
|
15
15
|
"accordion-marquee": "AccordionMarquee",
|
|
@@ -19,10 +19,10 @@ const componentMap = {
|
|
|
19
19
|
"parallax-carousel": "ParallaxCarousel",
|
|
20
20
|
"drag-carousel": "DragCarousel",
|
|
21
21
|
"basic-card": "Cards",
|
|
22
|
-
"edge-glow-
|
|
22
|
+
"edge-glow-form": "EdgeGlowForm",
|
|
23
|
+
"edge-glow-from": "EdgeGlowForm",
|
|
23
24
|
"glare-card": "GlareCard",
|
|
24
25
|
"card-stack": "CardStack",
|
|
25
|
-
"glass-profile": "GlassProfile",
|
|
26
26
|
"hover-text": "HoverText",
|
|
27
27
|
"hyper-card": "HyperCard",
|
|
28
28
|
"kinetic-threads-background": "KineticThreadsBackground",
|
|
@@ -33,6 +33,7 @@ const componentMap = {
|
|
|
33
33
|
"nav-dropdown": "NavDropDown",
|
|
34
34
|
"orbs-background": "OrbsBackground",
|
|
35
35
|
"pointer-follower": "PointerFollower",
|
|
36
|
+
"flip-profile": "FlipProfile",
|
|
36
37
|
"flip-profiler": "FlipProfile",
|
|
37
38
|
"parallax-profile": "ParallaxProfile",
|
|
38
39
|
"parallax-card": "ParallaxCard",
|
|
@@ -41,10 +42,18 @@ const componentMap = {
|
|
|
41
42
|
"scroll-marquee": "ScrollMarquee",
|
|
42
43
|
"smart-grid-card": "SmartGridCard",
|
|
43
44
|
"smoke-background": "SmokeBackground",
|
|
45
|
+
"tooltip": "ToolTip",
|
|
44
46
|
"tool-tip": "ToolTip",
|
|
45
47
|
"theme-toggle": "ThemeToggle",
|
|
46
48
|
"testimonials": "Testimonials",
|
|
47
|
-
"
|
|
49
|
+
"mask-view": "MaskView",
|
|
50
|
+
"water-drop-reveal": "MaskView",
|
|
51
|
+
};
|
|
52
|
+
// Packages only some components need, installed on top of the shared ones.
|
|
53
|
+
const extraDeps = {
|
|
54
|
+
Cards: ["react-router-dom"],
|
|
55
|
+
LuminousParticleOcean: ["@react-three/fiber"],
|
|
56
|
+
SmokeBackground: ["ogl"],
|
|
48
57
|
};
|
|
49
58
|
async function main() {
|
|
50
59
|
const args = process.argv.slice(2);
|
|
@@ -52,7 +61,7 @@ async function main() {
|
|
|
52
61
|
const rawInput = args[1];
|
|
53
62
|
if (command !== "add" || !rawInput) {
|
|
54
63
|
console.log(chalk.yellow("Usage:"));
|
|
55
|
-
console.log(" npx apex-ui-
|
|
64
|
+
console.log(" npx apex-ui-kit add <component-name>");
|
|
56
65
|
return;
|
|
57
66
|
}
|
|
58
67
|
const input = rawInput.toLowerCase();
|
|
@@ -60,7 +69,7 @@ async function main() {
|
|
|
60
69
|
if (!matchedName) {
|
|
61
70
|
console.log(chalk.red(`❌ Component '${rawInput}' not found in ApexUI-Kit.`));
|
|
62
71
|
console.log(chalk.gray("👉 Available components:"));
|
|
63
|
-
Object.
|
|
72
|
+
Object.keys(componentMap).forEach((c) => console.log(` • ${c}`));
|
|
64
73
|
return;
|
|
65
74
|
}
|
|
66
75
|
// Validate package.json
|
|
@@ -74,7 +83,7 @@ async function main() {
|
|
|
74
83
|
type: "text",
|
|
75
84
|
name: "dirInput",
|
|
76
85
|
message: "📁 Install in which folder? (use . for current)",
|
|
77
|
-
initial: "."
|
|
86
|
+
initial: ".",
|
|
78
87
|
});
|
|
79
88
|
let basePath = "";
|
|
80
89
|
if (dirInput.trim() === ".") {
|
|
@@ -84,8 +93,8 @@ async function main() {
|
|
|
84
93
|
message: "📍 Are you currently inside the folder where ApexUI-Kit should be?",
|
|
85
94
|
choices: [
|
|
86
95
|
{ title: "Yes (install here)", value: "yes" },
|
|
87
|
-
{ title: "No (I'll choose a path)", value: "no" }
|
|
88
|
-
]
|
|
96
|
+
{ title: "No (I'll choose a path)", value: "no" },
|
|
97
|
+
],
|
|
89
98
|
});
|
|
90
99
|
if (confirmInside === "yes") {
|
|
91
100
|
basePath = process.cwd();
|
|
@@ -95,7 +104,7 @@ async function main() {
|
|
|
95
104
|
type: "text",
|
|
96
105
|
name: "targetPath",
|
|
97
106
|
message: "📂 Enter target folder path (or use . for current)",
|
|
98
|
-
initial: "."
|
|
107
|
+
initial: ".",
|
|
99
108
|
});
|
|
100
109
|
basePath = path.resolve(targetPath.trim());
|
|
101
110
|
}
|
|
@@ -110,8 +119,8 @@ async function main() {
|
|
|
110
119
|
message: "🧠 Choose language",
|
|
111
120
|
choices: [
|
|
112
121
|
{ title: "JavaScript (JSX)", value: "jsx" },
|
|
113
|
-
{ title: "TypeScript (Coming Soon)", value: "ts", disabled: true }
|
|
114
|
-
]
|
|
122
|
+
{ title: "TypeScript (Coming Soon)", value: "ts", disabled: true },
|
|
123
|
+
],
|
|
115
124
|
});
|
|
116
125
|
// Install dependencies
|
|
117
126
|
console.log(chalk.cyan("📦 Installing dependencies..."));
|
|
@@ -122,7 +131,9 @@ async function main() {
|
|
|
122
131
|
"@fortawesome/fontawesome-svg-core",
|
|
123
132
|
"@fortawesome/free-solid-svg-icons",
|
|
124
133
|
"@fortawesome/react-fontawesome",
|
|
125
|
-
"react-icons"
|
|
134
|
+
"react-icons",
|
|
135
|
+
"lucide-react",
|
|
136
|
+
...(extraDeps[matchedName] ?? []),
|
|
126
137
|
];
|
|
127
138
|
try {
|
|
128
139
|
execSync(`npm install ${deps.join(" ")}`, { stdio: "inherit" });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "apex-ui-kit",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Apex UI Kit - A React component library for building modern user interfaces",
|
|
6
6
|
"keywords": [
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"type": "module",
|
|
24
24
|
"main": "dist/cli/index.js",
|
|
25
25
|
"bin": {
|
|
26
|
-
"apex-ui-kit": "
|
|
26
|
+
"apex-ui-kit": "dist/cli/index.js"
|
|
27
27
|
},
|
|
28
28
|
"files": [
|
|
29
29
|
"dist"
|
|
@@ -1,151 +0,0 @@
|
|
|
1
|
-
import React, { useState, useRef, useEffect } from "react";
|
|
2
|
-
import gsap from "gsap";
|
|
3
|
-
|
|
4
|
-
const WaterDropReveal = ({
|
|
5
|
-
text = "Discover the Water Drop Reveal Effect!\nMake your UI interactive, modern, and magical.\nHover to experience the reveal.",
|
|
6
|
-
dropCount = 3,
|
|
7
|
-
dropColor = "#a855f7",
|
|
8
|
-
animationSpeed = 0.4,
|
|
9
|
-
}) => {
|
|
10
|
-
// Limit dropCount to max 10
|
|
11
|
-
const safeDropCount = Math.min(Math.max(1, dropCount), 10);
|
|
12
|
-
const [hovered, setHovered] = useState(false);
|
|
13
|
-
const [mouse, setMouse] = useState({ x: 0.5, y: 0.5 });
|
|
14
|
-
const dropRef = useRef(null);
|
|
15
|
-
const textRef = useRef(null);
|
|
16
|
-
const mouseAnim = useRef({ x: 0.5, y: 0.5 });
|
|
17
|
-
|
|
18
|
-
useEffect(() => {
|
|
19
|
-
if (!hovered) return;
|
|
20
|
-
gsap.to(mouseAnim.current, {
|
|
21
|
-
x: mouse.x,
|
|
22
|
-
y: mouse.y,
|
|
23
|
-
duration: animationSpeed,
|
|
24
|
-
ease: "power2.out",
|
|
25
|
-
onUpdate: () => {
|
|
26
|
-
if (dropRef.current) {
|
|
27
|
-
dropRef.current.setAttribute("data-x", mouseAnim.current.x);
|
|
28
|
-
dropRef.current.setAttribute("data-y", mouseAnim.current.y);
|
|
29
|
-
}
|
|
30
|
-
if (textRef.current) {
|
|
31
|
-
const xShift = (mouseAnim.current.x - 0.5) * 20;
|
|
32
|
-
const yShift = (mouseAnim.current.y - 0.5) * 10;
|
|
33
|
-
const rotateX = -(mouseAnim.current.y - 0.5) * 10;
|
|
34
|
-
const rotateY = (mouseAnim.current.x - 0.5) * 15;
|
|
35
|
-
gsap.to(textRef.current, {
|
|
36
|
-
x: xShift,
|
|
37
|
-
y: yShift,
|
|
38
|
-
rotateX,
|
|
39
|
-
rotateY,
|
|
40
|
-
scale: 1.08,
|
|
41
|
-
transformPerspective: 600,
|
|
42
|
-
transformOrigin: "center",
|
|
43
|
-
duration: animationSpeed,
|
|
44
|
-
ease: "power3.out",
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
},
|
|
48
|
-
});
|
|
49
|
-
}, [mouse, hovered, animationSpeed]);
|
|
50
|
-
|
|
51
|
-
const handleMouseMove = (e) => {
|
|
52
|
-
const rect = e.currentTarget.getBoundingClientRect();
|
|
53
|
-
const x = (e.clientX - rect.left) / rect.width;
|
|
54
|
-
const y = (e.clientY - rect.top) / rect.height;
|
|
55
|
-
setMouse({ x, y });
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
const animX = hovered ? mouseAnim.current.x : 0.5;
|
|
59
|
-
const animY = hovered ? mouseAnim.current.y : 0.5;
|
|
60
|
-
const cx = 200 + (animX - 0.5) * 40;
|
|
61
|
-
const cy = 80 + (animY - 0.5) * 20;
|
|
62
|
-
|
|
63
|
-
return (
|
|
64
|
-
<div
|
|
65
|
-
className="relative w-full h-full rounded-2xl shadow-2xl flex items-center justify-center cursor-pointer overflow-hidden group border border-gray-800 transition-all duration-300"
|
|
66
|
-
onMouseEnter={() => setHovered(true)}
|
|
67
|
-
onMouseLeave={() => {
|
|
68
|
-
setHovered(false);
|
|
69
|
-
gsap.to(textRef.current, {
|
|
70
|
-
x: 0,
|
|
71
|
-
y: 0,
|
|
72
|
-
rotateX: 0,
|
|
73
|
-
rotateY: 0,
|
|
74
|
-
scale: 1,
|
|
75
|
-
duration: 0.6,
|
|
76
|
-
ease: "power2.out",
|
|
77
|
-
});
|
|
78
|
-
}}
|
|
79
|
-
onMouseMove={handleMouseMove}
|
|
80
|
-
style={{ minHeight: 260 }}
|
|
81
|
-
>
|
|
82
|
-
{/* Water Drop Layer */}
|
|
83
|
-
<div
|
|
84
|
-
ref={dropRef}
|
|
85
|
-
className={`pointer-events-none absolute left-1/2 top-1/2 z-10 -translate-x-1/2 -translate-y-1/2 transition-opacity duration-700 ${hovered ? "opacity-100" : "opacity-0"}`}
|
|
86
|
-
style={{ width: "100%", height: "100%" }}
|
|
87
|
-
>
|
|
88
|
-
<svg width="100%" height="100%" viewBox="0 0 400 160" preserveAspectRatio="xMidYMid slice">
|
|
89
|
-
<defs>
|
|
90
|
-
<filter id="drop-shadow" x="-30%" y="-30%" width="160%" height="160%">
|
|
91
|
-
<feDropShadow dx="0" dy="6" stdDeviation="18" flood-color="black" flood-opacity="60" />
|
|
92
|
-
</filter>
|
|
93
|
-
</defs>
|
|
94
|
-
{(() => {
|
|
95
|
-
// Dynamic radii and opacities for unique drops
|
|
96
|
-
const maxRadius = 160;
|
|
97
|
-
const minRadius = 36;
|
|
98
|
-
const maxOpacity = 0.7;
|
|
99
|
-
const minOpacity = 0.18;
|
|
100
|
-
const step = safeDropCount > 1 ? (maxRadius - minRadius) / (safeDropCount - 1) : 0;
|
|
101
|
-
const opacityStep = safeDropCount > 1 ? (maxOpacity - minOpacity) / (safeDropCount - 1) : 0;
|
|
102
|
-
return [...Array(safeDropCount)].map((_, i) => {
|
|
103
|
-
const r = Math.round(maxRadius - i * step);
|
|
104
|
-
const opacity = +(maxOpacity - i * opacityStep).toFixed(2);
|
|
105
|
-
return (
|
|
106
|
-
<circle
|
|
107
|
-
key={i}
|
|
108
|
-
cx={cx}
|
|
109
|
-
cy={cy}
|
|
110
|
-
r={hovered ? r : 0}
|
|
111
|
-
fill={dropColor}
|
|
112
|
-
fillOpacity={hovered ? opacity : 0}
|
|
113
|
-
style={{ transition: `r 0.7s, fill-opacity 0.7s` }}
|
|
114
|
-
filter="url(#drop-shadow)"
|
|
115
|
-
/>
|
|
116
|
-
);
|
|
117
|
-
});
|
|
118
|
-
})()}
|
|
119
|
-
</svg>
|
|
120
|
-
</div>
|
|
121
|
-
|
|
122
|
-
{/* Text Reveal with 3D */}
|
|
123
|
-
<span
|
|
124
|
-
ref={textRef}
|
|
125
|
-
className={`absolute whitespace-break-spaces z-20 max-sm:text-xl text-4xl font-semibold tracking-wide select-none transition-opacity duration-700 text-center w-full px-4 ${hovered ? "blur-0 opacity-100" : "text-gray-400 blur-sm opacity-60"}`}
|
|
126
|
-
style={{
|
|
127
|
-
left: 0,
|
|
128
|
-
letterSpacing: 0,
|
|
129
|
-
willChange: "transform",
|
|
130
|
-
background: hovered ? "linear-gradient(90deg, #fff, #38bdf8, #fff)" : undefined,
|
|
131
|
-
WebkitBackgroundClip: hovered ? "text" : undefined,
|
|
132
|
-
WebkitTextFillColor: hovered ? "transparent" : undefined,
|
|
133
|
-
backgroundClip: hovered ? "text" : undefined,
|
|
134
|
-
color: hovered ? undefined : undefined,
|
|
135
|
-
fontFamily: "Bitcount Prop Double, system-ui",
|
|
136
|
-
}}
|
|
137
|
-
>
|
|
138
|
-
{text}
|
|
139
|
-
</span>
|
|
140
|
-
|
|
141
|
-
{/* Hover Hint */}
|
|
142
|
-
{!hovered && (
|
|
143
|
-
<span className="absolute bottom-4 left-1/2 -translate-x-1/2 z-30 text-xs sm:text-sm text-white bg-black/40 px-3 py-1 rounded-full shadow-md animate-pulse pointer-events-none select-none">
|
|
144
|
-
Hover to reveal
|
|
145
|
-
</span>
|
|
146
|
-
)}
|
|
147
|
-
</div>
|
|
148
|
-
);
|
|
149
|
-
};
|
|
150
|
-
|
|
151
|
-
export default WaterDropReveal;
|