@stackvibe-studio/motion 0.2.0 → 0.3.0
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/README.md +47 -18
- package/dist/components/magnetic.d.ts +7 -0
- package/dist/components/marquee.d.ts +9 -0
- package/dist/components/number-ticker.d.ts +8 -0
- package/dist/components/tilt-card.d.ts +8 -0
- package/dist/index.cjs +216 -13
- package/dist/index.d.ts +4 -0
- package/dist/index.js +212 -13
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,35 +1,64 @@
|
|
|
1
1
|
# @stackvibe-studio/motion
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> Physics-based motion and animation primitives for StackVibe UI, powered by Framer Motion.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
[](https://www.npmjs.com/package/@stackvibe-studio/motion)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
- Spotlight cards with soft glow lighting
|
|
9
|
-
- Clean text reveal utilities
|
|
10
|
-
- New in v0.2.0: FadeInSection for storytelling and landing pages
|
|
11
|
-
|
|
12
|
-
## Install
|
|
8
|
+
## Installation
|
|
13
9
|
|
|
14
10
|
```bash
|
|
15
|
-
npm install @stackvibe-studio/motion
|
|
11
|
+
npm install @stackvibe-studio/motion framer-motion
|
|
12
|
+
# or
|
|
13
|
+
pnpm add @stackvibe-studio/motion framer-motion
|
|
16
14
|
```
|
|
17
15
|
|
|
18
|
-
##
|
|
16
|
+
## Primitives Included
|
|
17
|
+
|
|
18
|
+
- **`Magnetic`**: Physics-based cursor attraction that pulls buttons or badges toward the pointer.
|
|
19
|
+
- **`TiltCard`**: 3D perspective mouse-tracking tilt card with realistic specular glare reflection.
|
|
20
|
+
- **`NumberTicker`**: Smooth animated counter from 0 to N with cubic easing for metric showcases.
|
|
21
|
+
- **`Marquee`**: Infinite looping ticker for partners, logos, or news with pause-on-hover.
|
|
22
|
+
- **`SpotlightCard`**: Interactive card with dynamic radial cursor spotlight illumination.
|
|
23
|
+
- **`FadeIn`**: Scroll-aware reveal wrapper for onboarding sections and storytelling layouts.
|
|
24
|
+
- **`TextReveal`**: Typography entry animation for titles and headings.
|
|
25
|
+
|
|
26
|
+
## Usage
|
|
19
27
|
|
|
20
28
|
```tsx
|
|
21
|
-
import {
|
|
29
|
+
import { Magnetic, TiltCard, NumberTicker, Marquee } from '@stackvibe-studio/motion';
|
|
30
|
+
import { Button } from '@stackvibe-studio/ui';
|
|
22
31
|
|
|
23
|
-
export function
|
|
32
|
+
export function MotionDemo() {
|
|
24
33
|
return (
|
|
25
|
-
<
|
|
26
|
-
<
|
|
27
|
-
|
|
34
|
+
<div className="space-y-6">
|
|
35
|
+
<Magnetic strength={0.4}>
|
|
36
|
+
<Button size="lg">Magnetic Button</Button>
|
|
37
|
+
</Magnetic>
|
|
38
|
+
|
|
39
|
+
<TiltCard maxTilt={15} glare>
|
|
40
|
+
<h3>3D Tilt Surface</h3>
|
|
41
|
+
<p>Interactive mouse perspective.</p>
|
|
42
|
+
</TiltCard>
|
|
43
|
+
|
|
44
|
+
<div className="text-3xl font-bold">
|
|
45
|
+
<NumberTicker value={25000} prefix="$" suffix=" ARR" />
|
|
46
|
+
</div>
|
|
47
|
+
|
|
48
|
+
<Marquee speed={20}>
|
|
49
|
+
<span>Item 1</span>
|
|
50
|
+
<span>Item 2</span>
|
|
51
|
+
<span>Item 3</span>
|
|
52
|
+
</Marquee>
|
|
53
|
+
</div>
|
|
28
54
|
);
|
|
29
55
|
}
|
|
30
56
|
```
|
|
31
57
|
|
|
32
|
-
##
|
|
58
|
+
## Documentation
|
|
59
|
+
|
|
60
|
+
[https://stackvibe-studio.github.io/stackvibe-ui/docs](https://stackvibe-studio.github.io/stackvibe-ui/docs)
|
|
61
|
+
|
|
62
|
+
## License
|
|
33
63
|
|
|
34
|
-
|
|
35
|
-
- @stackvibe-studio/three
|
|
64
|
+
MIT © [StackVibe Studio](https://github.com/StackVibe-Studio)
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
export interface MarqueeProps {
|
|
3
|
+
children: ReactNode;
|
|
4
|
+
speed?: number;
|
|
5
|
+
direction?: 'left' | 'right';
|
|
6
|
+
pauseOnHover?: boolean;
|
|
7
|
+
className?: string;
|
|
8
|
+
}
|
|
9
|
+
export declare function Marquee({ children, speed, direction, pauseOnHover, className, }: MarqueeProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface NumberTickerProps {
|
|
2
|
+
value: number;
|
|
3
|
+
duration?: number;
|
|
4
|
+
className?: string;
|
|
5
|
+
prefix?: string;
|
|
6
|
+
suffix?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function NumberTicker({ value, duration, className, prefix, suffix, }: NumberTickerProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
export interface TiltCardProps {
|
|
3
|
+
children: ReactNode;
|
|
4
|
+
maxTilt?: number;
|
|
5
|
+
glare?: boolean;
|
|
6
|
+
className?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function TiltCard({ children, maxTilt, glare, className, }: TiltCardProps): import("react").JSX.Element;
|
package/dist/index.cjs
CHANGED
|
@@ -22,8 +22,12 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
22
22
|
var index_exports = {};
|
|
23
23
|
__export(index_exports, {
|
|
24
24
|
FadeInSection: () => FadeInSection,
|
|
25
|
+
Magnetic: () => Magnetic,
|
|
26
|
+
Marquee: () => Marquee,
|
|
27
|
+
NumberTicker: () => NumberTicker,
|
|
25
28
|
SpotlightCard: () => SpotlightCard,
|
|
26
|
-
TextReveal: () => TextReveal
|
|
29
|
+
TextReveal: () => TextReveal,
|
|
30
|
+
TiltCard: () => TiltCard
|
|
27
31
|
});
|
|
28
32
|
module.exports = __toCommonJS(index_exports);
|
|
29
33
|
|
|
@@ -51,9 +55,128 @@ function FadeInSection({
|
|
|
51
55
|
);
|
|
52
56
|
}
|
|
53
57
|
|
|
54
|
-
// src/components/
|
|
58
|
+
// src/components/magnetic.tsx
|
|
55
59
|
var import_react2 = require("motion/react");
|
|
60
|
+
var import_react3 = require("react");
|
|
56
61
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
62
|
+
function Magnetic({ children, strength = 0.35, className = "" }) {
|
|
63
|
+
const ref = (0, import_react3.useRef)(null);
|
|
64
|
+
const x = (0, import_react2.useMotionValue)(0);
|
|
65
|
+
const y = (0, import_react2.useMotionValue)(0);
|
|
66
|
+
const springConfig = { damping: 15, stiffness: 150, mass: 0.1 };
|
|
67
|
+
const springX = (0, import_react2.useSpring)(x, springConfig);
|
|
68
|
+
const springY = (0, import_react2.useSpring)(y, springConfig);
|
|
69
|
+
const handleMouseMove = (e) => {
|
|
70
|
+
if (!ref.current) return;
|
|
71
|
+
const { clientX, clientY } = e;
|
|
72
|
+
const { left, top, width, height } = ref.current.getBoundingClientRect();
|
|
73
|
+
const middleX = clientX - (left + width / 2);
|
|
74
|
+
const middleY = clientY - (top + height / 2);
|
|
75
|
+
x.set(middleX * strength);
|
|
76
|
+
y.set(middleY * strength);
|
|
77
|
+
};
|
|
78
|
+
const handleMouseLeave = () => {
|
|
79
|
+
x.set(0);
|
|
80
|
+
y.set(0);
|
|
81
|
+
};
|
|
82
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
83
|
+
import_react2.motion.div,
|
|
84
|
+
{
|
|
85
|
+
ref,
|
|
86
|
+
onMouseMove: handleMouseMove,
|
|
87
|
+
onMouseLeave: handleMouseLeave,
|
|
88
|
+
style: { x: springX, y: springY },
|
|
89
|
+
className: ["inline-block cursor-pointer", className].join(" "),
|
|
90
|
+
children
|
|
91
|
+
}
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// src/components/marquee.tsx
|
|
96
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
97
|
+
function Marquee({
|
|
98
|
+
children,
|
|
99
|
+
speed = 25,
|
|
100
|
+
direction = "left",
|
|
101
|
+
pauseOnHover = true,
|
|
102
|
+
className = ""
|
|
103
|
+
}) {
|
|
104
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
105
|
+
"div",
|
|
106
|
+
{
|
|
107
|
+
className: [
|
|
108
|
+
"group flex overflow-hidden select-none [mask-image:linear-gradient(to_right,transparent,white_10%,white_90%,transparent)]",
|
|
109
|
+
className
|
|
110
|
+
].join(" "),
|
|
111
|
+
children: [
|
|
112
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
113
|
+
"div",
|
|
114
|
+
{
|
|
115
|
+
className: [
|
|
116
|
+
"flex min-w-full shrink-0 items-center justify-around gap-8",
|
|
117
|
+
direction === "left" ? "animate-marquee" : "animate-marquee-reverse",
|
|
118
|
+
pauseOnHover ? "group-hover:[animation-play-state:paused]" : ""
|
|
119
|
+
].join(" "),
|
|
120
|
+
style: { animationDuration: `${speed}s` },
|
|
121
|
+
children
|
|
122
|
+
}
|
|
123
|
+
),
|
|
124
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
125
|
+
"div",
|
|
126
|
+
{
|
|
127
|
+
"aria-hidden": "true",
|
|
128
|
+
className: [
|
|
129
|
+
"flex min-w-full shrink-0 items-center justify-around gap-8",
|
|
130
|
+
direction === "left" ? "animate-marquee" : "animate-marquee-reverse",
|
|
131
|
+
pauseOnHover ? "group-hover:[animation-play-state:paused]" : ""
|
|
132
|
+
].join(" "),
|
|
133
|
+
style: { animationDuration: `${speed}s` },
|
|
134
|
+
children
|
|
135
|
+
}
|
|
136
|
+
)
|
|
137
|
+
]
|
|
138
|
+
}
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// src/components/number-ticker.tsx
|
|
143
|
+
var import_react4 = require("react");
|
|
144
|
+
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
145
|
+
function NumberTicker({
|
|
146
|
+
value,
|
|
147
|
+
duration = 1500,
|
|
148
|
+
className = "",
|
|
149
|
+
prefix = "",
|
|
150
|
+
suffix = ""
|
|
151
|
+
}) {
|
|
152
|
+
const [current, setCurrent] = (0, import_react4.useState)(0);
|
|
153
|
+
const startTimeRef = (0, import_react4.useRef)(null);
|
|
154
|
+
(0, import_react4.useEffect)(() => {
|
|
155
|
+
let animationFrame;
|
|
156
|
+
const animate = (timestamp) => {
|
|
157
|
+
if (!startTimeRef.current) startTimeRef.current = timestamp;
|
|
158
|
+
const progress = Math.min((timestamp - startTimeRef.current) / duration, 1);
|
|
159
|
+
const easeOut = 1 - Math.pow(1 - progress, 3);
|
|
160
|
+
setCurrent(Math.floor(easeOut * value));
|
|
161
|
+
if (progress < 1) {
|
|
162
|
+
animationFrame = requestAnimationFrame(animate);
|
|
163
|
+
} else {
|
|
164
|
+
setCurrent(value);
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
animationFrame = requestAnimationFrame(animate);
|
|
168
|
+
return () => cancelAnimationFrame(animationFrame);
|
|
169
|
+
}, [value, duration]);
|
|
170
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: ["tabular-nums font-mono", className].join(" "), children: [
|
|
171
|
+
prefix,
|
|
172
|
+
current.toLocaleString(),
|
|
173
|
+
suffix
|
|
174
|
+
] });
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// src/components/spotlight-card.tsx
|
|
178
|
+
var import_react5 = require("motion/react");
|
|
179
|
+
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
57
180
|
function SpotlightCard({
|
|
58
181
|
children,
|
|
59
182
|
glowColor = "#8b5cf6",
|
|
@@ -62,8 +185,8 @@ function SpotlightCard({
|
|
|
62
185
|
className = "",
|
|
63
186
|
...props
|
|
64
187
|
}) {
|
|
65
|
-
return /* @__PURE__ */ (0,
|
|
66
|
-
|
|
188
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
189
|
+
import_react5.motion.div,
|
|
67
190
|
{
|
|
68
191
|
...props,
|
|
69
192
|
whileHover: { scale: 1.01, y: -4 },
|
|
@@ -74,8 +197,8 @@ function SpotlightCard({
|
|
|
74
197
|
background: "linear-gradient(135deg, rgba(15,23,42,0.78), rgba(15,23,42,0.42))"
|
|
75
198
|
},
|
|
76
199
|
children: [
|
|
77
|
-
/* @__PURE__ */ (0,
|
|
78
|
-
|
|
200
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
201
|
+
import_react5.motion.div,
|
|
79
202
|
{
|
|
80
203
|
"aria-hidden": "true",
|
|
81
204
|
className: "pointer-events-none absolute inset-0 opacity-90",
|
|
@@ -86,34 +209,114 @@ function SpotlightCard({
|
|
|
86
209
|
}
|
|
87
210
|
}
|
|
88
211
|
),
|
|
89
|
-
/* @__PURE__ */ (0,
|
|
212
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "relative z-10", children })
|
|
90
213
|
]
|
|
91
214
|
}
|
|
92
215
|
);
|
|
93
216
|
}
|
|
94
217
|
|
|
95
218
|
// src/components/text-reveal.tsx
|
|
96
|
-
var
|
|
97
|
-
var
|
|
219
|
+
var import_react6 = require("motion/react");
|
|
220
|
+
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
98
221
|
function TextReveal({
|
|
99
222
|
as: Component = "h1",
|
|
100
223
|
children,
|
|
101
224
|
className = "",
|
|
102
225
|
...props
|
|
103
226
|
}) {
|
|
104
|
-
return /* @__PURE__ */ (0,
|
|
105
|
-
|
|
227
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
228
|
+
import_react6.motion.div,
|
|
106
229
|
{
|
|
107
230
|
initial: { opacity: 0, y: 18 },
|
|
108
231
|
animate: { opacity: 1, y: 0 },
|
|
109
232
|
transition: { duration: 0.5, ease: "easeOut" },
|
|
110
|
-
children: /* @__PURE__ */ (0,
|
|
233
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Component, { ...props, className, children })
|
|
234
|
+
}
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// src/components/tilt-card.tsx
|
|
239
|
+
var import_react7 = require("react");
|
|
240
|
+
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
241
|
+
function TiltCard({
|
|
242
|
+
children,
|
|
243
|
+
maxTilt = 15,
|
|
244
|
+
glare = true,
|
|
245
|
+
className = ""
|
|
246
|
+
}) {
|
|
247
|
+
const cardRef = (0, import_react7.useRef)(null);
|
|
248
|
+
const [rotateX, setRotateX] = (0, import_react7.useState)(0);
|
|
249
|
+
const [rotateY, setRotateY] = (0, import_react7.useState)(0);
|
|
250
|
+
const [glarePos, setGlarePos] = (0, import_react7.useState)({ x: 50, y: 50, opacity: 0 });
|
|
251
|
+
const handleMouseMove = (e) => {
|
|
252
|
+
if (!cardRef.current) return;
|
|
253
|
+
const rect = cardRef.current.getBoundingClientRect();
|
|
254
|
+
const x = e.clientX - rect.left;
|
|
255
|
+
const y = e.clientY - rect.top;
|
|
256
|
+
const centerX = rect.width / 2;
|
|
257
|
+
const centerY = rect.height / 2;
|
|
258
|
+
const rotX = (y - centerY) / centerY * -maxTilt;
|
|
259
|
+
const rotY = (x - centerX) / centerX * maxTilt;
|
|
260
|
+
setRotateX(rotX);
|
|
261
|
+
setRotateY(rotY);
|
|
262
|
+
if (glare) {
|
|
263
|
+
setGlarePos({
|
|
264
|
+
x: x / rect.width * 100,
|
|
265
|
+
y: y / rect.height * 100,
|
|
266
|
+
opacity: 0.25
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
const handleMouseLeave = () => {
|
|
271
|
+
setRotateX(0);
|
|
272
|
+
setRotateY(0);
|
|
273
|
+
setGlarePos((prev) => ({ ...prev, opacity: 0 }));
|
|
274
|
+
};
|
|
275
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
276
|
+
"div",
|
|
277
|
+
{
|
|
278
|
+
style: { perspective: "1000px" },
|
|
279
|
+
className: "inline-block w-full",
|
|
280
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
281
|
+
"div",
|
|
282
|
+
{
|
|
283
|
+
ref: cardRef,
|
|
284
|
+
onMouseMove: handleMouseMove,
|
|
285
|
+
onMouseLeave: handleMouseLeave,
|
|
286
|
+
style: {
|
|
287
|
+
transform: `rotateX(${rotateX}deg) rotateY(${rotateY}deg)`,
|
|
288
|
+
transition: "transform 0.15s ease-out",
|
|
289
|
+
transformStyle: "preserve-3d"
|
|
290
|
+
},
|
|
291
|
+
className: [
|
|
292
|
+
"relative overflow-hidden rounded-3xl border border-white/10 bg-slate-900/80 p-6 backdrop-blur-xl",
|
|
293
|
+
className
|
|
294
|
+
].join(" "),
|
|
295
|
+
children: [
|
|
296
|
+
glare && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
297
|
+
"div",
|
|
298
|
+
{
|
|
299
|
+
className: "pointer-events-none absolute -inset-px transition-opacity duration-300",
|
|
300
|
+
style: {
|
|
301
|
+
opacity: glarePos.opacity,
|
|
302
|
+
background: `radial-gradient(circle at ${glarePos.x}% ${glarePos.y}%, rgba(255,255,255,0.4), transparent 60%)`
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
),
|
|
306
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: { transform: "translateZ(20px)" }, children })
|
|
307
|
+
]
|
|
308
|
+
}
|
|
309
|
+
)
|
|
111
310
|
}
|
|
112
311
|
);
|
|
113
312
|
}
|
|
114
313
|
// Annotate the CommonJS export names for ESM import in node:
|
|
115
314
|
0 && (module.exports = {
|
|
116
315
|
FadeInSection,
|
|
316
|
+
Magnetic,
|
|
317
|
+
Marquee,
|
|
318
|
+
NumberTicker,
|
|
117
319
|
SpotlightCard,
|
|
118
|
-
TextReveal
|
|
320
|
+
TextReveal,
|
|
321
|
+
TiltCard
|
|
119
322
|
});
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
1
|
export * from './components/fade-in';
|
|
2
|
+
export * from './components/magnetic';
|
|
3
|
+
export * from './components/marquee';
|
|
4
|
+
export * from './components/number-ticker';
|
|
2
5
|
export * from './components/spotlight-card';
|
|
3
6
|
export * from './components/text-reveal';
|
|
7
|
+
export * from './components/tilt-card';
|
package/dist/index.js
CHANGED
|
@@ -24,9 +24,128 @@ function FadeInSection({
|
|
|
24
24
|
);
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
// src/components/magnetic.tsx
|
|
28
|
+
import { motion as motion2, useMotionValue, useSpring } from "motion/react";
|
|
29
|
+
import { useRef } from "react";
|
|
30
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
31
|
+
function Magnetic({ children, strength = 0.35, className = "" }) {
|
|
32
|
+
const ref = useRef(null);
|
|
33
|
+
const x = useMotionValue(0);
|
|
34
|
+
const y = useMotionValue(0);
|
|
35
|
+
const springConfig = { damping: 15, stiffness: 150, mass: 0.1 };
|
|
36
|
+
const springX = useSpring(x, springConfig);
|
|
37
|
+
const springY = useSpring(y, springConfig);
|
|
38
|
+
const handleMouseMove = (e) => {
|
|
39
|
+
if (!ref.current) return;
|
|
40
|
+
const { clientX, clientY } = e;
|
|
41
|
+
const { left, top, width, height } = ref.current.getBoundingClientRect();
|
|
42
|
+
const middleX = clientX - (left + width / 2);
|
|
43
|
+
const middleY = clientY - (top + height / 2);
|
|
44
|
+
x.set(middleX * strength);
|
|
45
|
+
y.set(middleY * strength);
|
|
46
|
+
};
|
|
47
|
+
const handleMouseLeave = () => {
|
|
48
|
+
x.set(0);
|
|
49
|
+
y.set(0);
|
|
50
|
+
};
|
|
51
|
+
return /* @__PURE__ */ jsx2(
|
|
52
|
+
motion2.div,
|
|
53
|
+
{
|
|
54
|
+
ref,
|
|
55
|
+
onMouseMove: handleMouseMove,
|
|
56
|
+
onMouseLeave: handleMouseLeave,
|
|
57
|
+
style: { x: springX, y: springY },
|
|
58
|
+
className: ["inline-block cursor-pointer", className].join(" "),
|
|
59
|
+
children
|
|
60
|
+
}
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// src/components/marquee.tsx
|
|
65
|
+
import { jsx as jsx3, jsxs } from "react/jsx-runtime";
|
|
66
|
+
function Marquee({
|
|
67
|
+
children,
|
|
68
|
+
speed = 25,
|
|
69
|
+
direction = "left",
|
|
70
|
+
pauseOnHover = true,
|
|
71
|
+
className = ""
|
|
72
|
+
}) {
|
|
73
|
+
return /* @__PURE__ */ jsxs(
|
|
74
|
+
"div",
|
|
75
|
+
{
|
|
76
|
+
className: [
|
|
77
|
+
"group flex overflow-hidden select-none [mask-image:linear-gradient(to_right,transparent,white_10%,white_90%,transparent)]",
|
|
78
|
+
className
|
|
79
|
+
].join(" "),
|
|
80
|
+
children: [
|
|
81
|
+
/* @__PURE__ */ jsx3(
|
|
82
|
+
"div",
|
|
83
|
+
{
|
|
84
|
+
className: [
|
|
85
|
+
"flex min-w-full shrink-0 items-center justify-around gap-8",
|
|
86
|
+
direction === "left" ? "animate-marquee" : "animate-marquee-reverse",
|
|
87
|
+
pauseOnHover ? "group-hover:[animation-play-state:paused]" : ""
|
|
88
|
+
].join(" "),
|
|
89
|
+
style: { animationDuration: `${speed}s` },
|
|
90
|
+
children
|
|
91
|
+
}
|
|
92
|
+
),
|
|
93
|
+
/* @__PURE__ */ jsx3(
|
|
94
|
+
"div",
|
|
95
|
+
{
|
|
96
|
+
"aria-hidden": "true",
|
|
97
|
+
className: [
|
|
98
|
+
"flex min-w-full shrink-0 items-center justify-around gap-8",
|
|
99
|
+
direction === "left" ? "animate-marquee" : "animate-marquee-reverse",
|
|
100
|
+
pauseOnHover ? "group-hover:[animation-play-state:paused]" : ""
|
|
101
|
+
].join(" "),
|
|
102
|
+
style: { animationDuration: `${speed}s` },
|
|
103
|
+
children
|
|
104
|
+
}
|
|
105
|
+
)
|
|
106
|
+
]
|
|
107
|
+
}
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// src/components/number-ticker.tsx
|
|
112
|
+
import { useEffect, useRef as useRef2, useState } from "react";
|
|
113
|
+
import { jsxs as jsxs2 } from "react/jsx-runtime";
|
|
114
|
+
function NumberTicker({
|
|
115
|
+
value,
|
|
116
|
+
duration = 1500,
|
|
117
|
+
className = "",
|
|
118
|
+
prefix = "",
|
|
119
|
+
suffix = ""
|
|
120
|
+
}) {
|
|
121
|
+
const [current, setCurrent] = useState(0);
|
|
122
|
+
const startTimeRef = useRef2(null);
|
|
123
|
+
useEffect(() => {
|
|
124
|
+
let animationFrame;
|
|
125
|
+
const animate = (timestamp) => {
|
|
126
|
+
if (!startTimeRef.current) startTimeRef.current = timestamp;
|
|
127
|
+
const progress = Math.min((timestamp - startTimeRef.current) / duration, 1);
|
|
128
|
+
const easeOut = 1 - Math.pow(1 - progress, 3);
|
|
129
|
+
setCurrent(Math.floor(easeOut * value));
|
|
130
|
+
if (progress < 1) {
|
|
131
|
+
animationFrame = requestAnimationFrame(animate);
|
|
132
|
+
} else {
|
|
133
|
+
setCurrent(value);
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
animationFrame = requestAnimationFrame(animate);
|
|
137
|
+
return () => cancelAnimationFrame(animationFrame);
|
|
138
|
+
}, [value, duration]);
|
|
139
|
+
return /* @__PURE__ */ jsxs2("span", { className: ["tabular-nums font-mono", className].join(" "), children: [
|
|
140
|
+
prefix,
|
|
141
|
+
current.toLocaleString(),
|
|
142
|
+
suffix
|
|
143
|
+
] });
|
|
144
|
+
}
|
|
145
|
+
|
|
27
146
|
// src/components/spotlight-card.tsx
|
|
28
|
-
import { motion as
|
|
29
|
-
import { jsx as
|
|
147
|
+
import { motion as motion3 } from "motion/react";
|
|
148
|
+
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
30
149
|
function SpotlightCard({
|
|
31
150
|
children,
|
|
32
151
|
glowColor = "#8b5cf6",
|
|
@@ -35,8 +154,8 @@ function SpotlightCard({
|
|
|
35
154
|
className = "",
|
|
36
155
|
...props
|
|
37
156
|
}) {
|
|
38
|
-
return /* @__PURE__ */
|
|
39
|
-
|
|
157
|
+
return /* @__PURE__ */ jsxs3(
|
|
158
|
+
motion3.div,
|
|
40
159
|
{
|
|
41
160
|
...props,
|
|
42
161
|
whileHover: { scale: 1.01, y: -4 },
|
|
@@ -47,8 +166,8 @@ function SpotlightCard({
|
|
|
47
166
|
background: "linear-gradient(135deg, rgba(15,23,42,0.78), rgba(15,23,42,0.42))"
|
|
48
167
|
},
|
|
49
168
|
children: [
|
|
50
|
-
/* @__PURE__ */
|
|
51
|
-
|
|
169
|
+
/* @__PURE__ */ jsx4(
|
|
170
|
+
motion3.div,
|
|
52
171
|
{
|
|
53
172
|
"aria-hidden": "true",
|
|
54
173
|
className: "pointer-events-none absolute inset-0 opacity-90",
|
|
@@ -59,33 +178,113 @@ function SpotlightCard({
|
|
|
59
178
|
}
|
|
60
179
|
}
|
|
61
180
|
),
|
|
62
|
-
/* @__PURE__ */
|
|
181
|
+
/* @__PURE__ */ jsx4("div", { className: "relative z-10", children })
|
|
63
182
|
]
|
|
64
183
|
}
|
|
65
184
|
);
|
|
66
185
|
}
|
|
67
186
|
|
|
68
187
|
// src/components/text-reveal.tsx
|
|
69
|
-
import { motion as
|
|
70
|
-
import { jsx as
|
|
188
|
+
import { motion as motion4 } from "motion/react";
|
|
189
|
+
import { jsx as jsx5 } from "react/jsx-runtime";
|
|
71
190
|
function TextReveal({
|
|
72
191
|
as: Component = "h1",
|
|
73
192
|
children,
|
|
74
193
|
className = "",
|
|
75
194
|
...props
|
|
76
195
|
}) {
|
|
77
|
-
return /* @__PURE__ */
|
|
78
|
-
|
|
196
|
+
return /* @__PURE__ */ jsx5(
|
|
197
|
+
motion4.div,
|
|
79
198
|
{
|
|
80
199
|
initial: { opacity: 0, y: 18 },
|
|
81
200
|
animate: { opacity: 1, y: 0 },
|
|
82
201
|
transition: { duration: 0.5, ease: "easeOut" },
|
|
83
|
-
children: /* @__PURE__ */
|
|
202
|
+
children: /* @__PURE__ */ jsx5(Component, { ...props, className, children })
|
|
203
|
+
}
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// src/components/tilt-card.tsx
|
|
208
|
+
import { useRef as useRef3, useState as useState2 } from "react";
|
|
209
|
+
import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
210
|
+
function TiltCard({
|
|
211
|
+
children,
|
|
212
|
+
maxTilt = 15,
|
|
213
|
+
glare = true,
|
|
214
|
+
className = ""
|
|
215
|
+
}) {
|
|
216
|
+
const cardRef = useRef3(null);
|
|
217
|
+
const [rotateX, setRotateX] = useState2(0);
|
|
218
|
+
const [rotateY, setRotateY] = useState2(0);
|
|
219
|
+
const [glarePos, setGlarePos] = useState2({ x: 50, y: 50, opacity: 0 });
|
|
220
|
+
const handleMouseMove = (e) => {
|
|
221
|
+
if (!cardRef.current) return;
|
|
222
|
+
const rect = cardRef.current.getBoundingClientRect();
|
|
223
|
+
const x = e.clientX - rect.left;
|
|
224
|
+
const y = e.clientY - rect.top;
|
|
225
|
+
const centerX = rect.width / 2;
|
|
226
|
+
const centerY = rect.height / 2;
|
|
227
|
+
const rotX = (y - centerY) / centerY * -maxTilt;
|
|
228
|
+
const rotY = (x - centerX) / centerX * maxTilt;
|
|
229
|
+
setRotateX(rotX);
|
|
230
|
+
setRotateY(rotY);
|
|
231
|
+
if (glare) {
|
|
232
|
+
setGlarePos({
|
|
233
|
+
x: x / rect.width * 100,
|
|
234
|
+
y: y / rect.height * 100,
|
|
235
|
+
opacity: 0.25
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
const handleMouseLeave = () => {
|
|
240
|
+
setRotateX(0);
|
|
241
|
+
setRotateY(0);
|
|
242
|
+
setGlarePos((prev) => ({ ...prev, opacity: 0 }));
|
|
243
|
+
};
|
|
244
|
+
return /* @__PURE__ */ jsx6(
|
|
245
|
+
"div",
|
|
246
|
+
{
|
|
247
|
+
style: { perspective: "1000px" },
|
|
248
|
+
className: "inline-block w-full",
|
|
249
|
+
children: /* @__PURE__ */ jsxs4(
|
|
250
|
+
"div",
|
|
251
|
+
{
|
|
252
|
+
ref: cardRef,
|
|
253
|
+
onMouseMove: handleMouseMove,
|
|
254
|
+
onMouseLeave: handleMouseLeave,
|
|
255
|
+
style: {
|
|
256
|
+
transform: `rotateX(${rotateX}deg) rotateY(${rotateY}deg)`,
|
|
257
|
+
transition: "transform 0.15s ease-out",
|
|
258
|
+
transformStyle: "preserve-3d"
|
|
259
|
+
},
|
|
260
|
+
className: [
|
|
261
|
+
"relative overflow-hidden rounded-3xl border border-white/10 bg-slate-900/80 p-6 backdrop-blur-xl",
|
|
262
|
+
className
|
|
263
|
+
].join(" "),
|
|
264
|
+
children: [
|
|
265
|
+
glare && /* @__PURE__ */ jsx6(
|
|
266
|
+
"div",
|
|
267
|
+
{
|
|
268
|
+
className: "pointer-events-none absolute -inset-px transition-opacity duration-300",
|
|
269
|
+
style: {
|
|
270
|
+
opacity: glarePos.opacity,
|
|
271
|
+
background: `radial-gradient(circle at ${glarePos.x}% ${glarePos.y}%, rgba(255,255,255,0.4), transparent 60%)`
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
),
|
|
275
|
+
/* @__PURE__ */ jsx6("div", { style: { transform: "translateZ(20px)" }, children })
|
|
276
|
+
]
|
|
277
|
+
}
|
|
278
|
+
)
|
|
84
279
|
}
|
|
85
280
|
);
|
|
86
281
|
}
|
|
87
282
|
export {
|
|
88
283
|
FadeInSection,
|
|
284
|
+
Magnetic,
|
|
285
|
+
Marquee,
|
|
286
|
+
NumberTicker,
|
|
89
287
|
SpotlightCard,
|
|
90
|
-
TextReveal
|
|
288
|
+
TextReveal,
|
|
289
|
+
TiltCard
|
|
91
290
|
};
|