create-vexcms 0.1.0-alpha.2 → 0.1.0-alpha.3
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 +11 -3
- package/dist/index.js +1183 -1013
- package/package.json +1 -1
- package/templates/base-nextjs/README.md +11 -3
- package/templates/base-nextjs/convex/tsconfig.json +2 -2
- package/templates/base-nextjs/package.json +6 -7
- package/templates/marketing-site/src/components/motion-primitives/animated-group.tsx +44 -120
- package/templates/marketing-site/src/components/motion-primitives/reveal.module.css +32 -0
- package/templates/marketing-site/src/components/motion-primitives/text-effect.tsx +41 -267
- package/templates/marketing-site/src/vexcms/blocks/Hero/index.tsx +10 -44
package/package.json
CHANGED
|
@@ -31,9 +31,17 @@ NEXT_PUBLIC_CONVEX_URL=https://your-deployment.convex.cloud
|
|
|
31
31
|
NEXT_PUBLIC_CONVEX_SITE_URL=https://your-deployment.convex.site
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
Better Auth runs inside your Convex deployment, not in the Next.js server —
|
|
35
|
+
it does **not** read `.env.local`. Set the same two values on the
|
|
36
|
+
deployment itself:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npx convex env set SITE_URL http://localhost:3010
|
|
40
|
+
npx convex env set BETTER_AUTH_SECRET <the BETTER_AUTH_SECRET value from .env.local>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Skipping this step is the most common cause of a `403` on your first sign-in
|
|
44
|
+
attempt.
|
|
37
45
|
|
|
38
46
|
### 4. Run the dev servers
|
|
39
47
|
|
|
@@ -8,16 +8,16 @@
|
|
|
8
8
|
"allowSyntheticDefaultImports": true,
|
|
9
9
|
"target": "ESNext",
|
|
10
10
|
"lib": ["ES2023", "dom", "dom.iterable"],
|
|
11
|
+
"types": ["node"],
|
|
11
12
|
"forceConsistentCasingInFileNames": true,
|
|
12
13
|
"module": "ESNext",
|
|
13
14
|
"isolatedModules": true,
|
|
14
15
|
"noEmit": true,
|
|
15
|
-
"baseUrl": ".",
|
|
16
16
|
"paths": {
|
|
17
17
|
"~/*": ["../src/*"],
|
|
18
18
|
"@convex/*": ["./*"]
|
|
19
19
|
}
|
|
20
20
|
},
|
|
21
|
-
"include": ["./**/*"],
|
|
21
|
+
"include": ["./**/*", "../src/vex.types.ts"],
|
|
22
22
|
"exclude": ["./_generated"]
|
|
23
23
|
}
|
|
@@ -24,18 +24,17 @@
|
|
|
24
24
|
"@t3-oss/env-nextjs": "0.13.11",
|
|
25
25
|
"@tanstack/react-form": "1.33.1",
|
|
26
26
|
"@tanstack/react-query": "5.101.2",
|
|
27
|
-
"@vexcms/better-auth": "~0.1.0-alpha.
|
|
28
|
-
"@vexcms/core": "~0.1.0-alpha.
|
|
29
|
-
"@vexcms/file-storage-convex": "~0.1.0-alpha.
|
|
30
|
-
"@vexcms/next": "~0.1.0-alpha.
|
|
31
|
-
"@vexcms/react": "~0.1.0-alpha.
|
|
27
|
+
"@vexcms/better-auth": "~0.1.0-alpha.3",
|
|
28
|
+
"@vexcms/core": "~0.1.0-alpha.3",
|
|
29
|
+
"@vexcms/file-storage-convex": "~0.1.0-alpha.3",
|
|
30
|
+
"@vexcms/next": "~0.1.0-alpha.3",
|
|
31
|
+
"@vexcms/react": "~0.1.0-alpha.3",
|
|
32
32
|
"better-auth": "1.6.23",
|
|
33
33
|
"class-variance-authority": "0.7.1",
|
|
34
34
|
"clsx": "2.1.1",
|
|
35
35
|
"convex": "1.44.0",
|
|
36
36
|
"convex-helpers": "0.1.120",
|
|
37
37
|
"lucide-react": "0.577.0",
|
|
38
|
-
"motion": "13.1.1",
|
|
39
38
|
"next": "16.3.3",
|
|
40
39
|
"nuqs": "2.9.0",
|
|
41
40
|
"react": "19.2.7",
|
|
@@ -53,7 +52,7 @@
|
|
|
53
52
|
"@types/node": "20.19.43",
|
|
54
53
|
"@types/react": "19.2.17",
|
|
55
54
|
"@types/react-dom": "19.2.3",
|
|
56
|
-
"@vexcms/cli": "~0.1.0-alpha.
|
|
55
|
+
"@vexcms/cli": "~0.1.0-alpha.3",
|
|
57
56
|
"babel-plugin-react-compiler": "1.0.0",
|
|
58
57
|
"eslint": "9.39.5",
|
|
59
58
|
"eslint-config-next": "15.5.9",
|
|
@@ -1,138 +1,62 @@
|
|
|
1
|
-
|
|
1
|
+
import React, { type ComponentPropsWithRef, type CSSProperties } from "react"
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import React, { type ComponentPropsWithRef } from "react"
|
|
3
|
+
import { cn } from "~/lib/utils"
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
| "blur"
|
|
8
|
-
| "blur-slide"
|
|
9
|
-
| "bounce"
|
|
10
|
-
| "fade"
|
|
11
|
-
| "flip"
|
|
12
|
-
| "rotate"
|
|
13
|
-
| "scale"
|
|
14
|
-
| "slide"
|
|
15
|
-
| "swing"
|
|
16
|
-
| "zoom"
|
|
5
|
+
import styles from "./reveal.module.css"
|
|
17
6
|
|
|
18
7
|
export type AnimatedGroupProps = ComponentPropsWithRef<"div"> & {
|
|
19
8
|
as?: React.ElementType
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
container?: Variants
|
|
25
|
-
item?: Variants
|
|
26
|
-
}
|
|
9
|
+
/** Seconds before the first child starts animating. */
|
|
10
|
+
delay?: number
|
|
11
|
+
/** Seconds between each child's start. */
|
|
12
|
+
stagger?: number
|
|
27
13
|
}
|
|
28
14
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
const defaultItemVariants: Variants = {
|
|
38
|
-
hidden: { opacity: 0 },
|
|
39
|
-
visible: { opacity: 1 },
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const presetVariants: Record<PresetType, Variants> = {
|
|
43
|
-
"blur-slide": {
|
|
44
|
-
hidden: { filter: "blur(4px)", y: 20 },
|
|
45
|
-
visible: { filter: "blur(0px)", y: 0 },
|
|
46
|
-
},
|
|
47
|
-
slide: {
|
|
48
|
-
hidden: { y: 20 },
|
|
49
|
-
visible: { y: 0 },
|
|
50
|
-
},
|
|
51
|
-
blur: {
|
|
52
|
-
hidden: { filter: "blur(4px)" },
|
|
53
|
-
visible: { filter: "blur(0px)" },
|
|
54
|
-
},
|
|
55
|
-
bounce: {
|
|
56
|
-
hidden: { y: -50 },
|
|
57
|
-
visible: {
|
|
58
|
-
transition: { type: "spring", damping: 10, stiffness: 400 },
|
|
59
|
-
y: 0,
|
|
60
|
-
},
|
|
61
|
-
},
|
|
62
|
-
fade: {},
|
|
63
|
-
flip: {
|
|
64
|
-
hidden: { rotateX: -90 },
|
|
65
|
-
visible: {
|
|
66
|
-
rotateX: 0,
|
|
67
|
-
transition: { type: "spring", damping: 20, stiffness: 300 },
|
|
68
|
-
},
|
|
69
|
-
},
|
|
70
|
-
rotate: {
|
|
71
|
-
hidden: { rotate: -180 },
|
|
72
|
-
visible: {
|
|
73
|
-
rotate: 0,
|
|
74
|
-
transition: { type: "spring", damping: 15, stiffness: 200 },
|
|
75
|
-
},
|
|
76
|
-
},
|
|
77
|
-
scale: {
|
|
78
|
-
hidden: { scale: 0.8 },
|
|
79
|
-
visible: { scale: 1 },
|
|
80
|
-
},
|
|
81
|
-
swing: {
|
|
82
|
-
hidden: { rotate: -10 },
|
|
83
|
-
visible: {
|
|
84
|
-
rotate: 0,
|
|
85
|
-
transition: { type: "spring", damping: 8, stiffness: 300 },
|
|
86
|
-
},
|
|
87
|
-
},
|
|
88
|
-
zoom: {
|
|
89
|
-
hidden: { scale: 0.5 },
|
|
90
|
-
visible: {
|
|
91
|
-
scale: 1,
|
|
92
|
-
transition: { type: "spring", damping: 20, stiffness: 300 },
|
|
93
|
-
},
|
|
94
|
-
},
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
const addDefaultVariants = (variants: Variants) => ({
|
|
98
|
-
hidden: { ...defaultItemVariants.hidden, ...variants.hidden },
|
|
99
|
-
visible: { ...defaultItemVariants.visible, ...variants.visible },
|
|
100
|
-
})
|
|
101
|
-
|
|
102
|
-
function AnimatedGroup({
|
|
103
|
-
as = "div",
|
|
104
|
-
asChild = "div",
|
|
15
|
+
/**
|
|
16
|
+
* Staggers a blur + fade + rise entrance across its direct children using a
|
|
17
|
+
* pure-CSS keyframe (see `reveal.module.css`) — no animation-library
|
|
18
|
+
* dependency. Each child is wrapped in a div carrying the animation with an
|
|
19
|
+
* incremented `animation-delay`.
|
|
20
|
+
*/
|
|
21
|
+
export function AnimatedGroup({
|
|
22
|
+
as: Tag = "div",
|
|
105
23
|
children,
|
|
106
24
|
className,
|
|
107
|
-
|
|
108
|
-
|
|
25
|
+
delay = 0,
|
|
26
|
+
stagger = 0.05,
|
|
109
27
|
...divProps
|
|
110
28
|
}: AnimatedGroupProps) {
|
|
111
|
-
const selectedVariants = {
|
|
112
|
-
container: addDefaultVariants(defaultContainerVariants),
|
|
113
|
-
item: addDefaultVariants(preset ? presetVariants[preset] : {}),
|
|
114
|
-
}
|
|
115
|
-
const containerVariants = variants?.container ?? selectedVariants.container
|
|
116
|
-
const itemVariants = variants?.item ?? selectedVariants.item
|
|
117
|
-
|
|
118
|
-
const MotionComponent = motion.create(as)
|
|
119
|
-
const MotionChild = motion.create(asChild)
|
|
120
|
-
|
|
121
29
|
return (
|
|
122
|
-
<
|
|
123
|
-
animate="visible"
|
|
124
|
-
className={className}
|
|
125
|
-
initial="hidden"
|
|
126
|
-
variants={containerVariants}
|
|
127
|
-
{...divProps}
|
|
128
|
-
>
|
|
30
|
+
<Tag className={className} {...divProps}>
|
|
129
31
|
{React.Children.map(children, (child, index) => (
|
|
130
|
-
<
|
|
32
|
+
<div
|
|
33
|
+
className={styles.item}
|
|
34
|
+
style={{ "--reveal-delay": `${delay + index * stagger}s` } as CSSProperties}
|
|
35
|
+
>
|
|
131
36
|
{child}
|
|
132
|
-
</
|
|
37
|
+
</div>
|
|
133
38
|
))}
|
|
134
|
-
</
|
|
39
|
+
</Tag>
|
|
135
40
|
)
|
|
136
41
|
}
|
|
137
42
|
|
|
138
|
-
|
|
43
|
+
/**
|
|
44
|
+
* Applies the same reveal animation to a single element without adding a
|
|
45
|
+
* wrapper div — for callers that only need one animated child.
|
|
46
|
+
*/
|
|
47
|
+
export function AnimatedItem({
|
|
48
|
+
children,
|
|
49
|
+
className,
|
|
50
|
+
delay = 0,
|
|
51
|
+
...divProps
|
|
52
|
+
}: ComponentPropsWithRef<"div"> & { delay?: number }) {
|
|
53
|
+
return (
|
|
54
|
+
<div
|
|
55
|
+
className={cn(styles.item, className)}
|
|
56
|
+
style={{ "--reveal-delay": `${delay}s` } as CSSProperties}
|
|
57
|
+
{...divProps}
|
|
58
|
+
>
|
|
59
|
+
{children}
|
|
60
|
+
</div>
|
|
61
|
+
)
|
|
62
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Shared "reveal" animation for AnimatedGroup and TextEffect — the
|
|
3
|
+
* blur + fade + rise entrance the hero uses, implemented in pure CSS so the
|
|
4
|
+
* template carries no animation-library dependency.
|
|
5
|
+
*
|
|
6
|
+
* `both` fill-mode keeps elements hidden until their delay elapses, which is
|
|
7
|
+
* what makes the stagger read as a sequence. Delay per element arrives via
|
|
8
|
+
* the `--reveal-delay` custom property.
|
|
9
|
+
*/
|
|
10
|
+
@keyframes reveal {
|
|
11
|
+
from {
|
|
12
|
+
opacity: 0;
|
|
13
|
+
filter: blur(12px);
|
|
14
|
+
transform: translateY(12px);
|
|
15
|
+
}
|
|
16
|
+
to {
|
|
17
|
+
opacity: 1;
|
|
18
|
+
filter: blur(0);
|
|
19
|
+
transform: translateY(0);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.item {
|
|
24
|
+
animation: reveal 1.1s cubic-bezier(0.25, 1, 0.5, 1) both;
|
|
25
|
+
animation-delay: var(--reveal-delay, 0s);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
@media (prefers-reduced-motion: reduce) {
|
|
29
|
+
.item {
|
|
30
|
+
animation: none;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -1,292 +1,66 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
AnimatePresence,
|
|
5
|
-
motion,
|
|
6
|
-
type TargetAndTransition,
|
|
7
|
-
type Transition,
|
|
8
|
-
type Variant,
|
|
9
|
-
type Variants,
|
|
10
|
-
} from "motion/react"
|
|
11
|
-
import React from "react"
|
|
1
|
+
import React, { type CSSProperties } from "react"
|
|
12
2
|
|
|
13
3
|
import { cn } from "~/lib/utils"
|
|
14
4
|
|
|
15
|
-
|
|
16
|
-
|
|
5
|
+
import styles from "./reveal.module.css"
|
|
6
|
+
|
|
7
|
+
export type PerType = "line" | "word"
|
|
17
8
|
|
|
18
9
|
export type TextEffectProps = {
|
|
19
10
|
as?: keyof React.JSX.IntrinsicElements
|
|
20
11
|
children: string
|
|
21
12
|
className?: string
|
|
22
|
-
|
|
13
|
+
/** Seconds before the first segment starts animating. */
|
|
23
14
|
delay?: number
|
|
24
|
-
|
|
25
|
-
onAnimationStart?: () => void
|
|
15
|
+
/** Split granularity for the stagger. Lines split on `\n`. */
|
|
26
16
|
per?: PerType
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
segmentWrapperClassName?: string
|
|
30
|
-
speedReveal?: number
|
|
31
|
-
speedSegment?: number
|
|
32
|
-
style?: React.CSSProperties
|
|
33
|
-
trigger?: boolean
|
|
34
|
-
variants?: {
|
|
35
|
-
container?: Variants
|
|
36
|
-
item?: Variants
|
|
37
|
-
}
|
|
17
|
+
/** Seconds between each segment's start. */
|
|
18
|
+
stagger?: number
|
|
38
19
|
}
|
|
39
20
|
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
word: 0.05,
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const defaultContainerVariants: Variants = {
|
|
47
|
-
hidden: { opacity: 0 },
|
|
48
|
-
exit: {
|
|
49
|
-
transition: { staggerChildren: 0.05, staggerDirection: -1 },
|
|
50
|
-
},
|
|
51
|
-
visible: {
|
|
52
|
-
opacity: 1,
|
|
53
|
-
transition: {
|
|
54
|
-
staggerChildren: 0.05,
|
|
55
|
-
},
|
|
56
|
-
},
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const defaultItemVariants: Variants = {
|
|
60
|
-
hidden: { opacity: 0 },
|
|
61
|
-
exit: { opacity: 0 },
|
|
62
|
-
visible: {
|
|
63
|
-
opacity: 1,
|
|
64
|
-
},
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const presetVariants: Record<
|
|
68
|
-
PresetType,
|
|
69
|
-
{ container: Variants; item: Variants }
|
|
70
|
-
> = {
|
|
71
|
-
slide: {
|
|
72
|
-
container: defaultContainerVariants,
|
|
73
|
-
item: {
|
|
74
|
-
hidden: { opacity: 0, y: 20 },
|
|
75
|
-
exit: { opacity: 0, y: 20 },
|
|
76
|
-
visible: { opacity: 1, y: 0 },
|
|
77
|
-
},
|
|
78
|
-
},
|
|
79
|
-
blur: {
|
|
80
|
-
container: defaultContainerVariants,
|
|
81
|
-
item: {
|
|
82
|
-
hidden: { filter: "blur(12px)", opacity: 0 },
|
|
83
|
-
exit: { filter: "blur(12px)", opacity: 0 },
|
|
84
|
-
visible: { filter: "blur(0px)", opacity: 1 },
|
|
85
|
-
},
|
|
86
|
-
},
|
|
87
|
-
fade: {
|
|
88
|
-
container: defaultContainerVariants,
|
|
89
|
-
item: {
|
|
90
|
-
hidden: { opacity: 0 },
|
|
91
|
-
exit: { opacity: 0 },
|
|
92
|
-
visible: { opacity: 1 },
|
|
93
|
-
},
|
|
94
|
-
},
|
|
95
|
-
"fade-in-blur": {
|
|
96
|
-
container: defaultContainerVariants,
|
|
97
|
-
item: {
|
|
98
|
-
hidden: { filter: "blur(12px)", opacity: 0, y: 20 },
|
|
99
|
-
exit: { filter: "blur(12px)", opacity: 0, y: 20 },
|
|
100
|
-
visible: { filter: "blur(0px)", opacity: 1, y: 0 },
|
|
101
|
-
},
|
|
102
|
-
},
|
|
103
|
-
scale: {
|
|
104
|
-
container: defaultContainerVariants,
|
|
105
|
-
item: {
|
|
106
|
-
hidden: { opacity: 0, scale: 0 },
|
|
107
|
-
exit: { opacity: 0, scale: 0 },
|
|
108
|
-
visible: { opacity: 1, scale: 1 },
|
|
109
|
-
},
|
|
110
|
-
},
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const AnimationComponent: React.FC<{
|
|
114
|
-
per: "char" | "line" | "word"
|
|
115
|
-
segment: string
|
|
116
|
-
segmentWrapperClassName?: string
|
|
117
|
-
variants: Variants
|
|
118
|
-
}> = React.memo(({ per, segment, segmentWrapperClassName, variants }) => {
|
|
119
|
-
const content =
|
|
120
|
-
per === "line" ? (
|
|
121
|
-
<motion.span className="block" variants={variants}>
|
|
122
|
-
{segment}
|
|
123
|
-
</motion.span>
|
|
124
|
-
) : per === "word" ? (
|
|
125
|
-
<motion.span
|
|
126
|
-
aria-hidden="true"
|
|
127
|
-
className="inline-block whitespace-pre"
|
|
128
|
-
variants={variants}
|
|
129
|
-
>
|
|
130
|
-
{segment}
|
|
131
|
-
</motion.span>
|
|
132
|
-
) : (
|
|
133
|
-
<motion.span className="inline-block whitespace-pre">
|
|
134
|
-
{segment.split("").map((char, charIndex) => (
|
|
135
|
-
<motion.span
|
|
136
|
-
aria-hidden="true"
|
|
137
|
-
className="inline-block whitespace-pre"
|
|
138
|
-
key={`char-${charIndex}`}
|
|
139
|
-
variants={variants}
|
|
140
|
-
>
|
|
141
|
-
{char}
|
|
142
|
-
</motion.span>
|
|
143
|
-
))}
|
|
144
|
-
</motion.span>
|
|
145
|
-
)
|
|
146
|
-
|
|
147
|
-
if (!segmentWrapperClassName) {
|
|
148
|
-
return content
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
const defaultWrapperClassName = per === "line" ? "block" : "inline-block"
|
|
152
|
-
|
|
153
|
-
return (
|
|
154
|
-
<span className={cn(defaultWrapperClassName, segmentWrapperClassName)}>
|
|
155
|
-
{content}
|
|
156
|
-
</span>
|
|
157
|
-
)
|
|
158
|
-
})
|
|
159
|
-
|
|
160
|
-
AnimationComponent.displayName = "AnimationComponent"
|
|
161
|
-
|
|
162
|
-
const splitText = (text: string, per: "char" | "line" | "word") => {
|
|
163
|
-
if (per === "line") return text.split("\n")
|
|
164
|
-
return text.split(/(\s+)/)
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
const hasTransition = (
|
|
168
|
-
variant: Variant,
|
|
169
|
-
): variant is TargetAndTransition & { transition?: Transition } => {
|
|
170
|
-
return (
|
|
171
|
-
typeof variant === "object" && variant !== null && "transition" in variant
|
|
172
|
-
)
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
const createVariantsWithTransition = (
|
|
176
|
-
baseVariants: Variants,
|
|
177
|
-
transition?: Transition & { exit?: Transition },
|
|
178
|
-
): Variants => {
|
|
179
|
-
if (!transition) return baseVariants
|
|
180
|
-
|
|
181
|
-
const { ...mainTransition } = transition
|
|
182
|
-
|
|
183
|
-
return {
|
|
184
|
-
...baseVariants,
|
|
185
|
-
exit: {
|
|
186
|
-
...baseVariants.exit,
|
|
187
|
-
transition: {
|
|
188
|
-
...(hasTransition(baseVariants.exit)
|
|
189
|
-
? baseVariants.exit.transition
|
|
190
|
-
: {}),
|
|
191
|
-
...mainTransition,
|
|
192
|
-
staggerDirection: -1,
|
|
193
|
-
},
|
|
194
|
-
},
|
|
195
|
-
visible: {
|
|
196
|
-
...baseVariants.visible,
|
|
197
|
-
transition: {
|
|
198
|
-
...(hasTransition(baseVariants.visible)
|
|
199
|
-
? baseVariants.visible.transition
|
|
200
|
-
: {}),
|
|
201
|
-
...mainTransition,
|
|
202
|
-
},
|
|
203
|
-
},
|
|
204
|
-
}
|
|
21
|
+
const defaultStagger: Record<PerType, number> = {
|
|
22
|
+
line: 0.15,
|
|
23
|
+
word: 0.04,
|
|
205
24
|
}
|
|
206
25
|
|
|
26
|
+
/**
|
|
27
|
+
* Staggered blur + fade + rise text entrance, split per word or per line —
|
|
28
|
+
* pure CSS (see `reveal.module.css`), no animation-library dependency.
|
|
29
|
+
* Word segments keep their trailing space inside the animated span so text
|
|
30
|
+
* still wraps naturally.
|
|
31
|
+
*/
|
|
207
32
|
export function TextEffect({
|
|
208
33
|
as = "p",
|
|
209
34
|
children,
|
|
210
35
|
className,
|
|
211
|
-
containerTransition,
|
|
212
36
|
delay = 0,
|
|
213
|
-
onAnimationComplete,
|
|
214
|
-
onAnimationStart,
|
|
215
37
|
per = "word",
|
|
216
|
-
|
|
217
|
-
segmentTransition,
|
|
218
|
-
segmentWrapperClassName,
|
|
219
|
-
speedReveal = 1,
|
|
220
|
-
speedSegment = 1,
|
|
221
|
-
style,
|
|
222
|
-
trigger = true,
|
|
223
|
-
variants,
|
|
38
|
+
stagger,
|
|
224
39
|
}: TextEffectProps) {
|
|
225
|
-
const
|
|
226
|
-
const
|
|
227
|
-
|
|
228
|
-
const
|
|
229
|
-
? presetVariants[preset]
|
|
230
|
-
: { container: defaultContainerVariants, item: defaultItemVariants }
|
|
231
|
-
|
|
232
|
-
const stagger = defaultStaggerTimes[per] / speedReveal
|
|
233
|
-
|
|
234
|
-
const baseDuration = 0.3 / speedSegment
|
|
235
|
-
|
|
236
|
-
const customStagger = hasTransition(variants?.container?.visible ?? {})
|
|
237
|
-
? (variants?.container?.visible as TargetAndTransition).transition
|
|
238
|
-
?.staggerChildren
|
|
239
|
-
: undefined
|
|
240
|
-
|
|
241
|
-
const customDelay = hasTransition(variants?.container?.visible ?? {})
|
|
242
|
-
? (variants?.container?.visible as TargetAndTransition).transition
|
|
243
|
-
?.delayChildren
|
|
244
|
-
: undefined
|
|
245
|
-
|
|
246
|
-
const computedVariants = {
|
|
247
|
-
container: createVariantsWithTransition(
|
|
248
|
-
variants?.container ?? baseVariants.container,
|
|
249
|
-
{
|
|
250
|
-
delayChildren: customDelay ?? delay,
|
|
251
|
-
staggerChildren: customStagger ?? stagger,
|
|
252
|
-
...containerTransition,
|
|
253
|
-
exit: {
|
|
254
|
-
staggerChildren: customStagger ?? stagger,
|
|
255
|
-
staggerDirection: -1,
|
|
256
|
-
},
|
|
257
|
-
},
|
|
258
|
-
),
|
|
259
|
-
item: createVariantsWithTransition(variants?.item ?? baseVariants.item, {
|
|
260
|
-
duration: baseDuration,
|
|
261
|
-
...segmentTransition,
|
|
262
|
-
}),
|
|
263
|
-
}
|
|
40
|
+
const Tag = as as React.ElementType
|
|
41
|
+
const segments =
|
|
42
|
+
per === "line" ? children.split("\n") : children.split(/(\s+)/).filter(Boolean)
|
|
43
|
+
const step = stagger ?? defaultStagger[per]
|
|
264
44
|
|
|
45
|
+
let animatedIndex = 0
|
|
265
46
|
return (
|
|
266
|
-
<
|
|
267
|
-
{
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
segmentWrapperClassName={segmentWrapperClassName}
|
|
285
|
-
variants={computedVariants.item}
|
|
286
|
-
/>
|
|
287
|
-
))}
|
|
288
|
-
</MotionTag>
|
|
289
|
-
)}
|
|
290
|
-
</AnimatePresence>
|
|
47
|
+
<Tag className={className}>
|
|
48
|
+
{segments.map((segment, index) => {
|
|
49
|
+
if (per === "word" && /^\s+$/.test(segment)) {
|
|
50
|
+
return <React.Fragment key={index}>{segment}</React.Fragment>
|
|
51
|
+
}
|
|
52
|
+
const segmentDelay = delay + animatedIndex * step
|
|
53
|
+
animatedIndex += 1
|
|
54
|
+
return (
|
|
55
|
+
<span
|
|
56
|
+
className={cn(styles.item, per === "line" ? "block" : "inline-block")}
|
|
57
|
+
key={index}
|
|
58
|
+
style={{ "--reveal-delay": `${segmentDelay}s` } as CSSProperties}
|
|
59
|
+
>
|
|
60
|
+
{segment}
|
|
61
|
+
</span>
|
|
62
|
+
)
|
|
63
|
+
})}
|
|
64
|
+
</Tag>
|
|
291
65
|
)
|
|
292
66
|
}
|