@stackvibe-studio/motion 0.1.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 ADDED
@@ -0,0 +1,64 @@
1
+ # @stackvibe-studio/motion
2
+
3
+ > Physics-based motion and animation primitives for StackVibe UI, powered by Framer Motion.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@stackvibe-studio/motion.svg)](https://www.npmjs.com/package/@stackvibe-studio/motion)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+
8
+ ## Installation
9
+
10
+ ```bash
11
+ npm install @stackvibe-studio/motion framer-motion
12
+ # or
13
+ pnpm add @stackvibe-studio/motion framer-motion
14
+ ```
15
+
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
27
+
28
+ ```tsx
29
+ import { Magnetic, TiltCard, NumberTicker, Marquee } from '@stackvibe-studio/motion';
30
+ import { Button } from '@stackvibe-studio/ui';
31
+
32
+ export function MotionDemo() {
33
+ return (
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>
54
+ );
55
+ }
56
+ ```
57
+
58
+ ## Documentation
59
+
60
+ [https://stackvibe-studio.github.io/stackvibe-ui/docs](https://stackvibe-studio.github.io/stackvibe-ui/docs)
61
+
62
+ ## License
63
+
64
+ MIT © [StackVibe Studio](https://github.com/StackVibe-Studio)
@@ -0,0 +1,8 @@
1
+ import type { HTMLMotionProps } from 'motion/react';
2
+ import type { ReactNode } from 'react';
3
+ export interface FadeInSectionProps extends HTMLMotionProps<'section'> {
4
+ children: ReactNode;
5
+ delay?: number;
6
+ y?: number;
7
+ }
8
+ export declare function FadeInSection({ children, delay, y, className, ...props }: FadeInSectionProps): import("react").JSX.Element;
@@ -0,0 +1,7 @@
1
+ import { type ReactNode } from 'react';
2
+ export interface MagneticProps {
3
+ children: ReactNode;
4
+ strength?: number;
5
+ className?: string;
6
+ }
7
+ export declare function Magnetic({ children, strength, className }: MagneticProps): import("react").JSX.Element;
@@ -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
@@ -1,4 +1,5 @@
1
1
  "use strict";
2
+ "use client";
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
@@ -20,14 +21,162 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
21
  // src/index.ts
21
22
  var index_exports = {};
22
23
  __export(index_exports, {
24
+ FadeInSection: () => FadeInSection,
25
+ Magnetic: () => Magnetic,
26
+ Marquee: () => Marquee,
27
+ NumberTicker: () => NumberTicker,
23
28
  SpotlightCard: () => SpotlightCard,
24
- TextReveal: () => TextReveal
29
+ TextReveal: () => TextReveal,
30
+ TiltCard: () => TiltCard
25
31
  });
26
32
  module.exports = __toCommonJS(index_exports);
27
33
 
28
- // src/components/spotlight-card.tsx
34
+ // src/components/fade-in.tsx
29
35
  var import_react = require("motion/react");
30
36
  var import_jsx_runtime = require("react/jsx-runtime");
37
+ function FadeInSection({
38
+ children,
39
+ delay = 0,
40
+ y = 24,
41
+ className = "",
42
+ ...props
43
+ }) {
44
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
45
+ import_react.motion.section,
46
+ {
47
+ ...props,
48
+ initial: { opacity: 0, y },
49
+ whileInView: { opacity: 1, y: 0 },
50
+ viewport: { once: true, amount: 0.2 },
51
+ transition: { duration: 0.5, delay, ease: "easeOut" },
52
+ className,
53
+ children
54
+ }
55
+ );
56
+ }
57
+
58
+ // src/components/magnetic.tsx
59
+ var import_react2 = require("motion/react");
60
+ var import_react3 = require("react");
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");
31
180
  function SpotlightCard({
32
181
  children,
33
182
  glowColor = "#8b5cf6",
@@ -36,8 +185,8 @@ function SpotlightCard({
36
185
  className = "",
37
186
  ...props
38
187
  }) {
39
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
40
- import_react.motion.div,
188
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
189
+ import_react5.motion.div,
41
190
  {
42
191
  ...props,
43
192
  whileHover: { scale: 1.01, y: -4 },
@@ -48,8 +197,8 @@ function SpotlightCard({
48
197
  background: "linear-gradient(135deg, rgba(15,23,42,0.78), rgba(15,23,42,0.42))"
49
198
  },
50
199
  children: [
51
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
52
- import_react.motion.div,
200
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
201
+ import_react5.motion.div,
53
202
  {
54
203
  "aria-hidden": "true",
55
204
  className: "pointer-events-none absolute inset-0 opacity-90",
@@ -60,33 +209,114 @@ function SpotlightCard({
60
209
  }
61
210
  }
62
211
  ),
63
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "relative z-10", children })
212
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "relative z-10", children })
64
213
  ]
65
214
  }
66
215
  );
67
216
  }
68
217
 
69
218
  // src/components/text-reveal.tsx
