@catalystsoftware/ui 1.0.5 → 1.0.6

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.
Files changed (155) hide show
  1. package/dist/data/data.tsx +29 -29
  2. package/dist/data/tailwind.config.js +3821 -261
  3. package/dist/data.tsx +29 -29
  4. package/package.json +4 -3
  5. package/components/catalyst-ui/buttons/burger.tsx +0 -207
  6. package/components/catalyst-ui/core/data-display/timeline.tsx +0 -210
  7. package/components/catalyst-ui/core/feedback/alert.tsx +0 -491
  8. package/components/catalyst-ui/core/feedback/spinner-1.tsx +0 -65
  9. package/components/catalyst-ui/core/feedback/toast.tsx +0 -1857
  10. package/components/catalyst-ui/core/navigation/menu.tsx +0 -164
  11. package/components/catalyst-ui/forms/toggle-class.tsx +0 -176
  12. package/components/catalyst-ui/hooks/use-copy-to-clipboard.tsx +0 -419
  13. package/components/catalyst-ui/hooks/use-counter.tsx +0 -13
  14. package/components/catalyst-ui/hooks/use-event-listener.tsx +0 -23
  15. package/components/catalyst-ui/hooks/use-export-markdown.tsx +0 -47
  16. package/components/catalyst-ui/hooks/use-focus.tsx +0 -17
  17. package/components/catalyst-ui/hooks/use-interval.tsx +0 -23
  18. package/components/catalyst-ui/hooks/use-is-client.tsx +0 -16
  19. package/components/catalyst-ui/hooks/use-media-query.tsx +0 -19
  20. package/components/catalyst-ui/hooks/use-mobile.tsx +0 -19
  21. package/components/catalyst-ui/hooks/use-resize-observer.tsx +0 -81
  22. package/components/catalyst-ui/hooks/use-timeout.tsx +0 -21
  23. package/components/catalyst-ui/hooks/use-timer.tsx +0 -209
  24. package/components/catalyst-ui/hooks/use-toggle.tsx +0 -12
  25. package/components/catalyst-ui/media/image.tsx +0 -13
  26. package/components/catalyst-ui/overlays/dual-sidebar.tsx +0 -4142
  27. package/components/catalyst-ui/overlays/sidebar-original.tsx +0 -726
  28. package/components/catalyst-ui/primitives/accordion.tsx +0 -250
  29. package/components/catalyst-ui/primitives/alert-dialog.tsx +0 -126
  30. package/components/catalyst-ui/primitives/aspect-ratio.tsx +0 -9
  31. package/components/catalyst-ui/primitives/avatar.tsx +0 -296
  32. package/components/catalyst-ui/primitives/badge.tsx +0 -57
  33. package/components/catalyst-ui/primitives/breadcrumb.tsx +0 -101
  34. package/components/catalyst-ui/primitives/button.tsx +0 -265
  35. package/components/catalyst-ui/primitives/calendar-v4.tsx +0 -208
  36. package/components/catalyst-ui/primitives/calendar.tsx +0 -295
  37. package/components/catalyst-ui/primitives/card.tsx +0 -618
  38. package/components/catalyst-ui/primitives/carousel.tsx +0 -238
  39. package/components/catalyst-ui/primitives/chart.tsx +0 -347
  40. package/components/catalyst-ui/primitives/checkbox.tsx +0 -225
  41. package/components/catalyst-ui/primitives/collapsible.tsx +0 -212
  42. package/components/catalyst-ui/primitives/command.tsx +0 -393
  43. package/components/catalyst-ui/primitives/context-menu.tsx +0 -236
  44. package/components/catalyst-ui/primitives/dialog.tsx +0 -471
  45. package/components/catalyst-ui/primitives/drawer.tsx +0 -761
  46. package/components/catalyst-ui/primitives/dropdown-menu.tsx +0 -290
  47. package/components/catalyst-ui/primitives/empty.tsx +0 -104
  48. package/components/catalyst-ui/primitives/field.tsx +0 -244
  49. package/components/catalyst-ui/primitives/hover-card.tsx +0 -124
  50. package/components/catalyst-ui/primitives/input-otp.tsx +0 -76
  51. package/components/catalyst-ui/primitives/input.tsx +0 -64
  52. package/components/catalyst-ui/primitives/item.tsx +0 -196
  53. package/components/catalyst-ui/primitives/kbd.tsx +0 -75
  54. package/components/catalyst-ui/primitives/label.tsx +0 -24
  55. package/components/catalyst-ui/primitives/navigation-menu.tsx +0 -150
  56. package/components/catalyst-ui/primitives/pagination.tsx +0 -198
  57. package/components/catalyst-ui/primitives/popover.tsx +0 -232
  58. package/components/catalyst-ui/primitives/progress.tsx +0 -34
  59. package/components/catalyst-ui/primitives/radio-group.tsx +0 -43
  60. package/components/catalyst-ui/primitives/resizable.tsx +0 -56
  61. package/components/catalyst-ui/primitives/select.tsx +0 -155
  62. package/components/catalyst-ui/primitives/separator.tsx +0 -74
  63. package/components/catalyst-ui/primitives/sheet.tsx +0 -126
  64. package/components/catalyst-ui/primitives/skeleton.tsx +0 -15
  65. package/components/catalyst-ui/primitives/slider.tsx +0 -27
  66. package/components/catalyst-ui/primitives/switch.tsx +0 -187
  67. package/components/catalyst-ui/primitives/tabs.tsx +0 -335
  68. package/components/catalyst-ui/primitives/textarea.tsx +0 -24
  69. package/components/catalyst-ui/primitives/toggle-group.tsx +0 -55
  70. package/components/catalyst-ui/primitives/toggle.tsx +0 -42
  71. package/components/catalyst-ui/primitives/tooltip.tsx +0 -116
  72. package/components/catalyst-ui/utils/basic-auth.tsx +0 -40
  73. package/components/catalyst-ui/utils/context-storage.tsx +0 -19
  74. package/components/catalyst-ui/utils/cors-middleware.tsx +0 -71
  75. package/components/catalyst-ui/utils/deferred-content.tsx +0 -595
  76. package/components/catalyst-ui/utils/honeypot-middleware.tsx +0 -38
  77. package/components/catalyst-ui/utils/incId.tsx +0 -75
  78. package/components/catalyst-ui/utils/jwk-auth.tsx +0 -36
  79. package/components/catalyst-ui/utils/request-id.tsx +0 -14
  80. package/components/catalyst-ui/utils/secure-headers.tsx +0 -37
  81. package/components/catalyst-ui/utils/server-timing.tsx +0 -23
  82. package/components/catalyst-ui/utils/utils.ts +0 -43
  83. package/components/catalyst-ui/utils/with-cookie.tsx +0 -43
  84. package/components/catalyst-ui/x/accordian-x.tsx +0 -428
  85. package/components/catalyst-ui/x/alert-x.tsx +0 -413
  86. package/components/catalyst-ui/x/animated-text-x.tsx +0 -2242
  87. package/components/catalyst-ui/x/avatar-x.tsx +0 -515
  88. package/components/catalyst-ui/x/badge-x.tsx +0 -670
  89. package/components/catalyst-ui/x/button-X.tsx +0 -2857
  90. package/components/catalyst-ui/x/button-group-x.tsx +0 -847
  91. package/components/catalyst-ui/x/calendar-x.tsx +0 -1910
  92. package/components/catalyst-ui/x/card-x.tsx +0 -2597
  93. package/components/catalyst-ui/x/checkbox-x.tsx +0 -656
  94. package/components/catalyst-ui/x/collapsible-x.tsx +0 -1360
  95. package/components/catalyst-ui/x/combobox-x.tsx +0 -911
  96. package/components/catalyst-ui/x/data-table-x.tsx +0 -1753
  97. package/components/catalyst-ui/x/date-picker-x.tsx +0 -648
  98. package/components/catalyst-ui/x/dialog-x.tsx +0 -659
  99. package/components/catalyst-ui/x/dropdown-menu-x.tsx +0 -612
  100. package/components/catalyst-ui/x/hover-card-x.tsx +0 -375
  101. package/components/catalyst-ui/x/icon-x.tsx +0 -840
  102. package/components/catalyst-ui/x/input-mask-x.tsx +0 -981
  103. package/components/catalyst-ui/x/input-otp-x.tsx +0 -659
  104. package/components/catalyst-ui/x/loader-x.tsx +0 -1757
  105. package/components/catalyst-ui/x/pagination-x.tsx +0 -622
  106. package/components/catalyst-ui/x/popover-x.tsx +0 -744
  107. package/components/catalyst-ui/x/radio-group-x.tsx +0 -499
  108. package/components/catalyst-ui/x/select-x.tsx +0 -1127
  109. package/components/catalyst-ui/x/sheet-x.tsx +0 -668
  110. package/components/catalyst-ui/x/switch-x.tsx +0 -681
  111. package/components/catalyst-ui/x/table-x.tsx +0 -574
  112. package/components/catalyst-ui/x/tabs-x.tsx +0 -839
  113. package/components/catalyst-ui/x/textarea-x.tsx +0 -1263
  114. package/components/catalyst-ui/x/tooltip-x.tsx +0 -396
  115. package/components/catalyst-ui/x/tracker-x.tsx +0 -560
  116. package/data/bg-data.tsx +0 -901
  117. package/data/buttons-data.tsx +0 -2327
  118. package/data/charts-data.tsx +0 -102
  119. package/data/chat-data.tsx +0 -83
  120. package/data/code-data.tsx +0 -1040
  121. package/data/comboboxes-data.tsx +0 -1843
  122. package/data/command-data.tsx +0 -1381
  123. package/data/core-data.tsx +0 -15953
  124. package/data/crm-data.tsx +0 -47
  125. package/data/data.tsx +0 -159
  126. package/data/date-and-time-data.tsx +0 -554
  127. package/data/dependencies.tsx +0 -7
  128. package/data/ecommerce-data.tsx +0 -1387
  129. package/data/forms-data.tsx +0 -7890
  130. package/data/hooks-data.tsx +0 -5487
  131. package/data/index.ts +0 -34
  132. package/data/inputs-data.tsx +0 -557
  133. package/data/interactive-data.tsx +0 -5394
  134. package/data/lofi-data.tsx +0 -18295
  135. package/data/marketing-data.tsx +0 -2546
  136. package/data/media-data.tsx +0 -1510
  137. package/data/motion-data.tsx +0 -5801
  138. package/data/overlay-data.tsx +0 -4136
  139. package/data/pdf-data.tsx +0 -124
  140. package/data/pos-data.tsx +0 -213
  141. package/data/postcss.config.js +0 -6
  142. package/data/primitive-data.tsx +0 -5170
  143. package/data/prompt-data.tsx +0 -1226
  144. package/data/requiredLibs.ts +0 -4
  145. package/data/sandbox-data.tsx +0 -1
  146. package/data/sidebars-data.tsx +0 -5421
  147. package/data/stacks-data.tsx +0 -32
  148. package/data/table-data.tsx +0 -706
  149. package/data/tailwind.config.js +0 -270
  150. package/data/tailwind.config.ngin.js +0 -3830
  151. package/data/tailwind.css +0 -431
  152. package/data/tools-data.tsx +0 -6910
  153. package/data/typography-data.tsx +0 -2050
  154. package/data/utils-data.tsx +0 -6500
  155. package/data/x-data.tsx +0 -1171
