init project

This commit is contained in:
ErkiKadhafi 2025-01-30 07:42:18 +07:00
parent c73a45047d
commit b759dbbdba
67 changed files with 12144 additions and 0 deletions

37
.gitignore vendored Normal file
View File

@ -0,0 +1,37 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env*.local
.env
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts

17
components.json Normal file
View File

@ -0,0 +1,17 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "default",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "tailwind.config.ts",
"css": "src/app/globals.css",
"baseColor": "slate",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils"
}
}

53
content/how-dev-ai.mdx Normal file
View File

@ -0,0 +1,53 @@
---
title: How Dev AI?
publishedAt: "2024-11-01"
summary: Introducing Acme.ai, a cutting-edge AI solution for modern businesses.
author: "dillionverma"
image: "/introducing.png"
---
We're excited to unveil **Acme.ai**, an innovative AI-powered platform designed to transform your business operations and skyrocket productivity. 🚀
## The Challenge We're Addressing
In today's AI-driven world, businesses face several hurdles:
- Overwhelming data analysis
- Inefficient decision-making processes
- Difficulty in predicting market trends
Acme.ai tackles these challenges head-on, offering a sophisticated AI solution that simplifies complex business processes.
## Our Mission
1. **Accelerate Decision-Making**: By leveraging AI to analyze vast datasets, we help you make informed decisions faster.
2. **Enhance Forecasting**: Our advanced predictive models provide accurate insights into future trends.
3. **Optimize Operations**: With AI-driven recommendations, streamline your business processes effortlessly.
## Core Capabilities
- **AI-Powered Dashboard**: Get real-time, AI-interpreted insights at a glance
- **Predictive Analytics**: Forecast trends and make data-driven decisions
- **Natural Language Processing**: Interact with your data using simple language queries
- **Automated Reporting**: Generate comprehensive reports with a single click
- **Customizable AI Models**: Tailor the AI to your specific industry needs
## Why Acme.ai Stands Out
> "Acme.ai has revolutionized our strategic planning. It's like having a crystal ball for our business!" - John Smith, CFO of FutureTech
Our AI solution isn't just a tool; it's your competitive edge. Here's how we compare:
| Feature | Acme.ai | Traditional BI Tools |
| ------------------------ | ------- | -------------------- |
| AI-Powered Insights | ✅ | ❌ |
| Predictive Capabilities | ✅ | ❌ |
| Natural Language Queries | ✅ | ❌ |
## Embarking on Your AI Journey
Getting started with Acme.ai is seamless:
1. Sign up for a demo
2. Integrate your data sources
3. Start unlocking AI-driven insights

View File

@ -0,0 +1,53 @@
---
title: Introducing Acme.ai
publishedAt: "2024-08-29"
summary: Introducing Acme.ai, a cutting-edge AI solution for modern businesses.
author: "dillionverma"
image: "/introducing.png"
---
We're excited to unveil **Acme.ai**, an innovative AI-powered platform designed to transform your business operations and skyrocket productivity. 🚀
## The Challenge We're Addressing
In today's AI-driven world, businesses face several hurdles:
- Overwhelming data analysis
- Inefficient decision-making processes
- Difficulty in predicting market trends
Acme.ai tackles these challenges head-on, offering a sophisticated AI solution that simplifies complex business processes.
## Our Mission
1. **Accelerate Decision-Making**: By leveraging AI to analyze vast datasets, we help you make informed decisions faster.
2. **Enhance Forecasting**: Our advanced predictive models provide accurate insights into future trends.
3. **Optimize Operations**: With AI-driven recommendations, streamline your business processes effortlessly.
## Core Capabilities
- **AI-Powered Dashboard**: Get real-time, AI-interpreted insights at a glance
- **Predictive Analytics**: Forecast trends and make data-driven decisions
- **Natural Language Processing**: Interact with your data using simple language queries
- **Automated Reporting**: Generate comprehensive reports with a single click
- **Customizable AI Models**: Tailor the AI to your specific industry needs
## Why Acme.ai Stands Out
> "Acme.ai has revolutionized our strategic planning. It's like having a crystal ball for our business!" - John Smith, CFO of FutureTech
Our AI solution isn't just a tool; it's your competitive edge. Here's how we compare:
| Feature | Acme.ai | Traditional BI Tools |
| ------------------------ | ------- | -------------------- |
| AI-Powered Insights | ✅ | ❌ |
| Predictive Capabilities | ✅ | ❌ |
| Natural Language Queries | ✅ | ❌ |
## Embarking on Your AI Journey
Getting started with Acme.ai is seamless:
1. Sign up for a demo
2. Integrate your data sources
3. Start unlocking AI-driven insights

53
content/why-dev-ai.mdx Normal file
View File

@ -0,0 +1,53 @@
---
title: Why Dev AI?
publishedAt: "2024-11-01"
summary: Introducing Acme.ai, a cutting-edge AI solution for modern businesses.
author: "dillionverma"
image: "/introducing.png"
---
We're excited to unveil **Acme.ai**, an innovative AI-powered platform designed to transform your business operations and skyrocket productivity. 🚀
## The Challenge We're Addressing
In today's AI-driven world, businesses face several hurdles:
- Overwhelming data analysis
- Inefficient decision-making processes
- Difficulty in predicting market trends
Acme.ai tackles these challenges head-on, offering a sophisticated AI solution that simplifies complex business processes.
## Our Mission
1. **Accelerate Decision-Making**: By leveraging AI to analyze vast datasets, we help you make informed decisions faster.
2. **Enhance Forecasting**: Our advanced predictive models provide accurate insights into future trends.
3. **Optimize Operations**: With AI-driven recommendations, streamline your business processes effortlessly.
## Core Capabilities
- **AI-Powered Dashboard**: Get real-time, AI-interpreted insights at a glance
- **Predictive Analytics**: Forecast trends and make data-driven decisions
- **Natural Language Processing**: Interact with your data using simple language queries
- **Automated Reporting**: Generate comprehensive reports with a single click
- **Customizable AI Models**: Tailor the AI to your specific industry needs
## Why Acme.ai Stands Out
> "Acme.ai has revolutionized our strategic planning. It's like having a crystal ball for our business!" - John Smith, CFO of FutureTech
Our AI solution isn't just a tool; it's your competitive edge. Here's how we compare:
| Feature | Acme.ai | Traditional BI Tools |
| ------------------------ | ------- | -------------------- |
| AI-Powered Insights | ✅ | ❌ |
| Predictive Capabilities | ✅ | ❌ |
| Natural Language Queries | ✅ | ❌ |
## Embarking on Your AI Journey
Getting started with Acme.ai is seamless:
1. Sign up for a demo
2. Integrate your data sources
3. Start unlocking AI-driven insights

9
next.config.mjs Normal file
View File

@ -0,0 +1,9 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
images: {
remotePatterns: [{ hostname: "localhost" }, { hostname: "randomuser.me" }],
},
transpilePackages: ["geist"],
};
export default nextConfig;

7807
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

52
package.json Normal file
View File

@ -0,0 +1,52 @@
{
"name": "devtool-magicui",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev --turbo",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@radix-ui/react-accordion": "^1.2.0",
"@radix-ui/react-avatar": "^1.1.1",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-slot": "^1.1.0",
"@splinetool/react-spline": "^4.0.0",
"@splinetool/runtime": "^1.9.37",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"framer-motion": "^11.3.21",
"geist": "^1.3.1",
"lucide-react": "^0.417.0",
"next": "15.0.3",
"next-themes": "^0.3.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-icons": "^5.2.1",
"rehype-pretty-code": "^0.14.0",
"rehype-stringify": "^10.0.1",
"remark-gfm": "^4.0.0",
"remark-parse": "^11.0.0",
"remark-rehype": "^11.1.1",
"shiki": "^1.22.0",
"tailwind-merge": "^2.4.0",
"tailwindcss-animate": "^1.0.7",
"unified": "^11.0.5",
"usehooks-ts": "^3.1.0",
"vaul": "^0.9.1"
},
"devDependencies": {
"@tailwindcss/typography": "^0.5.13",
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"eslint": "^8",
"eslint-config-next": "15.0.3",
"postcss": "^8",
"tailwindcss": "^3.4.1",
"typescript": "^5"
}
}

8
postcss.config.mjs Normal file
View File

@ -0,0 +1,8 @@
/** @type {import('postcss-load-config').Config} */
const config = {
plugins: {
tailwindcss: {},
},
};
export default config;

BIN
public/author.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
public/cube.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 176 KiB

BIN
public/dashboard.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 KiB

BIN
public/introducing.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

BIN
public/og.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

View File

@ -0,0 +1,127 @@
import Author from "@/components/blog-author";
import { CTA } from "@/components/sections/cta";
import { getPost } from "@/lib/blog";
import { siteConfig } from "@/lib/config";
import { formatDate } from "@/lib/utils";
import type { Metadata } from "next";
import Image from "next/image";
import { notFound } from "next/navigation";
import { Suspense } from "react";
export async function generateMetadata(props: {
params: Promise<{ slug: string }>;
}): Promise<Metadata | undefined> {
const params = await props.params;
let post = await getPost(params.slug);
let {
title,
publishedAt: publishedTime,
summary: description,
image,
} = post.metadata;
return {
title,
description,
openGraph: {
title,
description,
type: "article",
publishedTime,
url: `${siteConfig.url}/blog/${post.slug}`,
images: [
{
url: image,
},
],
},
twitter: {
card: "summary_large_image",
title,
description,
images: [image],
},
};
}
export default async function Page(props: {
params: Promise<{ slug: string }>;
searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
}) {
const params = await props.params;
const post = await getPost(params.slug);
if (!post) {
notFound();
}
return (
<section id="blog">
<script
type="application/ld+json"
suppressHydrationWarning
dangerouslySetInnerHTML={{
__html: JSON.stringify({
"@context": "https://schema.org",
"@type": "BlogPosting",
headline: post.metadata.title,
datePublished: post.metadata.publishedAt,
dateModified: post.metadata.publishedAt,
description: post.metadata.summary,
image: post.metadata.image
? `${siteConfig.url}${post.metadata.image}`
: `${siteConfig.url}/blog/${post.slug}/opengraph-image`,
url: `${siteConfig.url}/blog/${post.slug}`,
author: {
"@type": "Person",
name: siteConfig.name,
},
}),
}}
/>
<div className="mx-auto w-full max-w-[800px] px-4 sm:px-6 lg:px-8 space-y-4 my-12">
<Suspense
fallback={
<div className="mb-8 w-full h-64 bg-muted animate-pulse rounded-lg"></div>
}
>
{post.metadata.image && (
<div className="mb-8">
<Image
width={1920}
height={1080}
src={post.metadata.image}
alt={post.metadata.title}
className="w-full h-auto rounded-lg border"
/>
</div>
)}
</Suspense>
<div className="flex flex-col">
<h1 className="title font-medium text-3xl tracking-tighter">
{post.metadata.title}
</h1>
</div>
<div className="flex justify-between items-center text-sm">
<Suspense fallback={<p className="h-5" />}>
<div className="flex items-center space-x-2">
<time dateTime={post.metadata.publishedAt} className="text-sm">
{formatDate(post.metadata.publishedAt)}
</time>
</div>
</Suspense>
</div>
<div className="flex items-center space-x-2">
<Author
twitterUsername={post.metadata.author}
name={post.metadata.author}
image={"/author.jpg"}
/>
</div>
<article
className="prose dark:prose-invert mx-auto max-w-full"
dangerouslySetInnerHTML={{ __html: post.source }}
></article>
</div>
<CTA />
</section>
);
}

View File

@ -0,0 +1,16 @@
import { Footer } from "@/components/sections/footer";
import { Header } from "@/components/sections/header";
interface MarketingLayoutProps {
children: React.ReactNode;
}
export default async function Layout({ children }: MarketingLayoutProps) {
return (
<>
<Header />
<main>{children}</main>
<Footer />
</>
);
}

View File

@ -0,0 +1,39 @@
import BlogCard from "@/components/blog-card";
import { getBlogPosts } from "@/lib/blog";
import { siteConfig } from "@/lib/config";
import { constructMetadata } from "@/lib/utils";
export const metadata = constructMetadata({
title: "Blog",
description: `Latest news and updates from ${siteConfig.name}.`,
});
export default async function Blog() {
const allPosts = await getBlogPosts();
const articles = await Promise.all(
allPosts.sort((a, b) => b.publishedAt.localeCompare(a.publishedAt))
);
return (
<>
<div className="mx-auto w-full max-w-screen-xl px-2.5 lg:px-20 mt-24">
<div className="text-center py-16">
<h1 className="text-3xl font-bold text-foreground sm:text-4xl">
Articles
</h1>
<p className="mt-4 text-xl text-muted-foreground">
Latest news and updates from {siteConfig.name}
</p>
</div>
</div>
<div className="min-h-[50vh] bg-white/50 shadow-[inset_10px_-50px_94px_0_rgb(199,199,199,0.2)] backdrop-blur-lg">
<div className="mx-auto grid w-full max-w-screen-xl grid-cols-1 gap-8 px-2.5 py-10 lg:px-20 lg:grid-cols-3">
{articles.map((data, idx) => (
<BlogCard key={data.slug} data={data} priority={idx <= 1} />
))}
</div>
</div>
</>
);
}

BIN
src/app/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

125
src/app/globals.css Normal file
View File

@ -0,0 +1,125 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background: 210 20% 98%;
--foreground: 222.2 84% 4.9%;
--card: 210 20% 98%;
--card-foreground: 222.2 84% 4.9%;
--popover: 210 20% 98%;
--popover-foreground: 222.2 84% 4.9%;
--primary: 261 75.8% 75.7%;
--primary-foreground: 210 40% 98%;
--secondary: 215 20% 95%;
--secondary-foreground: 215 25% 10%;
--muted: 210 40% 96%;
--muted-foreground: 215.4 16.3% 46.9%;
--accent: 215 20% 95%;
--accent-foreground: 215 25% 10%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 40% 98%;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--ring: 222.2 84% 4.9%;
--radius: 0.5rem;
--chart-1: 210 76% 61%;
--chart-2: 215 58% 39%;
--chart-3: 220 37% 24%;
--chart-4: 225 74% 66%;
--chart-5: 230 87% 67%;
--color-1: 0 100% 63%;
--color-2: 270 100% 63%;
--color-3: 210 100% 63%;
--color-4: 195 100% 63%;
--color-5: 90 100% 63%;
--header-height: 3.5rem;
}
.dark {
--background: 225 15% 6%;
--foreground: 210 40% 98%;
--card: 225 15% 6%;
--card-foreground: 210 40% 98%;
--popover: 225 15% 6%;
--popover-foreground: 210 40% 98%;
--primary: 261 75.8% 75.7%;
--primary-foreground: 240 5.9% 10%;
--secondary: 225 10% 18%;
--secondary-foreground: 210 40% 98%;
--muted: 225 10% 12%;
--muted-foreground: 225 10% 70%;
--accent: 225 10% 18%;
--accent-foreground: 210 40% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 85.7% 97.3%;
--border: 225 10% 18%;
--input: 225 10% 18%;
--ring: 225 15% 80%;
--chart-1: 220 70% 50%;
--chart-2: 160 60% 45%;
--chart-3: 30 80% 55%;
--chart-4: 280 65% 60%;
--chart-5: 340 75% 55%;
}
}
@layer base {
:root {
--chart-1: 261 75.8% 75.7%;
--chart-2: 0 0 90;
--chart-3: 0 0 83;
--chart-4: 0 0 64;
--chart-5: 27 87% 67%;
font-family: Inter, sans-serif;
font-feature-settings: "cv02", "cv03", "cv04", "cv11", "salt";
}
.dark {
--chart-1: 261 75.8% 75.7%;
--chart-2: 160 60% 45%;
--chart-3: 30 80% 55%;
--chart-4: 280 65% 60%;
--chart-5: 340 75% 55%;
}
* {
@apply border-border;
}
@supports (font-variation-settings: normal) {
:root {
font-family: InterVariable, sans-serif;
font-feature-settings: "cv02", "cv03", "cv04", "cv11", "salt";
}
}
body {
@apply bg-background text-foreground;
}
/* width */
::-webkit-scrollbar {
width: 5px;
}
::-webkit-scrollbar:horizontal {
height: 5px;
}
/* Fix for scrollbar corner overlap */
::-webkit-scrollbar-corner {
background: transparent;
}
::-webkit-scrollbar-track {
background-color: transparent;
}
::-webkit-scrollbar-thumb {
background: hsl(var(--border));
}
}

