feat(website): homepage makeover

This commit is contained in:
Simon
2025-12-23 16:04:44 +08:00
parent 70d33a9756
commit 35fe51427c
12 changed files with 311 additions and 127 deletions

View File

@@ -0,0 +1,91 @@
import { ArrowRightIcon } from '@radix-ui/react-icons'
import { ComponentPropsWithoutRef, ReactNode } from 'react'
import { Button } from '@/components/ui/button'
import { cn } from '@/lib/utils'
interface BentoGridProps extends ComponentPropsWithoutRef<'div'> {
children: ReactNode
className?: string
}
interface BentoCardProps extends ComponentPropsWithoutRef<'div'> {
name: string
className: string
background: ReactNode
Icon: React.ElementType
description: string
href: string
cta: string
}
const BentoGrid = ({ children, className, ...props }: BentoGridProps) => {
return (
<div className={cn('grid w-full auto-rows-[22rem] grid-cols-3 gap-4', className)} {...props}>
{children}
</div>
)
}
const BentoCard = ({
name,
className,
background,
Icon,
description,
href,
cta,
...props
}: BentoCardProps) => (
<div
key={name}
className={cn(
'group relative col-span-3 flex flex-col justify-between overflow-hidden rounded-xl',
// light styles
'bg-background [box-shadow:0_0_0_1px_rgba(0,0,0,.03),0_2px_4px_rgba(0,0,0,.05),0_12px_24px_rgba(0,0,0,.05)]',
// dark styles
'dark:bg-background transform-gpu dark:[box-shadow:0_-20px_80px_-20px_#ffffff1f_inset] dark:[border:1px_solid_rgba(255,255,255,.1)]',
className
)}
{...props}
>
<div>{background}</div>
<div className="p-4">
<div className="pointer-events-none z-10 flex transform-gpu flex-col gap-1 transition-all duration-300 lg:group-hover:-translate-y-10">
<Icon className="h-12 w-12 origin-left transform-gpu text-neutral-700 transition-all duration-300 ease-in-out group-hover:scale-75" />
<h3 className="text-xl font-semibold text-neutral-700 dark:text-neutral-300">{name}</h3>
<p className="max-w-lg text-neutral-400">{description}</p>
</div>
<div
className={cn(
'pointer-events-none flex w-full translate-y-0 transform-gpu flex-row items-center transition-all duration-300 group-hover:translate-y-0 group-hover:opacity-100 lg:hidden'
)}
>
<Button variant="link" asChild size="sm" className="pointer-events-auto p-0">
<a href={href}>
{cta}
<ArrowRightIcon className="ms-2 h-4 w-4 rtl:rotate-180" />
</a>
</Button>
</div>
</div>
<div
className={cn(
'pointer-events-none absolute bottom-0 hidden w-full translate-y-10 transform-gpu flex-row items-center p-4 opacity-0 transition-all duration-300 group-hover:translate-y-0 group-hover:opacity-100 lg:flex'
)}
>
<Button variant="link" asChild size="sm" className="pointer-events-auto p-0">
<a href={href}>
{cta}
<ArrowRightIcon className="ms-2 h-4 w-4 rtl:rotate-180" />
</a>
</Button>
</div>
<div className="pointer-events-none absolute inset-0 transform-gpu transition-all duration-300 group-hover:bg-black/[.03] group-hover:dark:bg-neutral-800/10" />
</div>
)
export { BentoCard, BentoGrid }

View File

@@ -0,0 +1,79 @@
import {
AnimatePresence,
MotionProps,
UseInViewOptions,
Variants,
motion,
useInView,
} from 'motion/react'
import { useRef } from 'react'
type MarginType = UseInViewOptions['margin']
interface BlurFadeProps extends MotionProps {
children: React.ReactNode
className?: string
variant?: {
hidden: { y: number }
visible: { y: number }
}
duration?: number
delay?: number
offset?: number
direction?: 'up' | 'down' | 'left' | 'right'
inView?: boolean
inViewMargin?: MarginType
blur?: string
}
export function BlurFade({
children,
className,
variant,
duration = 0.4,
delay = 0,
offset = 6,
direction = 'down',
inView = false,
inViewMargin = '-50px',
blur = '6px',
...props
}: BlurFadeProps) {
const ref = useRef(null)
const inViewResult = useInView(ref, { once: true, margin: inViewMargin })
const isInView = !inView || inViewResult
const defaultVariants: Variants = {
hidden: {
[direction === 'left' || direction === 'right' ? 'x' : 'y']:
direction === 'right' || direction === 'down' ? -offset : offset,
opacity: 0,
filter: `blur(${blur})`,
},
visible: {
[direction === 'left' || direction === 'right' ? 'x' : 'y']: 0,
opacity: 1,
filter: `blur(0px)`,
},
}
const combinedVariants = variant || defaultVariants
return (
<AnimatePresence>
<motion.div
ref={ref}
initial="hidden"
animate={isInView ? 'visible' : 'hidden'}
exit="hidden"
variants={combinedVariants}
transition={{
delay: 0.04 + delay,
duration,
ease: 'easeOut',
}}
className={className}
{...props}
>
{children}
</motion.div>
</AnimatePresence>
)
}

View File

@@ -108,7 +108,7 @@ export const NeonGradientCard: React.FC<NeonGradientCardProps> = ({
'--pseudo-element-background-image': `linear-gradient(0deg, ${neonColors.firstColor}, ${neonColors.secondColor})`,
'--pseudo-element-width': `${dimensions.width + borderSize * 2}px`,
'--pseudo-element-height': `${dimensions.height + borderSize * 2}px`,
'--after-blur': `${dimensions.width / 3}px`,
'--after-blur': `${dimensions.width / 6}px`,
} as CSSProperties
}
className={cn('relative z-10 size-full rounded-[var(--border-radius)]', className)}
@@ -116,7 +116,7 @@ export const NeonGradientCard: React.FC<NeonGradientCardProps> = ({
>
<div
className={cn(
'relative size-full min-h-[inherit] rounded-[var(--card-content-radius)] bg-gray-100 p-6',
'relative size-full min-h-[inherit] rounded-[var(--card-content-radius)] bg-gray-100',
'before:absolute before:-top-[var(--border-size)] before:-left-[var(--border-size)] before:-z-10 before:block',
"before:h-[var(--pseudo-element-height)] before:w-[var(--pseudo-element-width)] before:rounded-[var(--border-radius)] before:content-['']",
'before:bg-[linear-gradient(0deg,var(--neon-first-color),var(--neon-second-color))] before:bg-[length:100%_200%]',

View File

@@ -141,7 +141,9 @@ export const SparklesText: React.FC<SparklesTextProps> = ({
{sparkles.map((sparkle) => (
<Sparkle key={sparkle.id} {...sparkle} />
))}
<strong>{children}</strong>
<strong className="bg-linear-to-r from-[var(--sparkles-first-color)] to-[var(--sparkles-second-color)] bg-clip-text text-transparent">
{children}
</strong>
</span>
</div>
)