@@ -1,2242 +0,0 @@
1
- import React, { FC, useEffect, useRef, CSSProperties, useState, useId, useMemo, ComponentPropsWithoutRef, ReactNode, useCallback, } from "react"
2
- import { cn } from "~/components/catalyst-ui"
3
- import { HTMLMotionProps, motion, useAnimation, useInView, AnimatePresence, MotionProps, Transition, Variants, MotionValue, useScroll, useTransform, animate, useMotionValue, } from "motion/react"
4
- import { cva, type VariantProps } from 'class-variance-authority'
5
-
6
- type AnimationVariant = | "Rotate" | "ExpandableTypewriter" | "TypingAnimation" | "Typewriter" | "Rewind" | "Reveal" | "Morph" | "Swoosh" | "Sliced" | "Shimmer" | "Matrix" | "Hyper" | "Glitch" | "Encrypted" | "Comic" | "AnimatedGlitch" | "default";
7
-
8
- /**
9
- * type AnimationVariant = | "Rotate" | "ExpandableTypewriter" | "TypingAnimation" | "Typewriter" | "Rewind" | "Reveal" | "Morph" | "Swoosh" | "Sliced" | "Shimmer" | "Matrix" | "Hyper" | "Glitch" | "Encrypted" | "Comic" | "AnimatedGlitch" | "default";
10
- * ============================ DEFAULT (AnimateText) ============================
11
- * text: string (default: "Welcome to the Matrix, Neo.!") - any variant that uses text as a prop will default to this
12
- * type?: "fadeIn" | "fadeInUp" | "popIn" | "shiftInUp" | "rollIn" | "whipIn" | "whipInUp" | "calmInUp" | "blurIn" | "blurInUp" | "blurInDown" | "slideUp" | "slideDown" | "slideLeft" | "slideRight" | "scaleUp" | "scaleDown"
13
- * className?: string
14
- * example: <AnimateText text="Roll In" type="rollIn" />
15
- * note: this being the default, theres no need to call the function with the variant prop in order to use, granting acces to 18 different text animations alone.
16
- *
17
- * ============================ AnimatedGlitch ==================================
18
- * text: string
19
- * as?: "h1" | "h2" | "h3" | "h4" | "h5" | "h6" | "p" | "span"
20
- * textClassName?: string
21
- * containerClassName?: string
22
- * colors?: { red: string, green: string, blue: string }
23
- * example: <AnimateText variant='AnimatedGlitch' text="GLITCH" colors={{ red: "#ff0000", green: "#00ff00", blue: "#0000ff" }} />
24
- *
25
- * ============================ Rotate ==========================================
26
- * words: string[] - Array of words to rotate through
27
- * duration?: number (default: 2500) - Time in ms between word changes
28
- * motionProps?: MotionProps - Custom motion properties
29
- * className?: string
30
- * example: <AnimateText variant='Rotate' words={["First", "Second", "Third"]} duration={2000} />
31
- *
32
- * ============================ ExpandableTypewriter ============================
33
- * delay: number - Initial delay before animation starts
34
- * texts: string[] - Array of texts to cycle through
35
- * baseText?: string - Initial base text to type out
36
- * example: <AnimateText variant='ExpandableTypewriter' delay={0.5} texts={["Hello", "World"]} baseText="Welcome: " />
37
- *
38
- * ============================ TypingAnimation =================================
39
- * children?: string - Single text to type
40
- * words?: string[] - Multiple words to cycle through
41
- * className?: string
42
- * duration?: number (default: 100) - Speed per character
43
- * typeSpeed?: number - Override duration for typing
44
- * deleteSpeed?: number - Speed for deleting (default: typeSpeed/2)
45
- * delay?: number (default: 0)
46
- * pauseDelay?: number (default: 1000)
47
- * loop?: boolean (default: false)
48
- * as?: React.ElementType (default: "span")
49
- * startOnView?: boolean (default: true)
50
- * showCursor?: boolean (default: true)
51
- * blinkCursor?: boolean (default: true)
52
- * cursorStyle?: "line" | "block" | "underscore"
53
- * example: <AnimateText variant='TypingAnimation' words={["TypeScript", "React", "Next.js"]} loop showCursor />
54
- *
55
- * ============================ Typewriter ======================================
56
- * sequences?: TypewriterSequence[] - Array of { text: string, deleteAfter?: boolean, pauseAfter?: number }
57
- * typingSpeed?: number (default: 50)
58
- * startDelay?: number (default: 500)
59
- * autoLoop?: boolean (default: true)
60
- * loopDelay?: number (default: 2000)
61
- * size?: "sm" | "md" | "lg"
62
- * className?: string
63
- * containerClassName?: string
64
- * example: <AnimateText variant='Typewriter' sequences={[{ text: "Hello", deleteAfter: true }]} size="md" />
65
- *
66
- * ============================ Rewind ==========================================
67
- * text?: string
68
- * className?: string
69
- * shadowColors?: { first?: string, second?: string, third?: string, fourth?: string, glow?: string }
70
- * example: <AnimateText variant='Rewind' text="HOVER ME" shadowColors={{ first: "#07bccc", glow: "#f40468" }} />
71
- *
72
- * ============================ Swoosh ==========================================
73
- * text?: string
74
- * shadowColors?: { first?: string, second?: string, third?: string, fourth?: string, glow?: string }
75
- * size?: "sm" | "md" | "lg" | "xl"
76
- * className?: string
77
- * disabled?: boolean
78
- * example: <AnimateText variant='Swoosh' text="SWOOSH" size="lg" />
79
- *
80
- * ============================ Sliced ==========================================
81
- * text: string
82
- * splitSpacing?: number (default: 2)
83
- * size?: "sm" | "md" | "lg" | "xl"
84
- * className?: string
85
- * containerClassName?: string
86
- * disabled?: boolean
87
- * example: <AnimateText variant='Sliced' text="SLICE" splitSpacing={4} size="lg" />
88
- *
89
- * ============================ Shimmer =========================================
90
- * text: string
91
- * duration?: number (default: 2.5)
92
- * variant?: "default" | "primary" | "secondary" | "accent"
93
- * size?: "sm" | "md" | "lg" | "xl"
94
- * className?: string
95
- * containerClassName?: string
96
- * disabled?: boolean
97
- * example: <AnimateText variant='Shimmer' text="Shimmer" duration={3} variant="primary" />
98
- *
99
- * ============================ Matrix ==========================================
100
- * text?: string (default: "Welcome to the Matrix, Neo.!")
101
- * initialDelay?: number (default: 200)
102
- * letterAnimationDuration?: number (default: 500)
103
- * letterInterval?: number (default: 100)
104
- * matrixColor?: string (default: "#00ff00")
105
- * loop?: boolean (default: false)
106
- * size?: "sm" | "md" | "lg"
107
- * alignment?: "left" | "center" | "right"
108
- * className?: string
109
- * example: <AnimateText variant='Matrix' text="MATRIX" matrixColor="#00ff00" loop />
110
- *
111
- * ============================ Glitch ==========================================
112
- * text: string
113
- * className?: string
114
- * intensity?: "low" | "medium" | "high"
115
- * duration?: number (default: 2000)
116
- * enabled?: boolean (default: true)
117
- * fontSize?: "sm" | "md" | "lg" | "xl" | "2xl"
118
- * example: <AnimateText variant='Glitch' text="GLITCH" intensity="high" fontSize="xl" />
119
- *
120
- * ============================ Encrypted =======================================
121
- * text: string
122
- * className?: string
123
- * revealDelayMs?: number (default: 50) - Time between revealing each character
124
- * charset?: string - Custom character set for encryption
125
- * flipDelayMs?: number (default: 50) - Time between gibberish flips
126
- * encryptedClassName?: string
127
- * revealedClassName?: string
128
- * example: <AnimateText variant='Encrypted' text="SECRET" revealDelayMs={100} />
129
- *
130
- * ============================ Hyper ===========================================
131
- * children: string
132
- * className?: string
133
- * duration?: number (default: 800)
134
- * delay?: number (default: 0)
135
- * as?: React.ElementType (default: "div")
136
- * startOnView?: boolean (default: false)
137
- * animateOnHover?: boolean (default: true)
138
- * characterSet?: string[] (default: A-Z)
139
- * example: <AnimateText variant='Hyper' duration={1000} animateOnHover>HYPERTEXT</AnimateText>
140
- *
141
- * ============================ Comic ===========================================
142
- * children: string - The text to display in comic style
143
- * className?: string
144
- * style?: CSSProperties
145
- * fontSize?: number (default: 5)
146
- * example: <AnimateText variant='Comic' fontSize={4}>POW!</AnimateText>
147
- *
148
- * ============================ Reveal ==========================================
149
- * children: string - Text to reveal on scroll
150
- * className?: string
151
- * example: <AnimateText variant='Reveal'>This text reveals as you scroll down the page</AnimateText>
152
- *
153
- * ============================ Morph ===========================================
154
- * children: string - Text that morphs between changes
155
- * as?: React.ElementType (default: "p")
156
- * className?: string
157
- * style?: React.CSSProperties
158
- * variants?: Variants - Custom Framer Motion variants
159
- * transition?: Transition - Custom Framer Motion transition
160
- * example: <AnimateText variant='Morph'>Morphing Text</AnimateText>
161
- *
162
- */
163
-
164
- function AnimatedTextX({
165
- text = "Welcome to the Matrix, Neo.",
166
- variant = 'default' | AnimationVariant,
167
- useCustomVariants = false,
168
- useBouncy = false,
169
- children,
170
- ...props }) {
171
-
172
- switch (variant) {
173
- case 'Rotate':
174
- return <WordRotate {...props} />;
175
- case 'ExpandableTypewriter':
176
- return <ExpandableTypewriter {...props} />;
177
- case 'TypingAnimation':
178
- return <TypingAnimation {...props} />;
179
- case 'Typewriter':
180
- return <Typewriter {...props} />;
181
- case 'Rewind':
182
- return <TextRewind text={text} {...props} />;
183
- case 'Swoosh':
184
- return <SwooshText text={text} {...props} />;
185
- case 'Sliced':
186
- return <SlicedText text={text} {...props} />;
187
- case 'Shimmer':
188
- return <ShimmerText text={text} {...props} />;
189
- case 'Matrix':
190
- return <MatrixText text={text} {...props} />;
191
- case 'AnimatedGlitch':
192
- return <AnimatedGlitchText text={text} {...props} />;
193
- case 'Glitch':
194
- return <GlitchText text={text} {...props} />;
195
- case 'Encrypted':
196
- return <EncryptedText text={text} {...props} />;
197
- case 'Reveal':
198
- return <TextReveal {...props}>{children}</TextReveal>;
199
- case 'Morph':
200
- return <TextMorph {...props} variants={useCustomVariants ? customVariants : undefined} transition={useBouncy ? bouncyTransition : smoothTransition}>{children}</TextMorph>;
201
- case 'Comic':
202
- return <ComicText {...props}>{children}</ComicText>;
203
- case 'Hyper':
204
- return <HyperText {...props}>{children}</HyperText>;
205
- case 'default':
206
- default:
207
- return <AnimateText text={text} {...props} />;
208
- }
209
- }
210
- AnimatedTextX.displayName = "AnimatedTextX"
211
-
212
- interface WordRotateProps {
213
- words: string[]
214
- duration?: number
215
- motionProps?: MotionProps
216
- className?: string
217
- }
218
-
219
- function WordRotate({
220
- words,
221
- duration = 2500,
222
- motionProps = {
223
- initial: { opacity: 0, y: -50 },
224
- animate: { opacity: 1, y: 0 },
225
- exit: { opacity: 0, y: 50 },
226
- transition: { duration: 0.25, ease: "easeOut" },
227
- },
228
- className,
229
- }: WordRotateProps) {
230
- const [index, setIndex] = useState(0)
231
-
232
- useEffect(() => {
233
- const interval = setInterval(() => {
234
- setIndex((prevIndex) => (prevIndex + 1) % words.length)
235
- }, duration)
236
-
237
- return () => clearInterval(interval)
238
- }, [words, duration])
239
-
240
- return (
241
- <div className="overflow-hidden py-2">
242
- <AnimatePresence mode="wait">
243
- <motion.h1
244
- key={words[index]}
245
- className={cn(className)}
246
- {...motionProps}
247
- >
248
- {words[index]}
249
- </motion.h1>
250
- </AnimatePresence>
251
- </div>
252
- )
253
- }
254
-
255
- interface ITypewriterProps {
256
- delay: number
257
- texts: string[]
258
- baseText?: string
259
- }
260
-
261
- function ExpandableTypewriter({ delay, texts, baseText = "" }: ITypewriterProps) {
262
- const [animationComplete, setAnimationComplete] = useState(false)
263
- const count = useMotionValue(0)
264
- const rounded = useTransform(count, (latest) => Math.round(latest))
265
- const displayText = useTransform(rounded, (latest) =>
266
- baseText.slice(0, latest)
267
- )
268
-
269
- useEffect(() => {
270
- const controls = animate(count, baseText.length, {
271
- type: "tween",
272
- delay,
273
- duration: 1,
274
- ease: [0.42, 0, 0.58, 1] as const,
275
- onComplete: () => setAnimationComplete(true),
276
- })
277
- return () => {
278
- controls.stop && controls.stop()
279
- }
280
- }, [count, baseText.length, delay])
281
-
282
- return (
283
- <span>
284
- <motion.span>{displayText}</motion.span>
285
- {animationComplete && (
286
- <RepeatedTextAnimation texts={texts} delay={delay + 1} />
287
- )}
288
- <BlinkingCursor />
289
- </span>
290
- )
291
- }
292
-
293
- interface IRepeatedTextAnimationProps {
294
- delay: number
295
- texts: string[]
296
- }
297
-
298
- const defaultTexts = [
299
- "quiz page with questions and answers",
300
- "blog Article Details Page Layout",
301
- "ecommerce dashboard with a sidebar",
302
- "ui like platform.openai.com....",
303
- "buttttton",
304
- "aop that tracks non-standard split sleep cycles",
305
- "transparent card to showcase achievements of a user",
306
- ]
307
- function RepeatedTextAnimation({
308
- delay,
309
- texts = defaultTexts,
310
- }: IRepeatedTextAnimationProps) {
311
- const textIndex = useMotionValue(0)
312
-
313
- const baseText = useTransform(textIndex, (latest) => texts[latest] || "")
314
- const count = useMotionValue(0)
315
- const rounded = useTransform(count, (latest) => Math.round(latest))
316
- const displayText = useTransform(rounded, (latest) =>
317
- baseText.get().slice(0, latest)
318
- )
319
- const updatedThisRound = useMotionValue(true)
320
-
321
- useEffect(() => {
322
- const animation = animate(count, 60, {
323
- type: "tween",
324
- delay,
325
- duration: 1,
326
- ease: [0.42, 0, 1, 1] as const,
327
- repeat: Infinity,
328
- repeatType: "reverse",
329
- repeatDelay: 1,
330
- onUpdate(latest) {
331
- if (updatedThisRound.get() && latest > 0) {
332
- updatedThisRound.set(false)
333
- } else if (!updatedThisRound.get() && latest === 0) {
334
- textIndex.set((textIndex.get() + 1) % texts.length)
335
- updatedThisRound.set(true)
336
- }
337
- },
338
- })
339
- return () => {
340
- animation.stop && animation.stop()
341
- }
342
- }, [count, delay, textIndex, texts, updatedThisRound])
343
-
344
- return <motion.span className="inline">{displayText}</motion.span>
345
- }
346
-
347
- const cursorVariants = {
348
- blinking: {
349
- opacity: [0, 0, 1, 1],
350
- transition: {
351
- duration: 1,
352
- repeat: Infinity,
353
- repeatDelay: 0,
354
- ease: [0, 0, 1, 1] as const,
355
- times: [0, 0.5, 0.5, 1],
356
- },
357
- },
358
- }
359
-
360
- function BlinkingCursor() {
361
- return (
362
- <motion.div
363
- variants={cursorVariants}
364
- animate="blinking"
365
- className="inline-block h-5 w-[1px] translate-y-1 bg-neutral-900"
366
- />
367
- )
368
- }
369
-
370
- interface TypingAnimationProps extends MotionProps {
371
- children?: string
372
- words?: string[]
373
- className?: string
374
- duration?: number
375
- typeSpeed?: number
376
- deleteSpeed?: number
377
- delay?: number
378
- pauseDelay?: number
379
- loop?: boolean
380
- as?: React.ElementType
381
- startOnView?: boolean
382
- showCursor?: boolean
383
- blinkCursor?: boolean
384
- cursorStyle?: "line" | "block" | "underscore"
385
- }
386
-
387
- function TypingAnimation({
388
- children,
389
- words,
390
- className,
391
- duration = 100,
392
- typeSpeed,
393
- deleteSpeed,
394
- delay = 0,
395
- pauseDelay = 1000,
396
- loop = false,
397
- as: Component = "span",
398
- startOnView = true,
399
- showCursor = true,
400
- blinkCursor = true,
401
- cursorStyle = "line",
402
- ...props
403
- }: TypingAnimationProps) {
404
- const MotionComponent = motion.create(Component, {
405
- forwardMotionProps: true,
406
- })
407
-
408
- const [displayedText, setDisplayedText] = useState<string>("")
409
- const [currentWordIndex, setCurrentWordIndex] = useState(0)
410
- const [currentCharIndex, setCurrentCharIndex] = useState(0)
411
- const [phase, setPhase] = useState<"typing" | "pause" | "deleting">("typing")
412
- const elementRef = useRef<HTMLElement | null>(null)
413
- const isInView = useInView(elementRef as React.RefObject<Element>, {
414
- amount: 0.3,
415
- once: true,
416
- })
417
-
418
- const wordsToAnimate = useMemo(
419
- () => words || (children ? [children] : []),
420
- [words, children]
421
- )
422
- const hasMultipleWords = wordsToAnimate.length > 1
423
-
424
- const typingSpeed = typeSpeed || duration
425
- const deletingSpeed = deleteSpeed || typingSpeed / 2
426
-
427
- const shouldStart = startOnView ? isInView : true
428
-
429
- useEffect(() => {
430
- if (!shouldStart || wordsToAnimate.length === 0) return
431
-
432
- const timeoutDelay =
433
- delay > 0 && displayedText === ""
434
- ? delay
435
- : phase === "typing"
436
- ? typingSpeed
437
- : phase === "deleting"
438
- ? deletingSpeed
439
- : pauseDelay
440
-
441
- const timeout = setTimeout(() => {
442
- const currentWord = wordsToAnimate[currentWordIndex] || ""
443
- const graphemes = Array.from(currentWord)
444
-
445
- switch (phase) {
446
- case "typing":
447
- if (currentCharIndex < graphemes.length) {
448
- setDisplayedText(graphemes.slice(0, currentCharIndex + 1).join(""))
449
- setCurrentCharIndex(currentCharIndex + 1)
450
- } else {
451
- if (hasMultipleWords || loop) {
452
- const isLastWord = currentWordIndex === wordsToAnimate.length - 1
453
- if (!isLastWord || loop) {
454
- setPhase("pause")
455
- }
456
- }
457
- }
458
- break
459
-
460
- case "pause":
461
- setPhase("deleting")
462
- break
463
-
464
- case "deleting":
465
- if (currentCharIndex > 0) {
466
- setDisplayedText(graphemes.slice(0, currentCharIndex - 1).join(""))
467
- setCurrentCharIndex(currentCharIndex - 1)
468
- } else {
469
- const nextIndex = (currentWordIndex + 1) % wordsToAnimate.length
470
- setCurrentWordIndex(nextIndex)
471
- setPhase("typing")
472
- }
473
- break
474
- }
475
- }, timeoutDelay)
476
-
477
- return () => clearTimeout(timeout)
478
- }, [
479
- shouldStart,
480
- phase,
481
- currentCharIndex,
482
- currentWordIndex,
483
- displayedText,
484
- wordsToAnimate,
485
- hasMultipleWords,
486
- loop,
487
- typingSpeed,
488
- deletingSpeed,
489
- pauseDelay,
490
- delay,
491
- ])
492
-
493
- const currentWordGraphemes = Array.from(
494
- wordsToAnimate[currentWordIndex] || ""
495
- )
496
- const isComplete =
497
- !loop &&
498
- currentWordIndex === wordsToAnimate.length - 1 &&
499
- currentCharIndex >= currentWordGraphemes.length &&
500
- phase !== "deleting"
501
-
502
- const shouldShowCursor =
503
- showCursor &&
504
- !isComplete &&
505
- (hasMultipleWords || loop || currentCharIndex < currentWordGraphemes.length)
506
-
507
- const getCursorChar = () => {
508
- switch (cursorStyle) {
509
- case "block":
510
- return "▌"
511
- case "underscore":
512
- return "_"
513
- case "line":
514
- default:
515
- return "|"
516
- }
517
- }
518
-
519
- return (
520
- <MotionComponent
521
- ref={elementRef}
522
- className={cn("leading-[5rem] tracking-[-0.02em]", className)}
523
- {...props}
524
- >
525
- {displayedText}
526
- {shouldShowCursor && (
527
- <span
528
- className={cn("inline-block", blinkCursor && "animate-blink-cursor")}
529
- >
530
- {getCursorChar()}
531
- </span>
532
- )}
533
- </MotionComponent>
534
- )
535
- }
536
-
537
- const typewriterVariants = cva(
538
- "inline-block animate-cursor border-foreground border-r-2 pr-1 font-mono tracking-tight",
539
- {
540
- variants: {
541
- size: {
542
- sm: "text-2xl md:text-3xl",
543
- md: "text-4xl md:text-6xl",
544
- lg: "text-6xl md:text-8xl",
545
- },
546
- },
547
- defaultVariants: {
548
- size: "md",
549
- },
550
- }
551
- )
552
-
553
- interface TypewriterSequence {
554
- text: string
555
- deleteAfter?: boolean
556
- pauseAfter?: number
557
- }
558
-
559
- interface TypewriterProps extends VariantProps<typeof typewriterVariants> {
560
- sequences?: TypewriterSequence[]
561
- typingSpeed?: number
562
- startDelay?: number
563
- autoLoop?: boolean
564
- loopDelay?: number
565
- className?: string
566
- containerClassName?: string
567
- }
568
-
569
- function Typewriter({
570
- sequences = [
571
- { text: "Typewriter", deleteAfter: true },
572
- { text: "Multiple Words", deleteAfter: true },
573
- { text: "Auto Loop", deleteAfter: false },
574
- ],
575
- typingSpeed = 50,
576
- startDelay = 500,
577
- autoLoop = true,
578
- loopDelay = 2000,
579
- size,
580
- className,
581
- containerClassName,
582
- }: TypewriterProps) {
583
- const textRef = useRef<HTMLSpanElement>(null)
584
- const containerRef = useRef<HTMLDivElement>(null)
585
-
586
- useEffect(() => {
587
- let isActive = true
588
-
589
- const typeText = async () => {
590
- const titleElement = textRef.current
591
- if (!titleElement) return
592
-
593
- while (isActive) {
594
- titleElement.textContent = ""
595
-
596
- await new Promise((resolve) => setTimeout(resolve, startDelay))
597
-
598
- for (const sequence of sequences) {
599
- if (!isActive) break
600
-
601
- for (let i = 0; i < sequence.text.length; i++) {
602
- if (!isActive) break
603
- titleElement.textContent = sequence.text.slice(0, i + 1)
604
- await new Promise((resolve) => setTimeout(resolve, typingSpeed))
605
- }
606
-
607
- if (sequence.pauseAfter) {
608
- await new Promise((resolve) =>
609
- setTimeout(resolve, sequence.pauseAfter)
610
- )
611
- }
612
-
613
- if (sequence.deleteAfter) {
614
- await new Promise((resolve) => setTimeout(resolve, 500))
615
- for (let i = sequence.text.length; i > 0; i--) {
616
- if (!isActive) break
617
- titleElement.textContent = sequence.text.slice(0, i)
618
- await new Promise((resolve) =>
619
- setTimeout(resolve, typingSpeed / 2)
620
- )
621
- }
622
- }
623
- }
624
-
625
- if (!(autoLoop && isActive)) break
626
-
627
- await new Promise((resolve) => setTimeout(resolve, loopDelay))
628
- }
629
- }
630
-
631
- typeText()
632
-
633
- return () => {
634
- isActive = false
635
- }
636
- }, [sequences, typingSpeed, startDelay, autoLoop, loopDelay])
637
-
638
- return (
639
- <div className={cn("relative mx-auto w-full max-w-4xl py-24", containerClassName)}>
640
- <div
641
- className="relative z-10 flex flex-col items-center justify-center text-center"
642
- ref={containerRef}
643
- >
644
- <div className="flex items-center gap-2">
645
- <span
646
- ref={textRef}
647
- className={cn(typewriterVariants({ size }), "text-foreground", className)}
648
- >
649
- {sequences[0].text}
650
- </span>
651
- </div>
652
- </div>
653
- </div>
654
- )
655
- }
656
-
657
- interface AnimatedTextProps {
658
- text?: string;
659
- className?: string;
660
- shadowColors?: {
661
- first?: string;
662
- second?: string;
663
- third?: string;
664
- fourth?: string;
665
- glow?: string;
666
- };
667
- }
668
-
669
- function TextRewind({
670
- text = "LINE",
671
- className = "",
672
- shadowColors = {
673
- first: "#07bccc",
674
- second: "#e601c0",
675
- third: "#e9019a",
676
- fourth: "#f40468",
677
- glow: "#f40468",
678
- },
679
- }: AnimatedTextProps) {
680
- const textShadowStyle = {
681
- textShadow: `10px 10px 0px ${shadowColors.first},
682
- 15px 15px 0px ${shadowColors.second},
683
- 20px 20px 0px ${shadowColors.third},
684
- 25px 25px 0px ${shadowColors.fourth},
685
- 45px 45px 10px ${shadowColors.glow}`,
686
- };
687
-
688
- const noShadowStyle = {
689
- textShadow: "none",
690
- };
691
-
692
- return (
693
- <div className="w-full text-center">
694
- <motion.div
695
- className={cn(
696
- "w-full text-center cursor-pointer text-3xl font-bold",
697
- "transition-all duration-200 ease-in-out tracking-widest",
698
- "text-black dark:text-white italic",
699
- "stroke-[#d6f4f4]",
700
- className
701
- )}
702
- style={textShadowStyle}
703
- whileHover={noShadowStyle}
704
- >
705
- {text}
706
- </motion.div>
707
- </div>
708
- );
709
- }
710
-
711
- interface TextRevealProps extends ComponentPropsWithoutRef<"div"> {
712
- children: string
713
- }
714
-
715
- function TextReveal({ children, className }: TextRevealProps) {
716
- const targetRef = useRef<HTMLDivElement | null>(null)
717
- const { scrollYProgress } = useScroll({
718
- target: targetRef,
719
- })
720
-
721
- if (typeof children !== "string") {
722
- throw new Error("TextReveal: children must be a string")
723
- }
724
-
725
- const words = children.split(" ")
726
-
727
- return (
728
- <div ref={targetRef} className={cn("relative z-0 h-[200vh]", className)}>
729
- <div
730
- className={
731
- "sticky top-0 mx-auto flex h-[50%] max-w-4xl items-center bg-transparent px-[1rem] py-[5rem]"
732
- }
733
- >
734
- <span
735
- ref={targetRef}
736
- className={
737
- "flex flex-wrap p-5 text-2xl font-bold text-black/20 md:p-8 md:text-3xl lg:p-10 lg:text-4xl xl:text-5xl dark:text-white/20"
738
- }
739
- >
740
- {words.map((word, i) => {
741
- const start = i / words.length
742
- const end = start + 1 / words.length
743
- return (
744
- <Word key={i} progress={scrollYProgress} range={[start, end]}>
745
- {word}
746
- </Word>
747
- )
748
- })}
749
- </span>
750
- </div>
751
- </div>
752
- )
753
- }
754
-
755
- interface WordProps {
756
- children: ReactNode
757
- progress: MotionValue<number>
758
- range: [number, number]
759
- }
760
-
761
- const Word: FC<WordProps> = ({ children, progress, range }) => {
762
- const opacity = useTransform(progress, range, [0, 1])
763
- return (
764
- <span className="xl:lg-3 relative mx-1 lg:mx-1.5">
765
- <span className="absolute opacity-30">{children}</span>
766
- <motion.span
767
- style={{ opacity: opacity }}
768
- className={"text-black dark:text-white"}
769
- >
770
- {children}
771
- </motion.span>
772
- </span>
773
- )
774
- }
775
-
776
- type TextMorphProps = {
777
- children: string
778
- as?: React.ElementType
779
- className?: string
780
- style?: React.CSSProperties
781
- variants?: Variants
782
- transition?: Transition
783
- }
784
-
785
- function TextMorph({
786
- children,
787
- as: Component = "p",
788
- className,
789
- style,
790
- variants,
791
- transition,
792
- }: TextMorphProps) {
793
- const uniqueId = useId()
794
-
795
- const characters = useMemo(() => {
796
- const charCounts: Record<string, number> = {}
797
-
798
- return children.split("").map((char, index) => {
799
- const lowerChar = char.toLowerCase()
800
- charCounts[lowerChar] = (charCounts[lowerChar] || 0) + 1
801
-
802
- return {
803
- id: `${uniqueId}-${lowerChar}${charCounts[lowerChar]}`,
804
- label:
805
- char === " "
806
- ? "\u00A0"
807
- : index === 0
808
- ? char.toUpperCase()
809
- : lowerChar,
810
- }
811
- })
812
- }, [children, uniqueId])
813
-
814
- const defaultVariants: Variants = {
815
- initial: { opacity: 0 },
816
- animate: { opacity: 1 },
817
- exit: { opacity: 0 },
818
- }
819
-
820
- const defaultTransition: Transition = {
821
- type: "spring",
822
- stiffness: 280,
823
- damping: 18,
824
- mass: 0.3,
825
- }
826
-
827
- return (
828
- <Component className={cn(className)} aria-label={children} style={style}>
829
- <AnimatePresence mode="popLayout" initial={false}>
830
- {characters.map((character) => (
831
- <motion.span
832
- key={character.id}
833
- layoutId={character.id}
834
- className="inline-block"
835
- aria-hidden="true"
836
- initial="initial"
837
- animate="animate"
838
- exit="exit"
839
- variants={variants || defaultVariants}
840
- transition={transition || defaultTransition}
841
- >
842
- {character.label}
843
- </motion.span>
844
- ))}
845
- </AnimatePresence>
846
- </Component>
847
- )
848
- }
849
-
850
- const swooshTextVariants = cva(
851
- "w-full text-center cursor-pointer font-bold transition-all duration-200 ease-in-out tracking-widest text-foreground italic",
852
- {
853
- variants: {
854
- size: {
855
- sm: "text-xl",
856
- md: "text-3xl",
857
- lg: "text-5xl",
858
- xl: "text-7xl",
859
- },
860
- },
861
- defaultVariants: {
862
- size: "md",
863
- },
864
- }
865
- )
866
-
867
- interface ShadowColors {
868
- first?: string
869
- second?: string
870
- third?: string
871
- fourth?: string
872
- glow?: string
873
- }
874
-
875
- interface SwooshTextProps extends VariantProps<typeof swooshTextVariants> {
876
- text?: string
877
- shadowColors?: ShadowColors
878
- className?: string
879
- disabled?: boolean
880
- }
881
-
882
- function SwooshText({
883
- text = "Hover Me",
884
- shadowColors = {
885
- first: "#07bccc",
886
- second: "#e601c0",
887
- third: "#e9019a",
888
- fourth: "#f40468",
889
- glow: "#f40468",
890
- },
891
- size,
892
- className,
893
- disabled = false,
894
- }: SwooshTextProps) {
895
- const [isHovered, setIsHovered] = React.useState(false)
896
-
897
- const textShadowStyle = {
898
- textShadow: `10px 10px 0px ${shadowColors.first},
899
- 15px 15px 0px ${shadowColors.second},
900
- 20px 20px 0px ${shadowColors.third},
901
- 25px 25px 0px ${shadowColors.fourth},
902
- 45px 45px 10px ${shadowColors.glow}`,
903
- }
904
-
905
- const noShadowStyle = {
906
- textShadow: "none",
907
- }
908
-
909
- return (
910
- <div className="w-full text-center">
911
- <div
912
- className={cn(
913
- swooshTextVariants({ size }),
914
- disabled && "cursor-not-allowed opacity-50",
915
- className
916
- )}
917
- style={isHovered && !disabled ? noShadowStyle : textShadowStyle}
918
- onMouseEnter={() => !disabled && setIsHovered(true)}
919
- onMouseLeave={() => !disabled && setIsHovered(false)}
920
- >
921
- {text}
922
- </div>
923
- </div>
924
- )
925
- }
926
-
927
- const slicedTextVariants = cva(
928
- "text-foreground",
929
- {
930
- variants: {
931
- size: {
932
- sm: "text-2xl",
933
- md: "text-4xl",
934
- lg: "text-6xl",
935
- xl: "text-8xl",
936
- },
937
- },
938
- defaultVariants: {
939
- size: "md",
940
- },
941
- }
942
- )
943
-
944
- interface SlicedTextProps extends VariantProps<typeof slicedTextVariants> {
945
- text: string
946
- splitSpacing?: number
947
- className?: string
948
- containerClassName?: string
949
- disabled?: boolean
950
- }
951
-
952
- function SlicedText({
953
- text = "Sliced Text",
954
- splitSpacing = 2,
955
- size,
956
- className,
957
- containerClassName,
958
- disabled = false,
959
- }: SlicedTextProps) {
960
- const [isHovered, setIsHovered] = useState(false)
961
-
962
- const topVariants = {
963
- default: {
964
- clipPath: "inset(0 0 50% 0)",
965
- transform: `translateY(${-splitSpacing / 2}px)`,
966
- opacity: 1,
967
- },
968
- hover: {
969
- clipPath: "inset(0 0 0 0)",
970
- transform: "translateY(0px)",
971
- opacity: 0,
972
- },
973
- }
974
-
975
- const bottomVariants = {
976
- default: {
977
- clipPath: "inset(50% 0 0 0)",
978
- transform: `translateY(${splitSpacing / 2}px)`,
979
- opacity: 1,
980
- },
981
- hover: {
982
- clipPath: "inset(0 0 0 0)",
983
- transform: "translateY(0px)",
984
- opacity: 1,
985
- },
986
- }
987
-
988
- const currentTopStyle = isHovered && !disabled ? topVariants.hover : topVariants.default
989
- const currentBottomStyle = isHovered && !disabled ? bottomVariants.hover : bottomVariants.default
990
-
991
- return (
992
- <div
993
- className={cn("w-full text-center relative inline-block", containerClassName)}
994
- onMouseEnter={() => !disabled && setIsHovered(true)}
995
- onMouseLeave={() => !disabled && setIsHovered(false)}
996
- >
997
- <div
998
- className={cn(
999
- "absolute w-full -ml-0.5 transition-all duration-100",
1000
- slicedTextVariants({ size }),
1001
- className
1002
- )}
1003
- style={{
1004
- clipPath: currentTopStyle.clipPath,
1005
- transform: currentTopStyle.transform,
1006
- opacity: currentTopStyle.opacity,
1007
- }}
1008
- >
1009
- {text}
1010
- </div>
1011
- <div
1012
- className={cn(
1013
- "absolute w-full transition-all duration-100",
1014
- slicedTextVariants({ size }),
1015
- className
1016
- )}
1017
- style={{
1018
- clipPath: currentBottomStyle.clipPath,
1019
- transform: currentBottomStyle.transform,
1020
- opacity: currentBottomStyle.opacity,
1021
- }}
1022
- >
1023
- {text}
1024
- </div>
1025
- <div className={cn("invisible", slicedTextVariants({ size }), className)}>
1026
- {text}
1027
- </div>
1028
- </div>
1029
- )
1030
- }
1031
-
1032
- const shimmerTextVariants = cva(
1033
- "font-bold bg-gradient-to-r bg-[length:200%_100%] bg-clip-text text-transparent",
1034
- {
1035
- variants: {
1036
- variant: {
1037
- default: "from-neutral-950 via-neutral-400 to-neutral-950 dark:from-white dark:via-neutral-600 dark:to-white",
1038
- primary: "from-primary via-primary/50 to-primary",
1039
- secondary: "from-secondary via-secondary/50 to-secondary",
1040
- accent: "from-accent via-accent/50 to-accent",
1041
- },
1042
- size: {
1043
- sm: "text-xl",
1044
- md: "text-3xl",
1045
- lg: "text-5xl",
1046
- xl: "text-7xl",
1047
- },
1048
- },
1049
- defaultVariants: {
1050
- variant: "default",
1051
- size: "md",
1052
- },
1053
- }
1054
- )
1055
-
1056
- interface ShimmerTextProps extends VariantProps<typeof shimmerTextVariants> {
1057
- text: string
1058
- duration?: number
1059
- className?: string
1060
- containerClassName?: string
1061
- disabled?: boolean
1062
- }
1063
-
1064
- function ShimmerText({
1065
- text = "Text Shimmer",
1066
- duration = 2.5,
1067
- variant,
1068
- size,
1069
- className,
1070
- containerClassName,
1071
- disabled = false,
1072
- }: ShimmerTextProps) {
1073
- const [position, setPosition] = React.useState("200% center")
1074
-
1075
- React.useEffect(() => {
1076
- if (disabled) return
1077
-
1078
- const startTime = Date.now()
1079
- let animationFrame: number
1080
-
1081
- const animate = () => {
1082
- const elapsed = Date.now() - startTime
1083
- const progress = (elapsed % (duration * 1000)) / (duration * 1000)
1084
- const xPosition = 200 - (progress * 400)
1085
- setPosition(`${xPosition}% center`)
1086
- animationFrame = requestAnimationFrame(animate)
1087
- }
1088
-
1089
- animationFrame = requestAnimationFrame(animate)
1090
- return () => cancelAnimationFrame(animationFrame)
1091
- }, [duration, disabled])
1092
-
1093
- return (
1094
- <div className={cn("flex items-center justify-center p-8", containerClassName)}>
1095
- <div className="relative px-4 py-2 overflow-hidden">
1096
- <h1
1097
- className={cn(shimmerTextVariants({ variant, size }), className)}
1098
- style={{
1099
- backgroundPosition: disabled ? "200% center" : position,
1100
- }}
1101
- >
1102
- {text}
1103
- </h1>
1104
- </div>
1105
- </div>
1106
- )
1107
- }
1108
-
1109
- const matrixTextVariants = cva(
1110
- "flex items-center justify-center text-foreground",
1111
- {
1112
- variants: {
1113
- size: {
1114
- sm: "text-2xl md:text-3xl",
1115
- md: "text-4xl md:text-6xl",
1116
- lg: "text-6xl md:text-8xl",
1117
- },
1118
- alignment: {
1119
- left: "justify-start",
1120
- center: "justify-center",
1121
- right: "justify-end",
1122
- },
1123
- },
1124
- defaultVariants: {
1125
- size: "md",
1126
- alignment: "center",
1127
- },
1128
- }
1129
- )
1130
-
1131
- interface LetterState {
1132
- char: string
1133
- isMatrix: boolean
1134
- isSpace: boolean
1135
- }
1136
-
1137
- interface MatrixTextProps extends VariantProps<typeof matrixTextVariants> {
1138
- text?: string
1139
- initialDelay?: number
1140
- letterAnimationDuration?: number
1141
- letterInterval?: number
1142
- matrixColor?: string
1143
- loop?: boolean
1144
- className?: string
1145
- }
1146
-
1147
- function MatrixText({
1148
- text = "HelloWorld!",
1149
- initialDelay = 200,
1150
- letterAnimationDuration = 500,
1151
- letterInterval = 100,
1152
- matrixColor = "#00ff00",
1153
- loop = false,
1154
- size,
1155
- alignment,
1156
- className,
1157
- }: MatrixTextProps) {
1158
- const [letters, setLetters] = useState<LetterState[]>(() =>
1159
- text.split("").map((char) => ({
1160
- char,
1161
- isMatrix: false,
1162
- isSpace: char === " ",
1163
- }))
1164
- )
1165
- const [isAnimating, setIsAnimating] = useState(false)
1166
- const [isHovering, setIsHovering] = useState(false)
1167
-
1168
- const getRandomChar = useCallback(
1169
- () => (Math.random() > 0.5 ? "1" : "0"),
1170
- []
1171
- )
1172
-
1173
- const animateLetter = useCallback(
1174
- (index: number) => {
1175
- if (index >= text.length) return
1176
-
1177
- requestAnimationFrame(() => {
1178
- setLetters((prev) => {
1179
- const newLetters = [...prev]
1180
- if (!newLetters[index].isSpace) {
1181
- newLetters[index] = {
1182
- ...newLetters[index],
1183
- char: getRandomChar(),
1184
- isMatrix: true,
1185
- }
1186
- }
1187
- return newLetters
1188
- })
1189
-
1190
- setTimeout(() => {
1191
- setLetters((prev) => {
1192
- const newLetters = [...prev]
1193
- newLetters[index] = {
1194
- ...newLetters[index],
1195
- char: text[index],
1196
- isMatrix: false,
1197
- }
1198
- return newLetters
1199
- })
1200
- }, letterAnimationDuration)
1201
- })
1202
- },
1203
- [getRandomChar, text, letterAnimationDuration]
1204
- )
1205
-
1206
- const startAnimation = useCallback(() => {
1207
- if (isAnimating) return
1208
-
1209
- setIsAnimating(true)
1210
- let currentIndex = 0
1211
-
1212
- const animate = () => {
1213
- if (currentIndex >= text.length) {
1214
- setIsAnimating(false)
1215
- return
1216
- }
1217
-
1218
- animateLetter(currentIndex)
1219
- currentIndex++
1220
- setTimeout(animate, letterInterval)
1221
- }
1222
-
1223
- animate()
1224
- }, [animateLetter, text, isAnimating, letterInterval])
1225
-
1226
- useEffect(() => {
1227
- const timer = setTimeout(startAnimation, initialDelay)
1228
- return () => clearTimeout(timer)
1229
- }, [])
1230
-
1231
- useEffect(() => {
1232
- if (!loop || isAnimating) return
1233
-
1234
- const loopTimer = setInterval(() => {
1235
- startAnimation()
1236
- }, text.length * letterInterval + letterAnimationDuration + 2000)
1237
-
1238
- return () => clearInterval(loopTimer)
1239
- }, [loop, isAnimating, startAnimation, text.length, letterInterval, letterAnimationDuration])
1240
-
1241
- const handleMouseEnter = () => {
1242
- setIsHovering(true)
1243
- startAnimation()
1244
- }
1245
-
1246
- const handleMouseLeave = () => {
1247
- setIsHovering(false)
1248
- }
1249
-
1250
- return (
1251
- <div
1252
- className={cn(matrixTextVariants({ size, alignment }), className)}
1253
- aria-label="Matrix text animation"
1254
- onMouseEnter={handleMouseEnter}
1255
- onMouseLeave={handleMouseLeave}
1256
- >
1257
- <div className="h-24 flex items-center justify-center">
1258
- <div className="flex flex-wrap items-center justify-center">
1259
- {letters.map((letter, index) => (
1260
- <div
1261
- key={`${index}-${letter.char}`}
1262
- className="font-mono w-[1ch] text-center overflow-hidden transition-all duration-100"
1263
- style={{
1264
- display: "inline-block",
1265
- fontVariantNumeric: "tabular-nums",
1266
- color: letter.isMatrix ? matrixColor : "currentColor",
1267
- textShadow: letter.isMatrix ? `0 2px 4px ${matrixColor}80` : "none",
1268
- }}
1269
- >
1270
- {letter.isSpace ? "\u00A0" : letter.char}
1271
- </div>
1272
- ))}
1273
- </div>
1274
- </div>
1275
- </div>
1276
- )
1277
- }
1278
-
1279
- type CharacterSet = string[] | readonly string[]
1280
-
1281
- interface HyperTextProps extends MotionProps {
1282
- children: string
1283
- className?: string
1284
- duration?: number
1285
- delay?: number
1286
- as?: React.ElementType
1287
- startOnView?: boolean
1288
- animateOnHover?: boolean
1289
- characterSet?: CharacterSet
1290
- }
1291
-
1292
- const DEFAULT_CHARACTER_SET = Object.freeze(
1293
- "ABCDEFGHIJKLMNOPQRSTUVWXYZ".split("")
1294
- ) as readonly string[]
1295
-
1296
- const getRandomInt = (max: number): number => Math.floor(Math.random() * max)
1297
-
1298
- function HyperText({
1299
- children,
1300
- className,
1301
- duration = 800,
1302
- delay = 0,
1303
- as: Component = "div",
1304
- startOnView = false,
1305
- animateOnHover = true,
1306
- characterSet = DEFAULT_CHARACTER_SET,
1307
- ...props
1308
- }: HyperTextProps) {
1309
- const MotionComponent = motion.create(Component, {
1310
- forwardMotionProps: true,
1311
- })
1312
-
1313
- const [displayText, setDisplayText] = useState<string[]>(() =>
1314
- children.split("")
1315
- )
1316
- const [isAnimating, setIsAnimating] = useState(false)
1317
- const iterationCount = useRef(0)
1318
- const elementRef = useRef<HTMLElement>(null)
1319
-
1320
- const handleAnimationTrigger = () => {
1321
- if (animateOnHover && !isAnimating) {
1322
- iterationCount.current = 0
1323
- setIsAnimating(true)
1324
- }
1325
- }
1326
-
1327
- useEffect(() => {
1328
- if (!startOnView) {
1329
- const startTimeout = setTimeout(() => {
1330
- setIsAnimating(true)
1331
- }, delay)
1332
- return () => clearTimeout(startTimeout)
1333
- }
1334
-
1335
- const observer = new IntersectionObserver(
1336
- ([entry]) => {
1337
- if (entry.isIntersecting) {
1338
- setTimeout(() => {
1339
- setIsAnimating(true)
1340
- }, delay)
1341
- observer.disconnect()
1342
- }
1343
- },
1344
- { threshold: 0.1, rootMargin: "-30% 0px -30% 0px" }
1345
- )
1346
-
1347
- if (elementRef.current) {
1348
- observer.observe(elementRef.current)
1349
- }
1350
-
1351
- return () => observer.disconnect()
1352
- }, [delay, startOnView])
1353
-
1354
- useEffect(() => {
1355
- if (!isAnimating) return
1356
-
1357
- const maxIterations = children.length
1358
- const startTime = performance.now()
1359
- let animationFrameId: number
1360
-
1361
- const animate = (currentTime: number) => {
1362
- const elapsed = currentTime - startTime
1363
- const progress = Math.min(elapsed / duration, 1)
1364
-
1365
- iterationCount.current = progress * maxIterations
1366
-
1367
- setDisplayText((currentText) =>
1368
- currentText.map((letter, index) =>
1369
- letter === " "
1370
- ? letter
1371
- : index <= iterationCount.current
1372
- ? children[index]
1373
- : characterSet[getRandomInt(characterSet.length)]
1374
- )
1375
- )
1376
-
1377
- if (progress < 1) {
1378
- animationFrameId = requestAnimationFrame(animate)
1379
- } else {
1380
- setIsAnimating(false)
1381
- }
1382
- }
1383
-
1384
- animationFrameId = requestAnimationFrame(animate)
1385
-
1386
- return () => cancelAnimationFrame(animationFrameId)
1387
- }, [children, duration, isAnimating, characterSet])
1388
-
1389
- return (
1390
- <MotionComponent
1391
- ref={elementRef}
1392
- className={cn("overflow-hidden py-2 text-4xl font-bold", className)}
1393
- onMouseEnter={handleAnimationTrigger}
1394
- {...props}
1395
- >
1396
- <AnimatePresence>
1397
- {displayText.map((letter, index) => (
1398
- <motion.span
1399
- key={index}
1400
- className={cn("font-mono", letter === " " ? "w-3" : "")}
1401
- >
1402
- {letter.toUpperCase()}
1403
- </motion.span>
1404
- ))}
1405
- </AnimatePresence>
1406
- </MotionComponent>
1407
- )
1408
- }
1409
-
1410
- interface GlitchTextProps {
1411
- text: string;
1412
- className?: string;
1413
- intensity?: 'low' | 'medium' | 'high';
1414
- duration?: number;
1415
- enabled?: boolean;
1416
- fontSize?: 'sm' | 'md' | 'lg' | 'xl' | '2xl';
1417
- }
1418
-
1419
- function GlitchText({
1420
- text,
1421
- className,
1422
- intensity = 'medium',
1423
- duration = 2000,
1424
- enabled = true,
1425
- fontSize = 'md'
1426
- }: GlitchTextProps) {
1427
- const [isGlitching, setIsGlitching] = useState(false);
1428
- const [glitchText, setGlitchText] = useState(text);
1429
-
1430
- const fontSizeClasses = {
1431
- sm: 'text-sm',
1432
- md: 'text-base',
1433
- lg: 'text-lg',
1434
- xl: 'text-xl',
1435
- '2xl': 'text-2xl'
1436
- };
1437
-
1438
- const intensityValues = {
1439
- low: { interval: 3000, glitchDuration: 300, changeCount: 3 },
1440
- medium: { interval: 2000, glitchDuration: 500, changeCount: 5 },
1441
- high: { interval: 1000, glitchDuration: 800, changeCount: 8 }
1442
- };
1443
-
1444
- const characters = '!@#$%^&*()_+-=[]{}|;:,.<>?~';
1445
-
1446
- const generateGlitchText = (originalText: string) => {
1447
- const textArray = originalText.split('');
1448
- const glitchCount = intensityValues[intensity].changeCount;
1449
-
1450
- for (let i = 0; i < glitchCount; i++) {
1451
- const randomIndex = Math.floor(Math.random() * textArray.length);
1452
- if (textArray[randomIndex] !== ' ') {
1453
- textArray[randomIndex] = characters[Math.floor(Math.random() * characters.length)];
1454
- }
1455
- }
1456
-
1457
- return textArray.join('');
1458
- };
1459
-
1460
- useEffect(() => {
1461
- if (!enabled) return;
1462
-
1463
- const glitchInterval = setInterval(() => {
1464
- setIsGlitching(true);
1465
- setGlitchText(generateGlitchText(text));
1466
-
1467
- setTimeout(() => {
1468
- setIsGlitching(false);
1469
- setGlitchText(text);
1470
- }, intensityValues[intensity].glitchDuration);
1471
- }, intensityValues[intensity].interval);
1472
-
1473
- return () => clearInterval(glitchInterval);
1474
- }, [text, intensity, enabled]);
1475
-
1476
- return (
1477
- <span className={cn(
1478
- 'inline-block relative',
1479
- fontSizeClasses[fontSize],
1480
- isGlitching && 'text-red-400',
1481
- className
1482
- )}>
1483
- {glitchText}
1484
- {isGlitching && (
1485
- <>
1486
- <span className="absolute top-0 left-1 text-blue-400 opacity-70 mix-blend-screen">
1487
- {glitchText}
1488
- </span>
1489
- <span className="absolute top-0 left-[-1px] text-green-400 opacity-50 mix-blend-screen">
1490
- {glitchText}
1491
- </span>
1492
- </>
1493
- )}
1494
- </span>
1495
- );
1496
- }
1497
-
1498
- type EncryptedTextProps = {
1499
- text: string;
1500
- className?: string;
1501
- /**
1502
- * Time in milliseconds between revealing each subsequent real character.
1503
- * Lower is faster. Defaults to 50ms per character.
1504
- */
1505
- revealDelayMs?: number;
1506
- /** Optional custom character set to use for the gibberish effect. */
1507
- charset?: string;
1508
- /**
1509
- * Time in milliseconds between gibberish flips for unrevealed characters.
1510
- * Lower is more jittery. Defaults to 50ms.
1511
- */
1512
- flipDelayMs?: number;
1513
- /** CSS class for styling the encrypted/scrambled characters */
1514
- encryptedClassName?: string;
1515
- /** CSS class for styling the revealed characters */
1516
- revealedClassName?: string;
1517
- };
1518
-
1519
- const DEFAULT_CHARSET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()_+-={}[];:,.<>/?";
1520
-
1521
- function generateRandomCharacter(charset: string): string {
1522
- const index = Math.floor(Math.random() * charset.length);
1523
- return charset.charAt(index);
1524
- }
1525
-
1526
- function generateGibberishPreservingSpaces(
1527
- original: string,
1528
- charset: string,
1529
- ): string {
1530
- if (!original) return "";
1531
- let result = "";
1532
- for (let i = 0; i < original.length; i += 1) {
1533
- const ch = original[i];
1534
- result += ch === " " ? " " : generateRandomCharacter(charset);
1535
- }
1536
- return result;
1537
- }
1538
-
1539
- const EncryptedText: React.FC<EncryptedTextProps> = ({
1540
- text,
1541
- className,
1542
- revealDelayMs = 50,
1543
- charset = DEFAULT_CHARSET,
1544
- flipDelayMs = 50,
1545
- encryptedClassName,
1546
- revealedClassName,
1547
- }) => {
1548
- const ref = useRef<HTMLSpanElement>(null);
1549
- const isInView = useInView(ref, { once: true });
1550
-
1551
- const [revealCount, setRevealCount] = useState<number>(0);
1552
- const animationFrameRef = useRef<number | null>(null);
1553
- const startTimeRef = useRef<number>(0);
1554
- const lastFlipTimeRef = useRef<number>(0);
1555
- const scrambleCharsRef = useRef<string[]>(
1556
- text ? generateGibberishPreservingSpaces(text, charset).split("") : [],
1557
- );
1558
-
1559
- useEffect(() => {
1560
- if (!isInView) return;
1561
-
1562
- // Reset state for a fresh animation whenever dependencies change
1563
- const initial = text
1564
- ? generateGibberishPreservingSpaces(text, charset)
1565
- : "";
1566
- scrambleCharsRef.current = initial.split("");
1567
- startTimeRef.current = performance.now();
1568
- lastFlipTimeRef.current = startTimeRef.current;
1569
- setRevealCount(0);
1570
-
1571
- let isCancelled = false;
1572
-
1573
- const update = (now: number) => {
1574
- if (isCancelled) return;
1575
-
1576
- const elapsedMs = now - startTimeRef.current;
1577
- const totalLength = text.length;
1578
- const currentRevealCount = Math.min(
1579
- totalLength,
1580
- Math.floor(elapsedMs / Math.max(1, revealDelayMs)),
1581
- );
1582
-
1583
- setRevealCount(currentRevealCount);
1584
-
1585
- if (currentRevealCount >= totalLength) {
1586
- return;
1587
- }
1588
-
1589
- // Re-randomize unrevealed scramble characters on an interval
1590
- const timeSinceLastFlip = now - lastFlipTimeRef.current;
1591
- if (timeSinceLastFlip >= Math.max(0, flipDelayMs)) {
1592
- for (let index = 0; index < totalLength; index += 1) {
1593
- if (index >= currentRevealCount) {
1594
- if (text[index] !== " ") {
1595
- scrambleCharsRef.current[index] =
1596
- generateRandomCharacter(charset);
1597
- } else {
1598
- scrambleCharsRef.current[index] = " ";
1599
- }
1600
- }
1601
- }
1602
- lastFlipTimeRef.current = now;
1603
- }
1604
-
1605
- animationFrameRef.current = requestAnimationFrame(update);
1606
- };
1607
-
1608
- animationFrameRef.current = requestAnimationFrame(update);
1609
-
1610
- return () => {
1611
- isCancelled = true;
1612
- if (animationFrameRef.current !== null) {
1613
- cancelAnimationFrame(animationFrameRef.current);
1614
- }
1615
- };
1616
- }, [isInView, text, revealDelayMs, charset, flipDelayMs]);
1617
-
1618
- if (!text) return null;
1619
-
1620
- return (
1621
- <motion.span
1622
- ref={ref}
1623
- className={cn(className)}
1624
- aria-label={text}
1625
- role="text"
1626
- >
1627
- {text.split("").map((char, index) => {
1628
- const isRevealed = index < revealCount;
1629
- const displayChar = isRevealed
1630
- ? char
1631
- : char === " "
1632
- ? " "
1633
- : (scrambleCharsRef.current[index] ??
1634
- generateRandomCharacter(charset));
1635
-
1636
- return (
1637
- <span
1638
- key={index}
1639
- className={cn(isRevealed ? revealedClassName : encryptedClassName)}
1640
- >
1641
- {displayChar}
1642
- </span>
1643
- );
1644
- })}
1645
- </motion.span>
1646
- );
1647
- };
1648
-
1649
-
1650
- type ComicTextProps = {
1651
- children: string
1652
- className?: string
1653
- style?: CSSProperties
1654
- fontSize?: number
1655
- }
1656
-
1657
- function ComicText({
1658
- children,
1659
- className,
1660
- style,
1661
- fontSize = 5,
1662
- }: ComicTextProps) {
1663
- if (typeof children !== "string") {
1664
- throw new Error("children must be a string")
1665
- }
1666
-
1667
- const dotColor = "#EF4444"
1668
- const backgroundColor = "#FACC15"
1669
-
1670
- return (
1671
- <motion.div
1672
- className={cn("text-center select-none", className)}
1673
- style={{
1674
- fontSize: `${fontSize}rem`,
1675
- fontFamily: "'Bangers', 'Comic Sans MS', 'Impact', sans-serif",
1676
- fontWeight: "900",
1677
- WebkitTextStroke: `${fontSize * 0.35}px #000000`, // Thick black outline
1678
- transform: "skewX(-10deg)",
1679
- textTransform: "uppercase",
1680
- filter: `
1681
- drop-shadow(5px 5px 0px #000000)
1682
- drop-shadow(3px 3px 0px ${dotColor})
1683
- `,
1684
- backgroundColor: backgroundColor,
1685
- backgroundImage: `radial-gradient(circle at 1px 1px, ${dotColor} 1px, transparent 0)`,
1686
- backgroundSize: "8px 8px",
1687
- backgroundClip: "text",
1688
- WebkitBackgroundClip: "text",
1689
- WebkitTextFillColor: "transparent",
1690
- ...style,
1691
- }}
1692
- initial={{ opacity: 0, scale: 0.8, rotate: -2 }}
1693
- animate={{ opacity: 1, scale: 1, rotate: 0 }}
1694
- transition={{
1695
- duration: 0.6,
1696
- ease: [0.175, 0.885, 0.32, 1.275],
1697
- type: "spring",
1698
- }}
1699
- >
1700
- {children}
1701
- </motion.div>
1702
- )
1703
- }
1704
-
1705
- type AnimationType = | "fadeIn" | "fadeInUp" | "popIn" | "shiftInUp" | "rollIn" | "whipIn" | "whipInUp" | "calmInUp" | "blurIn" | "blurInUp" | "blurInDown" | "slideUp" | "slideDown" | "slideLeft" | "slideRight" | "scaleUp" | "scaleDown";
1706
-
1707
- interface Props extends HTMLMotionProps<"div"> {
1708
- text: string
1709
- type?: AnimationType
1710
- delay?: number
1711
- duration?: number
1712
- }
1713
-
1714
- const animationVariants = {
1715
- fadeIn: {
1716
- container: {
1717
- hidden: { opacity: 0 },
1718
- visible: (i: number = 1) => ({
1719
- opacity: 1,
1720
- transition: { staggerChildren: 0.05, delayChildren: i * 0.3 },
1721
- }),
1722
- },
1723
- child: {
1724
- visible: {
1725
- opacity: 1,
1726
- y: 0,
1727
- transition: {
1728
- type: "spring" as const,
1729
- damping: 12,
1730
- stiffness: 100,
1731
- },
1732
- },
1733
- hidden: { opacity: 0, y: 10 },
1734
- },
1735
- },
1736
- fadeInUp: {
1737
- container: {
1738
- hidden: { opacity: 0 },
1739
- visible: {
1740
- opacity: 1,
1741
- transition: { staggerChildren: 0.1, delayChildren: 0.2 },
1742
- },
1743
- },
1744
- child: {
1745
- visible: { opacity: 1, y: 0, transition: { duration: 0.5 } },
1746
- hidden: { opacity: 0, y: 20 },
1747
- },
1748
- },
1749
- popIn: {
1750
- container: {
1751
- hidden: { scale: 0 },
1752
- visible: {
1753
- scale: 1,
1754
- transition: { staggerChildren: 0.05, delayChildren: 0.2 },
1755
- },
1756
- },
1757
- child: {
1758
- visible: {
1759
- opacity: 1,
1760
- scale: 1.1,
1761
- transition: { type: "spring" as const, damping: 15, stiffness: 400 },
1762
- },
1763
- hidden: { opacity: 0, scale: 0 },
1764
- },
1765
- },
1766
- calmInUp: {
1767
- container: {
1768
- hidden: {},
1769
- visible: (i: number = 1) => ({
1770
- transition: { staggerChildren: 0.01, delayChildren: 0.2 * i },
1771
- }),
1772
- },
1773
- child: {
1774
- hidden: {
1775
- y: "200%",
1776
- transition: {
1777
- ease: [0.455, 0.03, 0.515, 0.955] as const,
1778
- duration: 0.85,
1779
- },
1780
- },
1781
- visible: {
1782
- y: 0,
1783
- transition: {
1784
- ease: [0.125, 0.92, 0.69, 0.975] as const, // Drawing attention to dynamic content or interactive elements, where the animation needs to be engaging but not abrupt
1785
- duration: 0.75,
1786
- // ease: [0.455, 0.03, 0.515, 0.955], // smooth and gradual acceleration followed by a steady deceleration towards the end of the animation
1787
- // ease: [0.115, 0.955, 0.655, 0.939], // smooth and gradual acceleration followed by a steady deceleration towards the end of the animation
1788
- // ease: [0.09, 0.88, 0.68, 0.98], // Very Gentle Onset, Swift Mid-Section, Soft Landing
1789
- // ease: [0.11, 0.97, 0.64, 0.945], // Minimal Start, Energetic Acceleration, Smooth Closure
1790
- },
1791
- },
1792
- },
1793
- },
1794
- shiftInUp: {
1795
- container: {
1796
- hidden: {},
1797
- visible: (i: number = 1) => ({
1798
- transition: { staggerChildren: 0.01, delayChildren: 0.2 * i },
1799
- }),
1800
- },
1801
- child: {
1802
- hidden: {
1803
- y: "100%", // Starting from below but not too far to ensure a dramatic but manageable shift.
1804
- transition: {
1805
- ease: [0.75, 0, 0.25, 1] as const, // Starting quickly
1806
- duration: 0.6, // Shortened duration for a more dramatic start
1807
- },
1808
- },
1809
- visible: {
1810
- y: 0,
1811
- transition: {
1812
- duration: 0.8, // Slightly longer to accommodate the slow middle and swift end
1813
- ease: [0.22, 1, 0.36, 1] as const, // This easing function starts quickly (dramatic shift), slows down (slow middle), and ends quickly (clean swift end)
1814
- },
1815
- },
1816
- },
1817
- },
1818
- whipInUp: {
1819
- container: {
1820
- hidden: {},
1821
- visible: (i: number = 1) => ({
1822
- transition: { staggerChildren: 0.01, delayChildren: 0.2 * i },
1823
- }),
1824
- },
1825
- child: {
1826
- hidden: {
1827
- y: "200%",
1828
- transition: {
1829
- ease: [0.455, 0.03, 0.515, 0.955] as const,
1830
- duration: 0.45,
1831
- },
1832
- },
1833
- visible: {
1834
- y: 0,
1835
- transition: {
1836
- ease: [0.5, -0.15, 0.25, 1.05] as const,
1837
- duration: 0.75,
1838
- },
1839
- },
1840
- },
1841
- },
1842
- rollIn: {
1843
- container: {
1844
- hidden: {},
1845
- visible: {},
1846
- },
1847
- child: {
1848
- hidden: {
1849
- opacity: 0,
1850
- y: `0.25em`,
1851
- },
1852
- visible: {
1853
- opacity: 1,
1854
- y: `0em`,
1855
- transition: {
1856
- duration: 0.65,
1857
- ease: [0.65, 0, 0.75, 1] as const, // Great! Swift Beginning, Prolonged Ease, Quick Finish
1858
- // ease: [0.75, 0.05, 0.85, 1], // Quick Start, Smooth Middle, Sharp End
1859
- // ease: [0.7, -0.25, 0.9, 1.25], // Fast Acceleration, Gentle Slowdown, Sudden Snap
1860
- // ease: [0.7, -0.5, 0.85, 1.5], // Quick Leap, Soft Glide, Snappy Closure
1861
- },
1862
- },
1863
- },
1864
- },
1865
- whipIn: {
1866
- container: {
1867
- hidden: {},
1868
- visible: {},
1869
- },
1870
- child: {
1871
- hidden: {
1872
- opacity: 0,
1873
- y: `0.35em`,
1874
- },
1875
- visible: {
1876
- opacity: 1,
1877
- y: `0em`,
1878
- transition: {
1879
- duration: 0.45,
1880
- // ease: [0.75, 0.05, 0.85, 1], // Quick Start, Smooth Middle, Sharp End
1881
- // ease: [0.7, -0.25, 0.9, 1.25], // Fast Acceleration, Gentle Slowdown, Sudden Snap
1882
- // ease: [0.65, 0, 0.75, 1], // Great! Swift Beginning, Prolonged Ease, Quick Finish
1883
- ease: [0.85, 0.1, 0.9, 1.2] as const, // Rapid Initiation, Subtle Slow, Sharp Conclusion
1884
- },
1885
- },
1886
- },
1887
- },
1888
- blurIn: {
1889
- container: {
1890
- hidden: { opacity: 0 },
1891
- visible: {
1892
- opacity: 1,
1893
- transition: { staggerChildren: 0.03, delayChildren: 0.2 },
1894
- },
1895
- },
1896
- child: {
1897
- visible: {
1898
- opacity: 1,
1899
- filter: "blur(0px)",
1900
- transition: { duration: 0.6 },
1901
- },
1902
- hidden: { opacity: 0, filter: "blur(10px)" },
1903
- },
1904
- },
1905
- blurInUp: {
1906
- container: {
1907
- hidden: { opacity: 0 },
1908
- visible: {
1909
- opacity: 1,
1910
- transition: { staggerChildren: 0.04, delayChildren: 0.2 },
1911
- },
1912
- },
1913
- child: {
1914
- visible: {
1915
- opacity: 1,
1916
- y: 0,
1917
- filter: "blur(0px)",
1918
- transition: { duration: 0.7 },
1919
- },
1920
- hidden: { opacity: 0, y: 20, filter: "blur(8px)" },
1921
- },
1922
- },
1923
- blurInDown: {
1924
- container: {
1925
- hidden: { opacity: 0 },
1926
- visible: {
1927
- opacity: 1,
1928
- transition: { staggerChildren: 0.04, delayChildren: 0.2 },
1929
- },
1930
- },
1931
- child: {
1932
- visible: {
1933
- opacity: 1,
1934
- y: 0,
1935
- filter: "blur(0px)",
1936
- transition: { duration: 0.7 },
1937
- },
1938
- hidden: { opacity: 0, y: -20, filter: "blur(8px)" },
1939
- },
1940
- },
1941
- slideUp: {
1942
- container: {
1943
- hidden: {},
1944
- visible: {
1945
- transition: { staggerChildren: 0.03, delayChildren: 0.2 },
1946
- },
1947
- },
1948
- child: {
1949
- visible: {
1950
- y: 0,
1951
- transition: { duration: 0.5, ease: [0.22, 1, 0.36, 1] as const },
1952
- },
1953
- hidden: { y: 30 },
1954
- },
1955
- },
1956
- slideDown: {
1957
- container: {
1958
- hidden: {},
1959
- visible: {
1960
- transition: { staggerChildren: 0.03, delayChildren: 0.2 },
1961
- },
1962
- },
1963
- child: {
1964
- visible: {
1965
- y: 0,
1966
- transition: { duration: 0.5, ease: [0.22, 1, 0.36, 1] as const },
1967
- },
1968
- hidden: { y: -30 },
1969
- },
1970
- },
1971
- slideLeft: {
1972
- container: {
1973
- hidden: {},
1974
- visible: {
1975
- transition: { staggerChildren: 0.03, delayChildren: 0.2 },
1976
- },
1977
- },
1978
- child: {
1979
- visible: {
1980
- x: 0,
1981
- transition: { duration: 0.5, ease: [0.22, 1, 0.36, 1] as const },
1982
- },
1983
- hidden: { x: 30 },
1984
- },
1985
- },
1986
- slideRight: {
1987
- container: {
1988
- hidden: {},
1989
- visible: {
1990
- transition: { staggerChildren: 0.03, delayChildren: 0.2 },
1991
- },
1992
- },
1993
- child: {
1994
- visible: {
1995
- x: 0,
1996
- transition: { duration: 0.5, ease: [0.22, 1, 0.36, 1] as const },
1997
- },
1998
- hidden: { x: -30 },
1999
- },
2000
- },
2001
- scaleUp: {
2002
- container: {
2003
- hidden: {},
2004
- visible: {
2005
- transition: { staggerChildren: 0.04, delayChildren: 0.2 },
2006
- },
2007
- },
2008
- child: {
2009
- visible: {
2010
- scale: 1,
2011
- opacity: 1,
2012
- transition: {
2013
- duration: 0.5,
2014
- ease: [0.34, 1.56, 0.64, 1] as const,
2015
- },
2016
- },
2017
- hidden: { scale: 0.5, opacity: 0 },
2018
- },
2019
- },
2020
- scaleDown: {
2021
- container: {
2022
- hidden: {},
2023
- visible: {
2024
- transition: { staggerChildren: 0.04, delayChildren: 0.2 },
2025
- },
2026
- },
2027
- child: {
2028
- visible: {
2029
- scale: 1,
2030
- opacity: 1,
2031
- transition: {
2032
- duration: 0.5,
2033
- ease: [0.34, 1.56, 0.64, 1] as const,
2034
- },
2035
- },
2036
- hidden: { scale: 1.5, opacity: 0 },
2037
- },
2038
- },
2039
- }
2040
-
2041
- const AnimateText: FC<Props> = ({
2042
- text,
2043
- type = "whipInUp",
2044
- variant = 'default',
2045
- ...props
2046
- }: Props) => {
2047
- // const { ref, inView } = useInView({
2048
- // threshold: 0.5,
2049
- // triggerOnce: true,
2050
- // });
2051
-
2052
- const ref = useRef(null)
2053
- const isInView = useInView(ref, { once: true })
2054
-
2055
- const letters = Array.from(text)
2056
- const { container, child } = animationVariants[type]
2057
-
2058
- const ctrls = useAnimation()
2059
-
2060
- // useEffect(() => {
2061
- // if (isInView) {
2062
- // ctrls.start("visible");
2063
- // }
2064
- // if (!isInView) {
2065
- // ctrls.start("hidden");
2066
- // }
2067
- // }, [ctrls, isInView]);
2068
-
2069
- if (type === "rollIn" || type === "whipIn") {
2070
- return (
2071
- <h2 className="mt-10 text-3xl font-black text-black dark:text-neutral-100 py-5 pb-8 px-8 md:text-5xl">
2072
- {text.split(" ").map((word, index) => {
2073
- return (
2074
- <motion.span
2075
- ref={ref}
2076
- className="inline-block mr-[0.25em] whitespace-nowrap"
2077
- aria-hidden="true"
2078
- key={index}
2079
- initial="hidden"
2080
- animate="visible"
2081
- variants={container}
2082
- transition={{
2083
- delayChildren: index * 0.13,
2084
- // delayChildren: index * 0.35,
2085
- staggerChildren: 0.025,
2086
- // staggerChildren: 0.05,
2087
- }}
2088
- >
2089
- {word.split("").map((character, index) => {
2090
- return (
2091
- <motion.span
2092
- aria-hidden="true"
2093
- key={index}
2094
- variants={child}
2095
- className="inline-block -mr-[0.01em]"
2096
- >
2097
- {character}
2098
- </motion.span>
2099
- )
2100
- })}
2101
- </motion.span>
2102
- )
2103
- })}
2104
- </h2>
2105
- )
2106
- }
2107
-
2108
- return (
2109
- <motion.h2
2110
- style={{ display: "flex", overflow: "hidden" }}
2111
- role="heading"
2112
- variants={container}
2113
- initial="hidden"
2114
- animate="visible"
2115
- className="mt-10 text-4xl font-black text-black dark:text-neutral-100 py-5 pb-8 px-8 md:text-5xl"
2116
- {...props}
2117
- >
2118
- {letters.map((letter, index) => (
2119
- <motion.span key={index} variants={child}>
2120
- {letter === " " ? "\u00A0" : letter}
2121
- </motion.span>
2122
- ))}
2123
- </motion.h2>
2124
- )
2125
- }
2126
-
2127
- interface GlitchTextProps extends React.HTMLAttributes<HTMLDivElement> {
2128
- text: string
2129
- as?: "h1" | "h2" | "h3" | "h4" | "h5" | "h6" | "p" | "span"
2130
- textClassName?: string
2131
- containerClassName?: string
2132
- colors?: {
2133
- red: string
2134
- green: string
2135
- blue: string
2136
- }
2137
- }
2138
-
2139
- const AnimatedGlitchText = React.forwardRef<HTMLDivElement, GlitchTextProps>(
2140
- ({
2141
- text,
2142
- as: Component = "h1",
2143
- className,
2144
- textClassName,
2145
- containerClassName,
2146
- colors = {
2147
- red: "#ff0000",
2148
- green: "#00ff00",
2149
- blue: "#0000ff"
2150
- },
2151
- ...props
2152
- }, ref) => {
2153
- return (
2154
- <div
2155
- ref={ref}
2156
- className={cn(
2157
- "flex items-center justify-center p-4",
2158
- "min-h-[200px]",
2159
- className
2160
- )}
2161
- {...props}
2162
- >
2163
- <div className={cn(
2164
- "relative",
2165
- containerClassName
2166
- )}>
2167
- <motion.div
2168
- className={cn(
2169
- "text-6xl font-bold absolute",
2170
- "mix-blend-multiply dark:mix-blend-screen",
2171
- textClassName
2172
- )}
2173
- animate={{
2174
- x: [-2, 2, -2],
2175
- y: [0, -1, 1],
2176
- skew: [0, -2, 2],
2177
- opacity: [1, 0.8, 0.9],
2178
- color: [colors.red, colors.red, colors.red]
2179
- }}
2180
- transition={{
2181
- duration: 0.15,
2182
- repeat: Infinity,
2183
- repeatType: "mirror",
2184
- ease: "anticipate"
2185
- }}
2186
- >
2187
- {text}
2188
- </motion.div>
2189
- <motion.div
2190
- className={cn(
2191
- "text-6xl font-bold absolute",
2192
- "mix-blend-multiply dark:mix-blend-screen",
2193
- textClassName
2194
- )}
2195
- animate={{
2196
- x: [2, -2, 2],
2197
- y: [1, -1, 0],
2198
- skew: [-2, 2, 0],
2199
- opacity: [0.9, 1, 0.8],
2200
- color: [colors.green, colors.green, colors.green]
2201
- }}
2202
- transition={{
2203
- duration: 0.13,
2204
- repeat: Infinity,
2205
- repeatType: "mirror",
2206
- ease: "anticipate"
2207
- }}
2208
- >
2209
- {text}
2210
- </motion.div>
2211
- <motion.div
2212
- className={cn(
2213
- "text-6xl font-bold",
2214
- "mix-blend-multiply dark:mix-blend-screen",
2215
- textClassName
2216
- )}
2217
- animate={{
2218
- x: [-1, 1, -1],
2219
- y: [-1, 1, 0],
2220
- skew: [2, -2, 0],
2221
- opacity: [0.8, 0.9, 1],
2222
- color: [colors.blue, colors.blue, colors.blue]
2223
- }}
2224
- transition={{
2225
- duration: 0.11,
2226
- repeat: Infinity,
2227
- repeatType: "mirror",
2228
- ease: "anticipate"
2229
- }}
2230
- >
2231
- {text}
2232
- </motion.div>
2233
- </div>
2234
- </div>
2235
- )
2236
- }
2237
- )
2238
- AnimatedGlitchText.displayName = "AnimatedGlitchText"
2239
-
2240
-
2241
-
2242
- export { AnimatedTextX }