51
src/app/layout.tsx Normal file
View File

@ -0,0 +1,51 @@
import { TailwindIndicator } from "@/components/tailwind-indicator";
import { ThemeProvider } from "@/components/theme-provider";
import { ThemeToggle } from "@/components/theme-toggle";
import { siteConfig } from "@/lib/config";
import { cn, constructMetadata } from "@/lib/utils";
import { GeistMono } from "geist/font/mono";
import { GeistSans } from "geist/font/sans";
import type { Metadata, Viewport } from "next";
import "./globals.css";
export const metadata: Metadata = constructMetadata({
title: `${siteConfig.name} | ${siteConfig.description}`,
});
export const viewport: Viewport = {
colorScheme: "dark",
themeColor: [
{ media: "(prefers-color-scheme: light)", color: "white" },
{ media: "(prefers-color-scheme: dark)", color: "black" },
],
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html
lang="en"
suppressHydrationWarning
className={`${GeistSans.variable} ${GeistMono.variable}`}
>
<body
className={cn(
"min-h-screen bg-background antialiased w-full mx-auto scroll-smooth font-sans"
)}
>
<ThemeProvider
attribute="class"
defaultTheme="dark"
enableSystem={false}
>
{children}
<ThemeToggle />
<TailwindIndicator />
</ThemeProvider>
</body>
</html>
);
}

103
src/app/og/route.tsx Normal file
View File

@ -0,0 +1,103 @@
import { Icons } from "@/components/icons";
import { siteConfig } from "@/lib/config";
import { ImageResponse } from "next/og";
import { NextRequest } from "next/server";
export const runtime = "edge";
export async function GET(req: NextRequest) {
const { searchParams } = req.nextUrl;
const postTitle = searchParams.get("title") || siteConfig.description;
const font = fetch(
new URL("../../assets/fonts/Inter-SemiBold.ttf", import.meta.url)
).then((res) => res.arrayBuffer());
const fontData = await font;
return new ImageResponse(
(
<div
style={{
height: "100%",
width: "100%",
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
backgroundColor: "#fff",
// set background image if needed
backgroundImage: `url(${siteConfig.url}/og.png)`,
fontSize: 32,
fontWeight: 600,
}}
>
<div
style={{
position: "relative",
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
top: "125px",
}}
>
<Icons.logo
style={{
color: "#fff",
width: "64px",
height: "64px",
}}
/>
<div
style={{
display: "flex",
justifyContent: "center",
alignItems: "center",
fontSize: "64px",
fontWeight: "600",
marginTop: "24px",
textAlign: "center",
color: "#fff",
width: "60%",
letterSpacing: "-0.05em", // Added tighter tracking
}}
>
{postTitle}
</div>
<div
style={{
display: "flex",
fontSize: "16px",
fontWeight: "500",
marginTop: "16px",
color: "#fff",
}}
>
{siteConfig.name}
</div>
</div>
<img
src={`${siteConfig.url}/cube.png`}
width={500}
style={{
position: "relative",
bottom: -100,
aspectRatio: "auto",
}}
/>
</div>
),
{
width: 1200,
height: 630,
fonts: [
{
name: "Inter",
data: fontData,
style: "normal",
},
],
}
);
}

33
src/app/page.tsx Normal file
View File

@ -0,0 +1,33 @@
import { Blog } from "@/components/sections/blog";
import { Community } from "@/components/sections/community";
import { CTA } from "@/components/sections/cta";
import { Examples } from "@/components/sections/examples";
import { Features } from "@/components/sections/features";
import { Footer } from "@/components/sections/footer";
import { Header } from "@/components/sections/header";
import { Hero } from "@/components/sections/hero";
import { Logos } from "@/components/sections/logos";
import { Pricing } from "@/components/sections/pricing";
import { Statistics } from "@/components/sections/statistics";
import { Testimonials } from "@/components/sections/testimonials";
import { UseCases } from "@/components/sections/use-cases";
export default function Home() {
return (
<main>
<Header />
<Hero />
<Logos />
<Examples />
<UseCases />
<Features />
<Statistics />
<Testimonials />
<Pricing />
<Community />
<Blog />
<CTA />
<Footer />
</main>
);
}

15
src/app/sitemap.ts Normal file
View File

@ -0,0 +1,15 @@
import { MetadataRoute } from "next";
import { headers } from "next/headers";
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const headersList = await headers();
let domain = headersList.get("host") as string;
let protocol = "https";
return [
{
url: `${protocol}://${domain}`,
lastModified: new Date(),
},
];
}

Binary file not shown.

View File

@ -0,0 +1,128 @@
"use client";
import React from "react";
import { cn } from "@/lib/utils";
export function AuroraText({
className,
children,
}: {
className?: string;
children: React.ReactNode;
}) {
return (
<span
className={cn(
"relative overflow-hidden inline-flex bg-background",
className
)}
>
{children}
<div className="aurora absolute inset-0 pointer-events-none mix-blend-lighten dark:mix-blend-darken">
{[...Array(5)].map((_, i) => (
<div
key={i}
className="aurora__item absolute w-[60vw] h-[60vw]"
style={{
backgroundColor: `hsl(var(--color-${i + 1}))`,
filter: "blur(1rem)",
animation: `aurora-border 6s ease-in-out infinite, aurora-${
i + 1
} 12s ease-in-out infinite alternate`,
mixBlendMode: "overlay",
...getInitialPosition(i),
}}
/>
))}
</div>
<style jsx>{`
@keyframes aurora-border {
0%,
100% {
border-radius: 37% 29% 27% 27% / 28% 25% 41% 37%;
}
25% {
border-radius: 47% 29% 39% 49% / 61% 19% 66% 26%;
}
50% {
border-radius: 57% 23% 47% 72% / 63% 17% 66% 33%;
}
75% {
border-radius: 28% 49% 29% 100% / 93% 20% 64% 25%;
}
}
@keyframes aurora-1 {
0%,
100% {
top: 0;
right: 0;
}
50% {
top: 50%;
right: 25%;
}
75% {
top: 25%;
right: 50%;
}
}
@keyframes aurora-2 {
0%,
100% {
top: 0;
left: 0;
}
60% {
top: 75%;
left: 25%;
}
85% {
top: 50%;
left: 50%;
}
}
@keyframes aurora-3 {
0%,
100% {
bottom: 0;
left: 0;
}
40% {
bottom: 50%;
left: 25%;
}
65% {
bottom: 25%;
left: 50%;
}
}
@keyframes aurora-4 {
0%,
100% {
bottom: 0;
right: 0;
}
50% {
bottom: 25%;
right: 40%;
}
90% {
bottom: 50%;
right: 25%;
}
}
`}</style>
</span>
);
}
function getInitialPosition(index: number): React.CSSProperties {
const positions = [
{ top: "-50%" },
{ right: 0, top: 0 },
{ left: 0, bottom: 0 },
{ right: 0, bottom: "-50%" },
];
return positions[index] || {};
}

View File

@ -0,0 +1,70 @@
import { formatDate } from "@/lib/utils";
import Image from "next/image";
import Link from "next/link";
export default function Author({
name,
image,
twitterUsername,
updatedAt,
imageOnly,
}: {
name: string;
image: string;
twitterUsername: string;
updatedAt?: string;
imageOnly?: boolean;
}) {
if (imageOnly) {
return (
<Image
src={image}
alt={name}
width={36}
height={36}
className="rounded-full transition-all group-hover:brightness-90"
/>
);
}
if (updatedAt) {
return (
<div className="flex items-center space-x-3">
<Image
src={image}
alt={name}
width={36}
height={36}
className="rounded-full"
/>
<div className="flex flex-col">
<p className="text-sm text-muted-foreground">Written by {name}</p>
<time dateTime={updatedAt} className="text-sm font-light">
Last updated {formatDate(updatedAt)}
</time>
</div>
</div>
);
}
return (
<Link
href={`https://twitter.com/${twitterUsername}`}
className="group flex items-center space-x-3"
target="_blank"
rel="noopener noreferrer"
>
<Image
src={image}
alt={name}
width={40}
height={40}
className="rounded-full transition-all group-hover:brightness-90"
/>
<div className="flex flex-col">
<p className="font-medium text-foreground">{name}</p>
<p className="text-sm text-muted-foreground">@{twitterUsername}</p>
</div>
</Link>
);
}

View File

@ -0,0 +1,41 @@
import { Post } from "@/lib/blog";
import { formatDate } from "@/lib/utils";
import Image from "next/image";
import Link from "next/link";
export default function BlogCard({
data,
priority,
}: {
data: Post;
priority?: boolean;
}) {
return (
<Link
href={`/blog/${data.slug}`}
className="bg-background transition-colors hover:bg-secondary/20 p-4 last:border-b-0 lg:border-r last:lg:border-r-0 border-b lg:border-b-0"
>
{data.image && (
<Image
className="object-cover border"
src={data.image}
width={1200}
height={630}
alt={data.title}
priority={priority}
/>
)}
{!data.image && <div className="bg-gray-200 h-[180px] mb-4 rounded" />}
<p className="my-2">
<time
dateTime={data.publishedAt}
className="text-xs text-muted-foreground"
>
{formatDate(data.publishedAt)}
</time>
</p>
<h3 className="text-xl font-medium mb-2">{data.title}</h3>
<p className="text-muted-foreground">{data.summary}</p>
</Link>
);
}

View File

@ -0,0 +1,49 @@
"use client";
import React, { useState } from "react";
interface FeatureOption {
id: number;
title: string;
description: string;
code: string;
}
interface FeatureSelectorProps {
features: FeatureOption[];
}
export const FeatureSelector: React.FC<FeatureSelectorProps> = ({
features,
}) => {
const [selectedIndex, setSelectedIndex] = useState<number>(0);
return (
<div className="grid grid-cols-1 md:grid-cols-5 relative">
<div className="md:col-span-2 border-b md:border-b-0 bg-background md:border-r border-border sticky top-[var(--header-height)]">
<div className="flex md:flex-col feature-btn-container overflow-x-auto p-4 pb-2">
{features.map((option, index) => (
<button
key={option.id}
onClick={() => setSelectedIndex(index)}
className={`flex-shrink-0 w-64 md:w-full text-left p-4 mb-2 mr-2 last:mr-0 md:mr-0 rounded border border-border ${
selectedIndex === index ? "bg-accent/70" : "hover:bg-muted/50"
}`}
>
<h3 className="font-medium tracking-tight">{option.title}</h3>
<p className="text-sm text-muted-foreground">
{option.description}
</p>
</button>
))}
</div>
</div>
<div className="col-span-1 md:col-span-3">
<div
className="bg-background font-mono text-sm [&>pre]:!bg-transparent [&>pre]:p-4 [&_code]:break-all md:max-h-[45vh] overflow-scroll"
dangerouslySetInnerHTML={{ __html: features[selectedIndex].code }}
/>
</div>
</div>
);
};

144
src/components/icons.tsx Normal file
View File

