Skip to content

Commit

Permalink
Merge pull request #49 from getcronit/rework-landing-page
Browse files Browse the repository at this point in the history
docs: rework landing page
  • Loading branch information
schettn authored Nov 11, 2024
2 parents a87d141 + cf91d03 commit 3cc9d5c
Show file tree
Hide file tree
Showing 10 changed files with 767 additions and 45 deletions.
Binary file modified docs/bun.lockb
Binary file not shown.
15 changes: 8 additions & 7 deletions docs/components/clipboard-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
import {useState} from 'react'
import {Copy, Check} from 'lucide-react'
import {useCopyToClipboard} from 'usehooks-ts'
import {Button} from '@/components/ui/button'
import {Button, ButtonProps} from '@/components/ui/button'

interface ClipboardButtonProps {
interface ClipboardButtonProps extends ButtonProps {
text: string
}

export function ClipboardButton(
{text}: ClipboardButtonProps = {text: 'Copy me!'}
{text, ...rest}: ClipboardButtonProps = {text: 'Copy me!'}
) {
const [isCopied, setIsCopied] = useState(false)
const [_, copy] = useCopyToClipboard()
Expand All @@ -24,14 +24,15 @@ export function ClipboardButton(
return (
<Button
variant="outline"
size="icon"
className="font-mono text-sm"
onClick={handleCopy}
aria-label={isCopied ? 'Copied!' : 'Copy to clipboard'}>
{text}
aria-label={isCopied ? 'Copied!' : 'Copy to clipboard'}
{...rest}>
{isCopied ? (
<Check className="ml-2 h-4 w-4 text-green-500" />
<Check className="size-4 text-green-500" />
) : (
<Copy className="ml-2 h-4 w-4" />
<Copy className="size-4" />
)}
</Button>
)
Expand Down
464 changes: 438 additions & 26 deletions docs/components/landing.tsx

Large diffs are not rendered by default.

8 changes: 5 additions & 3 deletions docs/components/ui/card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,18 @@ import {cn} from '@/lib/utils'

const Card = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({className, ...props}, ref) => (
React.HTMLAttributes<HTMLDivElement> & {showGradient?: boolean}
>(({className, showGradient = true, ...props}, ref) => (
<div
ref={ref}
className={cn(
'rounded-xl bg-card text-card-foreground shadow-sm group relative border border-slate-200 dark:border-slate-800',
className
)}
{...props}>
<div className="absolute -inset-px rounded-xl border-2 border-transparent opacity-0 [background:linear-gradient(var(--quick-links-hover-bg,theme(colors.sky.50)),var(--quick-links-hover-bg,theme(colors.sky.50)))_padding-box,linear-gradient(to_top,theme(colors.indigo.400),theme(colors.cyan.400),theme(colors.sky.500))_border-box] group-hover:opacity-100 dark:[--quick-links-hover-bg:theme(colors.slate.800)] -z-10" />
{showGradient && (
<div className="absolute -inset-px rounded-xl border-2 border-transparent opacity-0 [background:linear-gradient(var(--quick-links-hover-bg,theme(colors.sky.50)),var(--quick-links-hover-bg,theme(colors.sky.50)))_padding-box,linear-gradient(to_top,theme(colors.indigo.400),theme(colors.cyan.400),theme(colors.sky.500))_border-box] group-hover:opacity-100 dark:[--quick-links-hover-bg:theme(colors.slate.800)] -z-10" />
)}
{props.children}
</div>
))
Expand Down
169 changes: 169 additions & 0 deletions docs/components/ui/glowing-stars-card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
'use client'

import React, {useEffect, useRef, useState} from 'react'
import {AnimatePresence, motion} from 'framer-motion'
import {cn} from '@/lib/utils'
import {useTheme} from 'nextra-theme-docs'

export const GlowingStarsCard = ({
className,
children,
hoverEfect = true
}: {
className?: string
children?: React.ReactNode
hoverEfect?: boolean
}) => {
const [mouseEnter, setMouseEnter] = useState(false)

return (
<div
onMouseEnter={() => {
if (!hoverEfect) return
setMouseEnter(true)
}}
onMouseLeave={() => {
if (!hoverEfect) return
setMouseEnter(false)
}}
className={cn(
'p-6 max-w-md h-full w-full rounded-xl border border-[#eaeaea] dark:border-gray-800',
className
)}>
<div className="flex justify-center items-center">
<Illustration mouseEnter={mouseEnter} />
</div>
<div className="px-2 pb-6">{children}</div>
</div>
)
}

export const GlowingStarsDescription = ({
className,
children
}: {
className?: string
children?: React.ReactNode
}) => {
return (
<p className={cn('text-base text-white max-w-[16rem]', className)}>
{children}
</p>
)
}

export const GlowingStarsTitle = ({
className,
children
}: {
className?: string
children?: React.ReactNode
}) => {
return (
<h2 className={cn('font-bold text-2xl text-[#eaeaea]', className)}>
{children}
</h2>
)
}

export const Illustration = ({mouseEnter}: {mouseEnter: boolean}) => {
const stars = 108
const columns = 18

const [glowingStars, setGlowingStars] = useState<number[]>([])

const highlightedStars = useRef<number[]>([])

useEffect(() => {
const interval = setInterval(() => {
highlightedStars.current = Array.from({length: 5}, () =>
Math.floor(Math.random() * stars)
)
setGlowingStars([...highlightedStars.current])
}, 3000)

return () => clearInterval(interval)
}, [])

return (
<div
className="h-[180px] p-1 w-full"
style={{
display: 'grid',
gridTemplateColumns: `repeat(${columns}, 1fr)`,
gap: `1px`
}}>
{[...Array(stars)].map((_, starIdx) => {
const isGlowing = glowingStars.includes(starIdx)
const delay = (starIdx % 10) * 0.1
const staticDelay = starIdx * 0.01
return (
<div
key={`matrix-col-${starIdx}}`}
className="relative flex items-center justify-center">
<Star
isGlowing={mouseEnter ? true : isGlowing}
delay={mouseEnter ? staticDelay : delay}
/>
{mouseEnter && <Glow delay={staticDelay} />}
<AnimatePresence mode="wait">
{isGlowing && <Glow delay={delay} />}
</AnimatePresence>
</div>
)
})}
</div>
)
}

const Star = ({isGlowing, delay}: {isGlowing: boolean; delay: number}) => {
const {resolvedTheme} = useTheme()
const isDarkTheme = resolvedTheme === 'dark'
return (
<motion.div
key={delay}
initial={{
scale: 1
}}
animate={{
scale: isGlowing ? [1, 1.2, 2.5, 2.2, 1.5] : 1,
background: isGlowing
? isDarkTheme
? '#fff'
: '#f3f3f3'
: isDarkTheme
? '#666'
: '#4f4f4f'
}}
transition={{
duration: 2,
ease: 'easeInOut',
delay: delay
}}
className={cn(
'bg-[#4f4f4f] dark:bg-[#666] size-[2px] dark:size-[1px] rounded-full relative z-20'
)}></motion.div>
)
}

const Glow = ({delay}: {delay: number}) => {
return (
<motion.div
initial={{
opacity: 0
}}
animate={{
opacity: 1
}}
transition={{
duration: 2,
ease: 'easeInOut',
delay: delay
}}
exit={{
opacity: 0
}}
className="absolute left-1/2 -translate-x-1/2 z-10 h-[4px] w-[4px] rounded-full bg-blue-500 blur-[1px] shadow-2xl shadow-blue-400"
/>
)
}
118 changes: 118 additions & 0 deletions docs/components/ui/hover-border-gradient.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
'use client'
import React, {useState, useEffect} from 'react'
import {motion} from 'framer-motion'
import {useTheme} from 'nextra-theme-docs'

import {cn} from '@/lib/utils'

type Direction = 'TOP' | 'LEFT' | 'BOTTOM' | 'RIGHT'

export function HoverBorderGradient({
children,
containerClassName,
className,
as: Tag = 'button',
duration = 1,
clockwise = true,
...props
}: React.PropsWithChildren<
{
as?: React.ElementType
containerClassName?: string
className?: string
duration?: number
clockwise?: boolean
} & React.HTMLAttributes<HTMLElement>
>) {
const [hovered, setHovered] = useState<boolean>(false)
const [direction, setDirection] = useState<Direction>('TOP')

const {resolvedTheme} = useTheme()

// Usually, we wouldnt need this, but the useTheme hook from nextra-theme-docs seem to have (a) bug(s)
const theme =
resolvedTheme && resolvedTheme === 'system'
? 'dark'
: resolvedTheme ?? 'dark'

const rotateDirection = (currentDirection: Direction): Direction => {
const directions: Direction[] = ['TOP', 'LEFT', 'BOTTOM', 'RIGHT']
const currentIndex = directions.indexOf(currentDirection)
const nextIndex = clockwise
? (currentIndex - 1 + directions.length) % directions.length
: (currentIndex + 1) % directions.length
return directions[nextIndex]
}

const movingMap: Record<'dark' | 'light', Record<Direction, string>> = {
dark: {
TOP: 'radial-gradient(20.7% 50% at 50% 0%, #3275F8 0%, rgba(255, 255, 255, 0) 100%)',
LEFT: 'radial-gradient(16.6% 43.1% at 0% 50%, #3275F8 0%, rgba(255, 255, 255, 0) 100%)',
BOTTOM:
'radial-gradient(20.7% 50% at 50% 100%, #3275F8 0%, rgba(255, 255, 255, 0) 100%)',
RIGHT:
'radial-gradient(16.2% 41.199999999999996% at 100% 50%, #3275F8 0%, rgba(255, 255, 255, 0) 100%)'
},
light: {
TOP: 'radial-gradient(20.7% 50% at 50% 0%, #3275F8 0%, rgba(255, 255, 255, 1) 100%)',
LEFT: 'radial-gradient(16.6% 43.1% at 0% 50%, #3275F8 0%, rgba(255, 255, 255, 1) 100%)',
BOTTOM:
'radial-gradient(20.7% 50% at 50% 100%, #3275F8 0%, rgba(255, 255, 255, 1) 100%)',
RIGHT:
'radial-gradient(16.2% 41.199999999999996% at 100% 50%, #3275F8 0%, rgba(255, 255, 255, 1) 100%)'
}
}

const highlight =
theme === 'dark'
? 'radial-gradient(75% 181.15942028985506% at 50% 50%, #3275F8 0%, rgba(255, 255, 255, 0) 100%)'
: 'radial-gradient(75% 181.15942028985506% at 50% 50%, #3275F8 0%, rgba(255, 255, 255, 1) 100%)'

useEffect(() => {
if (!hovered) {
const interval = setInterval(() => {
setDirection(prevState => rotateDirection(prevState))
}, duration * 1000)
return () => clearInterval(interval)
}
}, [hovered])
return (
<Tag
onMouseEnter={(event: React.MouseEvent<HTMLDivElement>) => {
setHovered(true)
}}
onMouseLeave={() => setHovered(false)}
className={cn(
'relative flex rounded-full border content-center bg-border dark:hover:bg-white/10 transition duration-500 dark:bg-white/20 items-center flex-col flex-nowrap gap-10 h-min justify-center overflow-visible p-px decoration-clone w-fit',
containerClassName
)}
{...props}>
<div
className={cn(
'w-auto text-primary z-10 bg-white dark:bg-[#0a0a0a] px-4 py-2 rounded-[inherit]',
className
)}>
{children}
</div>
<motion.div
className={cn(
'flex-none inset-0 overflow-hidden absolute z-0 rounded-[inherit]'
)}
style={{
filter: 'blur(2px)',
position: 'absolute',
width: '100%',
height: '100%'
}}
initial={{background: movingMap[direction]}}
animate={{
background: hovered
? [movingMap[theme][direction], highlight]
: movingMap[theme][direction]
}}
transition={{ease: 'linear', duration: duration ?? 1}}
/>
<div className="bg-black absolute z-1 flex-none inset-[2px] rounded-[100px]" />
</Tag>
)
}
10 changes: 9 additions & 1 deletion docs/next.config.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
const withNextra = require('nextra')({
theme: 'nextra-theme-docs',
themeConfig: './theme.config.tsx'
themeConfig: './theme.config.tsx',
cleanDistDir: true
})

module.exports = withNextra({
images: {
domains: [
'raw.githubusercontent.com',
'avatars.githubusercontent.com',
'upload.wikimedia.org'
]
},
async redirects() {
return [
{
Expand Down
2 changes: 2 additions & 0 deletions docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
"@vercel/analytics": "^1.3.1",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"framer-motion": "^11.11.11",
"lottie-react": "^2.4.0",
"lucide-react": "^0.440.0",
"next": "^13.0.6",
"nextra": "latest",
Expand Down
2 changes: 1 addition & 1 deletion docs/pages/_meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"display": "hidden",
"theme": {
"timestamp": false,
"layout": "full"
"layout": "raw"
}
},
"docs": {
Expand Down
Loading

1 comment on commit 3cc9d5c

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deploy preview for pylon-docs ready!

✅ Preview
https://pylon-docs-gi3h6qtt6-schettns-projects.vercel.app

Built with commit 3cc9d5c.
This pull request is being automatically deployed with vercel-action

Please sign in to comment.