Skip to content

Commit 70784b5

Browse files
Merge pull request #41 from StabilityNexus/sarthak
Requested changes
2 parents 52e2c95 + 0510a4a commit 70784b5

5 files changed

Lines changed: 345 additions & 9 deletions

File tree

app/page.tsx

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { useState, useEffect, useRef } from "react"
55
import { Button } from "@/components/ui/button"
66
import { Card, CardContent } from "@/components/ui/card"
77
import { Badge } from "@/components/ui/badge"
8+
import DecryptedText from "@/components/DecryptedText"
89

910
import { HackathonData, getHackathonStatus } from "@/hooks/useHackathons"
1011
import { getPublicClient } from "@wagmi/core"
@@ -220,10 +221,28 @@ export default function HomePage() {
220221
<div className="relative z-10 -mt-24 pb-8">
221222
<h1 className="text-6xl lg:text-7xl font-black leading-tight">
222223
<span className="block text-transparent bg-clip-text bg-gradient-to-r from-amber-600 via-orange-500 to-amber-700 drop-shadow-sm">
223-
Welcome to the Hub for
224+
<DecryptedText
225+
text="Welcome to the On-Chain"
226+
animateOn="view"
227+
speed={75}
228+
maxIterations={15}
229+
sequential={true}
230+
revealDirection="start"
231+
className=""
232+
encryptedClassName="text-amber-400/40"
233+
/>
224234
</span>
225235
<span className="block text-6xl lg:text-8xl font-extrabold text-transparent bg-clip-text bg-gradient-to-r from-amber-700 via-orange-600 to-amber-800 mt-4 tracking-tight">
226-
On-Chain Hackathons.
236+
<DecryptedText
237+
text="Hackathon Hub."
238+
animateOn="view"
239+
speed={100}
240+
maxIterations={20}
241+
sequential={true}
242+
revealDirection="center"
243+
className=""
244+
encryptedClassName="text-orange-400/30"
245+
/>
227246
</span>
228247
</h1>
229248

@@ -232,7 +251,16 @@ export default function HomePage() {
232251

233252
<div className="mt-8">
234253
<p className="text-xl lg:text-2xl font-semibold text-transparent bg-clip-text bg-gradient-to-r from-amber-600 to-orange-600 tracking-wider">
235-
Transparent. Permissionless. Easy.
254+
<DecryptedText
255+
text="Transparent. Permissionless. Intuitive."
256+
animateOn="view"
257+
speed={60}
258+
maxIterations={12}
259+
sequential={true}
260+
revealDirection="start"
261+
className=""
262+
encryptedClassName="text-amber-500/30"
263+
/>
236264
</p>
237265
</div>
238266
</div>
@@ -268,7 +296,6 @@ export default function HomePage() {
268296
{/* Revolution Text - Moved Higher */}
269297
<div className="relative -mt-8">
270298
{/* Small decorative elements */}
271-
<div className="absolute -top-2 -right-2 w-8 h-8 bg-gradient-to-br from-amber-300 to-orange-300 rounded-full opacity-50"></div>
272299

273300
<div className="relative z-10 mt-">
274301
<div className="flex items-center justify-between">

components/DecryptedText.tsx

Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
import { useEffect, useState, useRef, ReactNode } from 'react'
2+
import { motion, HTMLMotionProps } from 'motion/react'
3+
4+
const styles = {
5+
wrapper: {
6+
display: 'inline-block',
7+
whiteSpace: 'pre-wrap',
8+
},
9+
srOnly: {
10+
position: 'absolute' as 'absolute',
11+
width: '1px',
12+
height: '1px',
13+
padding: 0,
14+
margin: '-1px',
15+
overflow: 'hidden',
16+
clip: 'rect(0,0,0,0)',
17+
border: 0,
18+
},
19+
}
20+
21+
interface DecryptedTextProps extends HTMLMotionProps<'span'> {
22+
text: string
23+
speed?: number
24+
maxIterations?: number
25+
sequential?: boolean
26+
revealDirection?: 'start' | 'end' | 'center'
27+
useOriginalCharsOnly?: boolean
28+
characters?: string
29+
className?: string
30+
parentClassName?: string
31+
encryptedClassName?: string
32+
animateOn?: 'view' | 'hover'
33+
}
34+
35+
export default function DecryptedText({
36+
text,
37+
speed = 50,
38+
maxIterations = 10,
39+
sequential = false,
40+
revealDirection = 'start',
41+
useOriginalCharsOnly = false,
42+
characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!@#$%^&*()_+',
43+
className = '',
44+
parentClassName = '',
45+
encryptedClassName = '',
46+
animateOn = 'hover',
47+
...props
48+
}: DecryptedTextProps) {
49+
const [displayText, setDisplayText] = useState<string>(text)
50+
const [isHovering, setIsHovering] = useState<boolean>(false)
51+
const [isScrambling, setIsScrambling] = useState<boolean>(false)
52+
const [revealedIndices, setRevealedIndices] = useState<Set<number>>(new Set())
53+
const [hasAnimated, setHasAnimated] = useState<boolean>(false)
54+
const containerRef = useRef<HTMLSpanElement>(null)
55+
56+
useEffect(() => {
57+
let interval: NodeJS.Timeout;
58+
let currentIteration = 0
59+
60+
const getNextIndex = (revealedSet: Set<number>): number => {
61+
const textLength = text.length
62+
switch (revealDirection) {
63+
case 'start':
64+
return revealedSet.size
65+
case 'end':
66+
return textLength - 1 - revealedSet.size
67+
case 'center': {
68+
const middle = Math.floor(textLength / 2)
69+
const offset = Math.floor(revealedSet.size / 2)
70+
const nextIndex =
71+
revealedSet.size % 2 === 0
72+
? middle + offset
73+
: middle - offset - 1
74+
75+
if (nextIndex >= 0 && nextIndex < textLength && !revealedSet.has(nextIndex)) {
76+
return nextIndex
77+
}
78+
79+
for (let i = 0; i < textLength; i++) {
80+
if (!revealedSet.has(i)) return i
81+
}
82+
return 0
83+
}
84+
default:
85+
return revealedSet.size
86+
}
87+
}
88+
89+
const availableChars = useOriginalCharsOnly
90+
? Array.from(new Set(text.split(''))).filter((char) => char !== ' ')
91+
: characters.split('')
92+
93+
const shuffleText = (originalText: string, currentRevealed: Set<number>): string => {
94+
if (useOriginalCharsOnly) {
95+
const positions = originalText.split('').map((char, i) => ({
96+
char,
97+
isSpace: char === ' ',
98+
index: i,
99+
isRevealed: currentRevealed.has(i),
100+
}))
101+
102+
const nonSpaceChars = positions
103+
.filter((p) => !p.isSpace && !p.isRevealed)
104+
.map((p) => p.char)
105+
106+
for (let i = nonSpaceChars.length - 1; i > 0; i--) {
107+
const j = Math.floor(Math.random() * (i + 1))
108+
;[nonSpaceChars[i], nonSpaceChars[j]] = [nonSpaceChars[j], nonSpaceChars[i]]
109+
}
110+
111+
let charIndex = 0
112+
return positions
113+
.map((p) => {
114+
if (p.isSpace) return ' '
115+
if (p.isRevealed) return originalText[p.index]
116+
return nonSpaceChars[charIndex++]
117+
})
118+
.join('')
119+
} else {
120+
return originalText
121+
.split('')
122+
.map((char, i) => {
123+
if (char === ' ') return ' '
124+
if (currentRevealed.has(i)) return originalText[i]
125+
return availableChars[Math.floor(Math.random() * availableChars.length)]
126+
})
127+
.join('')
128+
}
129+
}
130+
131+
if (isHovering) {
132+
setIsScrambling(true)
133+
interval = setInterval(() => {
134+
setRevealedIndices((prevRevealed) => {
135+
if (sequential) {
136+
if (prevRevealed.size < text.length) {
137+
const nextIndex = getNextIndex(prevRevealed)
138+
const newRevealed = new Set(prevRevealed)
139+
newRevealed.add(nextIndex)
140+
setDisplayText(shuffleText(text, newRevealed))
141+
return newRevealed
142+
} else {
143+
clearInterval(interval)
144+
setIsScrambling(false)
145+
return prevRevealed
146+
}
147+
} else {
148+
setDisplayText(shuffleText(text, prevRevealed))
149+
currentIteration++
150+
if (currentIteration >= maxIterations) {
151+
clearInterval(interval)
152+
setIsScrambling(false)
153+
setDisplayText(text)
154+
}
155+
return prevRevealed
156+
}
157+
})
158+
}, speed)
159+
} else {
160+
setDisplayText(text)
161+
setRevealedIndices(new Set())
162+
setIsScrambling(false)
163+
}
164+
165+
return () => {
166+
if (interval) clearInterval(interval)
167+
}
168+
}, [
169+
isHovering,
170+
text,
171+
speed,
172+
maxIterations,
173+
sequential,
174+
revealDirection,
175+
characters,
176+
useOriginalCharsOnly,
177+
])
178+
179+
useEffect(() => {
180+
if (animateOn !== 'view') return
181+
182+
const observerCallback = (entries: IntersectionObserverEntry[]) => {
183+
entries.forEach((entry) => {
184+
if (entry.isIntersecting && !hasAnimated) {
185+
setIsHovering(true)
186+
setHasAnimated(true)
187+
}
188+
})
189+
}
190+
191+
const observerOptions = {
192+
root: null,
193+
rootMargin: '0px',
194+
threshold: 0.1,
195+
}
196+
197+
const observer = new IntersectionObserver(observerCallback, observerOptions)
198+
const currentRef = containerRef.current
199+
if (currentRef) {
200+
observer.observe(currentRef)
201+
}
202+
203+
return () => {
204+
if (currentRef) {
205+
observer.unobserve(currentRef)
206+
}
207+
}
208+
}, [animateOn, hasAnimated])
209+
210+
const hoverProps =
211+
animateOn === 'hover'
212+
? {
213+
onMouseEnter: () => setIsHovering(true),
214+
onMouseLeave: () => setIsHovering(false),
215+
}
216+
: {}
217+
218+
return (
219+
<motion.span className={parentClassName} ref={containerRef} style={styles.wrapper} {...hoverProps} {...props}>
220+
<span style={styles.srOnly}>{displayText}</span>
221+
222+
<span aria-hidden="true">
223+
{displayText.split('').map((char, index) => {
224+
const isRevealedOrDone =
225+
revealedIndices.has(index) || !isScrambling || !isHovering
226+
227+
return (
228+
<span
229+
key={index}
230+
className={isRevealedOrDone ? className : encryptedClassName}
231+
>
232+
{char}
233+
</span>
234+
)
235+
})}
236+
</span>
237+
</motion.span>
238+
)
239+
}

components/navigation.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,10 @@ export default function Navigation() {
3333
: "text-gray-700 hover:text-amber-800 hover:bg-amber-50/80"
3434
} transition-all duration-200 font-medium`}
3535
>
36-
Explorer
36+
Explorer Hackathons
3737
</Button>
3838
</Link>
39-
<Link href="/myHackathons">
39+
<Link href="/createHackathon">
4040
<Button
4141
variant="ghost"
4242
className={`${
@@ -45,10 +45,10 @@ export default function Navigation() {
4545
: "text-gray-700 hover:text-amber-800 hover:bg-amber-50/80"
4646
} transition-all duration-200 font-medium`}
4747
>
48-
My Hackathons
48+
Organize a Hackathon
4949
</Button>
5050
</Link>
51-
<Link href="/createHackathon">
51+
<Link href="/myHackathons">
5252
<Button
5353
variant="ghost"
5454
className={`${
@@ -57,7 +57,7 @@ export default function Navigation() {
5757
: "text-gray-700 hover:text-amber-800 hover:bg-amber-50/80"
5858
} transition-all duration-200 font-medium`}
5959
>
60-
Create
60+
My Hackathons
6161
</Button>
6262
</Link>
6363
</nav>

0 commit comments

Comments
 (0)