@ -0,0 +1,144 @@
import { DiscordLogoIcon } from "@radix-ui/react-icons";
type IconProps = React.HTMLAttributes<SVGElement>;
export const Icons = {
logo: (props: IconProps) => (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
{...props}
>
<polyline points="4 17 10 11 4 5"></polyline>
<line x1="12" x2="20" y1="19" y2="19"></line>
</svg>
),
discord: DiscordLogoIcon,
twitter: (props: IconProps) => (
<svg
height="23"
viewBox="0 0 1200 1227"
width="23"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
fill="currentColor"
d="M714.163 519.284L1160.89 0H1055.03L667.137 450.887L357.328 0H0L468.492 681.821L0 1226.37H105.866L515.491 750.218L842.672 1226.37H1200L714.137 519.284H714.163ZM569.165 687.828L521.697 619.934L144.011 79.6944H306.615L611.412 515.685L658.88 583.579L1055.08 1150.3H892.476L569.165 687.854V687.828Z"
/>
</svg>
),
github: (props: IconProps) => (
<svg viewBox="0 0 438.549 438.549" {...props}>
<path
fill="currentColor"
d="M409.132 114.573c-19.608-33.596-46.205-60.194-79.798-79.8-33.598-19.607-70.277-29.408-110.063-29.408-39.781 0-76.472 9.804-110.063 29.408-33.596 19.605-60.192 46.204-79.8 79.8C9.803 148.168 0 184.854 0 224.63c0 47.78 13.94 90.745 41.827 128.906 27.884 38.164 63.906 64.572 108.063 79.227 5.14.954 8.945.283 11.419-1.996 2.475-2.282 3.711-5.14 3.711-8.562 0-.571-.049-5.708-.144-15.417a2549.81 2549.81 0 01-.144-25.406l-6.567 1.136c-4.187.767-9.469 1.092-15.846 1-6.374-.089-12.991-.757-19.842-1.999-6.854-1.231-13.229-4.086-19.13-8.559-5.898-4.473-10.085-10.328-12.56-17.556l-2.855-6.57c-1.903-4.374-4.899-9.233-8.992-14.559-4.093-5.331-8.232-8.945-12.419-10.848l-1.999-1.431c-1.332-.951-2.568-2.098-3.711-3.429-1.142-1.331-1.997-2.663-2.568-3.997-.572-1.335-.098-2.43 1.427-3.289 1.525-.859 4.281-1.276 8.28-1.276l5.708.853c3.807.763 8.516 3.042 14.133 6.851 5.614 3.806 10.229 8.754 13.846 14.842 4.38 7.806 9.657 13.754 15.846 17.847 6.184 4.093 12.419 6.136 18.699 6.136 6.28 0 11.704-.476 16.274-1.423 4.565-.952 8.848-2.383 12.847-4.285 1.713-12.758 6.377-22.559 13.988-29.41-10.848-1.14-20.601-2.857-29.264-5.14-8.658-2.286-17.605-5.996-26.835-11.14-9.235-5.137-16.896-11.516-22.985-19.126-6.09-7.614-11.088-17.61-14.987-29.979-3.901-12.374-5.852-26.648-5.852-42.826 0-23.035 7.52-42.637 22.557-58.817-7.044-17.318-6.379-36.732 1.997-58.24 5.52-1.715 13.706-.428 24.554 3.853 10.85 4.283 18.794 7.952 23.84 10.994 5.046 3.041 9.089 5.618 12.135 7.708 17.705-4.947 35.976-7.421 54.818-7.421s37.117 2.474 54.823 7.421l10.849-6.849c7.419-4.57 16.18-8.758 26.262-12.565 10.088-3.805 17.802-4.853 23.134-3.138 8.562 21.509 9.325 40.922 2.279 58.24 15.036 16.18 22.559 35.787 22.559 58.817 0 16.178-1.958 30.497-5.853 42.966-3.9 12.471-8.941 22.457-15.125 29.979-6.191 7.521-13.901 13.85-23.131 18.986-9.232 5.14-18.182 8.85-26.84 11.136-8.662 2.286-18.415 4.004-29.263 5.146 9.894 8.562 14.842 22.077 14.842 40.539v60.237c0 3.422 1.19 6.279 3.572 8.562 2.379 2.279 6.136 2.95 11.276 1.995 44.163-14.653 80.185-41.062 108.068-79.226 27.88-38.161 41.825-81.126 41.825-128.906-.01-39.771-9.818-76.454-29.414-110.049z"
></path>
</svg>
),
radix: (props: IconProps) => (
<svg viewBox="0 0 25 25" fill="none" {...props}>
<path
d="M12 25C7.58173 25 4 21.4183 4 17C4 12.5817 7.58173 9 12 9V25Z"
fill="currentcolor"
></path>
<path d="M12 0H4V8H12V0Z" fill="currentcolor"></path>
<path
d="M17 8C19.2091 8 21 6.20914 21 4C21 1.79086 19.2091 0 17 0C14.7909 0 13 1.79086 13 4C13 6.20914 14.7909 8 17 8Z"
fill="currentcolor"
></path>
</svg>
),
aria: (props: IconProps) => (
<svg role="img" viewBox="0 0 24 24" fill="currentColor" {...props}>
<path d="M13.966 22.624l-1.69-4.281H8.122l3.892-9.144 5.662 13.425zM8.884 1.376H0v21.248zm15.116 0h-8.884L24 22.624Z" />
</svg>
),
npm: (props: IconProps) => (
<svg viewBox="0 0 24 24" {...props}>
<path
d="M1.763 0C.786 0 0 .786 0 1.763v20.474C0 23.214.786 24 1.763 24h20.474c.977 0 1.763-.786 1.763-1.763V1.763C24 .786 23.214 0 22.237 0zM5.13 5.323l13.837.019-.009 13.836h-3.464l.01-10.382h-3.456L12.04 19.17H5.113z"
fill="currentColor"
/>
</svg>
),
yarn: (props: IconProps) => (
<svg viewBox="0 0 24 24" {...props}>
<path
d="M12 0C5.375 0 0 5.375 0 12s5.375 12 12 12 12-5.375 12-12S18.625 0 12 0zm.768 4.105c.183 0 .363.053.525.157.125.083.287.185.755 1.154.31-.088.468-.042.551-.019.204.056.366.19.463.375.477.917.542 2.553.334 3.605-.241 1.232-.755 2.029-1.131 2.576.324.329.778.899 1.117 1.825.278.774.31 1.478.273 2.015a5.51 5.51 0 0 0 .602-.329c.593-.366 1.487-.917 2.553-.931.714-.009 1.269.445 1.353 1.103a1.23 1.23 0 0 1-.945 1.362c-.649.158-.95.278-1.821.843-1.232.797-2.539 1.242-3.012 1.39a1.686 1.686 0 0 1-.704.343c-.737.181-3.266.315-3.466.315h-.046c-.783 0-1.214-.241-1.45-.491-.658.329-1.51.19-2.122-.134a1.078 1.078 0 0 1-.58-1.153 1.243 1.243 0 0 1-.153-.195c-.162-.25-.528-.936-.454-1.946.056-.723.556-1.367.88-1.71a5.522 5.522 0 0 1 .408-2.256c.306-.727.885-1.348 1.32-1.737-.32-.537-.644-1.367-.329-2.21.227-.602.412-.936.82-1.08h-.005c.199-.074.389-.153.486-.259a3.418 3.418 0 0 1 2.298-1.103c.037-.093.079-.185.125-.283.31-.658.639-1.029 1.024-1.168a.94.94 0 0 1 .328-.06zm.006.7c-.507.016-1.001 1.519-1.001 1.519s-1.27-.204-2.266.871c-.199.218-.468.334-.746.44-.079.028-.176.023-.417.672-.371.991.625 2.094.625 2.094s-1.186.839-1.626 1.881c-.486 1.144-.338 2.261-.338 2.261s-.843.732-.899 1.487c-.051.663.139 1.2.343 1.515.227.343.51.176.51.176s-.561.653-.037.931c.477.25 1.283.394 1.71-.037.31-.31.371-1.001.486-1.283.028-.065.12.111.209.199.097.093.264.195.264.195s-.755.324-.445 1.066c.102.246.468.403 1.066.398.222-.005 2.664-.139 3.313-.296.375-.088.505-.283.505-.283s1.566-.431 2.998-1.357c.917-.598 1.293-.76 2.034-.936.612-.148.57-1.098-.241-1.084-.839.009-1.575.44-2.196.825-1.163.718-1.742.672-1.742.672l-.018-.032c-.079-.13.371-1.293-.134-2.678-.547-1.515-1.413-1.881-1.344-1.997.297-.5 1.038-1.297 1.334-2.78.176-.899.13-2.377-.269-3.151-.074-.144-.732.241-.732.241s-.616-1.371-.788-1.483a.271.271 0 0 0-.157-.046z"
fill="currentColor"
/>
</svg>
),
pnpm: (props: IconProps) => (
<svg viewBox="0 0 24 24" {...props}>
<path
d="M0 0v7.5h7.5V0zm8.25 0v7.5h7.498V0zm8.25 0v7.5H24V0zM8.25 8.25v7.5h7.498v-7.5zm8.25 0v7.5H24v-7.5zM0 16.5V24h7.5v-7.5zm8.25 0V24h7.498v-7.5zm8.25 0V24H24v-7.5z"
fill="currentColor"
/>
</svg>
),
react: (props: IconProps) => (
<svg viewBox="0 0 24 24" {...props}>
<path
d="M14.23 12.004a2.236 2.236 0 0 1-2.235 2.236 2.236 2.236 0 0 1-2.236-2.236 2.236 2.236 0 0 1 2.235-2.236 2.236 2.236 0 0 1 2.236 2.236zm2.648-10.69c-1.346 0-3.107.96-4.888 2.622-1.78-1.653-3.542-2.602-4.887-2.602-.41 0-.783.093-1.106.278-1.375.793-1.683 3.264-.973 6.365C1.98 8.917 0 10.42 0 12.004c0 1.59 1.99 3.097 5.043 4.03-.704 3.113-.39 5.588.988 6.38.32.187.69.275 1.102.275 1.345 0 3.107-.96 4.888-2.624 1.78 1.654 3.542 2.603 4.887 2.603.41 0 .783-.09 1.106-.275 1.374-.792 1.683-3.263.973-6.365C22.02 15.096 24 13.59 24 12.004c0-1.59-1.99-3.097-5.043-4.032.704-3.11.39-5.587-.988-6.38-.318-.184-.688-.277-1.092-.278zm-.005 1.09v.006c.225 0 .406.044.558.127.666.382.955 1.835.73 3.704-.054.46-.142.945-.25 1.44-.96-.236-2.006-.417-3.107-.534-.66-.905-1.345-1.727-2.035-2.447 1.592-1.48 3.087-2.292 4.105-2.295zm-9.77.02c1.012 0 2.514.808 4.11 2.28-.686.72-1.37 1.537-2.02 2.442-1.107.117-2.154.298-3.113.538-.112-.49-.195-.964-.254-1.42-.23-1.868.054-3.32.714-3.707.19-.09.4-.127.563-.132zm4.882 3.05c.455.468.91.992 1.36 1.564-.44-.02-.89-.034-1.345-.034-.46 0-.915.01-1.36.034.44-.572.895-1.096 1.345-1.565zM12 8.1c.74 0 1.477.034 2.202.093.406.582.802 1.203 1.183 1.86.372.64.71 1.29 1.018 1.946-.308.655-.646 1.31-1.013 1.95-.38.66-.773 1.288-1.18 1.87-.728.063-1.466.098-2.21.098-.74 0-1.477-.035-2.202-.093-.406-.582-.802-1.204-1.183-1.86-.372-.64-.71-1.29-1.018-1.946.303-.657.646-1.313 1.013-1.954.38-.66.773-1.286 1.18-1.868.728-.064 1.466-.098 2.21-.098zm-3.635.254c-.24.377-.48.763-.704 1.16-.225.39-.435.782-.635 1.174-.265-.656-.49-1.31-.676-1.947.64-.15 1.315-.283 2.015-.386zm7.26 0c.695.103 1.365.23 2.006.387-.18.632-.405 1.282-.66 1.933-.2-.39-.41-.783-.64-1.174-.225-.392-.465-.774-.705-1.146zm3.063.675c.484.15.944.317 1.375.498 1.732.74 2.852 1.708 2.852 2.476-.005.768-1.125 1.74-2.857 2.475-.42.18-.88.342-1.355.493-.28-.958-.646-1.956-1.1-2.98.45-1.017.81-2.01 1.085-2.964zm-13.395.004c.278.96.645 1.957 1.1 2.98-.45 1.017-.812 2.01-1.086 2.964-.484-.15-.944-.318-1.37-.5-1.732-.737-2.852-1.706-2.852-2.474 0-.768 1.12-1.742 2.852-2.476.42-.18.88-.342 1.356-.494zm11.678 4.28c.265.657.49 1.312.676 1.948-.64.157-1.316.29-2.016.39.24-.375.48-.762.705-1.158.225-.39.435-.788.636-1.18zm-9.945.02c.2.392.41.783.64 1.175.23.39.465.772.705 1.143-.695-.102-1.365-.23-2.006-.386.18-.63.406-1.282.66-1.933zM17.92 16.32c.112.493.2.968.254 1.423.23 1.868-.054 3.32-.714 3.708-.147.09-.338.128-.563.128-1.012 0-2.514-.807-4.11-2.28.686-.72 1.37-1.536 2.02-2.44 1.107-.118 2.154-.3 3.113-.54zm-11.83.01c.96.234 2.006.415 3.107.532.66.905 1.345 1.727 2.035 2.446-1.595 1.483-3.092 2.295-4.11 2.295-.22-.005-.406-.05-.553-.132-.666-.38-.955-1.834-.73-3.703.054-.46.142-.944.25-1.438zm4.56.64c.44.02.89.034 1.345.034.46 0 .915-.01 1.36-.034-.44.572-.895 1.095-1.345 1.565-.455-.47-.91-.993-1.36-1.565z"
fill="currentColor"
/>
</svg>
),
tailwind: (props: IconProps) => (
<svg viewBox="0 0 24 24" {...props}>
<path
d="M12.001,4.8c-3.2,0-5.2,1.6-6,4.8c1.2-1.6,2.6-2.2,4.2-1.8c0.913,0.228,1.565,0.89,2.288,1.624 C13.666,10.618,15.027,12,18.001,12c3.2,0,5.2-1.6,6-4.8c-1.2,1.6-2.6,2.2-4.2,1.8c-0.913-0.228-1.565-0.89-2.288-1.624 C16.337,6.182,14.976,4.8,12.001,4.8z M6.001,12c-3.2,0-5.2,1.6-6,4.8c1.2-1.6,2.6-2.2,4.2-1.8c0.913,0.228,1.565,0.89,2.288,1.624 c1.177,1.194,2.538,2.576,5.512,2.576c3.2,0,5.2-1.6,6-4.8c-1.2,1.6-2.6,2.2-4.2,1.8c-0.913-0.228-1.565-0.89-2.288-1.624 C10.337,13.382,8.976,12,6.001,12z"
fill="currentColor"
/>
</svg>
),
google: (props: IconProps) => (
<svg role="img" viewBox="0 0 24 24" {...props}>
<path
fill="currentColor"
d="M12.48 10.92v3.28h7.84c-.24 1.84-.853 3.187-1.787 4.133-1.147 1.147-2.933 2.4-6.053 2.4-4.827 0-8.6-3.893-8.6-8.72s3.773-8.72 8.6-8.72c2.6 0 4.507 1.027 5.907 2.347l2.307-2.307C18.747 1.44 16.133 0 12.48 0 5.867 0 .307 5.387.307 12s5.56 12 12.173 12c3.573 0 6.267-1.173 8.373-3.36 2.16-2.16 2.84-5.213 2.84-7.667 0-.76-.053-1.467-.173-2.053H12.48z"
/>
</svg>
),
apple: (props: IconProps) => (
<svg role="img" viewBox="0 0 24 24" {...props}>
<path
d="M12.152 6.896c-.948 0-2.415-1.078-3.96-1.04-2.04.027-3.91 1.183-4.961 3.014-2.117 3.675-.546 9.103 1.519 12.09 1.013 1.454 2.208 3.09 3.792 3.039 1.52-.065 2.09-.987 3.935-.987 1.831 0 2.35.987 3.96.948 1.637-.026 2.676-1.48 3.676-2.948 1.156-1.688 1.636-3.325 1.662-3.415-.039-.013-3.182-1.221-3.22-4.857-.026-3.04 2.48-4.494 2.597-4.559-1.429-2.09-3.623-2.324-4.39-2.376-2-.156-3.675 1.09-4.61 1.09zM15.53 3.83c.843-1.012 1.4-2.427 1.245-3.83-1.207.052-2.662.805-3.532 1.818-.78.896-1.454 2.338-1.273 3.714 1.338.104 2.715-.688 3.559-1.701"
fill="currentColor"
/>
</svg>
),
paypal: (props: IconProps) => (
<svg role="img" viewBox="0 0 24 24" {...props}>
<path
d="M7.076 21.337H2.47a.641.641 0 0 1-.633-.74L4.944.901C5.026.382 5.474 0 5.998 0h7.46c2.57 0 4.578.543 5.69 1.81 1.01 1.15 1.304 2.42 1.012 4.287-.023.143-.047.288-.077.437-.983 5.05-4.349 6.797-8.647 6.797h-2.19c-.524 0-.968.382-1.05.9l-1.12 7.106zm14.146-14.42a3.35 3.35 0 0 0-.607-.541c-.013.076-.026.175-.041.254-.93 4.778-4.005 7.201-9.138 7.201h-2.19a.563.563 0 0 0-.556.479l-1.187 7.527h-.506l-.24 1.516a.56.56 0 0 0 .554.647h3.882c.46 0 .85-.334.922-.788.06-.26.76-4.852.816-5.09a.932.932 0 0 1 .923-.788h.58c3.76 0 6.705-1.528 7.565-5.946.36-1.847.174-3.388-.777-4.471z"
fill="currentColor"
/>
</svg>
),
spinner: (props: IconProps) => (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
{...props}
>
<path d="M21 12a9 9 0 1 1-6.219-8.56" />
</svg>
),
};

View File

@ -0,0 +1,49 @@
import { Icons } from "@/components/icons";
import { buttonVariants } from "@/components/ui/button";
import {
Drawer,
DrawerContent,
DrawerDescription,
DrawerFooter,
DrawerHeader,
DrawerTitle,
DrawerTrigger,
} from "@/components/ui/drawer";
import { siteConfig } from "@/lib/config";
import { cn } from "@/lib/utils";
import Link from "next/link";
import { IoMenuSharp } from "react-icons/io5";
export function MobileDrawer() {
return (
<Drawer>
<DrawerTrigger>
<IoMenuSharp className="text-2xl" />
</DrawerTrigger>
<DrawerContent>
<DrawerHeader className="px-6">
<Link
href="/"
title="brand-logo"
className="relative mr-6 flex items-center space-x-2"
>
<Icons.logo className="w-auto h-[40px]" />
<DrawerTitle>{siteConfig.name}</DrawerTitle>
</Link>
<DrawerDescription>{siteConfig.description}</DrawerDescription>
</DrawerHeader>
<DrawerFooter>
<Link
href="#"
className={cn(
buttonVariants({ variant: "default" }),
"text-white rounded-full group"
)}
>
{siteConfig.cta}
</Link>
</DrawerFooter>
</DrawerContent>
</Drawer>
);
}

View File

