Skip to content

Commit 6bd5fb5

Browse files
authored
Merge pull request #11 from kinotio/develop
feat: create hero section
2 parents 0d36e44 + eb8251b commit 6bd5fb5

File tree

12 files changed

+380
-52
lines changed

12 files changed

+380
-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: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
/* eslint-disable react-hooks/exhaustive-deps */
2+
/* eslint-disable @typescript-eslint/no-unused-vars */
3+
/* eslint-disable @typescript-eslint/no-explicit-any */
4+
'use client'
5+
6+
import { useEffect, useId, useRef, useState } from 'react'
7+
import { motion } from 'framer-motion'
8+
9+
import { cn } from '@/lib/utils'
10+
11+
interface GridPatternProps {
12+
width?: number
13+
height?: number
14+
x?: number
15+
y?: number
16+
strokeDasharray?: any
17+
numSquares?: number
18+
className?: string
19+
maxOpacity?: number
20+
duration?: number
21+
repeatDelay?: number
22+
}
23+
24+
export function GridPattern({
25+
width = 40,
26+
height = 40,
27+
x = -1,
28+
y = -1,
29+
strokeDasharray = 0,
30+
numSquares = 50,
31+
className,
32+
maxOpacity = 0.5,
33+
duration = 4,
34+
repeatDelay = 0.5,
35+
...props
36+
}: GridPatternProps) {
37+
const id = useId()
38+
const containerRef = useRef(null)
39+
const [dimensions, setDimensions] = useState({ width: 0, height: 0 })
40+
const [squares, setSquares] = useState(() => generateSquares(numSquares))
41+
42+
function getPos() {
43+
return [
44+
Math.floor((Math.random() * dimensions.width) / width),
45+
Math.floor((Math.random() * dimensions.height) / height)
46+
]
47+
}
48+
49+
// Adjust the generateSquares function to return objects with an id, x, and y
50+
function generateSquares(count: number) {
51+
return Array.from({ length: count }, (_, i) => ({
52+
id: i,
53+
pos: getPos()
54+
}))
55+
}
56+
57+
// Function to update a single square's position
58+
const updateSquarePosition = (id: number) => {
59+
setSquares((currentSquares) =>
60+
currentSquares.map((sq) =>
61+
sq.id === id
62+
? {
63+
...sq,
64+
pos: getPos()
65+
}
66+
: sq
67+
)
68+
)
69+
}
70+
71+
// Update squares to animate in
72+
useEffect(() => {
73+
if (dimensions.width && dimensions.height) {
74+
setSquares(generateSquares(numSquares))
75+
}
76+
// eslint-disable-next-line react-hooks/exhaustive-deps
77+
}, [dimensions, numSquares])
78+
79+
// Resize observer to update container dimensions
80+
useEffect(() => {
81+
const resizeObserver = new ResizeObserver((entries) => {
82+
for (const entry of entries) {
83+
setDimensions({
84+
width: entry.contentRect.width,
85+
height: entry.contentRect.height
86+
})
87+
}
88+
})
89+
90+
if (containerRef.current) {
91+
resizeObserver.observe(containerRef.current)
92+
}
93+
94+
return () => {
95+
if (containerRef.current) {
96+
resizeObserver.unobserve(containerRef.current)
97+
}
98+
}
99+
}, [containerRef])
100+
101+
return (
102+
<svg
103+
ref={containerRef}
104+
aria-hidden='true'
105+
className={cn(
106+
'pointer-events-none absolute inset-0 h-full w-full fill-gray-400/30 stroke-gray-400/30',
107+
className
108+
)}
109+
{...props}
110+
>
111+
<defs>
112+
<pattern id={id} width={width} height={height} patternUnits='userSpaceOnUse' x={x} y={y}>
113+
<path d={`M.5 ${height}V.5H${width}`} fill='none' strokeDasharray={strokeDasharray} />
114+
</pattern>
115+
</defs>
116+
<rect width='100%' height='100%' fill={`url(#${id})`} />
117+
<svg x={x} y={y} className='overflow-visible'>
118+
{squares.map(({ pos: [x, y], id }, index) => (
119+
<motion.rect
120+
initial={{ opacity: 0 }}
121+
animate={{ opacity: maxOpacity }}
122+
transition={{
123+
duration,
124+
repeat: 1,
125+
delay: index * 0.1,
126+
repeatType: 'reverse'
127+
}}
128+
onAnimationComplete={() => updateSquarePosition(id)}
129+
key={`${x}-${y}-${index}`}
130+
width={width - 1}
131+
height={height - 1}
132+
x={x * width + 1}
133+
y={y * height + 1}
134+
fill='currentColor'
135+
strokeWidth='0'
136+
/>
137+
))}
138+
</svg>
139+
</svg>
140+
)
141+
}
142+
143+
export default GridPattern

src/components/ui/blur-fade.tsx

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

src/pages/docs/_meta.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
const meta = {
2+
index: 'Documentations',
23
drowser: 'Drowser',
34
gelda: 'Gelda'
45
}

src/pages/docs/index.mdx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Index

0 commit comments

Comments
 (0)