Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"clsx": "^2.1.1",
"geist": "^1.4.2",
"lucide-react": "^0.513.0",
"motion": "^12.23.16",
"next": "15.3.3",
"next-sitemap": "^4.2.3",
"next-themes": "^0.4.6",
Expand Down
16 changes: 6 additions & 10 deletions src/components/home/hero.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { ArrowRight, Github, Copy, Eye, Code2, Zap } from "lucide-react";
import { APP_CONFIG } from "@/lib/constants";
import { WritingText } from "../ui/texts/writing";
import { HighlightText } from "../ui/texts/highlight";
import { GradientText } from "../ui/texts/gradient";

interface HeroProps {
activePattern?: string | null;
Expand Down Expand Up @@ -59,18 +62,11 @@ export default function Hero({ theme }: HeroProps) {
className={`font-medium transition-colors duration-300 ${isPatternDark ? "text-white" : "text-gray-900 dark:text-gray-50"
}`}
>
Craft Beautiful
<WritingText text="Craft Beautiful" />
</span>
</h1>
<h2 className="text-2xl sm:text-3xl md:text-4xl lg:text-4xl font-bold tracking-tight">
<span
className={`bg-gradient-to-r bg-[200%_auto] bg-clip-text leading-tight text-transparent transition-all duration-300 ${isPatternDark
? "from-neutral-100 via-slate-400 to-neutral-400"
: "from-neutral-900 via-slate-500 to-neutral-500 dark:from-neutral-100 dark:via-slate-400 dark:to-neutral-400"
}`}
>
Patterns Backgrounds
</span>
<HighlightText text="Patterns Backgrounds" className={`bg-gradient-to-r ${isPatternDark ? "!from-blue-500/80 !to-purple-500/80 text-white" : "!from-blue-300 !to-purple-300 text-rose-500"}`} />
</h2>
</div>

Expand All @@ -81,7 +77,7 @@ export default function Hero({ theme }: HeroProps) {
>
Professional-grade background patterns and gradients. Easily copy the
code and seamlessly integrate it into your projects.
<span className="block">Crafted with modern CSS and Tailwind</span>
<GradientText text="Crafted with modern CSS and Tailwind" className="text-xl font-bold" neon/>
</p>

{/* Feature highlights */}
Expand Down
58 changes: 58 additions & 0 deletions src/components/ui/texts/gradient.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
'use client';

import * as React from 'react';
import { motion, type Transition } from 'motion/react';

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

type GradientTextProps = React.ComponentProps<'span'> & {
text: string;
gradient?: string;
neon?: boolean;
transition?: Transition;
};

function GradientText({
text,
className,
gradient = 'linear-gradient(90deg, #3b82f6 0%, #a855f7 20%, #ec4899 50%, #a855f7 80%, #3b82f6 100%)',
neon = false,
transition = { duration: 50, repeat: Infinity, ease: 'linear' },
...props
}: GradientTextProps) {
const baseStyle: React.CSSProperties = {
backgroundImage: gradient,
};

return (
<span
data-slot="gradient-text"
className={cn('relative inline-block', className)}
{...props}
>
<motion.span
className="m-0 text-transparent bg-clip-text bg-[length:700%_100%] bg-[position:0%_0%]"
style={baseStyle}
initial={{ backgroundPosition: '0% 0%' }}
animate={{ backgroundPosition: '500% 100%' }}
transition={transition}
>
{text}
</motion.span>

{neon && (
<motion.span
className="m-0 absolute top-0 left-0 text-transparent bg-clip-text blur-[8px] mix-blend-plus-lighter bg-[length:700%_100%] bg-[position:0%_0%]"
style={baseStyle}
initial={{ backgroundPosition: '0% 0%' }}
animate={{ backgroundPosition: '500% 100%' }}
transition={transition}
>
{text}
</motion.span>
)}
</span>
);
}

export { GradientText, type GradientTextProps };
65 changes: 65 additions & 0 deletions src/components/ui/texts/highlight.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
'use client';

import * as React from 'react';
import {
motion,
useInView,
type HTMLMotionProps,
type Transition,
type UseInViewOptions,
} from 'motion/react';

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

type HighlightTextProps = HTMLMotionProps<'span'> & {
text: string;
inView?: boolean;
inViewMargin?: UseInViewOptions['margin'];
inViewOnce?: boolean;
transition?: Transition;
};

function HighlightText({
ref,
text,
className,
inView = false,
inViewMargin = '0px',
transition = { duration: 2, ease: 'easeInOut' },
...props
}: HighlightTextProps) {
const localRef = React.useRef<HTMLSpanElement>(null);
React.useImperativeHandle(ref, () => localRef.current as HTMLSpanElement);

const inViewResult = useInView(localRef, {
once: true,
margin: inViewMargin,
});
const isInView = !inView || inViewResult;

return (
<motion.span
ref={localRef}
data-slot="highlight-text"
initial={{
backgroundSize: '0% 100%',
}}
animate={isInView ? { backgroundSize: '100% 100%' } : undefined}
transition={transition}
style={{
backgroundRepeat: 'no-repeat',
backgroundPosition: 'left center',
display: 'inline',
}}
className={cn(
`relative inline-block px-2 py-1 rounded-lg bg-gradient-to-r from-blue-200 to-purple-200 dark:from-blue-500 dark:to-purple-500`,
className,
)}
{...props}
>
{text}
</motion.span>
);
}

export { HighlightText, type HighlightTextProps };
62 changes: 62 additions & 0 deletions src/components/ui/texts/writing.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
'use client';

import * as React from 'react';
import {
motion,
useInView,
type Transition,
type UseInViewOptions,
} from 'motion/react';

type WritingTextProps = Omit<React.ComponentProps<'span'>, 'children'> & {
transition?: Transition;
inView?: boolean;
inViewMargin?: UseInViewOptions['margin'];
inViewOnce?: boolean;
spacing?: number | string;
text: string;
};

function WritingText({
ref,
inView = false,
inViewMargin = '0px',
inViewOnce = true,
spacing = 5,
text,
transition = { type: 'spring', bounce: 0, duration: 2, delay: 0.3 },
...props
}: WritingTextProps) {
const localRef = React.useRef<HTMLSpanElement>(null);
React.useImperativeHandle(ref, () => localRef.current as HTMLSpanElement);

const inViewResult = useInView(localRef, {
once: inViewOnce,
margin: inViewMargin,
});
const isInView = !inView || inViewResult;

const words = React.useMemo(() => text.split(' '), [text]);

return (
<span ref={localRef} data-slot="writing-text" {...props}>
{words.map((word, index) => (
<motion.span
key={index}
className="inline-block will-change-transform will-change-opacity"
style={{ marginRight: spacing }}
initial={{ opacity: 0, y: 10 }}
animate={isInView ? { opacity: 1, y: 0 } : undefined}
transition={{
...transition,
delay: index * (transition?.delay ?? 0),
}}
>
{word}{' '}
</motion.span>
))}
</span>
);
}

export { WritingText, type WritingTextProps };