@ -0,0 +1,96 @@
"use client";
import FlickeringGrid from "@/components/ui/flickering-grid";
import { cn } from "@/lib/utils";
import React, { forwardRef, useRef } from "react";
interface SectionProps {
id?: string;
title?: string;
subtitle?: string;
description?: string;
children?: React.ReactNode;
className?: string;
align?: "left" | "center" | "right";
}
const Section = forwardRef<HTMLElement, SectionProps>(
(
{ id, title, subtitle, description, children, className, align },
forwardedRef
) => {
const internalRef = useRef<HTMLElement>(null);
const ref = forwardedRef || internalRef;
const sectionId = title ? title.toLowerCase().replace(/\s+/g, "-") : id;
const alignmentClass =
align === "left"
? "text-left"
: align === "right"
? "text-right"
: "text-center";
return (
<section id={id} ref={ref}>
<div className={cn("relative mx-auto container", className)}>
{(title || subtitle || description) && (
<div
className={cn(
alignmentClass,
"relative mx-auto border-x border-t overflow-hidden p-2 py-8 md:p-12"
)}
>
{title && (
<h2 className="text-sm text-muted-foreground text-balance font-semibold tracking-tigh uppercase">
{title}
</h2>
)}
{subtitle && (
<h3
className={cn(
"mx-0 mt-4 max-w-lg text-5xl text-balance font-bold sm:max-w-none sm:text-4xl md:text-5xl lg:text-6xl leading-[1.2] tracking-tighter text-foreground lowercase",
align === "center"
? "mx-auto"
: align === "right"
? "ml-auto"
: ""
)}
>
{subtitle}
</h3>
)}
{description && (
<p
className={cn(
"mt-6 text-lg leading-8 text-muted-foreground text-balance max-w-2xl",
align === "center"
? "mx-auto"
: align === "right"
? "ml-auto"
: ""
)}
>
{description}
</p>
)}
<div className="pointer-events-none absolute bottom-0 left-0 right-0 h-full w-full bg-gradient-to-t from-background dark:from-background -z-10 from-50%" />
<FlickeringGrid
squareSize={4}
gridGap={4}
color="#6B7280"
maxOpacity={0.2}
flickerChance={0.1}
className="-z-20 absolute inset-0 size-full"
/>
</div>
)}
{children}
</div>
</section>
);
}
);
Section.displayName = "Section";
export { Section };

View File

@ -0,0 +1,21 @@
import BlogCard from "@/components/blog-card";
import { Section } from "@/components/section";
import { getBlogPosts } from "@/lib/blog";
export async function Blog() {
const allPosts = await getBlogPosts();
const articles = await Promise.all(
allPosts.sort((a, b) => b.publishedAt.localeCompare(a.publishedAt))
);
return (
<Section id="blog" title="Blog">
<div className="grid grid-cols-1 lg:grid-cols-3 border border-b-0">
{articles.map((data, idx) => (
<BlogCard key={data.slug} data={data} priority={idx <= 1} />
))}
</div>
</Section>
);
}

View File

@ -0,0 +1,73 @@
"use client";
import { Icons } from "@/components/icons";
import { Section } from "@/components/section";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { Button } from "@/components/ui/button";
import { Ripple } from "@/components/ui/ripple";
const contributors = [
{
name: "Alice Johnson",
avatar:
"https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=500&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8NHx8cG9ydHJhaXR8ZW58MHx8MHx8fDA%3D",
},
{
name: "Bob Brown",
avatar:
"https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=500&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8MTh8fHBvcnRyYWl0fGVufDB8fDB8fHww",
},
{
name: "Charlie Davis",
avatar:
"https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=500&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8MTJ8fHBvcnRyYWl0fGVufDB8fDB8fHww",
},
{
name: "Diana Evans",
avatar:
"https://images.unsplash.com/photo-1438761681033-6461ffad8d80?w=500&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8Mjh8fHBvcnRyYWl0fGVufDB8fDB8fHww",
},
{
name: "Ethan Ford",
avatar:
"https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?w=500&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8MzJ8fHBvcnRyYWl0fGVufDB8fDB8fHww",
},
];
export function Community() {
return (
<Section id="community" title="Community">
<div className="border-x border-t overflow-hidden relative">
<Ripple />
<div className="p-6 text-center py-12">
<p className="text-muted-foreground mb-6 text-balance max-w-prose mx-auto font-medium">
We&apos;re grateful for the amazing open-source community that helps
make our project better every day.
</p>
<div className="flex justify-center -space-x-6 mb-8">
{contributors.map((contributor, index) => (
<div key={index}>
<Avatar className="size-12 relative border-2 border-background bg-muted">
<AvatarImage
src={contributor.avatar}
alt={contributor.name}
className="object-cover"
/>
<AvatarFallback className="text-lg font-semibold">
{contributor.name.charAt(0)}
</AvatarFallback>
</Avatar>
</div>
))}
</div>
<div className="flex justify-center">
<Button variant="secondary" className="flex items-center gap-2">
<Icons.github className="h-5 w-5" />
Become a contributor
</Button>
</div>
</div>
</div>
</Section>
);
}

View File

@ -0,0 +1,18 @@
import { Section } from "@/components/section";
import { Button } from "@/components/ui/button";
export function CTA() {
return (
<Section id="cta">
<div className="border overflow-hidden relative text-center py-16 mx-auto">
<p className="max-w-3xl text-foreground mb-6 text-balance mx-auto font-medium text-3xl">
Ready to build your next AI agent?
</p>
<div className="flex justify-center">
<Button className="flex items-center gap-2">Get Started</Button>
</div>
</div>
</Section>
);
}

View File

@ -0,0 +1,207 @@
import { FeatureSelector } from "@/components/feature-selector";
import { Section } from "@/components/section";
import { codeToHtml } from "shiki";
interface FeatureOption {
id: number;
title: string;
description: string;
code: string;
}
const featureOptions: FeatureOption[] = [
{
id: 1,
title: "Simple Agent Workflow",
description: "Create a basic AI agent workflow with multiple agents.",
code: `import { Swarm, Agent } from 'ai-agent-sdk';
const client = new Swarm();
const transferToAgentB = (): Agent => {
return agentB;
};
const agentA = new Agent({
name: "Agent A",
instructions: "You are a helpful agent.",
functions: [transferToAgentB],
});
const agentB = new Agent({
name: "Agent B",
instructions: "Only speak in Haikus.",
});
const run = async () => {
const response = await client.run({
agent: agentA,
messages: [{ role: "user", content: "I want to talk to agent B." }],
});
console.log('Response:', response);
};
run();`,
},
{
id: 2,
title: "Multi-Agent Collaboration",
description:
"Set up multiple AI agents to work together on a complex task.",
code: `import { Agent, MultiAgentSystem } from 'ai-agent-sdk';
const researchAgent = new Agent('Researcher');
const analysisAgent = new Agent('Analyst');
const reportAgent = new Agent('Reporter');
const system = new MultiAgentSystem('MarketResearch');
system.addAgent(researchAgent, {
task: 'collectData',
output: 'rawData'
});
system.addAgent(analysisAgent, {
task: 'analyzeData',
input: 'rawData',
output: 'analysisResults'
});
system.addAgent(reportAgent, {
task: 'generateReport',
input: 'analysisResults',
output: 'finalReport'
});
const runResearch = async () => {
const finalReport = await system.run();
console.log('Research completed:', finalReport);
};
runResearch();`,
},
{
id: 3,
title: "Tool Integration",
description: "Integrate external tools and APIs into an AI agent workflow.",
code: `import { Agent, Tool } from 'ai-agent-sdk';
import { Configuration, OpenAIApi } from 'openai';
const agent = new Agent('ResearchAssistant');
const openaiTool = new Tool('OpenAI', {
action: async (prompt: string) => {
const configuration = new Configuration({
apiKey: process.env.OPENAI_API_KEY,
});
const openai = new OpenAIApi(configuration);
const response = await openai.createCompletion({
model: "text-davinci-002",
prompt,
});
return response.data.choices[0].text;
}
});
const searchTool = new Tool('GoogleSearch', {
action: async (query: string) => {
const url = new URL('https://www.googleapis.com/customsearch/v1');
url.searchParams.append('key', process.env.GOOGLE_API_KEY);
url.searchParams.append('cx', process.env.GOOGLE_SEARCH_ENGINE_ID);
url.searchParams.append('q', query);
const response = await fetch(url);
const data = await response.json();
return data.items.slice(0, 5);
}
});
agent.addTool(openaiTool);
agent.addTool(searchTool);
const performResearch = async (topic: string) => {
const researchResult = await agent.performResearch(topic);
console.log('Research results:', researchResult);
};
performResearch('AI advancements in 2023');`,
},
{
id: 4,
title: "Customizable Agent Behavior",
description:
"Design a specialized AI agent with custom decision-making logic.",
code: `import { Agent, KnowledgeBase } from 'ai-agent-sdk';
class CustomerSupportAgent extends Agent {
private knowledgeBase: KnowledgeBase;
constructor(name: string) {
super(name);
this.knowledgeBase = new KnowledgeBase('support-docs.json');
}
async decideAction(input: string): Promise<string> {
if (this.isSimpleQuery(input)) {
return this.provideDirectAnswer(input);
} else if (this.needsEscalation(input)) {
return this.escalateToHuman(input);
} else {
return this.generateDetailedResponse(input);
}
}
private isSimpleQuery(input: string): boolean {
// Custom logic to determine if the query is simple
return input.split(' ').length < 5;
}
private needsEscalation(input: string): boolean {
// Custom logic to decide if human intervention is needed
return input.toLowerCase().includes('urgent') || input.toLowerCase().includes('complaint');
}
private async provideDirectAnswer(input: string): Promise<string> {
return this.knowledgeBase.getQuickAnswer(input);
}
private async escalateToHuman(input: string): Promise<string> {
// Logic to forward the query to a human support agent
return "Your query has been escalated to our human support team. They will contact you shortly.";
}
private async generateDetailedResponse(input: string): Promise<string> {
// Use AI to generate a detailed response
return this.generateResponse(input);
}
}
const handleCustomerQuery = async (query: string) => {
const supportAgent = new CustomerSupportAgent('HelpDesk');
const response = await supportAgent.handleQuery(query);
console.log('Agent response:', response);
};
handleCustomerQuery("How do I reset my password?");`,
},
];
export async function Examples() {
const features = await Promise.all(
featureOptions.map(async (feature) => ({
...feature,
code: await codeToHtml(feature.code, {
lang: "typescript",
theme: "github-dark",
}),
}))
);
return (
<Section id="examples">
<div className="border-x border-t">
<FeatureSelector features={features} />
</div>
</Section>
);
}

View File

@ -0,0 +1,45 @@
import { Section } from "@/components/section";
import { siteConfig } from "@/lib/config";
import { cn } from "@/lib/utils";
import Link from "next/link";
export function Features() {
const services = siteConfig.features;
return (
<Section id="features" title="Features">
<div className="border-x border-t">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3">
{services.map(({ name, description, icon: Icon }, index) => (
<div
key={index}
className={cn(
"flex flex-col gap-y-2 items-center justify-center py-8 px-4 border-b transition-colors hover:bg-secondary/20",
"last:border-b-0",
"md:[&:nth-child(2n+1)]:border-r md:[&:nth-child(n+5)]:border-b-0",
"lg:[&:nth-child(3n)]:border-r-0 lg:[&:nth-child(n+4)]:border-b-0 lg:border-r"
)}
>
<div className="flex flex-col gap-y-2 items-center">
<div className="bg-gradient-to-b from-primary to-primary/80 p-2 rounded-lg text-white transition-colors group-hover:from-secondary group-hover:to-secondary/80">
{Icon}
</div>
<h2 className="text-xl font-medium text-card-foreground text-center text-balance">
{name}
</h2>
</div>
<p className="text-sm text-muted-foreground text-balance text-center max-w-md mx-auto">
{description}
</p>
<Link
href="#"
className="text-sm text-primary hover:underline underline-offset-4 transition-colors hover:text-secondary-foreground"
>
Learn more &gt;
</Link>
</div>
))}
</div>
</div>
</Section>
);
}

View File

@ -0,0 +1,49 @@
import { Icons } from "@/components/icons";
import { BorderText } from "@/components/ui/border-number";
import { siteConfig } from "@/lib/config";
export function Footer() {
return (
<footer className="flex flex-col gap-y-5 rounded-lg px-7 py-5 container">
<div className="flex items-center justify-between">
<div className="flex items-center gap-x-2">
<Icons.logo className="h-5 w-5" />
<h2 className="text-lg font-bold text-foreground">
{siteConfig.name}
</h2>
</div>
<div className="flex gap-x-2">
{siteConfig.footer.socialLinks.map((link, index) => (
<a
key={index}
href={link.url}
className="flex h-5 w-5 items-center justify-center text-muted-foreground transition-all duration-100 ease-linear hover:text-foreground hover:underline hover:underline-offset-4"
>
{link.icon}
</a>
))}
</div>
</div>
<div className="flex flex-col justify-between gap-y-5 md:flex-row md:items-center">
<ul className="flex flex-col gap-x-5 gap-y-2 text-muted-foreground md:flex-row md:items-center">
{siteConfig.footer.links.map((link, index) => (
<li
key={index}
className="text-[15px]/normal font-medium text-muted-foreground transition-all duration-100 ease-linear hover:text-foreground hover:underline hover:underline-offset-4"
>
<a href={link.url}>{link.text}</a>
</li>
))}
</ul>
<div className="flex items-center justify-between text-sm font-medium tracking-tight text-muted-foreground">
<p>{siteConfig.footer.bottomText}</p>
</div>
</div>
<BorderText
text={siteConfig.footer.brandText}
className="text-[clamp(3rem,15vw,10rem)] overflow-hidden font-mono tracking-tighter font-medium"
/>
</footer>
);
}

View File

@ -0,0 +1,43 @@
"use client";
import { Icons } from "@/components/icons";
import { MobileDrawer } from "@/components/mobile-drawer";
import { buttonVariants } from "@/components/ui/button";
import { easeInOutCubic } from "@/lib/animation";
import { siteConfig } from "@/lib/config";
import { cn } from "@/lib/utils";
import { AnimatePresence, motion, useAnimation } from "framer-motion";
import Link from "next/link";
import { useEffect, useState } from "react";
export function Header() {
return (
<header className="sticky top-0 h-[var(--header-height)] z-50 p-0 bg-background/60 backdrop-blur">
<div className="flex justify-between items-center container mx-auto p-2">
<Link
href="/"
title="brand-logo"
className="relative mr-6 flex items-center space-x-2"
>
<Icons.logo className="w-auto" />
<span className="font-semibold text-lg">{siteConfig.name}</span>
</Link>
<div className="hidden lg:block">
<Link
href="#"
className={cn(
buttonVariants({ variant: "default" }),
"h-8 text-primary-foreground rounded-lg group tracking-tight font-medium"
)}
>
{siteConfig.cta}
</Link>
</div>
<div className="mt-2 cursor-pointer block lg:hidden">
<MobileDrawer />
</div>
</div>
<hr className="absolute w-full bottom-0" />
</header>
);
}

View File

