@launchui/launch-ui 1.0.0 → 1.0.7
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/package.json +1 -1
- package/registry/components/3d-card/3d-card-2.tsx +258 -0
- package/registry/components/3d-card/{index.tsx → 3d-card.tsx} +33 -12
- package/registry/components/3d-pin-card/3d-pin-card.tsx +172 -0
- package/registry/components/Keyboard/Keyboard.tsx +885 -0
- package/registry/components/comet-card/comet-card.tsx +122 -0
- package/registry/components/macbook-scroll/macbook-scroll.tsx +611 -0
- package/registry/components/text-flipping-board/text-flipping-board.tsx +449 -0
package/package.json
CHANGED
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import React, { createContext, useState, useContext, useRef, useEffect } from "react";
|
|
4
|
+
import { motion, useMotionValue, useSpring, useTransform } from "motion/react";
|
|
5
|
+
|
|
6
|
+
const MouseEnterContext = createContext<[boolean, React.Dispatch<React.SetStateAction<boolean>>] | undefined>(undefined);
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The main provider managing the perspective grid and mouse rotation values.
|
|
10
|
+
*/
|
|
11
|
+
export const CardContainer = ({
|
|
12
|
+
children,
|
|
13
|
+
className,
|
|
14
|
+
containerClassName,
|
|
15
|
+
style,
|
|
16
|
+
}: {
|
|
17
|
+
children: React.ReactNode;
|
|
18
|
+
className?: string;
|
|
19
|
+
containerClassName?: string;
|
|
20
|
+
style?: React.CSSProperties;
|
|
21
|
+
}) => {
|
|
22
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
23
|
+
const [isMouseEntered, setIsMouseEntered] = useState(false);
|
|
24
|
+
|
|
25
|
+
const x = useMotionValue(0);
|
|
26
|
+
const y = useMotionValue(0);
|
|
27
|
+
|
|
28
|
+
const springX = useSpring(x, { stiffness: 150, damping: 15 });
|
|
29
|
+
const springY = useSpring(y, { stiffness: 150, damping: 15 });
|
|
30
|
+
|
|
31
|
+
const rotateX = useTransform(springY, [-0.5, 0.5], [15, -15]);
|
|
32
|
+
const rotateY = useTransform(springX, [-0.5, 0.5], [-15, 15]);
|
|
33
|
+
|
|
34
|
+
const handleMouseMove = (e: React.MouseEvent<HTMLDivElement>) => {
|
|
35
|
+
if (!containerRef.current) return;
|
|
36
|
+
const { left, top, width, height } = containerRef.current.getBoundingClientRect();
|
|
37
|
+
|
|
38
|
+
const currentX = (e.clientX - left) / width - 0.5;
|
|
39
|
+
const currentY = (e.clientY - top) / height - 0.5;
|
|
40
|
+
|
|
41
|
+
x.set(currentX);
|
|
42
|
+
y.set(currentY);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const handleMouseEnter = () => {
|
|
46
|
+
setIsMouseEntered(true);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const handleMouseLeave = () => {
|
|
50
|
+
setIsMouseEntered(false);
|
|
51
|
+
x.set(0);
|
|
52
|
+
y.set(0);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<MouseEnterContext.Provider value={[isMouseEntered, setIsMouseEntered]}>
|
|
57
|
+
<div
|
|
58
|
+
className={`flex items-center justify-center ${containerClassName}`}
|
|
59
|
+
style={{
|
|
60
|
+
perspective: "1000px",
|
|
61
|
+
}}
|
|
62
|
+
>
|
|
63
|
+
<motion.div
|
|
64
|
+
ref={containerRef}
|
|
65
|
+
onMouseEnter={handleMouseEnter}
|
|
66
|
+
onMouseMove={handleMouseMove}
|
|
67
|
+
onMouseLeave={handleMouseLeave}
|
|
68
|
+
style={{
|
|
69
|
+
transformStyle: "preserve-3d",
|
|
70
|
+
rotateX,
|
|
71
|
+
rotateY,
|
|
72
|
+
...style
|
|
73
|
+
}}
|
|
74
|
+
className={`flex items-center justify-center relative transition-all duration-200 ease-linear ${className}`}
|
|
75
|
+
>
|
|
76
|
+
{children}
|
|
77
|
+
</motion.div>
|
|
78
|
+
</div>
|
|
79
|
+
</MouseEnterContext.Provider>
|
|
80
|
+
);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* The inner canvas preserving standard layout properties while inheriting perspective physics.
|
|
85
|
+
*/
|
|
86
|
+
export const CardBody = ({
|
|
87
|
+
children,
|
|
88
|
+
className,
|
|
89
|
+
style,
|
|
90
|
+
}: {
|
|
91
|
+
children: React.ReactNode;
|
|
92
|
+
className?: string;
|
|
93
|
+
style?: React.CSSProperties;
|
|
94
|
+
}) => {
|
|
95
|
+
return (
|
|
96
|
+
<div
|
|
97
|
+
style={{
|
|
98
|
+
transformStyle: "preserve-3d",
|
|
99
|
+
...style
|
|
100
|
+
}}
|
|
101
|
+
className={`[transform-style:preserve-3d] [&>*]:[transform-style:preserve-3d] ${className}`}
|
|
102
|
+
>
|
|
103
|
+
{children}
|
|
104
|
+
</div>
|
|
105
|
+
);
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* High level utility granting elements elevation along the Z axis dynamically triggered on hover.
|
|
110
|
+
*/
|
|
111
|
+
export const CardItem = ({
|
|
112
|
+
as: Tag = "div",
|
|
113
|
+
children,
|
|
114
|
+
className,
|
|
115
|
+
translateX = 0,
|
|
116
|
+
translateY = 0,
|
|
117
|
+
translateZ = 0,
|
|
118
|
+
rotateX = 0,
|
|
119
|
+
rotateY = 0,
|
|
120
|
+
rotateZ = 0,
|
|
121
|
+
...rest
|
|
122
|
+
}: {
|
|
123
|
+
as?: any;
|
|
124
|
+
children: React.ReactNode;
|
|
125
|
+
className?: string;
|
|
126
|
+
translateX?: number | string;
|
|
127
|
+
translateY?: number | string;
|
|
128
|
+
translateZ?: number | string;
|
|
129
|
+
rotateX?: number | string;
|
|
130
|
+
rotateY?: number | string;
|
|
131
|
+
rotateZ?: number | string;
|
|
132
|
+
[key: string]: any;
|
|
133
|
+
}) => {
|
|
134
|
+
const ref = useRef<HTMLDivElement>(null);
|
|
135
|
+
const context = useContext(MouseEnterContext);
|
|
136
|
+
const isMouseEntered = context ? context[0] : false;
|
|
137
|
+
|
|
138
|
+
useEffect(() => {
|
|
139
|
+
handleAnimations();
|
|
140
|
+
}, [isMouseEntered]);
|
|
141
|
+
|
|
142
|
+
const handleAnimations = () => {
|
|
143
|
+
if (!ref.current) return;
|
|
144
|
+
if (isMouseEntered) {
|
|
145
|
+
ref.current.style.transform = `translateX(${translateX}px) translateY(${translateY}px) translateZ(${translateZ}px) rotateX(${rotateX}deg) rotateY(${rotateY}deg) rotateZ(${rotateZ}deg)`;
|
|
146
|
+
} else {
|
|
147
|
+
ref.current.style.transform = `translateX(0px) translateY(0px) translateZ(0px) rotateX(0deg) rotateY(0deg) rotateZ(0deg)`;
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
return (
|
|
152
|
+
<Tag
|
|
153
|
+
ref={ref}
|
|
154
|
+
className={`transition duration-200 ease-out ${className}`}
|
|
155
|
+
{...rest}
|
|
156
|
+
>
|
|
157
|
+
{children}
|
|
158
|
+
</Tag>
|
|
159
|
+
);
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* SECOND VARIANT: Implements complex rotation offsets on inner components.
|
|
164
|
+
*/
|
|
165
|
+
interface ThreeDCard2Props {
|
|
166
|
+
title?: string;
|
|
167
|
+
description?: string;
|
|
168
|
+
imageUrl?: string;
|
|
169
|
+
ctaText?: string;
|
|
170
|
+
ctaHref?: string;
|
|
171
|
+
actionText?: string;
|
|
172
|
+
actionHref?: string;
|
|
173
|
+
className?: string;
|
|
174
|
+
containerClassName?: string;
|
|
175
|
+
style?: React.CSSProperties;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export function ThreeDCard2({
|
|
179
|
+
title = "Make things float in air",
|
|
180
|
+
description = "Hover over this card to unleash the power of CSS perspective",
|
|
181
|
+
imageUrl = "https://images.unsplash.com/photo-1441974231531-c6227db76b6e?q=80&w=2560&auto=format&fit=crop",
|
|
182
|
+
ctaText = "Try now \u2192",
|
|
183
|
+
ctaHref = "#",
|
|
184
|
+
actionText = "Sign up",
|
|
185
|
+
actionHref = "#",
|
|
186
|
+
className,
|
|
187
|
+
containerClassName,
|
|
188
|
+
style
|
|
189
|
+
}: ThreeDCard2Props) {
|
|
190
|
+
return (
|
|
191
|
+
<CardContainer className={`inter-var w-full flex justify-center items-center ${containerClassName}`}>
|
|
192
|
+
<CardBody
|
|
193
|
+
style={style}
|
|
194
|
+
className={`bg-gray-50 relative group/card dark:hover:shadow-2xl dark:hover:shadow-emerald-500/[0.1] dark:bg-black dark:border-white/[0.2] border-black/[0.1] w-full max-w-[22rem] sm:max-w-[30rem] h-auto rounded-xl p-6 border ${className}`}
|
|
195
|
+
>
|
|
196
|
+
|
|
197
|
+
{/* Title Elevated */}
|
|
198
|
+
<CardItem
|
|
199
|
+
translateZ="50"
|
|
200
|
+
className="text-xl font-bold text-neutral-600 dark:text-white"
|
|
201
|
+
>
|
|
202
|
+
{title}
|
|
203
|
+
</CardItem>
|
|
204
|
+
|
|
205
|
+
{/* Description Elevated slightly less */}
|
|
206
|
+
<CardItem
|
|
207
|
+
as="p"
|
|
208
|
+
translateZ="60"
|
|
209
|
+
className="text-neutral-500 text-sm max-w-sm mt-2 dark:text-neutral-300"
|
|
210
|
+
>
|
|
211
|
+
{description}
|
|
212
|
+
</CardItem>
|
|
213
|
+
|
|
214
|
+
{/* ADVANCED VARIANT: imagery uses explicit rotateX and rotateZ defaults on top of Z depth */}
|
|
215
|
+
<CardItem
|
|
216
|
+
translateZ="100"
|
|
217
|
+
rotateX={20}
|
|
218
|
+
rotateZ={-10}
|
|
219
|
+
className="w-full mt-4"
|
|
220
|
+
>
|
|
221
|
+
<img
|
|
222
|
+
src={imageUrl}
|
|
223
|
+
height="1000"
|
|
224
|
+
width="1000"
|
|
225
|
+
className="h-60 w-full object-cover rounded-xl group-hover/card:shadow-xl"
|
|
226
|
+
alt="thumbnail"
|
|
227
|
+
/>
|
|
228
|
+
</CardItem>
|
|
229
|
+
|
|
230
|
+
{/* Footer actions stack */}
|
|
231
|
+
<div className="flex justify-between items-center mt-20">
|
|
232
|
+
|
|
233
|
+
{/* ADVANCED VARIANT: uses negative horizontal translation on trigger */}
|
|
234
|
+
<CardItem
|
|
235
|
+
translateZ={20}
|
|
236
|
+
translateX={-40}
|
|
237
|
+
as="a"
|
|
238
|
+
href={ctaHref}
|
|
239
|
+
className="px-4 py-2 rounded-xl text-xs font-normal text-black dark:text-white hover:underline underline-offset-4 cursor-pointer inline-block"
|
|
240
|
+
>
|
|
241
|
+
{ctaText}
|
|
242
|
+
</CardItem>
|
|
243
|
+
|
|
244
|
+
{/* ADVANCED VARIANT: uses positive horizontal translation on trigger */}
|
|
245
|
+
<CardItem
|
|
246
|
+
translateZ={20}
|
|
247
|
+
translateX={40}
|
|
248
|
+
as="a"
|
|
249
|
+
href={actionHref}
|
|
250
|
+
className="px-4 py-2 rounded-xl bg-black dark:bg-white dark:text-black text-white text-xs font-bold shadow-md transition-all active:scale-95 cursor-pointer inline-block"
|
|
251
|
+
>
|
|
252
|
+
{actionText}
|
|
253
|
+
</CardItem>
|
|
254
|
+
</div>
|
|
255
|
+
</CardBody>
|
|
256
|
+
</CardContainer>
|
|
257
|
+
);
|
|
258
|
+
}
|
|
@@ -12,10 +12,12 @@ export const CardContainer = ({
|
|
|
12
12
|
children,
|
|
13
13
|
className,
|
|
14
14
|
containerClassName,
|
|
15
|
+
style,
|
|
15
16
|
}: {
|
|
16
17
|
children: React.ReactNode;
|
|
17
18
|
className?: string;
|
|
18
19
|
containerClassName?: string;
|
|
20
|
+
style?: React.CSSProperties;
|
|
19
21
|
}) => {
|
|
20
22
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
21
23
|
const [isMouseEntered, setIsMouseEntered] = useState(false);
|
|
@@ -72,6 +74,7 @@ export const CardContainer = ({
|
|
|
72
74
|
transformStyle: "preserve-3d",
|
|
73
75
|
rotateX,
|
|
74
76
|
rotateY,
|
|
77
|
+
...style
|
|
75
78
|
}}
|
|
76
79
|
className={`flex items-center justify-center relative transition-all duration-200 ease-linear ${className}`}
|
|
77
80
|
>
|
|
@@ -88,16 +91,19 @@ export const CardContainer = ({
|
|
|
88
91
|
export const CardBody = ({
|
|
89
92
|
children,
|
|
90
93
|
className,
|
|
94
|
+
style,
|
|
91
95
|
}: {
|
|
92
96
|
children: React.ReactNode;
|
|
93
97
|
className?: string;
|
|
98
|
+
style?: React.CSSProperties;
|
|
94
99
|
}) => {
|
|
95
100
|
return (
|
|
96
101
|
<div
|
|
97
102
|
style={{
|
|
98
103
|
transformStyle: "preserve-3d",
|
|
104
|
+
...style
|
|
99
105
|
}}
|
|
100
|
-
className={`
|
|
106
|
+
className={`[transform-style:preserve-3d] [&>*]:[transform-style:preserve-3d] ${className}`}
|
|
101
107
|
>
|
|
102
108
|
{children}
|
|
103
109
|
</div>
|
|
@@ -167,7 +173,12 @@ interface ThreeDCardProps {
|
|
|
167
173
|
description?: string;
|
|
168
174
|
imageUrl?: string;
|
|
169
175
|
ctaText?: string;
|
|
176
|
+
ctaHref?: string;
|
|
170
177
|
actionText?: string;
|
|
178
|
+
actionHref?: string;
|
|
179
|
+
className?: string;
|
|
180
|
+
containerClassName?: string;
|
|
181
|
+
style?: React.CSSProperties;
|
|
171
182
|
}
|
|
172
183
|
|
|
173
184
|
export function ThreeDCard({
|
|
@@ -175,16 +186,24 @@ export function ThreeDCard({
|
|
|
175
186
|
description = "Hover over this card to unleash the power of CSS perspective",
|
|
176
187
|
imageUrl = "https://images.unsplash.com/photo-1441974231531-c6227db76b6e?q=80&w=2560&auto=format&fit=crop",
|
|
177
188
|
ctaText = "Try now \u2192",
|
|
178
|
-
|
|
189
|
+
ctaHref = "#",
|
|
190
|
+
actionText = "Sign up",
|
|
191
|
+
actionHref = "#",
|
|
192
|
+
className,
|
|
193
|
+
containerClassName,
|
|
194
|
+
style
|
|
179
195
|
}: ThreeDCardProps) {
|
|
180
196
|
return (
|
|
181
|
-
<CardContainer className=
|
|
182
|
-
<CardBody
|
|
197
|
+
<CardContainer className={`inter-var w-full flex justify-center items-center ${containerClassName}`}>
|
|
198
|
+
<CardBody
|
|
199
|
+
style={style}
|
|
200
|
+
className={`bg-gray-50 relative group/card dark:hover:shadow-2xl dark:hover:shadow-emerald-500/[0.1] dark:bg-black dark:border-white/[0.2] border-black/[0.1] w-full max-w-[22rem] sm:max-w-[30rem] h-auto rounded-xl p-6 border ${className}`}
|
|
201
|
+
>
|
|
183
202
|
|
|
184
203
|
{/* Title Elevated */}
|
|
185
204
|
<CardItem
|
|
186
205
|
translateZ="50"
|
|
187
|
-
className="text-
|
|
206
|
+
className="text-xl font-bold text-neutral-600 dark:text-white"
|
|
188
207
|
>
|
|
189
208
|
{title}
|
|
190
209
|
</CardItem>
|
|
@@ -193,18 +212,18 @@ export function ThreeDCard({
|
|
|
193
212
|
<CardItem
|
|
194
213
|
as="p"
|
|
195
214
|
translateZ="60"
|
|
196
|
-
className="text-neutral-500 text-
|
|
215
|
+
className="text-neutral-500 text-sm max-w-sm mt-2 dark:text-neutral-300"
|
|
197
216
|
>
|
|
198
217
|
{description}
|
|
199
218
|
</CardItem>
|
|
200
219
|
|
|
201
220
|
{/* Imagery container with maximum depth and radius */}
|
|
202
|
-
<CardItem translateZ="100" className="w-full mt-
|
|
221
|
+
<CardItem translateZ="100" className="w-full mt-4">
|
|
203
222
|
<img
|
|
204
223
|
src={imageUrl}
|
|
205
224
|
height="1000"
|
|
206
225
|
width="1000"
|
|
207
|
-
className="h-60 w-full object-cover rounded-xl group-hover/card:shadow-xl
|
|
226
|
+
className="h-60 w-full object-cover rounded-xl group-hover/card:shadow-xl"
|
|
208
227
|
alt="thumbnail"
|
|
209
228
|
/>
|
|
210
229
|
</CardItem>
|
|
@@ -214,8 +233,9 @@ export function ThreeDCard({
|
|
|
214
233
|
{/* Lower left navigation prompt */}
|
|
215
234
|
<CardItem
|
|
216
235
|
translateZ={20}
|
|
217
|
-
as="
|
|
218
|
-
|
|
236
|
+
as="a"
|
|
237
|
+
href={ctaHref}
|
|
238
|
+
className="px-4 py-2 rounded-xl text-xs font-normal text-black dark:text-white hover:underline underline-offset-4 cursor-pointer inline-block"
|
|
219
239
|
>
|
|
220
240
|
{ctaText}
|
|
221
241
|
</CardItem>
|
|
@@ -223,8 +243,9 @@ export function ThreeDCard({
|
|
|
223
243
|
{/* Right primary action capsule */}
|
|
224
244
|
<CardItem
|
|
225
245
|
translateZ={20}
|
|
226
|
-
as="
|
|
227
|
-
|
|
246
|
+
as="a"
|
|
247
|
+
href={actionHref}
|
|
248
|
+
className="px-4 py-2 rounded-xl bg-black dark:bg-white dark:text-black text-white text-xs font-bold shadow-md transition-all active:scale-95 cursor-pointer inline-block"
|
|
228
249
|
>
|
|
229
250
|
{actionText}
|
|
230
251
|
</CardItem>
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import React, { useState } from "react";
|
|
4
|
+
import { motion } from "motion/react";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Internal utility replacing external lib requirements ensuring zero-friction installations.
|
|
8
|
+
*/
|
|
9
|
+
const cn = (...classes: any[]) => {
|
|
10
|
+
return classes.filter(Boolean).join(" ");
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const PinContainer = ({
|
|
14
|
+
children,
|
|
15
|
+
title,
|
|
16
|
+
href,
|
|
17
|
+
className,
|
|
18
|
+
containerClassName,
|
|
19
|
+
}: {
|
|
20
|
+
children: React.ReactNode;
|
|
21
|
+
title?: string;
|
|
22
|
+
href?: string;
|
|
23
|
+
className?: string;
|
|
24
|
+
containerClassName?: string;
|
|
25
|
+
}) => {
|
|
26
|
+
const [transform, setTransform] = useState(
|
|
27
|
+
"translate(-50%,-50%) rotateX(0deg)"
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
const onMouseEnter = () => {
|
|
31
|
+
setTransform("translate(-50%,-50%) rotateX(40deg) scale(0.8)");
|
|
32
|
+
};
|
|
33
|
+
const onMouseLeave = () => {
|
|
34
|
+
setTransform("translate(-50%,-50%) rotateX(0deg) scale(1)");
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<a
|
|
39
|
+
className={cn(
|
|
40
|
+
"relative group/pin z-50 cursor-pointer",
|
|
41
|
+
containerClassName
|
|
42
|
+
)}
|
|
43
|
+
onMouseEnter={onMouseEnter}
|
|
44
|
+
onMouseLeave={onMouseLeave}
|
|
45
|
+
href={href || "/"}
|
|
46
|
+
>
|
|
47
|
+
<div
|
|
48
|
+
style={{
|
|
49
|
+
perspective: "1000px",
|
|
50
|
+
transform: "rotateX(70deg) translateZ(0deg)",
|
|
51
|
+
}}
|
|
52
|
+
className="absolute left-1/2 top-1/2 ml-[0.09375rem] mt-4 -translate-x-1/2 -translate-y-1/2"
|
|
53
|
+
>
|
|
54
|
+
<div
|
|
55
|
+
style={{
|
|
56
|
+
transform: transform,
|
|
57
|
+
}}
|
|
58
|
+
className="absolute left-1/2 p-4 top-1/2 flex justify-start items-start rounded-2xl shadow-[0_8px_16px_rgb(0_0_0/0.4)] bg-black border border-white/[0.1] group-hover/pin:border-white/[0.2] transition duration-700 overflow-hidden"
|
|
59
|
+
>
|
|
60
|
+
<div className={cn(" relative z-50 ", className)}>{children}</div>
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
<PinPerspective title={title} href={href} />
|
|
64
|
+
</a>
|
|
65
|
+
);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export const PinPerspective = ({
|
|
69
|
+
title,
|
|
70
|
+
href,
|
|
71
|
+
}: {
|
|
72
|
+
title?: string;
|
|
73
|
+
href?: string;
|
|
74
|
+
}) => {
|
|
75
|
+
return (
|
|
76
|
+
<motion.div className="pointer-events-none w-96 h-80 flex items-center justify-center opacity-0 group-hover/pin:opacity-100 z-[60] transition duration-500">
|
|
77
|
+
<div className=" w-full h-full -mt-7 flex-none inset-0">
|
|
78
|
+
<div className="absolute top-0 inset-x-0 flex justify-center">
|
|
79
|
+
<a
|
|
80
|
+
href={href}
|
|
81
|
+
target={"_blank"}
|
|
82
|
+
className="relative flex space-x-2 items-center z-10 rounded-full bg-zinc-950 py-0.5 px-4 ring-1 ring-white/10 "
|
|
83
|
+
>
|
|
84
|
+
<span className="relative z-20 text-white text-xs font-bold inline-block py-0.5">
|
|
85
|
+
{title}
|
|
86
|
+
</span>
|
|
87
|
+
|
|
88
|
+
<span className="absolute -bottom-0 left-[1.125rem] h-px w-[calc(100%-2.25rem)] bg-gradient-to-r from-emerald-400/0 via-emerald-400/90 to-emerald-400/0 transition-opacity duration-500 group-hover/btn:opacity-40"></span>
|
|
89
|
+
</a>
|
|
90
|
+
</div>
|
|
91
|
+
|
|
92
|
+
<div
|
|
93
|
+
style={{
|
|
94
|
+
perspective: "1000px",
|
|
95
|
+
transform: "rotateX(70deg) translateZ(0)",
|
|
96
|
+
}}
|
|
97
|
+
className="absolute left-1/2 top-1/2 ml-[0.09375rem] mt-4 -translate-x-1/2 -translate-y-1/2"
|
|
98
|
+
>
|
|
99
|
+
<>
|
|
100
|
+
<motion.div
|
|
101
|
+
initial={{
|
|
102
|
+
opacity: 0,
|
|
103
|
+
scale: 0,
|
|
104
|
+
x: "-50%",
|
|
105
|
+
y: "-50%",
|
|
106
|
+
}}
|
|
107
|
+
animate={{
|
|
108
|
+
opacity: [0, 1, 0.5, 0],
|
|
109
|
+
scale: 1,
|
|
110
|
+
|
|
111
|
+
z: 0,
|
|
112
|
+
}}
|
|
113
|
+
transition={{
|
|
114
|
+
duration: 6,
|
|
115
|
+
repeat: Infinity,
|
|
116
|
+
delay: 0,
|
|
117
|
+
}}
|
|
118
|
+
className="absolute left-1/2 top-1/2 h-[11.25rem] w-[11.25rem] rounded-[50%] bg-sky-500/[0.08] shadow-[0_8px_16px_rgb(0_0_0/0.4)]"
|
|
119
|
+
></motion.div>
|
|
120
|
+
<motion.div
|
|
121
|
+
initial={{
|
|
122
|
+
opacity: 0,
|
|
123
|
+
scale: 0,
|
|
124
|
+
x: "-50%",
|
|
125
|
+
y: "-50%",
|
|
126
|
+
}}
|
|
127
|
+
animate={{
|
|
128
|
+
opacity: [0, 1, 0.5, 0],
|
|
129
|
+
scale: 1,
|
|
130
|
+
|
|
131
|
+
z: 0,
|
|
132
|
+
}}
|
|
133
|
+
transition={{
|
|
134
|
+
duration: 6,
|
|
135
|
+
repeat: Infinity,
|
|
136
|
+
delay: 2,
|
|
137
|
+
}}
|
|
138
|
+
className="absolute left-1/2 top-1/2 h-[11.25rem] w-[11.25rem] rounded-[50%] bg-sky-500/[0.08] shadow-[0_8px_16px_rgb(0_0_0/0.4)]"
|
|
139
|
+
></motion.div>
|
|
140
|
+
<motion.div
|
|
141
|
+
initial={{
|
|
142
|
+
opacity: 0,
|
|
143
|
+
scale: 0,
|
|
144
|
+
x: "-50%",
|
|
145
|
+
y: "-50%",
|
|
146
|
+
}}
|
|
147
|
+
animate={{
|
|
148
|
+
opacity: [0, 1, 0.5, 0],
|
|
149
|
+
scale: 1,
|
|
150
|
+
|
|
151
|
+
z: 0,
|
|
152
|
+
}}
|
|
153
|
+
transition={{
|
|
154
|
+
duration: 6,
|
|
155
|
+
repeat: Infinity,
|
|
156
|
+
delay: 4,
|
|
157
|
+
}}
|
|
158
|
+
className="absolute left-1/2 top-1/2 h-[11.25rem] w-[11.25rem] rounded-[50%] bg-sky-500/[0.08] shadow-[0_8px_16px_rgb(0_0_0/0.4)]"
|
|
159
|
+
></motion.div>
|
|
160
|
+
</>
|
|
161
|
+
</div>
|
|
162
|
+
|
|
163
|
+
<>
|
|
164
|
+
<motion.div className="absolute right-1/2 bottom-1/2 bg-gradient-to-b from-transparent to-cyan-500 translate-y-[14px] w-px h-20 group-hover/pin:h-40 blur-[2px]" />
|
|
165
|
+
<motion.div className="absolute right-1/2 bottom-1/2 bg-gradient-to-b from-transparent to-cyan-500 translate-y-[14px] w-px h-20 group-hover/pin:h-40 " />
|
|
166
|
+
<motion.div className="absolute right-1/2 translate-x-[1.5px] bottom-1/2 bg-cyan-600 translate-y-[14px] w-[4px] h-[4px] rounded-full z-40 blur-[3px]" />
|
|
167
|
+
<motion.div className="absolute right-1/2 translate-x-[0.5px] bottom-1/2 bg-cyan-300 translate-y-[14px] w-[2px] h-[2px] rounded-full z-40 " />
|
|
168
|
+
</>
|
|
169
|
+
</div>
|
|
170
|
+
</motion.div>
|
|
171
|
+
);
|
|
172
|
+
};
|