70
- var import_react2 = require("motion/react");
71
- var import_jsx_runtime2 = require("react/jsx-runtime");
219
+ var import_react6 = require("motion/react");
220
+ var import_jsx_runtime6 = require("react/jsx-runtime");
72
221
  function TextReveal({
73
222
  as: Component = "h1",
74
223
  children,
75
224
  className = "",
76
225
  ...props
77
226
  }) {
78
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
79
- import_react2.motion.div,
227
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
228
+ import_react6.motion.div,
80
229
  {
81
230
  initial: { opacity: 0, y: 18 },
82
231
  animate: { opacity: 1, y: 0 },
83
232
  transition: { duration: 0.5, ease: "easeOut" },
84
- children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Component, { ...props, className, children })
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
+ )
85
310
  }
86
311
  );
87
312
  }
88
313
  // Annotate the CommonJS export names for ESM import in node:
89
314
  0 && (module.exports = {
315
+ FadeInSection,
316
+ Magnetic,
317
+ Marquee,
318
+ NumberTicker,
90
319
  SpotlightCard,
91
- TextReveal
320
+ TextReveal,
321
+ TiltCard
92
322
  });
package/dist/index.d.ts CHANGED
@@ -1,2 +1,7 @@
1
+ export * from './components/fade-in';
2
+ export * from './components/magnetic';
3
+ export * from './components/marquee';
4
+ export * from './components/number-ticker';
1
5
  export * from './components/spotlight-card';
2
6
  export * from './components/text-reveal';
7
+ export * from './components/tilt-card';
package/dist/index.js CHANGED
@@ -1,6 +1,151 @@
1
- // src/components/spotlight-card.tsx
1
+ "use client";
2
+
3
+ // src/components/fade-in.tsx
2
4
  import { motion } from "motion/react";
3
- import { jsx, jsxs } from "react/jsx-runtime";
5
+ import { jsx } from "react/jsx-runtime";
6
+ function FadeInSection({
7
+ children,
8
+ delay = 0,
9
+ y = 24,
10
+ className = "",
11
+ ...props
12
+ }) {
13
+ return /* @__PURE__ */ jsx(
14
+ motion.section,
15
+ {
16
+ ...props,
17
+ initial: { opacity: 0, y },
18
+ whileInView: { opacity: 1, y: 0 },
19
+ viewport: { once: true, amount: 0.2 },
20
+ transition: { duration: 0.5, delay, ease: "easeOut" },
21
+ className,
22
+ children
23
+ }
24
+ );
25
+ }
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
+
146
+ // src/components/spotlight-card.tsx
147
+ import { motion as motion3 } from "motion/react";
148
+ import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
4
149
  function SpotlightCard({
5
150
  children,
6
151
  glowColor = "#8b5cf6",
@@ -9,8 +154,8 @@ function SpotlightCard({
9
154
  className = "",
10
155
  ...props
11
156
  }) {
12
- return /* @__PURE__ */ jsxs(
13
- motion.div,
157
+ return /* @__PURE__ */ jsxs3(
158
+ motion3.div,
14
159
  {
15
160
  ...props,
16
161
  whileHover: { scale: 1.01, y: -4 },
@@ -21,8 +166,8 @@ function SpotlightCard({
21
166
  background: "linear-gradient(135deg, rgba(15,23,42,0.78), rgba(15,23,42,0.42))"
22
167
  },
23
168
  children: [
24
- /* @__PURE__ */ jsx(
25
- motion.div,
169
+ /* @__PURE__ */ jsx4(
170
+ motion3.div,
26
171
  {
27
172
  "aria-hidden": "true",
28
173
  className: "pointer-events-none absolute inset-0 opacity-90",
@@ -33,32 +178,113 @@ function SpotlightCard({
33
178
  }
34
179
  }
35
180
  ),
36
- /* @__PURE__ */ jsx("div", { className: "relative z-10", children })
181
+ /* @__PURE__ */ jsx4("div", { className: "relative z-10", children })
37
182
  ]
38
183
  }
39
184
  );
40
185
  }
41
186
 
42
187
  // src/components/text-reveal.tsx
43
- import { motion as motion2 } from "motion/react";
44
- import { jsx as jsx2 } from "react/jsx-runtime";
188
+ import { motion as motion4 } from "motion/react";
189
+ import { jsx as jsx5 } from "react/jsx-runtime";
45
190
  function TextReveal({
46
191
  as: Component = "h1",
47
192
  children,
48
193
  className = "",
49
194
  ...props
50
195
  }) {
51
- return /* @__PURE__ */ jsx2(
52
- motion2.div,
196
+ return /* @__PURE__ */ jsx5(
197
+ motion4.div,
53
198
  {
54
199
  initial: { opacity: 0, y: 18 },
55
200
  animate: { opacity: 1, y: 0 },
56
201
  transition: { duration: 0.5, ease: "easeOut" },
57
- children: /* @__PURE__ */ jsx2(Component, { ...props, className, children })
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
+ )
58
279
  }
59
280
  );
60
281
  }
61
282
  export {
283
+ FadeInSection,
284
+ Magnetic,
285
+ Marquee,
286
+ NumberTicker,
62
287
  SpotlightCard,
63
- TextReveal
288
+ TextReveal,
289
+ TiltCard
64
290
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stackvibe-studio/motion",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "description": "Motion primitives for StackVibe UI.",
6
6
  "repository": {