@ -0,0 +1,179 @@
"use client";
import { AuroraText } from "@/components/aurora-text";
import { Icons } from "@/components/icons";
import { Section } from "@/components/section";
import { buttonVariants } from "@/components/ui/button";
import { siteConfig } from "@/lib/config";
import { cn } from "@/lib/utils";
import { motion } from "framer-motion";
import Link from "next/link";
import { lazy, Suspense, useEffect, useState } from "react";
const ease = [0.16, 1, 0.3, 1];
function HeroPill() {
return (
<motion.a
href="/blog/introducing-dev-ai"
className="flex w-auto items-center space-x-2 rounded-full bg-primary/20 px-2 py-1 ring-1 ring-accent whitespace-pre"
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, ease }}
>
<div className="w-fit rounded-full bg-accent px-2 py-0.5 text-left text-xs font-medium text-primary sm:text-sm">
🛠 New
</div>
<p className="text-xs font-medium text-primary sm:text-sm">
Introducing AI Agent SDK
</p>
<svg
width="12"
height="12"
className="ml-1"
viewBox="0 0 12 12"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M8.78141 5.33312L5.20541 1.75712L6.14808 0.814453L11.3334 5.99979L6.14808 11.1851L5.20541 10.2425L8.78141 6.66645H0.666748V5.33312H8.78141Z"
fill="hsl(var(--primary))"
/>
</svg>
</motion.a>
);
}
function HeroTitles() {
return (
<div className="flex w-full max-w-3xl flex-col overflow-hidden pt-8">
<motion.h1
className="text-left text-4xl font-semibold leading-tighter text-foreground sm:text-5xl md:text-6xl tracking-tighter"
initial={{ filter: "blur(10px)", opacity: 0, y: 50 }}
animate={{ filter: "blur(0px)", opacity: 1, y: 0 }}
transition={{
duration: 1,
ease,
staggerChildren: 0.2,
}}
>
<motion.span
className="inline-block text-balance"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{
duration: 0.8,
delay: 0.5,
ease,
}}
>
<AuroraText className="leading-normal">
{siteConfig.hero.title}
</AuroraText>
</motion.span>
</motion.h1>
<motion.p
className="text-left max-w-xl leading-normal text-muted-foreground sm:text-lg sm:leading-normal text-balance"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{
delay: 0.6,
duration: 0.8,
ease,
}}
>
{siteConfig.hero.description}
</motion.p>
</div>
);
}
function HeroCTA() {
return (
<div className="relative mt-6">
<motion.div
className="flex w-full max-w-2xl flex-col items-start justify-start space-y-4 sm:flex-row sm:space-x-4 sm:space-y-0"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.8, duration: 0.8, ease }}
>
<Link
href="/download"
className={cn(
buttonVariants({ variant: "default" }),
"w-full sm:w-auto text-background flex gap-2 rounded-lg"
)}
>
<Icons.logo className="h-6 w-6" />
{siteConfig.hero.cta}
</Link>
</motion.div>
<motion.p
className="mt-3 text-sm text-muted-foreground text-left"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 1.0, duration: 0.8 }}
>
{siteConfig.hero.ctaDescription}
</motion.p>
</div>
);
}
const LazySpline = lazy(() => import("@splinetool/react-spline"));
export function Hero() {
const [showSpline, setShowSpline] = useState(false);
const [isMobile, setIsMobile] = useState(false);
useEffect(() => {
const checkMobile = () => {
setIsMobile(window.innerWidth < 1024); // Assuming 1024px is the breakpoint for lg
};
checkMobile();
window.addEventListener("resize", checkMobile);
return () => window.removeEventListener("resize", checkMobile);
}, []);
useEffect(() => {
// Don't show on mobile
if (!isMobile) {
const timer = setTimeout(() => {
setShowSpline(true);
}, 1000);
return () => clearTimeout(timer);
}
}, [isMobile]);
return (
<Section id="hero">
<div className="relative grid grid-cols-1 lg:grid-cols-2 gap-x-8 w-full p-6 lg:p-12 border-x overflow-hidden">
<div className="flex flex-col justify-start items-start lg:col-span-1">
<HeroPill />
<HeroTitles />
<HeroCTA />
</div>
{!isMobile && (
<div className="relative lg:h-full lg:col-span-1">
<Suspense>
{showSpline && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.8, delay: 1 }}
>
<LazySpline
scene="https://prod.spline.design/mZBrYNcnoESGlTUG/scene.splinecode"
className="absolute inset-0 w-full h-full origin-top-left flex items-center justify-center"
/>
</motion.div>
)}
</Suspense>
</div>
)}
</div>
</Section>
);
}

View File

@ -0,0 +1,66 @@
"use client";
import { Section } from "@/components/section";
import { AnimatePresence, motion } from "framer-motion";
import Image from "next/image";
import { useEffect, useState } from "react";
const companies = [
"Google",
"Microsoft",
"Amazon",
"Netflix",
"YouTube",
"Instagram",
];
const companies2 = ["Spotify", "Dropbox", "Tinder", "Slack", "Zoom", "Shopify"];
export function Logos() {
const [currentSet, setCurrentSet] = useState(companies);
useEffect(() => {
const interval = setInterval(() => {
setCurrentSet((prev) => (prev === companies ? companies2 : companies));
}, 5000);
return () => clearInterval(interval);
}, []);
return (
<Section id="logos">
<div className="border-x border-t">
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-6">
{companies.map((_, idx) => (
<div
key={idx}
className="flex group items-center justify-center p-4 border-r border-t last:border-r-0 sm:last:border-r md:[&:nth-child(3n)]:border-r md:[&:nth-child(6n)]:border-r-0 md:[&:nth-child(3)]:border-r [&:nth-child(-n+2)]:border-t-0 sm:[&:nth-child(-n+3)]:border-t-0 sm:[&:nth-child(3n)]:border-r-0 md:[&:nth-child(-n+6)]:border-t-0 [&:nth-child(2n)]:border-r-0 sm:[&:nth-child(2n)]:border-r"
>
<AnimatePresence mode="wait">
<motion.div
key={currentSet[idx]}
initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.8 }}
transition={{
duration: 0.5,
ease: "easeInOut",
delay: Math.random() * 0.5,
}}
>
<Image
width={112}
height={40}
src={`https://cdn.magicui.design/companies/${currentSet[idx]}.svg`}
className="h-10 w-28 dark:brightness-0 dark:invert grayscale hover:grayscale-0 hover:brightness-100 dark:hover:brightness-0 dark:hover:invert transition-all duration-200 ease-out opacity-30 hover:opacity-100"
alt={currentSet[idx]}
/>
</motion.div>
</AnimatePresence>
</div>
))}
</div>
</div>
</Section>
);
}

View File

@ -0,0 +1,228 @@
"use client";
import { Section } from "@/components/section";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { siteConfig } from "@/lib/config";
import { cn } from "@/lib/utils";
import { motion } from "framer-motion";
import { Check } from "lucide-react";
import { useState } from "react";
interface TabsProps {
activeTab: string;
setActiveTab: (tab: "yearly" | "monthly") => void;
className?: string;
children: (activeTab: string) => React.ReactNode;
}
interface TabsListProps {
children: React.ReactNode;
}
interface TabsTriggerProps {
value: string;
onClick: () => void;
children: React.ReactNode;
isActive: boolean;
}
const Tabs = ({ activeTab, setActiveTab, className, children }: TabsProps) => {
return (
<div
className={cn(
"mx-auto flex w-full items-center justify-center",
className
)}
>
{children(activeTab)}
</div>
);
};
const TabsList = ({ children }: TabsListProps) => {
return (
<div className="relative flex w-fit items-center rounded-full border p-1.5">
{children}
</div>
);
};
const TabsTrigger = ({
value,
onClick,
children,
isActive,
}: TabsTriggerProps) => {
return (
<button
onClick={onClick}
className={cn("relative z-[1] px-4 py-2", { "z-0": isActive })}
>
{isActive && (
<motion.div
layoutId="active-tab"
className="absolute inset-0 rounded-full bg-accent"
transition={{
duration: 0.2,
type: "spring",
stiffness: 300,
damping: 25,
velocity: 2,
}}
/>
)}
<span
className={cn(
"relative block text-sm font-medium duration-200",
isActive ? "delay-100 text-primary" : ""
)}
>
{children}
</span>
</button>
);
};
function PricingTier({
tier,
billingCycle,
}: {
tier: (typeof siteConfig.pricing)[0];
billingCycle: "monthly" | "yearly";
}) {
return (
<div
className={cn(
"outline-focus transition-transform-background relative z-10 box-border grid h-full w-full overflow-hidden text-foreground motion-reduce:transition-none lg:border-r border-t last:border-r-0",
tier.popular ? "bg-primary/5" : "text-foreground"
)}
>
<div className="flex flex-col h-full">
<CardHeader className="border-b p-4 grid grid-rows-2 h-fit">
<CardTitle className="flex items-center justify-between">
<span className="text-sm font-medium text-muted-foreground">
{tier.name}
</span>
{tier.popular && (
<Badge
variant="secondary"
className="bg-primary text-primary-foreground hover:bg-secondary-foreground"
>
Most Popular
</Badge>
)}
</CardTitle>
<div className="pt-2 text-3xl font-bold">
<motion.div
key={tier.price[billingCycle]}
initial={{
opacity: 0,
x: billingCycle === "yearly" ? -10 : 10,
filter: "blur(5px)",
}}
animate={{ opacity: 1, x: 0, filter: "blur(0px)" }}
transition={{
duration: 0.25,
ease: [0.4, 0, 0.2, 1],
}}
>
{tier.price[billingCycle]}
<span className="text-sm font-medium text-muted-foreground">
/ {tier.frequency[billingCycle]}
</span>
</motion.div>
</div>
<p className="text-[15px] font-medium text-muted-foreground">
{tier.description}
</p>
</CardHeader>
<CardContent className="flex-grow p-4 pt-5">
<ul className="space-y-2">
{tier.features.map((feature, featureIndex) => (
<li key={featureIndex} className="flex items-center">
<Check className="mr-2 size-4 text-green-500" />
<span className="font-medium">{feature}</span>
</li>
))}
</ul>
</CardContent>
<Button
size="lg"
className={cn(
"w-full rounded-none shadow-none",
tier.popular
? "bg-primary text-primary-foreground hover:bg-secondary-foreground"
: "bg-muted text-foreground hover:bg-muted/80"
)}
>
{tier.cta}
</Button>
</div>
</div>
);
}
export function Pricing() {
const [billingCycle, setBillingCycle] = useState<"monthly" | "yearly">(
"yearly"
);
const handleTabChange = (tab: "yearly" | "monthly") => {
setBillingCycle(tab);
};
return (
<Section id="pricing" title="Pricing">
<div className="border border-b-0 grid grid-rows-1">
<div className="grid grid-rows-1 gap-y-10 p-10">
<div className="text-center">
<h2 className="text-3xl md:text-5xl font-bold tracking-tighter text-balance">
Simple pricing for everyone.
</h2>
<p className="mt-6 text-balance text-muted-foreground">
Choose an <strong>affordable plan</strong> that&apos;s packed with
the best features for engaging your audience, creating customer
loyalty, and driving sales.
</p>
</div>
<Tabs
activeTab={billingCycle}
setActiveTab={handleTabChange}
className="mx-auto w-full max-w-md"
>
{(activeTab) => (
<TabsList>
{["yearly", "monthly"].map((tab) => (
<TabsTrigger
key={tab}
value={tab}
onClick={() => handleTabChange(tab as "yearly" | "monthly")}
isActive={activeTab === tab}
>
{tab.charAt(0).toUpperCase() + tab.slice(1)}
{tab === "yearly" && (
<span className="ml-2 text-xs font-semibold text-green-500">
Save 25%
</span>
)}
</TabsTrigger>
))}
</TabsList>
)}
</Tabs>
</div>
<div className="grid grid-cols-1 lg:grid-cols-3">
{siteConfig.pricing.map((tier, index) => (
<PricingTier key={index} tier={tier} billingCycle={billingCycle} />
))}
</div>
</div>
</Section>
);
}

View File

@ -0,0 +1,74 @@
"use client";
import { Icons } from "@/components/icons";
import { Section } from "@/components/section";
import { BorderText } from "@/components/ui/border-number";
import Link from "next/link";
const stats = [
{
title: "10K+",
subtitle: "Stars on GitHub",
icon: <Icons.github className="h-5 w-5" />,
},
{
title: "50K+",
subtitle: "Discord Members",
icon: <Icons.discord className="h-5 w-5" />,
},
{
title: "1M+",
subtitle: "Downloads",
icon: <Icons.npm className="h-5 w-5" />,
},
];
export function Statistics() {
return (
<Section id="statistics" title="Statistics">
<div
className="border-x border-t"
style={{
backgroundImage:
"radial-gradient(circle at bottom center, hsl(var(--secondary) / 0.4), hsl(var(--background)))",
}}
>
<div className="grid grid-cols-1 sm:grid-cols-3">
{stats.map((stat, idx) => (
<Link
href="#"
key={idx}
className="flex flex-col items-center justify-center py-8 px-4 border-b sm:border-b-0 last:border-b-0 sm:border-r sm:last:border-r-0 [&:nth-child(-n+2)]:border-t-0 sm:[&:nth-child(-n+3)]:border-t-0 relative group overflow-hidden"
>
<div className="absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition-all transform translate-x-full -translate-y-full group-hover:translate-x-0 group-hover:translate-y-0 duration-300 ease-in-out">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<line x1="7" y1="17" x2="17" y2="7"></line>
<polyline points="7 7 17 7 17 17"></polyline>
</svg>
</div>
<div className="text-center relative">
<BorderText text={stat.title} />
<div className="flex items-center justify-center gap-2 mt-2">
{stat.icon}
<p className="text-sm text-muted-foreground">
{stat.subtitle}
</p>
</div>
</div>
</Link>
))}
</div>
</div>
</Section>
);
}

View File

@ -0,0 +1,66 @@
"use client";
import { Section } from "@/components/section";
import { Button } from "@/components/ui/button";
import { siteConfig } from "@/lib/config";
import { cn } from "@/lib/utils";
import { motion } from "framer-motion";
import { useState } from "react";
export function Testimonials() {
const [showAll, setShowAll] = useState(false);
const initialDisplayCount = 9;
return (
<Section id="testimonials" title="Testimonials">
<div className="border-t">
<div className="columns-1 sm:columns-2 lg:columns-3 gap-0 lg:bg-grid-3 border-r pb-24 sm:bg-grid-2 relative bg-grid-1">
<div className="pointer-events-none absolute bottom-0 left-1/2 -translate-x-1/2 h-2/6 w-[calc(100%-2px)] overflow-hidden bg-gradient-to-t from-background to-transparent"></div>
<Button
variant="outline"
className="absolute bottom-12 left-1/2 -translate-x-1/2 border h-10 w-fit px-5 flex items-center justify-center z-10"
onClick={() => setShowAll(!showAll)}
>
{showAll ? "Show less" : "See more"}
</Button>
{siteConfig.testimonials.map((testimonial, index) => (
<motion.div
key={index}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: index * 0.05 }}
className={cn(
"flex flex-col border-b break-inside-avoid border-l",
"transition-colors hover:bg-secondary/20",
!showAll && index >= initialDisplayCount && "hidden"
)}
>
<div className="px-4 py-5 sm:p-6 flex-grow">
<div className="flex items-center gap-4 mb-4">
{testimonial.image && (
<img
src={testimonial.image}
alt={testimonial.name}
className="w-12 h-12 rounded-full object-cover"
/>
)}
<div>
<h3 className="text-lg font-medium text-foreground">
{testimonial.name}
</h3>
<p className="text-sm text-muted-foreground">
{testimonial.company}
</p>
</div>
</div>
<p>{testimonial.text}</p>
</div>
</motion.div>
))}
</div>
</div>
</Section>
);
}

View File

