Skip to content

Commit 12e0843

Browse files
committed
feat(): Adding hero text
1 parent 5c9d3ba commit 12e0843

8 files changed

Lines changed: 969 additions & 1 deletion

File tree

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@
3232
"@testing-library/jest-dom": "^5.17.0",
3333
"@testing-library/react": "^13.4.0",
3434
"@testing-library/user-event": "^13.5.0",
35+
"@tsparticles/engine": "^3.7.1",
36+
"@tsparticles/react": "^3.0.0",
37+
"@tsparticles/slim": "^3.7.1",
3538
"@types/jest": "^29.5.12",
3639
"@types/node": "^16.18.91",
3740
"@types/react": "^18.2.69",

src/components/CoverText/Cover.tsx

Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
'use client';
2+
import React, { useEffect, useId, useState } from 'react';
3+
import { AnimatePresence, motion } from 'framer-motion';
4+
import { useRef } from 'react';
5+
import { SparklesCore } from './Sparkle';
6+
7+
export const Cover = ({ children }: { children?: React.ReactNode }) => {
8+
const [hovered, setHovered] = useState(false);
9+
10+
const ref = useRef<HTMLDivElement>(null);
11+
12+
const [containerWidth, setContainerWidth] = useState(0);
13+
const [beamPositions, setBeamPositions] = useState<number[]>([]);
14+
15+
useEffect(() => {
16+
if (ref.current) {
17+
setContainerWidth(ref.current?.clientWidth ?? 0);
18+
19+
const height = ref.current?.clientHeight ?? 0;
20+
const numberOfBeams = Math.floor(height / 10); // Adjust the divisor to control the spacing
21+
const positions = Array.from(
22+
{ length: numberOfBeams },
23+
(_, i) => (i + 1) * (height / (numberOfBeams + 1)),
24+
);
25+
setBeamPositions(positions);
26+
}
27+
}, [ref.current]);
28+
29+
return (
30+
<div
31+
onMouseEnter={() => setHovered(true)}
32+
onMouseLeave={() => setHovered(false)}
33+
ref={ref}
34+
className="relative hover:bg-neutral-900 group/cover inline-block dark:bg-neutral-900 bg-neutral-100 px-2 py-2 transition duration-200 rounded-sm"
35+
>
36+
<AnimatePresence>
37+
{hovered && (
38+
<motion.div
39+
initial={{ opacity: 0 }}
40+
animate={{ opacity: 1 }}
41+
exit={{ opacity: 0 }}
42+
transition={{
43+
opacity: {
44+
duration: 0.2,
45+
},
46+
}}
47+
className="h-full w-full overflow-hidden absolute inset-0"
48+
>
49+
<motion.div
50+
animate={{
51+
translateX: ['-50%', '0%'],
52+
}}
53+
transition={{
54+
translateX: {
55+
duration: 10,
56+
ease: 'linear',
57+
repeat: Infinity,
58+
},
59+
}}
60+
className="w-[200%] h-full flex"
61+
>
62+
<SparklesCore
63+
background="transparent"
64+
minSize={0.4}
65+
maxSize={1}
66+
particleDensity={500}
67+
className="w-full h-full"
68+
particleColor="#FFFFFF"
69+
/>
70+
<SparklesCore
71+
background="transparent"
72+
minSize={0.4}
73+
maxSize={1}
74+
particleDensity={500}
75+
className="w-full h-full"
76+
particleColor="#FFFFFF"
77+
/>
78+
</motion.div>
79+
</motion.div>
80+
)}
81+
</AnimatePresence>
82+
{beamPositions.map((position, index) => (
83+
<Beam
84+
key={index}
85+
hovered={hovered}
86+
duration={Math.random() * 2 + 1}
87+
delay={Math.random() * 2 + 1}
88+
width={containerWidth}
89+
style={{
90+
top: `${position}px`,
91+
}}
92+
/>
93+
))}
94+
<motion.span
95+
key={String(hovered)}
96+
animate={{
97+
scale: hovered ? 0.8 : 1,
98+
x: hovered ? [0, -30, 30, -30, 30, 0] : 0,
99+
y: hovered ? [0, 30, -30, 30, -30, 0] : 0,
100+
}}
101+
exit={{
102+
filter: 'none',
103+
scale: 1,
104+
x: 0,
105+
y: 0,
106+
}}
107+
transition={{
108+
duration: 0.2,
109+
x: {
110+
duration: 0.2,
111+
repeat: Infinity,
112+
repeatType: 'loop',
113+
},
114+
y: {
115+
duration: 0.2,
116+
repeat: Infinity,
117+
repeatType: 'loop',
118+
},
119+
scale: {
120+
duration: 0.2,
121+
},
122+
filter: {
123+
duration: 0.2,
124+
},
125+
}}
126+
className={
127+
'dark:text-white inline-block text-neutral-900 relative z-20 group-hover/cover:text-white transition duration-200'
128+
}
129+
>
130+
{children}
131+
</motion.span>
132+
<CircleIcon className="absolute -right-[2px] -top-[2px]" />
133+
<CircleIcon className="absolute -bottom-[2px] -right-[2px]" delay={0.4} />
134+
<CircleIcon className="absolute -left-[2px] -top-[2px]" delay={0.8} />
135+
<CircleIcon className="absolute -bottom-[2px] -left-[2px]" delay={1.6} />
136+
</div>
137+
);
138+
};
139+
140+
export const Beam = ({
141+
className,
142+
delay,
143+
duration,
144+
hovered,
145+
width = 600,
146+
...svgProps
147+
}: {
148+
className?: string;
149+
delay?: number;
150+
duration?: number;
151+
hovered?: boolean;
152+
width?: number;
153+
} & React.ComponentProps<typeof motion.svg>) => {
154+
const id = useId();
155+
156+
return (
157+
<motion.svg
158+
width={width ?? '600'}
159+
height="1"
160+
viewBox={`0 0 ${width ?? '600'} 1`}
161+
fill="none"
162+
xmlns="http://www.w3.org/2000/svg"
163+
className={'absolute inset-x-0 w-full'}
164+
{...svgProps}
165+
>
166+
<motion.path
167+
d={`M0 0.5H${width ?? '600'}`}
168+
stroke={`url(#svgGradient-${id})`}
169+
/>
170+
171+
<defs>
172+
<motion.linearGradient
173+
id={`svgGradient-${id}`}
174+
key={String(hovered)}
175+
gradientUnits="userSpaceOnUse"
176+
initial={{
177+
x1: '0%',
178+
x2: hovered ? '-10%' : '-5%',
179+
y1: 0,
180+
y2: 0,
181+
}}
182+
animate={{
183+
x1: '110%',
184+
x2: hovered ? '100%' : '105%',
185+
y1: 0,
186+
y2: 0,
187+
}}
188+
transition={{
189+
duration: hovered ? 0.5 : duration ?? 2,
190+
ease: 'linear',
191+
repeat: Infinity,
192+
delay: hovered ? Math.random() * (1 - 0.2) + 0.2 : 0,
193+
repeatDelay: hovered ? Math.random() * (2 - 1) + 1 : delay ?? 1,
194+
}}
195+
>
196+
<stop stopColor="#2EB9DF" stopOpacity="0" />
197+
<stop stopColor="#3b82f6" />
198+
<stop offset="1" stopColor="#3b82f6" stopOpacity="0" />
199+
</motion.linearGradient>
200+
</defs>
201+
</motion.svg>
202+
);
203+
};
204+
205+
export const CircleIcon = ({
206+
className,
207+
delay,
208+
}: {
209+
className?: string;
210+
delay?: number;
211+
}) => {
212+
return (
213+
<div
214+
className={
215+
`pointer-events-none animate-pulse group-hover/cover:hidden group-hover/cover:opacity-100 group h-2 w-2 rounded-full bg-neutral-600 dark:bg-white opacity-20 group-hover/cover:bg-white ` +
216+
className
217+
}
218+
></div>
219+
);
220+
};
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { Box } from '@chakra-ui/react';
2+
import { Cover } from './Cover';
3+
4+
const CoverText = ({
5+
text,
6+
highlightedText,
7+
}: {
8+
text: string;
9+
highlightedText: string;
10+
}) => {
11+
return (
12+
<Box position={'absolute'} left={56} top={'200px'}>
13+
<h1 className="text-4xl md:text-4xl lg:text-6xl font-semibold max-w-7xl mx-auto mt-6 relative z-20 py-6 bg-clip-text text-transparent bg-gradient-to-b from-neutral-800 via-neutral-700 to-neutral-700 dark:from-neutral-800 dark:via-white dark:to-white">
14+
{text} <br />
15+
<br /> <Cover>{highlightedText}</Cover>
16+
</h1>
17+
</Box>
18+
);
19+
};
20+
21+
export default CoverText;

0 commit comments

Comments
 (0)