Skip to content

Commit bb5109c

Browse files
committed
feat: create hero section
1 parent 9eb0af9 commit bb5109c

File tree

10 files changed

+391
-52
lines changed

10 files changed

+391
-52
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"@radix-ui/react-slot": "^1.1.0",
1818
"class-variance-authority": "^0.7.0",
1919
"clsx": "^2.1.1",
20+
"framer-motion": "^11.11.9",
2021
"lucide-react": "^0.453.0",
2122
"next": "14.2.3",
2223
"next-themes": "^0.3.0",

pnpm-lock.yaml

Lines changed: 24 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
File renamed without changes.
File renamed without changes.

src/components/sections/hero.tsx

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
'use client'
2+
3+
import { ChevronRight, Github } from 'lucide-react'
4+
// import Link from 'next/link'
5+
6+
import { Button } from '@/components/ui/button'
7+
8+
import AnimatedGridPattern from '@/components/ui/animated-grid-pattern'
9+
import BlurFade from '@/components/ui/blur-fade'
10+
11+
import { cn } from '@/lib/utils'
12+
13+
export const Hero = () => {
14+
return (
15+
<section className='w-full relative'>
16+
<div className='container relative z-10 grid place-items-center lg:max-w-screen-xl gap-8 mx-auto py-20 md:py-28'>
17+
<div className='space-y-8'>
18+
<BlurFade delay={0.1}>
19+
<div className='mx-auto text-center text-7xl md:text-9xl font-bold'>
20+
<h1>Explore our documentations</h1>
21+
</div>
22+
</BlurFade>
23+
24+
<div className='space-y-4 md:space-y-0 md:space-x-4'>
25+
<BlurFade delay={0.3}>
26+
<div className='mt-6 gap-2 flex justify-center'>
27+
<Button
28+
className='w-5/6 md:w-1/4 font-bold group/arrow'
29+
onClick={() => window.open('/docs', '_blank')}
30+
>
31+
Documentation
32+
<ChevronRight className='size-5 ml-2 group-hover/arrow:translate-x-1 transition-transform' />
33+
</Button>
34+
<Button
35+
className='w-5/6 md:w-1/4 font-bold group/arrow'
36+
variant='outline'
37+
onClick={() => {
38+
window.open('https://github.com/kinotio/drowser', '_blank')
39+
}}
40+
>
41+
<Github className='size-5 mr-2' />
42+
Github
43+
<ChevronRight className='size-5 ml-2 group-hover/arrow:translate-x-1 transition-transform' />
44+
</Button>
45+
</div>
46+
</BlurFade>
47+
</div>
48+
</div>
49+
</div>
50+
51+
<AnimatedGridPattern
52+
numSquares={30}
53+
className={cn(
54+
'[mask-image:radial-gradient(600px_circle_at_center,white,transparent)]',
55+
'inset-x-0 inset-y-[-30%]'
56+
)}
57+
/>
58+
</section>
59+
)
60+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { ReactNode } from "react";
2+
3+
import { cn } from "@/lib/utils";
4+
5+
export default function AnimatedGradientText({
6+
children,
7+
className,
8+
}: {
9+
children: ReactNode;
10+
className?: string;
11+
}) {
12+
return (
13+
<div
14+
className={cn(
15+
"group relative mx-auto flex max-w-fit flex-row items-center justify-center rounded-2xl bg-white/40 px-4 py-1.5 text-sm font-medium shadow-[inset_0_-8px_10px_#8fdfff1f] backdrop-blur-sm transition-shadow duration-500 ease-out [--bg-size:300%] hover:shadow-[inset_0_-5px_10px_#8fdfff3f] dark:bg-black/40",
16+
className,
17+
)}
18+
>
19+
<div
20+
className={`absolute inset-0 block h-full w-full animate-gradient bg-gradient-to-r from-[#ffaa40]/50 via-[#9c40ff]/50 to-[#ffaa40]/50 bg-[length:var(--bg-size)_100%] p-[1px] ![mask-composite:subtract] [border-radius:inherit] [mask:linear-gradient(#fff_0_0)_content-box,linear-gradient(#fff_0_0)]`}
21+
/>
22+
23+
{children}
24+
</div>
25+
);
26+
}
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
"use client";
2+
3+
import { useEffect, useId, useRef, useState } from "react";
4+
import { motion } from "framer-motion";
5+
6+
import { cn } from "@/lib/utils";
7+
8+
interface GridPatternProps {
9+
width?: number;
10+
height?: number;
11+
x?: number;
12+
y?: number;
13+
strokeDasharray?: any;
14+
numSquares?: number;
15+
className?: string;
16+
maxOpacity?: number;
17+
duration?: number;
18+
repeatDelay?: number;
19+
}
20+
21+
export function GridPattern({
22+
width = 40,
23+
height = 40,
24+
x = -1,
25+
y = -1,
26+
strokeDasharray = 0,
27+
numSquares = 50,
28+
className,
29+
maxOpacity = 0.5,
30+
duration = 4,
31+
repeatDelay = 0.5,
32+
...props
33+
}: GridPatternProps) {
34+
const id = useId();
35+
const containerRef = useRef(null);
36+
const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
37+
const [squares, setSquares] = useState(() => generateSquares(numSquares));
38+
39+
function getPos() {
40+
return [
41+
Math.floor((Math.random() * dimensions.width) / width),
42+
Math.floor((Math.random() * dimensions.height) / height),
43+
];
44+
}
45+
46+
// Adjust the generateSquares function to return objects with an id, x, and y
47+
function generateSquares(count: number) {
48+
return Array.from({ length: count }, (_, i) => ({
49+
id: i,
50+
pos: getPos(),
51+
}));
52+
}
53+
54+
// Function to update a single square's position
55+
const updateSquarePosition = (id: number) => {
56+
setSquares((currentSquares) =>
57+
currentSquares.map((sq) =>
58+
sq.id === id
59+
? {
60+
...sq,
61+
pos: getPos(),
62+
}
63+
: sq,
64+
),
65+
);
66+
};
67+
68+
// Update squares to animate in
69+
useEffect(() => {
70+
if (dimensions.width && dimensions.height) {
71+
setSquares(generateSquares(numSquares));
72+
}
73+
}, [dimensions, numSquares]);
74+
75+
// Resize observer to update container dimensions
76+
useEffect(() => {
77+
const resizeObserver = new ResizeObserver((entries) => {
78+
for (let entry of entries) {
79+
setDimensions({
80+
width: entry.contentRect.width,
81+
height: entry.contentRect.height,
82+
});
83+
}
84+
});
85+
86+
if (containerRef.current) {
87+
resizeObserver.observe(containerRef.current);
88+
}
89+
90+
return () => {
91+
if (containerRef.current) {
92+
resizeObserver.unobserve(containerRef.current);
93+
}
94+
};
95+
}, [containerRef]);
96+
97+
return (
98+
<svg
99+
ref={containerRef}
100+
aria-hidden="true"
101+
className={cn(
102+
"pointer-events-none absolute inset-0 h-full w-full fill-gray-400/30 stroke-gray-400/30",
103+
className,
104+
)}
105+
{...props}
106+
>
107+
<defs>
108+
<pattern
109+
id={id}
110+
width={width}
111+
height={height}
112+
patternUnits="userSpaceOnUse"
113+
x={x}
114+
y={y}
115+
>
116+
<path
117+
d={`M.5 ${height}V.5H${width}`}
118+
fill="none"
119+
strokeDasharray={strokeDasharray}
120+
/>
121+
</pattern>
122+
</defs>
123+
<rect width="100%" height="100%" fill={`url(#${id})`} />
124+
<svg x={x} y={y} className="overflow-visible">
125+
{squares.map(({ pos: [x, y], id }, index) => (
126+
<motion.rect
127+
initial={{ opacity: 0 }}
128+
animate={{ opacity: maxOpacity }}
129+
transition={{
130+
duration,
131+
repeat: 1,
132+
delay: index * 0.1,
133+
repeatType: "reverse",
134+
}}
135+
onAnimationComplete={() => updateSquarePosition(id)}
136+
key={`${x}-${y}-${index}`}
137+
width={width - 1}
138+
height={height - 1}
139+
x={x * width + 1}
140+
y={y * height + 1}
141+
fill="currentColor"
142+
strokeWidth="0"
143+
/>
144+
))}
145+
</svg>
146+
</svg>
147+
);
148+
}
149+
150+
export default GridPattern;

src/components/ui/blur-fade.tsx

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
"use client";
2+
3+
import { useRef } from "react";
4+
import {
5+
AnimatePresence,
6+
motion,
7+
useInView,
8+
UseInViewOptions,
9+
Variants,
10+
} from "framer-motion";
11+
12+
type MarginType = UseInViewOptions["margin"];
13+
14+
interface BlurFadeProps {
15+
children: React.ReactNode;
16+
className?: string;
17+
variant?: {
18+
hidden: { y: number };
19+
visible: { y: number };
20+
};
21+
duration?: number;
22+
delay?: number;
23+
yOffset?: number;
24+
inView?: boolean;
25+
inViewMargin?: MarginType;
26+
blur?: string;
27+
}
28+
29+
export default function BlurFade({
30+
children,
31+
className,
32+
variant,
33+
duration = 0.4,
34+
delay = 0,
35+
yOffset = 6,
36+
inView = false,
37+
inViewMargin = "-50px",
38+
blur = "6px",
39+
}: BlurFadeProps) {
40+
const ref = useRef(null);
41+
const inViewResult = useInView(ref, { once: true, margin: inViewMargin });
42+
const isInView = !inView || inViewResult;
43+
const defaultVariants: Variants = {
44+
hidden: { y: yOffset, opacity: 0, filter: `blur(${blur})` },
45+
visible: { y: -yOffset, opacity: 1, filter: `blur(0px)` },
46+
};
47+
const combinedVariants = variant || defaultVariants;
48+
return (
49+
<AnimatePresence>
50+
<motion.div
51+
ref={ref}
52+
initial="hidden"
53+
animate={isInView ? "visible" : "hidden"}
54+
exit="hidden"
55+
variants={combinedVariants}
56+
transition={{
57+
delay: 0.04 + delay,
58+
duration,
59+
ease: "easeOut",
60+
}}
61+
className={className}
62+
>
63+
{children}
64+
</motion.div>
65+
</AnimatePresence>
66+
);
67+
}

src/pages/index.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,16 @@ const inter = Inter({ subsets: ['latin'] })
33

44
import { ThemeProvider } from '@/components/theme-provider'
55

6-
import { Header } from '@/components/header'
7-
import { Footer } from '@/components/footer'
6+
import { Header } from '@/components/common/header'
7+
import { Footer } from '@/components/common/footer'
8+
import { Hero } from '@/components/sections/hero'
89

910
export default function Home() {
1011
return (
1112
<ThemeProvider attribute='class' defaultTheme='system' enableSystem disableTransitionOnChange>
1213
<div className={inter.className}>
1314
<Header />
14-
<h1>Hello docs</h1>
15+
<Hero />
1516
<Footer />
1617
</div>
1718
</ThemeProvider>

0 commit comments

Comments
 (0)