@ -0,0 +1,362 @@
"use client";
import { Section } from "@/components/section";
import OrbitingCircles from "@/components/ui/orbiting-circles";
import { cubicBezier, motion } from "framer-motion";
import {
AlertTriangleIcon,
BrainCircuitIcon,
DatabaseIcon,
GitForkIcon,
HeadsetIcon,
InfoIcon,
MessageSquareIcon,
SearchIcon,
SquareTerminal,
UserSearch,
XCircleIcon,
} from "lucide-react";
const containerVariants = {
initial: {},
whileHover: {
transition: {
staggerChildren: 0.1,
},
},
};
export function Card1() {
const variant1 = {
initial: {
scale: 0.87,
transition: {
delay: 0.05,
duration: 0.2,
ease: "linear",
},
},
whileHover: {
scale: 0.8,
boxShadow:
"rgba(245,40,145,0.35) 0px 20px 70px -10px, rgba(36,42,66,0.04) 0px 10px 24px -8px, rgba(36,42,66,0.06) 0px 1px 4px -1px",
transition: {
delay: 0.05,
duration: 0.2,
ease: "linear",
},
},
};
const variant2 = {
initial: {
y: -27,
scale: 0.95,
transition: {
delay: 0,
duration: 0.2,
ease: "linear",
},
},
whileHover: {
y: -55,
scale: 0.87,
boxShadow:
"rgba(39,127,245,0.15) 0px 20px 70px -10px, rgba(36,42,66,0.04) 0px 10px 24px -8px, rgba(36,42,66,0.06) 0px 1px 4px -1px",
transition: {
delay: 0,
duration: 0.2,
ease: "linear",
},
},
};
const variant3 = {
initial: {
y: -25,
opacity: 0,
scale: 1,
transition: {
delay: 0.05,
duration: 0.2,
ease: "linear",
},
},
whileHover: {
y: -45,
opacity: 1,
scale: 1,
boxShadow:
"rgba(39,245,76,0.15) 10px 20px 70px -20px, rgba(36,42,66,0.04) 0px 10px 24px -8px, rgba(36,42,66,0.06) 0px 1px 4px -1px",
transition: {
delay: 0.05,
duration: 0.2,
ease: "easeInOut",
},
},
};
const containerVariants = {
initial: {},
whileHover: {
transition: {
staggerChildren: 0.1,
},
},
};
return (
<div className="p-0 h-full overflow-hidden border-b lg:border-b-0 lg:border-r">
<motion.div
variants={containerVariants}
initial="initial"
whileHover="whileHover"
className="flex flex-col gap-y-5 items-center justify-between h-full w-full cursor-pointer"
>
<div className="flex h-full w-full items-center justify-center rounded-t-xl border-b">
<div className="relative flex flex-col items-center justify-center gap-y-2 p-10">
<motion.div
variants={variant1}
className="z-[1] flex h-full w-full items-center justify-between gap-x-2 rounded-md border bg-background p-5 px-2.5 "
>
<div className="h-8 w-8 rounded-full bg-blue-500 flex items-center justify-center">
<SearchIcon className="h-5 w-5 text-white" />
</div>
<div className="flex flex-col gap-y-2">
<div className="h-2 w-32 rounded-full bg-neutral-800/50 dark:bg-neutral-200/80"></div>
<div className="h-2 w-48 rounded-full bg-slate-400/50"></div>
<div className="text-xs text-neutral-500">
Google Search API integration
</div>
</div>
</motion.div>
<motion.div
variants={variant2}
className="z-[2] flex h-full w-full items-center justify-between gap-x-2 rounded-md border bg-background p-5 px-2.5 "
>
<div className="h-8 w-8 rounded-full bg-green-500 flex items-center justify-center">
<DatabaseIcon className="h-5 w-5 text-white" />
</div>
<div className="flex flex-col gap-y-2">
<div className="h-2 w-32 rounded-full bg-neutral-800/50 dark:bg-neutral-200/80"></div>
<div className="h-2 w-48 rounded-full bg-slate-400/50"></div>
<div className="h-2 w-20 rounded-full bg-slate-400/50"></div>
<div className="text-xs text-neutral-500">
PostgreSQL database connection
</div>
</div>
</motion.div>
<motion.div
variants={variant3}
className="absolute bottom-0 z-[3] m-auto flex h-fit w-fit items-center justify-between gap-x-2 rounded-md border bg-background p-5 px-2.5 "
>
<div className="h-8 w-8 rounded-full bg-purple-500 flex items-center justify-center">
<MessageSquareIcon className="h-5 w-5 text-white" />
</div>
<div className="flex flex-col gap-y-2">
<div className="h-2 w-32 rounded-full bg-neutral-800/50 dark:bg-neutral-200/80"></div>
<div className="h-2 w-48 rounded-full bg-slate-400/50"></div>
<div className="h-2 w-20 rounded-full bg-slate-400/50"></div>
<div className="h-2 w-48 rounded-full bg-slate-400/50"></div>
<div className="text-xs text-neutral-500">
OpenAI GPT-3.5 API integration
</div>
</div>
</motion.div>
</div>
</div>
<div className="flex flex-col gap-y-1 px-5 pb-4 items-start w-full">
<h2 className="font-semibold tracking-tight text-lg">
Tool Integration
</h2>
<p className="text-sm text-muted-foreground">
Seamlessly integrate external APIs and tools into agent workflows.
</p>
</div>
</motion.div>
</div>
);
}
const Card2 = () => {
const logs = [
{
id: 1,
type: "info",
timestamp: "2023-12-15 14:23:45",
message: "Agent initialized. Starting task execution.",
icon: (
<div className="h-8 w-8 rounded-full bg-blue-500 flex items-center justify-center">
<InfoIcon className="h-5 w-5 text-white" />
</div>
),
},
{
id: 2,
type: "action",
timestamp: "2023-12-15 14:23:47",
message: "Retrieving data from external API...",
icon: (
<div className="h-8 w-8 rounded-full bg-green-500 flex items-center justify-center">
<DatabaseIcon className="h-5 w-5 text-white" />
</div>
),
},
{
id: 3,
type: "decision",
timestamp: "2023-12-15 14:23:50",
message: "Analyzing data. Confidence: 85%",
icon: (
<div className="h-8 w-8 rounded-full bg-purple-500 flex items-center justify-center">
<BrainCircuitIcon className="h-5 w-5 text-white" />
</div>
),
},
{
id: 4,
type: "warning",
timestamp: "2023-12-15 14:23:52",
message: "Potential anomaly detected in dataset.",
icon: (
<div className="h-8 w-8 rounded-full bg-yellow-500 flex items-center justify-center">
<AlertTriangleIcon className="h-5 w-5 text-white" />
</div>
),
},
{
id: 5,
type: "error",
timestamp: "2023-12-15 14:23:55",
message: "Failed to connect to secondary database.",
icon: (
<div className="h-8 w-8 rounded-full bg-red-500 flex items-center justify-center">
<XCircleIcon className="h-5 w-5 text-white" />
</div>
),
},
];
return (
<div className="p-0 h-full overflow-hidden border-b lg:border-b-0 lg:border-r">
<motion.div
variants={containerVariants}
initial="initial"
whileHover="whileHover"
className="flex flex-col gap-y-5 items-center justify-between h-full w-full cursor-pointer"
>
<div className="border-b items-center justify-center overflow-hidden bg-transparent rounded-t-xl h-4/5 w-full flex ">
<motion.div className="p-5 rounded-t-md cursor-pointer overflow-hidden h-[270px] flex flex-col gap-y-3.5 w-full">
{logs.map((log, index) => (
<motion.div
key={log.id}
className="p-4 bg-transparent backdrop-blur-md shadow-[0px_0px_40px_-25px_rgba(0,0,0,0.25)] border border-border origin-right w-full rounded-md flex items-center"
custom={index}
variants={{
initial: (index: number) => ({
y: 0,
scale: index === 4 ? 0.9 : 1,
opacity: 1,
transition: {
delay: 0.05,
duration: 0.2,
ease: cubicBezier(0.22, 1, 0.36, 1),
},
}),
whileHover: (index: number) => ({
y: -85,
opacity: index === 4 ? 1 : 0.6,
scale: index === 0 ? 0.85 : index === 4 ? 1.1 : 1,
transition: {
delay: 0.05,
duration: 0.2,
ease: cubicBezier(0.22, 1, 0.36, 1),
},
}),
}}
transition={{
type: "spring",
damping: 40,
stiffness: 600,
}}
>
<div className="mr-3">{log.icon}</div>
<div className="flex-grow">
<p className="text-foreground text-xs font-medium">
[{log.timestamp}] {log.type.toUpperCase()}
</p>
<p className="text-muted-foreground text-xs">{log.message}</p>
</div>
</motion.div>
))}
</motion.div>
</div>
<div className="flex flex-col gap-y-1 px-5 pb-4 items-start w-full">
<h2 className="font-semibold tracking-tight text-lg">
Monitor agent activity
</h2>
<p className="text-sm text-muted-foreground">
Track and analyze your AI agent performance with detailed activity
logs.
</p>
</div>
</motion.div>
</div>
);
};
const Card3 = () => {
return (
<div className="p-0 min-h-[500px] lg:min-h-fit overflow-hidden border-b lg:border-b-0 -z-0">
<div className="relative flex flex-col gap-y-5 items-center justify-between h-full w-full">
<div className="border-b items-center justify-center overflow-hidden rounded-t-xl h-4/5 w-full flex">
<div className="relative flex items-center justify-center h-full w-full">
<div className="absolute top-0 right-0 bottom-0 left-0 bg-[radial-gradient(circle,hsl(var(--accent)/0.3)_0%,transparent_100%)]"></div>
<OrbitingCircles duration={15} delay={0} radius={40} reverse>
<div className="h-8 w-8 rounded-full bg-blue-500 flex items-center justify-center">
<HeadsetIcon className="h-5 w-5 text-white" />
</div>
</OrbitingCircles>
<OrbitingCircles duration={15} delay={20} radius={80}>
<div className="h-8 w-8 rounded-full bg-green-500 flex items-center justify-center">
<SquareTerminal className="h-5 w-5 text-white" />
</div>
</OrbitingCircles>
<OrbitingCircles radius={120} duration={20} delay={20}>
<div className="h-8 w-8 rounded-full bg-purple-500 flex items-center justify-center">
<UserSearch className="h-5 w-5 text-white" />
</div>
</OrbitingCircles>
<OrbitingCircles radius={160} duration={40} delay={20}>
<div className="h-8 w-8 rounded-full bg-yellow-500 flex items-center justify-center">
<MessageSquareIcon className="h-5 w-5 text-white" />
</div>
</OrbitingCircles>
<OrbitingCircles radius={200} duration={30}>
<div className="h-8 w-8 rounded-full bg-red-500 flex items-center justify-center">
<GitForkIcon className="h-5 w-5 text-white" />
</div>
</OrbitingCircles>
</div>
</div>
<div className="flex flex-col gap-y-1 px-5 pb-4 items-start w-full">
<h2 className="font-semibold tracking-tight text-lg">
Build once, run anywhere
</h2>
<p className="text-sm text-muted-foreground">
Create AI agents that work seamlessly across different platforms.
</p>
</div>
</div>
</div>
);
};
export function UseCases() {
return (
<Section id="use-cases" title="Use Cases">
<div className="grid lg:grid-cols-3 h-full border border-b-0">
<Card1 />
<Card2 />
<Card3 />
</div>
</Section>
);
}

View File

@ -0,0 +1,14 @@
export function TailwindIndicator() {
// Don't show in production
if (process.env.NODE_ENV === "production") return null;
return (
<div className="fixed bottom-12 left-3 z-50 flex h-6 w-6 items-center justify-center rounded-full bg-gray-800 p-3 font-mono text-xs text-white">
<div className="block sm:hidden">xs</div>
<div className="hidden sm:block md:hidden">sm</div>
<div className="hidden md:block lg:hidden">md</div>
<div className="hidden lg:block xl:hidden">lg</div>
<div className="hidden xl:block 2xl:hidden">xl</div>
<div className="hidden 2xl:block">2xl</div>
</div>
);
}

View File

@ -0,0 +1,8 @@
"use client";
import { ThemeProvider as NextThemesProvider } from "next-themes";
import { ThemeProviderProps } from "next-themes/dist/types";
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
}

View File

@ -0,0 +1,23 @@
"use client";
import { Button } from "@/components/ui/button";
import { Moon, Sun } from "lucide-react";
import { useTheme } from "next-themes";
export function ThemeToggle() {
const { setTheme, theme } = useTheme();
// Don't show in production
if (process.env.NODE_ENV === "production") return null;
return (
<Button
variant="ghost"
size="icon"
className="fixed bottom-1 left-1 z-50"
onClick={() => setTheme(theme === "light" ? "dark" : "light")}
>
<Sun className="h-[1.5rem] w-[1.3rem] dark:hidden" />
<Moon className="hidden h-5 w-5 dark:block" />
<span className="sr-only">Toggle theme</span>
</Button>
);
}

View File

@ -0,0 +1,58 @@
"use client"
import * as React from "react"
import * as AccordionPrimitive from "@radix-ui/react-accordion"
import { ChevronDown } from "lucide-react"
import { cn } from "@/lib/utils"
const Accordion = AccordionPrimitive.Root
const AccordionItem = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
>(({ className, ...props }, ref) => (
<AccordionPrimitive.Item
ref={ref}
className={cn("border-b", className)}
{...props}
/>
))
AccordionItem.displayName = "AccordionItem"
const AccordionTrigger = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<AccordionPrimitive.Header className="flex">
<AccordionPrimitive.Trigger
ref={ref}
className={cn(
"flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180",
className
)}
{...props}
>
{children}
<ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200" />
</AccordionPrimitive.Trigger>
</AccordionPrimitive.Header>
))
AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
const AccordionContent = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<AccordionPrimitive.Content
ref={ref}
className="overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
{...props}
>
<div className={cn("pb-4 pt-0", className)}>{children}</div>
</AccordionPrimitive.Content>
))
AccordionContent.displayName = AccordionPrimitive.Content.displayName
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }

View File

@ -0,0 +1,50 @@
"use client"
import * as React from "react"
import * as AvatarPrimitive from "@radix-ui/react-avatar"
import { cn } from "@/lib/utils"
const Avatar = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Root
ref={ref}
className={cn(
"relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
className
)}
{...props}
/>
))
Avatar.displayName = AvatarPrimitive.Root.displayName
const AvatarImage = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Image>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Image
ref={ref}
className={cn("aspect-square h-full w-full", className)}
{...props}
/>
))
AvatarImage.displayName = AvatarPrimitive.Image.displayName
const AvatarFallback = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Fallback>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Fallback
ref={ref}
className={cn(
"flex h-full w-full items-center justify-center rounded-full bg-muted",
className
)}
{...props}
/>
))
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
export { Avatar, AvatarImage, AvatarFallback }

View File

@ -0,0 +1,36 @@
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const badgeVariants = cva(
"inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
{
variants: {
variant: {
default:
"border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
secondary:
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
destructive:
"border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
outline: "text-foreground",
},
},
defaultVariants: {
variant: "default",
},
}
)
export interface BadgeProps
extends React.HTMLAttributes<HTMLDivElement>,
VariantProps<typeof badgeVariants> {}
function Badge({ className, variant, ...props }: BadgeProps) {
return (
<div className={cn(badgeVariants({ variant }), className)} {...props} />
)
}
export { Badge, badgeVariants }

View File

@ -0,0 +1,26 @@
import { cn } from "@/lib/utils";
import React, { forwardRef } from "react";
interface BorderTextProps extends React.HTMLAttributes<HTMLDivElement> {
text: string;
}
export const BorderText = forwardRef<HTMLDivElement, BorderTextProps>(
({ text, className, ...props }, ref) => {
return (
<div className="flex items-center justify-center">
<span
ref={ref}
style={{ "--text": `'${text}'` } as React.CSSProperties}
className={cn(
`relative font-mono pointer-events-none text-center text-[6rem] font-bold leading-none before:bg-gradient-to-b before:from-neutral-300 before:to-neutral-200/70 dark:before:from-neutral-700/70 dark:before:to-neutral-800/30 before:to-80% before:bg-clip-text before:text-transparent before:content-[var(--text)] after:absolute after:inset-0 after:bg-neutral-400/70 dark:after:bg-neutral-600/70 after:bg-clip-text after:text-transparent after:mix-blend-darken dark:after:mix-blend-lighten after:content-[var(--text)] after:[text-shadow:0_1px_0_white] dark:after:[text-shadow:0_1px_0_black]`,
className
)}
{...props}
/>
</div>
);
}
);
BorderText.displayName = "BorderText";

View File

@ -0,0 +1,59 @@
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const buttonVariants = cva(
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline:
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
secondary:
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
orange:
"border border-input hover:bg-accent hover:text-accent-foreground",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
icon: "h-10 w-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button"
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
)
}
)
Button.displayName = "Button"
export { Button, buttonVariants }

View File

@ -0,0 +1,79 @@
import * as React from "react"
import { cn } from "@/lib/utils"
const Card = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn(
"rounded-lg border bg-card text-card-foreground shadow-sm",
className
)}
{...props}
/>
))
Card.displayName = "Card"
const CardHeader = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex flex-col space-y-1.5 p-6", className)}
{...props}
/>
))
CardHeader.displayName = "CardHeader"
const CardTitle = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLHeadingElement>
>(({ className, ...props }, ref) => (
<h3
ref={ref}
className={cn(
"text-2xl font-semibold leading-none tracking-tight",
className
)}
{...props}
/>
))
CardTitle.displayName = "CardTitle"
const CardDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => (
<p
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
))
CardDescription.displayName = "CardDescription"
const CardContent = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
))
CardContent.displayName = "CardContent"
const CardFooter = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex items-center p-6 pt-0", className)}
{...props}
/>
))
CardFooter.displayName = "CardFooter"
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }

View File

@ -0,0 +1,118 @@
"use client"
import * as React from "react"
import { Drawer as DrawerPrimitive } from "vaul"
import { cn } from "@/lib/utils"
const Drawer = ({
shouldScaleBackground = true,
...props
}: React.ComponentProps<typeof DrawerPrimitive.Root>) => (
<DrawerPrimitive.Root
shouldScaleBackground={shouldScaleBackground}
{...props}
/>
)
Drawer.displayName = "Drawer"
const DrawerTrigger = DrawerPrimitive.Trigger
const DrawerPortal = DrawerPrimitive.Portal
const DrawerClose = DrawerPrimitive.Close
const DrawerOverlay = React.forwardRef<
React.ElementRef<typeof DrawerPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<DrawerPrimitive.Overlay
ref={ref}
className={cn("fixed inset-0 z-50 bg-black/80", className)}
{...props}
/>
))
DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName
const DrawerContent = React.forwardRef<
React.ElementRef<typeof DrawerPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<DrawerPortal>
<DrawerOverlay />
<DrawerPrimitive.Content
ref={ref}
className={cn(
"fixed inset-x-0 bottom-0 z-50 mt-24 flex h-auto flex-col rounded-t-[10px] border bg-background",
className
)}
{...props}
>
<div className="mx-auto mt-4 h-2 w-[100px] rounded-full bg-muted" />
{children}
</DrawerPrimitive.Content>
</DrawerPortal>
))
DrawerContent.displayName = "DrawerContent"
const DrawerHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn("grid gap-1.5 p-4 text-center sm:text-left", className)}
{...props}
/>
)
DrawerHeader.displayName = "DrawerHeader"
const DrawerFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn("mt-auto flex flex-col gap-2 p-4", className)}
{...props}
/>
)
DrawerFooter.displayName = "DrawerFooter"
const DrawerTitle = React.forwardRef<
React.ElementRef<typeof DrawerPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Title>
>(({ className, ...props }, ref) => (
<DrawerPrimitive.Title
ref={ref}
className={cn(
"text-lg font-semibold leading-none tracking-tight",
className
)}
{...props}
/>
))
DrawerTitle.displayName = DrawerPrimitive.Title.displayName
const DrawerDescription = React.forwardRef<
React.ElementRef<typeof DrawerPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Description>
>(({ className, ...props }, ref) => (
<DrawerPrimitive.Description
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
))
DrawerDescription.displayName = DrawerPrimitive.Description.displayName
export {
Drawer,
DrawerPortal,
DrawerOverlay,
DrawerTrigger,
DrawerClose,
DrawerContent,
DrawerHeader,
DrawerFooter,
DrawerTitle,
DrawerDescription,
}

View File

@ -0,0 +1,196 @@
"use client";
import React, {
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from "react";
interface FlickeringGridProps {
squareSize?: number;
gridGap?: number;
flickerChance?: number;
color?: string;
width?: number;
height?: number;
className?: string;
maxOpacity?: number;
}
const FlickeringGrid: React.FC<FlickeringGridProps> = ({
squareSize = 4,
gridGap = 6,
flickerChance = 0.3,
color = "rgb(0, 0, 0)",
width,
height,
className,
maxOpacity = 0.3,
}) => {
const canvasRef = useRef<HTMLCanvasElement>(null);
const containerRef = useRef<HTMLDivElement>(null);
const [isInView, setIsInView] = useState(false);
const [canvasSize, setCanvasSize] = useState({ width: 0, height: 0 });
const memoizedColor = useMemo(() => {
const toRGBA = (color: string) => {
if (typeof window === "undefined") {
return `rgba(0, 0, 0,`;
}
const canvas = document.createElement("canvas");
canvas.width = canvas.height = 1;
const ctx = canvas.getContext("2d");
if (!ctx) return "rgba(255, 0, 0,";
ctx.fillStyle = color;
ctx.fillRect(0, 0, 1, 1);
const [r, g, b] = Array.from(ctx.getImageData(0, 0, 1, 1).data);
return `rgba(${r}, ${g}, ${b},`;
};
return toRGBA(color);
}, [color]);
const setupCanvas = useCallback(
(canvas: HTMLCanvasElement, width: number, height: number) => {
const dpr = window.devicePixelRatio || 1;
canvas.width = width * dpr;
canvas.height = height * dpr;
canvas.style.width = `${width}px`;
canvas.style.height = `${height}px`;
const cols = Math.floor(width / (squareSize + gridGap));
const rows = Math.floor(height / (squareSize + gridGap));
const squares = new Float32Array(cols * rows);
for (let i = 0; i < squares.length; i++) {
squares[i] = Math.random() * maxOpacity;
}
return { cols, rows, squares, dpr };
},
[squareSize, gridGap, maxOpacity]
);
const updateSquares = useCallback(
(squares: Float32Array, deltaTime: number) => {
for (let i = 0; i < squares.length; i++) {
if (Math.random() < flickerChance * deltaTime) {
squares[i] = Math.random() * maxOpacity;
}
}
},
[flickerChance, maxOpacity]
);
const drawGrid = useCallback(
(
ctx: CanvasRenderingContext2D,
width: number,
height: number,
cols: number,
rows: number,
squares: Float32Array,
dpr: number
) => {
ctx.clearRect(0, 0, width, height);
ctx.fillStyle = "transparent";
ctx.fillRect(0, 0, width, height);
for (let i = 0; i < cols; i++) {
for (let j = 0; j < rows; j++) {
const opacity = squares[i * rows + j];
ctx.fillStyle = `${memoizedColor}${opacity})`;
ctx.fillRect(
i * (squareSize + gridGap) * dpr,
j * (squareSize + gridGap) * dpr,
squareSize * dpr,
squareSize * dpr
);
}
}
},
[memoizedColor, squareSize, gridGap]
);
useEffect(() => {
const canvas = canvasRef.current;
const container = containerRef.current;
if (!canvas || !container) return;
const ctx = canvas.getContext("2d");
if (!ctx) return;
let animationFrameId: number;
let gridParams: ReturnType<typeof setupCanvas>;
const updateCanvasSize = () => {
const newWidth = width || container.clientWidth;
const newHeight = height || container.clientHeight;
setCanvasSize({ width: newWidth, height: newHeight });
gridParams = setupCanvas(canvas, newWidth, newHeight);
};
updateCanvasSize();
let lastTime = 0;
const animate = (time: number) => {
if (!isInView) return;
const deltaTime = (time - lastTime) / 1000;
lastTime = time;
updateSquares(gridParams.squares, deltaTime);
drawGrid(
ctx,
canvas.width,
canvas.height,
gridParams.cols,
gridParams.rows,
gridParams.squares,
gridParams.dpr
);
animationFrameId = requestAnimationFrame(animate);
};
const resizeObserver = new ResizeObserver(() => {
updateCanvasSize();
});
resizeObserver.observe(container);
const intersectionObserver = new IntersectionObserver(
([entry]) => {
setIsInView(entry.isIntersecting);
},
{ threshold: 0 }
);
intersectionObserver.observe(canvas);
if (isInView) {
animationFrameId = requestAnimationFrame(animate);
}
return () => {
cancelAnimationFrame(animationFrameId);
resizeObserver.disconnect();
intersectionObserver.disconnect();
};
}, [setupCanvas, updateSquares, drawGrid, width, height, isInView]);
return (
<div ref={containerRef} className={`w-full h-full ${className}`}>
<canvas
ref={canvasRef}
className="pointer-events-none"
style={{
width: canvasSize.width,
height: canvasSize.height,
}}
/>
</div>
);
};
export default FlickeringGrid;

View File

@ -0,0 +1,25 @@
import * as React from "react"
import { cn } from "@/lib/utils"
export interface InputProps
extends React.InputHTMLAttributes<HTMLInputElement> {}
const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ className, type, ...props }, ref) => {
return (
<input
type={type}
className={cn(
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
className
)}
ref={ref}
{...props}
/>
)
}
)
Input.displayName = "Input"
export { Input }

View File

@ -0,0 +1,26 @@
"use client"
import * as React from "react"
import * as LabelPrimitive from "@radix-ui/react-label"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const labelVariants = cva(
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
)
const Label = React.forwardRef<
React.ElementRef<typeof LabelPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
VariantProps<typeof labelVariants>
>(({ className, ...props }, ref) => (
<LabelPrimitive.Root
ref={ref}
className={cn(labelVariants(), className)}
{...props}
/>
))
Label.displayName = LabelPrimitive.Root.displayName
export { Label }

View File

@ -0,0 +1,51 @@
import { cn } from "@/lib/utils";
interface MarqueeProps {
className?: string;
reverse?: boolean;
pauseOnHover?: boolean;
children?: React.ReactNode;
vertical?: boolean;
repeat?: number;
[key: string]: any;
}
export default function Marquee({
className,
reverse,
pauseOnHover = false,
children,
vertical = false,
repeat = 4,
...props
}: MarqueeProps) {
return (
<div
{...props}
className={cn(
"group flex overflow-hidden p-2 [--duration:40s] [--gap:1rem] [gap:var(--gap)]",
{
"flex-row": !vertical,
"flex-col": vertical,
},
className,
)}
>
{Array(repeat)
.fill(0)
.map((_, i) => (
<div
key={i}
className={cn("flex shrink-0 justify-around [gap:var(--gap)]", {
"animate-marquee flex-row": !vertical,
"animate-marquee-vertical flex-col": vertical,
"group-hover:[animation-play-state:paused]": pauseOnHover,
"[animation-direction:reverse]": reverse,
})}
>
{children}
</div>
))}
</div>
);
}

View File

@ -0,0 +1,58 @@
import { cn } from "@/lib/utils";
export interface OrbitingCirclesProps {
className?: string;
children?: React.ReactNode;
reverse?: boolean;
duration?: number;
delay?: number;
radius?: number;
path?: boolean;
}
export default function OrbitingCircles({
className,
children,
reverse,
duration = 20,
delay = 10,
radius = 50,
path = true,
}: OrbitingCirclesProps) {
return (
<>
{path && (
<svg
xmlns="http://www.w3.org/2000/svg"
version="1.1"
className="pointer-events-none absolute inset-0 size-full"
>
<circle
className="stroke-border stroke-1 dark:stroke-border"
cx="50%"
cy="50%"
r={radius}
fill="none"
/>
</svg>
)}
<div
style={
{
"--duration": duration,
"--radius": radius,
"--delay": -delay,
} as React.CSSProperties
}
className={cn(
"absolute flex size-[2rem] transform-gpu animate-orbit items-center justify-center rounded-full border border-border bg-background [animation-delay:calc(var(--delay)*1000ms)] dark:bg-background",
{ "[animation-direction:reverse]": reverse },
className
)}
>
{children}
</div>
</>
);
}

View File

@ -0,0 +1,57 @@
import { CSSProperties, memo } from "react";
import { cn } from "@/lib/utils";
interface RippleProps {
mainCircleSize?: number;
mainCircleOpacity?: number;
numCircles?: number;
className?: string;
}
export const Ripple = memo(function Ripple({
mainCircleSize = 210,
mainCircleOpacity = 0.24,
numCircles = 8,
className,
}: RippleProps) {
return (
<div
className={cn(
"pointer-events-none select-none absolute inset-0 [mask-image:linear-gradient(to_bottom,white,transparent)]",
className
)}
>
{Array.from({ length: numCircles }, (_, i) => {
const size = mainCircleSize + i * 70;
const opacity = mainCircleOpacity - i * 0.03;
const animationDelay = `${i * 0.06}s`;
const borderStyle = i === numCircles - 1 ? "dashed" : "solid";
const borderOpacity = 5 + i * 5;
return (
<div
key={i}
className={`absolute animate-ripple rounded-full bg-foreground/25 shadow-xl border [--i:${i}]`}
style={
{
width: `${size}px`,
height: `${size}px`,
opacity,
animationDelay,
borderStyle,
borderWidth: "1px",
borderColor: `hsl(var(--foreground), ${borderOpacity / 100})`,
top: "50%",
left: "50%",
transform: "translate(-50%, -50%) scale(1)",
} as CSSProperties
}
/>
);
})}
</div>
);
});
Ripple.displayName = "Ripple";

4
src/lib/animation.ts Normal file
View File

@ -0,0 +1,4 @@
import { cubicBezier } from "framer-motion";
export const easeInOutCubic = cubicBezier(0.645, 0.045, 0.355, 1);
export const easeOutCubic = cubicBezier(0, 0, 0.58, 1);

96
src/lib/blog.ts Normal file
View File

@ -0,0 +1,96 @@
import { siteConfig } from "@/lib/config";
import fs from "fs";
import path from "path";
import rehypePrettyCode from "rehype-pretty-code";
import rehypeStringify from "rehype-stringify";
import remarkGfm from "remark-gfm";
import remarkParse from "remark-parse";
import remarkRehype from "remark-rehype";
import { unified } from "unified";
export type Post = {
title: string;
publishedAt: string;
summary: string;
author: string;
slug: string;
image?: string;
};
function parseFrontmatter(fileContent: string) {
let frontmatterRegex = /---\s*([\s\S]*?)\s*---/;
let match = frontmatterRegex.exec(fileContent);
let frontMatterBlock = match![1];
let content = fileContent.replace(frontmatterRegex, "").trim();
let frontMatterLines = frontMatterBlock.trim().split("\n");
let metadata: Partial<Post> = {};
frontMatterLines.forEach((line) => {
let [key, ...valueArr] = line.split(": ");
let value = valueArr.join(": ").trim();
value = value.replace(/^['"](.*)['"]$/, "$1"); // Remove quotes
metadata[key.trim() as keyof Post] = value;
});
return { data: metadata as Post, content };
}
function getMDXFiles(dir: string) {
return fs.readdirSync(dir).filter((file) => path.extname(file) === ".mdx");
}
export async function markdownToHTML(markdown: string) {
const p = await unified()
.use(remarkParse)
.use(remarkGfm)
.use(remarkRehype)
.use(rehypePrettyCode, {
// https://rehype-pretty.pages.dev/#usage
theme: {
light: "min-light",
dark: "min-dark",
},
keepBackground: false,
})
.use(rehypeStringify)
.process(markdown);
return p.toString();
}
export async function getPost(slug: string) {
const filePath = path.join("content", `${slug}.mdx`);
const source = fs.readFileSync(filePath, "utf-8");
const { content: rawContent, data: metadata } = parseFrontmatter(source);
const content = await markdownToHTML(rawContent);
const defaultImage = `${siteConfig.url}/og?title=${encodeURIComponent(
metadata.title
)}`;
return {
source: content,
metadata: {
...metadata,
image: metadata.image || defaultImage,
},
slug,
};
}
async function getAllPosts(dir: string) {
const mdxFiles = getMDXFiles(dir);
return Promise.all(
mdxFiles.map(async (file) => {
const slug = path.basename(file, path.extname(file));
const { metadata, source } = await getPost(slug);
return {
...metadata,
slug,
source,
};
})
);
}
export async function getBlogPosts() {
return getAllPosts(path.join(process.cwd(), "content"));
}

265
src/lib/config.tsx Normal file
View File

@ -0,0 +1,265 @@
import { Icons } from "@/components/icons";
import {
BrainIcon,
CodeIcon,
GlobeIcon,
PlugIcon,
UsersIcon,
ZapIcon,
} from "lucide-react";
export const BLUR_FADE_DELAY = 0.15;
export const siteConfig = {
name: "AI Agent SDK",
description: "Create AI Agents with just a few lines of code.",
cta: "Get Started",
url: process.env.NEXT_PUBLIC_APP_URL || "http://localhost:3000",
keywords: [
"AI Agent SDK",
"Multi-Agent Systems",
"Tool Integration",
"Workflow Automation",
],
links: {
email: "support@aiagentsdk.com",
twitter: "https://twitter.com/aiagentsdk",
discord: "https://discord.gg/aiagentsdk",
github: "https://github.com/aiagentsdk",
instagram: "https://instagram.com/aiagentsdk",
},
hero: {
title: "AI Agent SDK",
description:
"Create powerful AI agent workflows with just a few lines of code, enabling complex task automation and decision-making processes.",
cta: "Get Started",
ctaDescription: "Available for all major programming languages",
},
features: [
{
name: "Simple Agent Workflows",
description:
"Easily create and manage AI agent workflows with intuitive APIs.",
icon: <BrainIcon className="h-6 w-6" />,
},
{
name: "Multi-Agent Systems",
description:
"Build complex systems with multiple AI agents working together.",
icon: <UsersIcon className="h-6 w-6" />,
},
{
name: "Tool Integration",
description:
"Seamlessly integrate external tools and APIs into your agent workflows.",
icon: <PlugIcon className="h-6 w-6" />,
},
{
name: "Cross-Language Support",
description:
"Available in all major programming languages for maximum flexibility.",
icon: <GlobeIcon className="h-6 w-6" />,
},
{
name: "Customizable Agents",
description:
"Design and customize agents to fit your specific use case and requirements.",
icon: <CodeIcon className="h-6 w-6" />,
},
{
name: "Efficient Execution",
description:
"Optimize agent performance with built-in efficiency and scalability features.",
icon: <ZapIcon className="h-6 w-6" />,
},
],
pricing: [
{
name: "Basic",
price: { monthly: "$9", yearly: "$99" },
frequency: { monthly: "month", yearly: "year" },
description: "Perfect for individuals and small projects.",
features: [
"100 AI generations per month",
"Basic text-to-image conversion",
"Email support",
"Access to community forum",
],
cta: "Get Started",
},
{
name: "Pro",
price: { monthly: "$29", yearly: "$290" },
frequency: { monthly: "month", yearly: "year" },
description: "Ideal for professionals and growing businesses.",
features: [
"1000 AI generations per month",
"Advanced text-to-image conversion",
"Priority email support",
"API access",
"Custom AI model fine-tuning",
"Collaboration tools",
],
cta: "Get Started",
},
{
name: "Enterprise",
price: { monthly: "$999", yearly: "Custom" },
frequency: { monthly: "month", yearly: "year" },
description: "Tailored solutions for large organizations.",
features: [
"Unlimited AI generations",
"Dedicated account manager",
"24/7 phone and email support",
"Custom AI model development",
"On-premises deployment option",
"Advanced analytics and reporting",
],
popular: true,
cta: "Get Started",
},
],
footer: {
socialLinks: [
{
icon: <Icons.github className="h-5 w-5" />,
url: "#",
},
{
icon: <Icons.twitter className="h-5 w-5" />,
url: "#",
},
],
links: [
{ text: "Pricing", url: "#" },
{ text: "Contact", url: "#" },
],
bottomText: "All rights reserved.",
brandText: "AGENT SDK",
},
testimonials: [
{
id: 1,
text: "The AI Agent SDK has revolutionized how we build intelligent systems. It's incredibly intuitive and powerful.",
name: "Alice Johnson",
company: "OpenMind Labs",
image:
"https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=500&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8NHx8cG9ydHJhaXR8ZW58MHx8MHx8fDA%3D",
},
{
id: 2,
text: "We've significantly reduced development time for our AI projects using this SDK. The multi-agent feature is a game-changer.",
name: "Bob Brown",
company: "NeuralForge",
image:
"https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=500&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8MTh8fHBvcnRyYWl0fGVufDB8fDB8fHww",
},
{
id: 3,
text: "The cross-language support allowed us to seamlessly integrate AI agents into our existing tech stack.",
name: "Charlie Davis",
company: "CodeHarbor",
image:
"https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=500&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8MTJ8fHBvcnRyYWl0fGVufDB8fDB8fHww",
},
{
id: 4,
text: "The AI Agent SDK's tool integration feature has streamlined our workflow automation processes.",
name: "Diana Evans",
company: "AutomateX",
image:
"https://images.unsplash.com/photo-1438761681033-6461ffad8d80?w=500&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8Mjh8fHBvcnRyYWl0fGVufDB8fDB8fHww",
},
{
id: 5,
text: "The customizable agent behaviors have allowed us to create highly specialized AI solutions for our clients.",
name: "Ethan Ford",
company: "AICore",
image:
"https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?w=500&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8MzJ8fHBvcnRyYWl0fGVufDB8fDB8fHww",
},
{
id: 6,
text: "The AI Agent SDK's efficiency features have significantly improved our system's performance and scalability.",
name: "Fiona Grant",
company: "ScaleAI",
image:
"https://images.unsplash.com/photo-1544005313-94ddf0286df2?w=500&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8NDB8fHBvcnRyYWl0fGVufDB8fDB8fHww",
},
{
id: 7,
text: "The SDK's intuitive APIs have made it easy for our team to quickly prototype and deploy AI agent systems.",
name: "George Harris",
company: "RapidAI",
image:
"https://images.unsplash.com/photo-1506794778202-cad84cf45f1d?w=500&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8NDR8fHBvcnRyYWl0fGVufDB8fDB8fHww",
},
{
id: 8,
text: "The AI Agent SDK's multi-agent system has enabled us to build complex, collaborative AI solutions with ease.",
name: "Hannah Irving",
company: "CollabAI",
image:
"https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=500&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8NTJ8fHBvcnRyYWl0fGVufDB8fDB8fHww",
},
{
id: 9,
text: "The SDK's flexibility in integrating external tools has expanded our AI agents' capabilities tremendously.",
name: "Ian Johnson",
company: "FlexAI",
image:
"https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=500&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8NTZ8fHBvcnRyYWl0fGVufDB8fDB8fHww",
},
{
id: 10,
text: "The AI Agent SDK's documentation and support have made our learning curve much smoother.",
name: "Julia Kim",
company: "DevAI",
image:
"https://images.unsplash.com/photo-1531746020798-e6953c6e8e04?w=500&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8NjR8fHBvcnRyYWl0fGVufDB8fDB8fHww",
},
{
id: 11,
text: "We've seen a significant boost in our AI's decision-making capabilities thanks to the AI Agent SDK.",
name: "Kevin Lee",
company: "DecisionTech",
image:
"https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=500&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8NzB8fHBvcnRyYWl0fGVufDB8fDB8fHww",
},
{
id: 12,
text: "The SDK's multi-agent system has revolutionized our approach to complex problem-solving.",
name: "Laura Martinez",
company: "SolveX",
image:
"https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=500&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8NzZ8fHBvcnRyYWl0fGVufDB8fDB8fHww",
},
{
id: 13,
text: "The customization options in the AI Agent SDK have allowed us to create truly unique AI solutions.",
name: "Michael Chen",
company: "UniqueAI",
image:
"https://images.unsplash.com/photo-1506794778202-cad84cf45f1d?w=500&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8ODJ8fHBvcnRyYWl0fGVufDB8fDB8fHww",
},
{
id: 14,
text: "The efficiency of the AI Agent SDK has significantly reduced our development time and costs.",
name: "Natalie Wong",
company: "FastTrackAI",
image:
"https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=500&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8ODh8fHBvcnRyYWl0fGVufDB8fDB8fHww",
},
{
id: 15,
text: "The cross-language support has made it easy for our diverse team to collaborate on AI projects.",
name: "Oliver Smith",
company: "GlobalAI",
image:
"https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=500&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8OTR8fHBvcnRyYWl0fGVufDB8fDB8fHww",
},
],
};
export type SiteConfig = typeof siteConfig;

14
src/lib/fonts.ts Normal file
View File

@ -0,0 +1,14 @@
import {
JetBrains_Mono as FontMono,
Inter as FontSans,
} from "next/font/google";
export const fontSans = FontSans({
subsets: ["latin"],
variable: "--font-sans",
});
export const fontMono = FontMono({
subsets: ["latin"],
variable: "--font-mono",
});

89
src/lib/utils.ts Normal file
View File

@ -0,0 +1,89 @@
import { siteConfig } from "@/lib/config";
import { type ClassValue, clsx } from "clsx";
import { Metadata } from "next";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
export function absoluteUrl(path: string) {
return `${process.env.NEXT_PUBLIC_APP_URL || siteConfig.url}${path}`;
}
export function constructMetadata({
title = siteConfig.name,
description = siteConfig.description,
image = absoluteUrl("/og"),
...props
}: {
title?: string;
description?: string;
image?: string;
[key: string]: Metadata[keyof Metadata];
}): Metadata {
return {
title: {
template: "%s | " + siteConfig.name,
default: siteConfig.name,
},
description: description || siteConfig.description,
keywords: siteConfig.keywords,
openGraph: {
title,
description,
url: siteConfig.url,
siteName: siteConfig.name,
images: [
{
url: image,
width: 1200,
height: 630,
alt: title,
},
],
type: "website",
locale: "en_US",
},
icons: "/favicon.ico",
metadataBase: new URL(siteConfig.url),
authors: [
{
name: siteConfig.name,
url: siteConfig.url,
},
],
...props,
};
}
export function formatDate(date: string) {
let currentDate = new Date().getTime();
if (!date.includes("T")) {
date = `${date}T00:00:00`;
}
let targetDate = new Date(date).getTime();
let timeDifference = Math.abs(currentDate - targetDate);
let daysAgo = Math.floor(timeDifference / (1000 * 60 * 60 * 24));
let fullDate = new Date(date).toLocaleString("en-us", {
month: "long",
day: "numeric",
year: "numeric",
});
if (daysAgo < 1) {
return "Today";
} else if (daysAgo < 7) {
return `${fullDate} (${daysAgo}d ago)`;
} else if (daysAgo < 30) {
const weeksAgo = Math.floor(daysAgo / 7);
return `${fullDate} (${weeksAgo}w ago)`;
} else if (daysAgo < 365) {
const monthsAgo = Math.floor(daysAgo / 30);
return `${fullDate} (${monthsAgo}mo ago)`;
} else {
const yearsAgo = Math.floor(daysAgo / 365);
return `${fullDate} (${yearsAgo}y ago)`;
}
}

158
tailwind.config.ts Normal file
View File

@ -0,0 +1,158 @@
import type { Config } from "tailwindcss";
const config = {
darkMode: ["class"],
content: [
"./pages/**/*.{ts,tsx}",
"./components/**/*.{ts,tsx}",
"./app/**/*.{ts,tsx}",
"./src/**/*.{ts,tsx}",
],
prefix: "",
theme: {
container: {
center: true,
padding: "1rem",
screens: {
"2xl": "1000px",
},
},
extend: {
fontFamily: {
sans: ["var(--font-geist-sans)"],
mono: ["var(--font-geist-mono)"],
},
colors: {
border: "hsl(var(--border))",
input: "hsl(var(--input))",
ring: "hsl(var(--ring))",
background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))",
primary: {
DEFAULT: "hsl(var(--primary))",
foreground: "hsl(var(--primary-foreground))",
},
secondary: {
DEFAULT: "hsl(var(--secondary))",
foreground: "hsl(var(--secondary-foreground))",
},
destructive: {
DEFAULT: "hsl(var(--destructive))",
foreground: "hsl(var(--destructive-foreground))",
},
muted: {
DEFAULT: "hsl(var(--muted))",
foreground: "hsl(var(--muted-foreground))",
},
accent: {
DEFAULT: "hsl(var(--accent))",
foreground: "hsl(var(--accent-foreground))",
},
popover: {
DEFAULT: "hsl(var(--popover))",
foreground: "hsl(var(--popover-foreground))",
},
card: {
DEFAULT: "hsl(var(--card))",
foreground: "hsl(var(--card-foreground))",
},
},
borderRadius: {
lg: "var(--radius)",
md: "calc(var(--radius) - 2px)",
sm: "calc(var(--radius) - 4px)",
},
animation: {
marquee: "marquee var(--duration) linear infinite",
"marquee-vertical": "marquee-vertical var(--duration) linear infinite",
"border-beam": "border-beam calc(var(--duration)*1s) infinite linear",
"accordion-down": "accordion-down 0.2s ease-out",
"accordion-up": "accordion-up 0.2s ease-out",
ripple: "ripple var(--duration,2s) ease calc(var(--i, 0)*.2s) infinite",
orbit: "orbit calc(var(--duration)*1s) linear infinite",
},
keyframes: {
marquee: {
from: {
transform: "translateX(0)",
},
to: {
transform: "translateX(calc(-100% - var(--gap)))",
},
},
"marquee-vertical": {
from: {
transform: "translateY(0)",
},
to: {
transform: "translateY(calc(-100% - var(--gap)))",
},
},
"border-beam": {
"100%": {
"offset-distance": "100%",
},
},
"accordion-down": {
from: {
height: "0",
},
to: {
height: "var(--radix-accordion-content-height)",
},
},
"accordion-up": {
from: {
height: "var(--radix-accordion-content-height)",
},
to: {
height: "0",
},
},
ripple: {
"0%, 100%": {
transform: "translate(-50%, -50%) scale(1)",
},
"50%": {
transform: "translate(-50%, -50%) scale(0.9)",
},
},
orbit: {
"0%": {
transform:
"rotate(0deg) translateY(calc(var(--radius) * 1px)) rotate(0deg)",
},
"100%": {
transform:
"rotate(360deg) translateY(calc(var(--radius) * 1px)) rotate(-360deg)",
},
},
},
backgroundSize: {
"grid-1": "100% 100%",
"grid-2": "50% 100%",
"grid-3": "calc(100%/3) 100%",
"grid-4": "25% 100%",
"grid-5": "20% 100%",
"grid-6": "calc(100%/6) 100%",
},
backgroundImage: {
"grid-1":
"linear-gradient(to right, hsl(var(--border)) 1px, transparent 1px)",
"grid-2":
"linear-gradient(to right, hsl(var(--border)) 1px, transparent 1px)",
"grid-3":
"linear-gradient(to right, hsl(var(--border)) 1px, transparent 1px)",
"grid-4":
"linear-gradient(to right, hsl(var(--border)) 1px, transparent 1px)",
"grid-5":
"linear-gradient(to right, hsl(var(--border)) 1px, transparent 1px)",
"grid-6":
"linear-gradient(to right, hsl(var(--border)) 1px, transparent 1px)",
},
},
},
plugins: [require("tailwindcss-animate"), require("@tailwindcss/typography")],
} satisfies Config;
export default config;

28
tsconfig.json Normal file
View File

@ -0,0 +1,28 @@
{
"compilerOptions": {
"baseUrl": ".",
"target": "esnext",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}