chore(site): added site

This commit is contained in:
Nicholas Kissel 2026-01-25 19:07:05 -08:00
parent 29b159ca20
commit d5b5efebb6
41 changed files with 1887 additions and 43 deletions

View file

@ -0,0 +1,114 @@
'use client';
import { useState, useEffect } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { ArrowRight, Terminal, Check } from 'lucide-react';
const CTA_TITLES = [
'Run any coding agent in any sandbox — with Sandbox Agent.',
'One API for all coding agents — powered by Sandbox Agent.',
'Claude Code, Codex, Amp — unified with Sandbox Agent.',
'Swap agents without refactoring — thanks to Sandbox Agent.',
'Agentic backends, zero complexity — with Sandbox Agent.',
'Manage transcripts, maintain state — powered by Sandbox Agent.',
'Your agents deserve a universal API — Sandbox Agent delivers.',
'Build agentic sandboxes that scale — with Sandbox Agent.',
];
function AnimatedCTATitle() {
const [currentIndex, setCurrentIndex] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setCurrentIndex(prev => (prev + 1) % CTA_TITLES.length);
}, 3000);
return () => clearInterval(interval);
}, []);
return (
<h2 className='min-h-[1.2em] text-4xl font-medium tracking-tight text-white md:text-5xl'>
<AnimatePresence mode='wait'>
<motion.span
key={currentIndex}
initial={{ opacity: 0, y: 5 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -5 }}
transition={{ duration: 0.1 }}
style={{ display: 'block' }}
>
{CTA_TITLES[currentIndex]}
</motion.span>
</AnimatePresence>
</h2>
);
}
const CopyInstallButton = () => {
const [copied, setCopied] = useState(false);
const installCommand = 'npx rivet-dev/sandbox-agent';
const handleCopy = async () => {
try {
await navigator.clipboard.writeText(installCommand);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
} catch (err) {
console.error('Failed to copy:', err);
}
};
return (
<button
onClick={handleCopy}
className='inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md border border-white/10 bg-white/5 px-4 py-2 text-sm text-white subpixel-antialiased shadow-sm transition-colors hover:border-white/20'
>
{copied ? <Check className='h-4 w-4' /> : <Terminal className='h-4 w-4' />}
{installCommand}
</button>
);
};
export function CTASection() {
return (
<section className='relative overflow-hidden border-t border-white/10 px-6 py-32 text-center'>
<div className='absolute inset-0 z-0 bg-gradient-to-b from-black to-zinc-900/50' />
<motion.div
animate={{ opacity: [0.3, 0.5, 0.3] }}
transition={{ duration: 4, repeat: Infinity }}
className='pointer-events-none absolute inset-0 bg-[radial-gradient(ellipse_at_center,_var(--tw-gradient-stops))] from-zinc-500/10 via-transparent to-transparent opacity-50'
/>
<div className='relative z-10 mx-auto max-w-3xl'>
<div className='mb-8'>
<AnimatedCTATitle />
</div>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: 0.1 }}
className='mb-10 text-lg leading-relaxed text-zinc-400'
>
API for coding agents. <br className='hidden md:block' />
Deploy anywhere. Swap agents on demand.
</motion.p>
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: 0.2 }}
className='flex flex-col items-center justify-center gap-4 sm:flex-row'
>
<a
href='/docs'
className='inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md border border-white/10 bg-white px-4 py-2 text-sm text-black subpixel-antialiased shadow-sm transition-colors hover:bg-zinc-200'
>
Read the Docs
<ArrowRight className='h-4 w-4' />
</a>
<CopyInstallButton />
</motion.div>
</div>
</section>
);
}

View file

@ -0,0 +1,191 @@
'use client';
import { Workflow, Server, Database, Zap, Globe } from 'lucide-react';
import { FeatureIcon } from './ui/FeatureIcon';
import { CopyButton } from './ui/CopyButton';
export function FeatureGrid() {
return (
<section id="features" className="relative overflow-hidden border-t border-white/5 py-32">
<div className="relative z-10 mx-auto max-w-7xl px-6">
<div className="mb-16">
<h2 className="mb-6 text-3xl font-medium tracking-tight text-white md:text-5xl">
Full feature coverage. <br />
<span className="text-zinc-500">Solving the fundamental friction points.</span>
</h2>
<p className="text-lg leading-relaxed text-zinc-400">
Everything you need to ship agents in sandboxes in record time.
</p>
</div>
<div className="grid grid-cols-12 gap-4">
{/* Universal Agent API - Span 7 cols */}
<div className="col-span-12 lg:col-span-7 row-span-2 group relative flex flex-col gap-4 overflow-hidden rounded-2xl border border-white/5 bg-zinc-900/30 p-6 backdrop-blur-sm transition-colors duration-500 hover:bg-zinc-900/50 min-h-[400px]">
{/* Top Shine Highlight */}
<div className="absolute left-0 right-0 top-0 z-10 h-[1px] bg-gradient-to-r from-transparent via-white/20 to-transparent" />
{/* Top Left Reflection/Glow */}
<div className="pointer-events-none absolute inset-0 bg-[radial-gradient(circle_at_top_left,rgba(255,79,0,0.15)_0%,transparent_50%)] opacity-0 transition-opacity duration-500 group-hover:opacity-100" />
{/* Sharp Edge Highlight */}
<div className="pointer-events-none absolute left-0 top-0 z-20 h-24 w-24 rounded-tl-2xl border-l border-t border-orange-500 opacity-0 transition-opacity duration-500 [mask-image:linear-gradient(135deg,black_0%,transparent_50%)] group-hover:opacity-100" />
<div className="relative z-10 flex flex-col gap-4">
<div className="relative z-10 mb-2 flex items-center gap-3">
<FeatureIcon
icon={Workflow}
color="text-orange-400"
bgColor="bg-orange-500/10"
hoverBgColor="group-hover:bg-orange-500/20"
glowShadow="group-hover:shadow-[0_0_15px_rgba(255,79,0,0.5)]"
/>
<h4 className="text-sm font-medium uppercase tracking-wider text-white">Universal Agent API</h4>
</div>
<p className="text-zinc-400 leading-relaxed text-lg max-w-md">
Coding agents like Claude Code and Amp have custom scaffolds. We provide a single,
unified interface to swap between engines effortlessly.
</p>
</div>
<div className="mt-auto relative z-10 h-48 bg-black/50 rounded-xl border border-white/5 p-5 overflow-hidden font-mono text-xs">
<div className="flex gap-4 border-b border-white/5 pb-3 mb-4">
<span className="text-orange-400 border-b border-orange-400 pb-3">agent.spawn()</span>
<span className="text-zinc-600">agent.terminate()</span>
<span className="text-zinc-600">agent.logs()</span>
</div>
<div className="space-y-3">
<div className="flex items-center gap-3">
<div className="w-1.5 h-1.5 rounded-full bg-orange-500 animate-pulse" />
<span className="text-zinc-300">
Swapping provider to <span className="text-green-400">"daytona"</span>...
</span>
</div>
<div className="flex items-center gap-3">
<div className="w-1.5 h-1.5 rounded-full bg-green-500" />
<span className="text-zinc-300">Hot-reloading sandbox context...</span>
</div>
<div className="text-zinc-500 italic mt-4">// No code changes required</div>
</div>
</div>
</div>
{/* Server Mode - Span 5 cols */}
<div className="col-span-12 lg:col-span-5 group relative flex flex-col gap-4 overflow-hidden rounded-2xl border border-white/5 bg-zinc-900/30 p-6 backdrop-blur-sm transition-colors duration-500 hover:bg-zinc-900/50">
{/* Top Shine Highlight */}
<div className="absolute left-0 right-0 top-0 z-10 h-[1px] bg-gradient-to-r from-transparent via-white/20 to-transparent" />
{/* Top Left Reflection/Glow */}
<div className="pointer-events-none absolute inset-0 bg-[radial-gradient(circle_at_top_left,rgba(34,197,94,0.15)_0%,transparent_50%)] opacity-0 transition-opacity duration-500 group-hover:opacity-100" />
{/* Sharp Edge Highlight */}
<div className="pointer-events-none absolute left-0 top-0 z-20 h-24 w-24 rounded-tl-2xl border-l border-t border-green-500 opacity-0 transition-opacity duration-500 [mask-image:linear-gradient(135deg,black_0%,transparent_50%)] group-hover:opacity-100" />
<div className="relative z-10 mb-2 flex items-center gap-3">
<FeatureIcon
icon={Server}
color="text-green-400"
bgColor="bg-green-500/10"
hoverBgColor="group-hover:bg-green-500/20"
glowShadow="group-hover:shadow-[0_0_15px_rgba(34,197,94,0.5)]"
/>
<h4 className="text-sm font-medium uppercase tracking-wider text-white">Server Mode</h4>
</div>
<p className="text-zinc-400 text-sm leading-relaxed">
Run as an HTTP server within any sandbox. One command to bridge your agent to the
local environment.
</p>
<div className="mt-auto relative z-10 p-3 bg-black/40 rounded-lg border border-white/5 font-mono text-xs text-green-400">
$ sandbox-agent serve --port 4000
</div>
</div>
{/* Universal Schema - Span 5 cols */}
<div className="col-span-12 lg:col-span-5 group relative flex flex-col gap-4 overflow-hidden rounded-2xl border border-white/5 bg-zinc-900/30 p-6 backdrop-blur-sm transition-colors duration-500 hover:bg-zinc-900/50">
{/* Top Shine Highlight */}
<div className="absolute left-0 right-0 top-0 z-10 h-[1px] bg-gradient-to-r from-transparent via-white/20 to-transparent" />
{/* Top Left Reflection/Glow */}
<div className="pointer-events-none absolute inset-0 bg-[radial-gradient(circle_at_top_left,rgba(168,85,247,0.15)_0%,transparent_50%)] opacity-0 transition-opacity duration-500 group-hover:opacity-100" />
{/* Sharp Edge Highlight */}
<div className="pointer-events-none absolute left-0 top-0 z-20 h-24 w-24 rounded-tl-2xl border-l border-t border-purple-500 opacity-0 transition-opacity duration-500 [mask-image:linear-gradient(135deg,black_0%,transparent_50%)] group-hover:opacity-100" />
<div className="relative z-10 mb-2 flex items-center gap-3">
<FeatureIcon
icon={Database}
color="text-purple-400"
bgColor="bg-purple-500/10"
hoverBgColor="group-hover:bg-purple-500/20"
glowShadow="group-hover:shadow-[0_0_15px_rgba(168,85,247,0.5)]"
/>
<h4 className="text-sm font-medium uppercase tracking-wider text-white">Universal Schema</h4>
</div>
<p className="text-zinc-400 text-sm leading-relaxed">
Standardized session JSON to store and replay agent actions. Built-in adapters for
Postgres and ClickHouse.
</p>
</div>
{/* Rust Binary - Span 8 cols */}
<div className="col-span-12 md:col-span-8 group relative flex flex-col gap-4 overflow-hidden rounded-2xl border border-white/5 bg-zinc-900/30 p-6 backdrop-blur-sm transition-colors duration-500 hover:bg-zinc-900/50">
{/* Top Shine Highlight */}
<div className="absolute left-0 right-0 top-0 z-10 h-[1px] bg-gradient-to-r from-transparent via-white/20 to-transparent" />
{/* Top Left Reflection/Glow */}
<div className="pointer-events-none absolute inset-0 bg-[radial-gradient(circle_at_top_left,rgba(245,158,11,0.15)_0%,transparent_50%)] opacity-0 transition-opacity duration-500 group-hover:opacity-100" />
{/* Sharp Edge Highlight */}
<div className="pointer-events-none absolute left-0 top-0 z-20 h-24 w-24 rounded-tl-2xl border-l border-t border-amber-500 opacity-0 transition-opacity duration-500 [mask-image:linear-gradient(135deg,black_0%,transparent_50%)] group-hover:opacity-100" />
<div className="flex flex-col gap-4 relative z-10">
<div className="relative z-10 mb-2 flex items-center gap-3">
<FeatureIcon
icon={Zap}
color="text-amber-400"
bgColor="bg-amber-500/10"
hoverBgColor="group-hover:bg-amber-500/20"
glowShadow="group-hover:shadow-[0_0_15px_rgba(245,158,11,0.5)]"
/>
<h4 className="text-sm font-medium uppercase tracking-wider text-white">Rust Binary</h4>
</div>
<p className="text-zinc-400 text-sm leading-relaxed">
Statically-linked binary. Zero dependencies. 4MB total size. Instant startup with no runtime overhead.
</p>
</div>
<div className="mt-auto w-full relative z-10">
<div className="bg-black/50 rounded-xl border border-white/5 p-4 font-mono text-xs">
<div className="flex items-center gap-2 mb-2 text-zinc-500 text-[10px]">
<span className="w-1.5 h-1.5 rounded-full bg-green-500" />
Quick Install
</div>
<div className="flex items-center gap-2 text-amber-400">
<span className="text-zinc-500">$ </span>
<span className="text-zinc-300">curl -sSL https://sandboxagent.dev/install | sh</span>
<div className="ml-2 border-l border-white/10 pl-2">
<CopyButton text="curl -sSL https://sandboxagent.dev/install | sh" />
</div>
</div>
</div>
</div>
</div>
{/* Provider Agnostic - Span 4 cols */}
<div className="col-span-12 md:col-span-4 group relative flex flex-col gap-4 overflow-hidden rounded-2xl border border-white/5 bg-zinc-900/30 p-6 backdrop-blur-sm transition-colors duration-500 hover:bg-zinc-900/50">
{/* Top Shine Highlight */}
<div className="absolute left-0 right-0 top-0 z-10 h-[1px] bg-gradient-to-r from-transparent via-white/20 to-transparent" />
{/* Top Left Reflection/Glow */}
<div className="pointer-events-none absolute inset-0 bg-[radial-gradient(circle_at_top_left,rgba(59,130,246,0.15)_0%,transparent_50%)] opacity-0 transition-opacity duration-500 group-hover:opacity-100" />
{/* Sharp Edge Highlight */}
<div className="pointer-events-none absolute left-0 top-0 z-20 h-24 w-24 rounded-tl-2xl border-l border-t border-blue-500 opacity-0 transition-opacity duration-500 [mask-image:linear-gradient(135deg,black_0%,transparent_50%)] group-hover:opacity-100" />
<div className="relative z-10 mb-2 flex items-center gap-3">
<FeatureIcon
icon={Globe}
color="text-blue-400"
bgColor="bg-blue-500/10"
hoverBgColor="group-hover:bg-blue-500/20"
glowShadow="group-hover:shadow-[0_0_15px_rgba(59,130,246,0.5)]"
/>
<h4 className="text-sm font-medium uppercase tracking-wider text-white">Provider Agnostic</h4>
</div>
<p className="text-zinc-500 text-sm leading-relaxed">
Seamless support for E2B, Daytona, Vercel Sandboxes, and custom Docker.
</p>
</div>
</div>
</div>
</section>
);
}

View file

@ -0,0 +1,135 @@
'use client';
const footer = {
products: [
{ name: 'Actors', href: 'https://rivet.dev/docs/actors' },
{ name: 'Sandbox Agent SDK', href: '/docs' },
],
developers: [
{ name: 'Documentation', href: '/docs' },
{ name: 'Changelog', href: '/changelog' },
{ name: 'Blog', href: 'https://rivet.dev/blog' },
],
legal: [
{ name: 'Terms', href: 'https://rivet.dev/terms' },
{ name: 'Privacy Policy', href: 'https://rivet.dev/privacy' },
{ name: 'Acceptable Use', href: 'https://rivet.dev/acceptable-use' },
],
social: [
{
name: 'Discord',
href: 'https://discord.gg/sandbox-agent',
icon: (
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028c.462-.63.874-1.295 1.226-1.994a.076.076 0 0 0-.041-.106 13.107 13.107 0 0 1-1.872-.892.077.077 0 0 1-.008-.128 10.2 10.2 0 0 0 .372-.292.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127 12.299 12.299 0 0 1-1.873.892.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.839 19.839 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.956-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.955-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.946 2.418-2.157 2.418z" />
</svg>
),
},
{
name: 'GitHub',
href: 'https://github.com/rivet-dev/sandbox-agent',
icon: (
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" />
</svg>
),
},
{
name: 'Twitter',
href: 'https://x.com/rivet_dev',
icon: (
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z" />
</svg>
),
},
],
};
export function Footer() {
return (
<footer className="border-t border-white/10 bg-zinc-950">
<div className="mx-auto max-w-7xl px-6 py-12 lg:py-16">
<div className="xl:grid xl:grid-cols-12 xl:gap-16">
{/* Logo & Social */}
<div className="space-y-6 xl:col-span-4">
<img src="/rivet-logo-text-white.svg" alt="Rivet" width="80" height="24" className="w-20 shrink-0" />
<p className="text-sm leading-6 text-zinc-400">
Build and scale stateful workloads
</p>
<div className="flex space-x-4">
{footer.social.map((item) => (
<a
key={item.name}
href={item.href}
className="text-zinc-500 hover:text-white transition-colors"
target="_blank"
rel="noopener noreferrer"
>
<span className="sr-only">{item.name}</span>
{item.icon}
</a>
))}
</div>
</div>
{/* Links */}
<div className="mt-12 grid grid-cols-2 gap-8 md:grid-cols-3 xl:col-span-8 xl:mt-0">
<div>
<h3 className="text-sm font-semibold leading-6 text-white">Products</h3>
<ul role="list" className="mt-4 space-y-3">
{footer.products.map((item) => (
<li key={item.name}>
<a
href={item.href}
className="text-sm leading-6 text-zinc-400 hover:text-white transition-colors"
>
{item.name}
</a>
</li>
))}
</ul>
</div>
<div>
<h3 className="text-sm font-semibold leading-6 text-white">Developers</h3>
<ul role="list" className="mt-4 space-y-3">
{footer.developers.map((item) => (
<li key={item.name}>
<a
href={item.href}
className="text-sm leading-6 text-zinc-400 hover:text-white transition-colors"
>
{item.name}
</a>
</li>
))}
</ul>
</div>
<div>
<h3 className="text-sm font-semibold leading-6 text-white">Legal</h3>
<ul role="list" className="mt-4 space-y-3">
{footer.legal.map((item) => (
<li key={item.name}>
<a
href={item.href}
className="text-sm leading-6 text-zinc-400 hover:text-white transition-colors"
>
{item.name}
</a>
</li>
))}
</ul>
</div>
</div>
</div>
{/* Bottom */}
<div className="mt-12 border-t border-white/10 pt-8">
<p className="text-xs text-zinc-500 text-center">
&copy; {new Date().getFullYear()} Rivet Gaming, Inc. All rights reserved.
</p>
</div>
</div>
</footer>
);
}

View file

@ -0,0 +1,78 @@
'use client';
import { useEffect, useState } from 'react';
interface GitHubStarsProps extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
repo?: string;
}
function formatNumber(num: number): string {
if (num >= 1000) {
return `${(num / 1000).toFixed(1)}k`;
}
return num.toString();
}
export function GitHubStars({
repo = 'rivet-dev/sandbox-agent',
className,
...props
}: GitHubStarsProps) {
const [stars, setStars] = useState<number | null>(null);
useEffect(() => {
const cacheKey = `github-stars-${repo}`;
const cachedData = sessionStorage.getItem(cacheKey);
if (cachedData) {
const { stars: cachedStars, timestamp } = JSON.parse(cachedData);
// Check if cache is less than 5 minutes old
if (Date.now() - timestamp < 5 * 60 * 1000) {
setStars(cachedStars);
return;
}
}
fetch(`https://api.github.com/repos/${repo}`)
.then((response) => {
if (!response.ok) throw new Error('Failed to fetch');
return response.json();
})
.then((data) => {
const newStars = data.stargazers_count;
setStars(newStars);
sessionStorage.setItem(
cacheKey,
JSON.stringify({
stars: newStars,
timestamp: Date.now(),
}),
);
})
.catch((err) => {
console.error('Failed to fetch stars', err);
});
}, [repo]);
return (
<a
href={`https://github.com/${repo}`}
target="_blank"
rel="noreferrer"
className={className}
{...props}
>
<svg
className="w-5 h-5"
fill="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" />
</svg>
<span className="hidden md:inline">
{stars ? `${formatNumber(stars)} Stars` : 'GitHub'}
</span>
</a>
);
}

View file

@ -0,0 +1,161 @@
'use client';
import { useState } from 'react';
import { Terminal, Check, ArrowRight } from 'lucide-react';
const CopyInstallButton = () => {
const [copied, setCopied] = useState(false);
const installCommand = 'npx rivet-dev/sandbox-agent';
const handleCopy = async () => {
try {
await navigator.clipboard.writeText(installCommand);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
} catch (err) {
console.error('Failed to copy:', err);
}
};
return (
<button
onClick={handleCopy}
className='inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md border border-white/10 bg-white/5 px-4 py-2 text-sm text-white subpixel-antialiased shadow-sm transition-colors hover:border-white/20'
>
{copied ? <Check className='h-4 w-4' /> : <Terminal className='h-4 w-4' />}
{installCommand}
</button>
);
};
export function Hero() {
return (
<section className="relative pt-32 pb-24 overflow-hidden">
<div className="max-w-7xl mx-auto px-6 relative z-10">
<div className="flex flex-col lg:flex-row items-center gap-16">
<div className="flex-1 text-center lg:text-left">
<h1 className="mb-6 text-5xl font-medium leading-[1.1] tracking-tighter text-white md:text-7xl">
API for <br />
Sandbox Agents
</h1>
<p className="mt-8 text-xl text-zinc-400 leading-relaxed max-w-2xl mx-auto lg:mx-0">
One API to run Claude Code, Codex, and Amp inside any sandbox. Manage transcripts, maintain state, and swap agents with zero refactoring.
</p>
<div className="mt-10 flex flex-col items-center gap-4 sm:flex-row sm:justify-center lg:justify-start">
<a
href="/docs"
className='inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md border border-white/10 bg-white px-4 py-2 text-sm text-black subpixel-antialiased shadow-sm transition-colors hover:bg-zinc-200'
>
Read the Docs
<ArrowRight className='h-4 w-4' />
</a>
<CopyInstallButton />
</div>
<div className="mt-16 flex flex-col items-center lg:items-start gap-6">
<span className="text-sm font-mono uppercase tracking-widest text-white/60">
Supported Sandbox Providers
</span>
<div className="flex gap-10 items-center">
<DaytonaLogo />
<E2BLogo />
<VercelLogo />
</div>
</div>
</div>
<div className="flex-1 w-full max-w-xl">
<div className="relative group">
<div className="absolute -inset-1 rounded-xl bg-gradient-to-r from-zinc-700 to-zinc-800 opacity-20 blur" />
<div className="group relative overflow-hidden rounded-xl border border-white/10 bg-zinc-900/50 shadow-2xl backdrop-blur-xl">
<div className="flex items-center justify-between border-b border-white/5 bg-white/5 px-4 py-3">
<div className="flex items-center gap-2">
<div className="h-3 w-3 rounded-full border border-zinc-500/50 bg-zinc-500/20" />
<div className="h-3 w-3 rounded-full border border-zinc-500/50 bg-zinc-500/20" />
<div className="h-3 w-3 rounded-full border border-zinc-500/50 bg-zinc-500/20" />
</div>
<div className="font-mono text-xs text-zinc-500">example_agent.ts</div>
</div>
<div className="p-6 font-mono text-sm leading-relaxed">
<CodeLine num="01">
<span className="text-purple-400">import</span>{' '}
<span className="text-white">{'{ SandboxAgent }'}</span>{' '}
<span className="text-purple-400">from</span>{' '}
<span className="text-green-400">"@sandbox/sdk"</span>;
</CodeLine>
<CodeLine num="02">
<span className="text-zinc-500">// Start Claude Code in an E2B sandbox</span>
</CodeLine>
<CodeLine num="03">
<span className="text-purple-400">const</span>{' '}
<span className="text-white">agent = </span>
<span className="text-purple-400">await</span>{' '}
<span className="text-white">SandboxAgent.</span>
<span className="text-blue-400">spawn</span>
<span className="text-white">{'({'}</span>
</CodeLine>
<CodeLine num="04">
<span className="text-white"> provider: </span>
<span className="text-green-400">"e2b"</span>,
</CodeLine>
<CodeLine num="05">
<span className="text-white"> engine: </span>
<span className="text-green-400">"claude-code"</span>
</CodeLine>
<CodeLine num="06">
<span className="text-white">{'}'});</span>
</CodeLine>
<div className="h-4" />
<CodeLine num="07">
<span className="text-purple-400">const</span>{' '}
<span className="text-white">transcript = </span>
<span className="text-purple-400">await</span>{' '}
<span className="text-white">agent.</span>
<span className="text-blue-400">getTranscript</span>
<span className="text-white">();</span>
</CodeLine>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
);
}
function CodeLine({ num, children }: { num: string; children: React.ReactNode }) {
return (
<div className="flex gap-4">
<span className="text-zinc-600 select-none">{num}</span>
{children}
</div>
);
}
function DaytonaLogo() {
return (
<a href="https://daytona.io" target="_blank" rel="noopener noreferrer" className="opacity-60 hover:opacity-100 transition-opacity">
<img src="/logos/daytona.svg" alt="Daytona" className="h-6 w-auto" style={{ filter: 'brightness(0) invert(1)' }} />
</a>
);
}
function E2BLogo() {
return (
<a href="https://e2b.dev" target="_blank" rel="noopener noreferrer" className="opacity-60 hover:opacity-100 transition-opacity">
<img src="/logos/e2b.svg" alt="E2B" className="h-7 w-auto" style={{ filter: 'brightness(0) invert(1)' }} />
</a>
);
}
function VercelLogo() {
return (
<a href="https://vercel.com/docs/sandbox" target="_blank" rel="noopener noreferrer" className="opacity-60 hover:opacity-100 transition-opacity flex items-center gap-2">
<svg viewBox="0 0 24 24" className="h-7 w-7 fill-current text-white">
<path d="M24 22.525H0l12-21.05 12 21.05z"/>
</svg>
</a>
);
}

View file

@ -0,0 +1,35 @@
'use client';
const integrations = [
'Daytona',
'E2B',
'AI SDK',
'Anthropic',
'OpenAI',
'Docker',
'Fly.io',
'AWS Nitro',
'Postgres',
'ClickHouse',
'Rivet',
];
export function Integrations() {
return (
<section id="integrations" className="py-24 bg-zinc-900/20 border-t border-white/5 relative overflow-hidden">
<div className="max-w-4xl mx-auto px-6 text-center">
<h2 className="text-3xl font-bold text-white mb-6">Works with your stack</h2>
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
{integrations.map((item) => (
<div
key={item}
className="h-16 flex items-center justify-center rounded-xl border border-white/5 bg-zinc-900/50 text-zinc-300 font-mono text-sm hover:border-accent/40 hover:text-accent transition-all cursor-default"
>
{item}
</div>
))}
</div>
</div>
</section>
);
}

View file

@ -0,0 +1,144 @@
'use client';
import { useState, useEffect } from 'react';
import { Menu, X } from 'lucide-react';
import { GitHubStars } from './GitHubStars';
function NavItem({ href, children }: { href: string; children: React.ReactNode }) {
return (
<div className="px-2.5 py-2 opacity-60 hover:opacity-100 transition-all duration-200">
<a href={href} className="text-white text-sm">
{children}
</a>
</div>
);
}
export function Navigation() {
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
const [isScrolled, setIsScrolled] = useState(false);
useEffect(() => {
const handleScroll = () => {
setIsScrolled(window.scrollY > 20);
};
window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll);
}, []);
return (
<div className="fixed top-0 z-50 w-full max-w-[1200px] md:left-1/2 md:top-4 md:-translate-x-1/2 md:px-8">
<div
className={`relative before:pointer-events-none before:absolute before:inset-[-1px] before:z-20 before:hidden before:rounded-2xl before:border before:content-[''] before:transition-colors before:duration-300 before:ease-in-out md:before:block ${
isScrolled ? "before:border-white/10" : "before:border-transparent"
}`}
>
<div
className={`absolute inset-0 -z-[1] hidden overflow-hidden rounded-2xl transition-all duration-300 ease-in-out md:block ${
isScrolled
? "bg-black/80 backdrop-blur-lg"
: "bg-black backdrop-blur-none"
}`}
/>
<header
className={`bg-black/60 border-b-transparent sticky top-0 z-10 flex flex-col items-center border-b backdrop-blur pt-2 pb-2 md:static md:bg-transparent md:rounded-2xl md:max-w-[1200px] md:border-transparent md:backdrop-none md:backdrop-blur-none transition-all hover:opacity-100 ${
isScrolled ? "opacity-100" : "opacity-80"
}`}
>
<div className="flex w-full items-center justify-between px-3">
{/* Left side: Logo + Nav */}
<div className="flex items-center gap-4">
<a href="/" className="flex items-center gap-3">
<img src="/rivet-icon.svg" alt="Rivet" className="size-8" />
<span className="text-white/30">|</span>
<span className="text-white text-sm font-semibold">Sandbox Agent SDK</span>
</a>
{/* Desktop Nav */}
<div className="hidden md:flex items-center">
<NavItem href="/docs">Docs</NavItem>
<NavItem href="/changelog">Changelog</NavItem>
</div>
</div>
{/* Right side */}
<div className="hidden md:flex flex-row items-center">
<a
href="https://discord.gg/sandbox-agent"
className="inline-flex items-center justify-center whitespace-nowrap rounded-md border border-white/10 px-4 py-2 h-10 text-sm mr-2 hover:border-white/20 text-white/90 hover:text-white transition-colors"
aria-label="Discord"
>
<svg
className="w-5 h-5"
fill="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028c.462-.63.874-1.295 1.226-1.994a.076.076 0 0 0-.041-.106 13.107 13.107 0 0 1-1.872-.892.077.077 0 0 1-.008-.128 10.2 10.2 0 0 0 .372-.292.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127 12.299 12.299 0 0 1-1.873.892.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.839 19.839 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.956-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.955-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.946 2.418-2.157 2.418z" />
</svg>
</a>
<GitHubStars
repo="rivet-dev/sandbox-agent"
className="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md border border-white/10 bg-white/5 px-4 py-2 h-10 text-sm text-white shadow-sm hover:border-white/20 transition-colors"
/>
</div>
{/* Mobile menu button */}
<button
className="md:hidden text-zinc-400 hover:text-white p-2"
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
>
{mobileMenuOpen ? <X className="w-6 h-6" /> : <Menu className="w-6 h-6" />}
</button>
</div>
</header>
</div>
{/* Mobile menu */}
{mobileMenuOpen && (
<div className="md:hidden border border-white/10 bg-black/95 backdrop-blur-lg rounded-2xl mt-2 mx-2">
<div className="px-4 py-4 space-y-2">
<a
href="/docs"
className="block py-2 px-3 text-white/80 hover:text-white hover:bg-white/5 rounded-lg transition-colors"
onClick={() => setMobileMenuOpen(false)}
>
Docs
</a>
<a
href="/changelog"
className="block py-2 px-3 text-white/80 hover:text-white hover:bg-white/5 rounded-lg transition-colors"
onClick={() => setMobileMenuOpen(false)}
>
Changelog
</a>
<div className="border-t border-white/10 pt-2 mt-2 space-y-2">
<a
href="https://discord.gg/sandbox-agent"
className="flex items-center gap-2 py-2 px-3 text-white/80 hover:text-white hover:bg-white/5 rounded-lg transition-colors"
onClick={() => setMobileMenuOpen(false)}
aria-label="Discord"
>
<svg
className="w-5 h-5"
fill="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028c.462-.63.874-1.295 1.226-1.994a.076.076 0 0 0-.041-.106 13.107 13.107 0 0 1-1.872-.892.077.077 0 0 1-.008-.128 10.2 10.2 0 0 0 .372-.292.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127 12.299 12.299 0 0 1-1.873.892.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.839 19.839 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.956-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.955-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.946 2.418-2.157 2.418z" />
</svg>
<span>Discord</span>
</a>
<GitHubStars
repo="rivet-dev/sandbox-agent"
className="flex items-center gap-2 py-2 px-3 text-white/80 hover:text-white hover:bg-white/5 rounded-lg transition-colors"
onClick={() => setMobileMenuOpen(false)}
/>
</div>
</div>
</div>
)}
</div>
);
}

View file

@ -0,0 +1,224 @@
'use client';
import { motion } from 'framer-motion';
const frictions = [
{
number: '01',
title: 'Fragmented Agent Scaffolds',
description:
'Every coding agent (Claude Code, Amp, OpenCode) uses proprietary plumbing. Swapping agents means rewriting your entire infrastructure bridge.',
solution: 'Unified control plane for all agent engines.',
visual: (
<div className="mt-6 space-y-3">
<div className="flex items-center gap-3">
<div className="h-px flex-1 bg-gradient-to-r from-red-500/50 to-transparent" />
<span className="text-xs text-zinc-500">Claude Bridge</span>
</div>
<div className="flex items-center gap-3">
<div className="h-px flex-1 bg-gradient-to-r from-red-500/50 to-transparent" />
<span className="text-xs text-zinc-500">Amp Bridge</span>
</div>
<div className="flex items-center gap-3 mt-4">
<div className="h-px flex-1 bg-gradient-to-r from-green-500 to-green-500/20" />
<span className="text-xs text-green-400 font-medium">API</span>
</div>
<div className="mt-4 bg-black/60 rounded-lg border border-white/5 p-3 font-mono text-xs">
<div className="text-zinc-500">
<span className="text-zinc-600">01</span>{' '}
<span className="text-purple-400">agent</span>
<span className="text-zinc-400">.</span>
<span className="text-blue-400">spawn</span>
<span className="text-zinc-400">(</span>
<span className="text-green-400">"claude-code"</span>
<span className="text-zinc-400">)</span>
</div>
<div className="text-zinc-500">
<span className="text-zinc-600">02</span>{' '}
<span className="text-purple-400">agent</span>
<span className="text-zinc-400">.</span>
<span className="text-blue-400">spawn</span>
<span className="text-zinc-400">(</span>
<span className="text-green-400">"amp"</span>
<span className="text-zinc-400">)</span>
</div>
<div className="text-zinc-600 mt-2">// Exactly same methods</div>
</div>
</div>
),
accentColor: 'orange',
},
{
number: '02',
title: 'Deploy Anywhere',
description:
'Sandbox providers like E2B, Daytona, and Vercel each have unique strengths. Building integrations for each one from scratch is tedious.',
solution: 'One SDK, every provider. Deploy to any sandbox platform with a single config change.',
visual: (
<div className="mt-6">
<div className="flex items-center justify-between gap-2 text-xs">
<div className="flex-1 text-center py-2 px-3 rounded-lg bg-zinc-800/50 border border-white/5 text-zinc-400">
E2B
</div>
<div className="text-zinc-500">+</div>
<div className="flex-1 text-center py-2 px-3 rounded-lg bg-zinc-800/50 border border-white/5 text-zinc-400">
Daytona
</div>
<div className="text-zinc-500">+</div>
<div className="flex-1 text-center py-2 px-3 rounded-lg bg-zinc-800/50 border border-white/5 text-zinc-400">
Vercel
</div>
</div>
<div className="mt-4 bg-black/60 rounded-lg border border-white/5 p-3 font-mono text-xs">
<div className="text-zinc-500 mb-1"># Works with all providers</div>
<div>
<span className="text-green-400">SANDBOX_PROVIDER</span>
<span className="text-zinc-400">=</span>
<span className="text-amber-400">"daytona"</span>
</div>
</div>
</div>
),
accentColor: 'purple',
},
{
number: '03',
title: 'Transient State',
description:
'Transcripts and session data are usually lost when the sandbox dies. Debugging becomes impossible.',
solution: 'Standardized session JSON. Stream events to your own storage in real-time.',
visual: (
<div className="mt-6">
<div className="bg-black/60 rounded-lg border border-white/5 p-3 font-mono text-xs overflow-hidden">
<div className="text-zinc-500 mb-2"># Session persisted automatically</div>
<div className="space-y-1">
<div>
<span className="text-blue-400">"events"</span>
<span className="text-zinc-400">: [</span>
</div>
<div className="pl-4">
<span className="text-zinc-400">{'{ '}</span>
<span className="text-blue-400">"type"</span>
<span className="text-zinc-400">: </span>
<span className="text-green-400">"tool_call"</span>
<span className="text-zinc-400">{' }'}</span>
</div>
<div className="pl-4">
<span className="text-zinc-400">{'{ '}</span>
<span className="text-blue-400">"type"</span>
<span className="text-zinc-400">: </span>
<span className="text-green-400">"message"</span>
<span className="text-zinc-400">{' }'}</span>
</div>
<div className="text-zinc-400">]</div>
</div>
<div className="mt-3 flex items-center gap-2 text-zinc-500">
<span className="w-1.5 h-1.5 rounded-full bg-green-500 animate-pulse" />
<span>Streaming to Rivet Actors</span>
</div>
</div>
</div>
),
accentColor: 'blue',
},
];
const accentStyles = {
orange: {
gradient: 'from-orange-500/20',
border: 'border-orange-500/30',
glow: 'rgba(255,79,0,0.15)',
number: 'text-orange-500',
},
purple: {
gradient: 'from-purple-500/20',
border: 'border-purple-500/30',
glow: 'rgba(168,85,247,0.15)',
number: 'text-purple-500',
},
blue: {
gradient: 'from-blue-500/20',
border: 'border-blue-500/30',
glow: 'rgba(59,130,246,0.15)',
number: 'text-blue-500',
},
};
export function PainPoints() {
return (
<section className="relative overflow-hidden border-t border-white/5 py-32">
<div className="mx-auto max-w-7xl px-6">
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5 }}
className="mb-16"
>
<h2 className="mb-6 text-3xl font-medium tracking-tight text-white md:text-5xl">
Building coding agents is hard.
</h2>
<p className="max-w-2xl text-lg leading-relaxed text-zinc-400">
Integrating coding agents into your product means dealing with fragmented tooling,
provider-specific APIs, and ephemeral state.
</p>
</motion.div>
<div className="grid gap-6 md:grid-cols-3">
{frictions.map((friction, index) => {
const styles = accentStyles[friction.accentColor as keyof typeof accentStyles];
return (
<motion.div
key={friction.number}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: index * 0.1 }}
className="group relative flex flex-col overflow-hidden rounded-2xl border border-white/5 bg-zinc-900/30 p-6 backdrop-blur-sm transition-colors duration-500 hover:bg-zinc-900/50"
>
{/* Top shine */}
<div className="absolute left-0 right-0 top-0 z-10 h-[1px] bg-gradient-to-r from-transparent via-white/20 to-transparent" />
{/* Hover glow */}
<div
className="pointer-events-none absolute inset-0 opacity-0 transition-opacity duration-500 group-hover:opacity-100"
style={{
background: `radial-gradient(circle at top left, ${styles.glow} 0%, transparent 50%)`,
}}
/>
{/* Corner highlight */}
<div
className={`pointer-events-none absolute left-0 top-0 z-20 h-24 w-24 rounded-tl-2xl border-l border-t ${styles.border} opacity-0 transition-opacity duration-500 [mask-image:linear-gradient(135deg,black_0%,transparent_50%)] group-hover:opacity-100`}
/>
<div className="relative z-10">
{/* Friction number */}
<div className="mb-4 flex items-center gap-3">
<span className={`font-mono text-xs ${styles.number}`}>
Friction #{friction.number}
</span>
</div>
{/* Title */}
<h3 className="mb-3 text-xl font-medium text-white">{friction.title}</h3>
{/* Description */}
<p className="text-sm leading-relaxed text-zinc-500">{friction.description}</p>
{/* Solution */}
<div className="mt-4 border-t border-white/5 pt-4">
<p className="text-sm font-medium text-zinc-300">{friction.solution}</p>
</div>
{/* Visual */}
{friction.visual}
</div>
</motion.div>
);
})}
</div>
</div>
</section>
);
}

View file

@ -0,0 +1,53 @@
'use client';
import { Workflow, Database, Server } from 'lucide-react';
import { FeatureIcon } from './ui/FeatureIcon';
const problems = [
{
title: 'Universal Agent API',
desc: 'Coding agents like Claude Code and Amp have custom scaffolds. We provide a single API to swap between them effortlessly.',
icon: Workflow,
color: 'text-accent',
},
{
title: 'Universal Transcripts',
desc: 'Maintaining agent history is hard when the agent manages its own session. Our schema makes retrieval and storage simple.',
icon: Database,
color: 'text-purple-400',
},
{
title: 'Agents in Sandboxes',
desc: 'Run a simple curl command inside any sandbox to spawn an HTTP server that bridges the agent to your system.',
icon: Server,
color: 'text-green-400',
},
];
export function ProblemsSolved() {
return (
<section id="features" className="py-24 bg-zinc-950 border-y border-white/5">
<div className="max-w-7xl mx-auto px-6">
<div className="text-center mb-16">
<h2 className="text-3xl font-bold text-white mb-4">Why Sandbox Agent SDK?</h2>
<p className="text-zinc-400 max-w-xl mx-auto">
Solving the three fundamental friction points of agentic software development.
</p>
</div>
<div className="grid md:grid-cols-3 gap-8">
{problems.map((item, idx) => (
<div
key={idx}
className="group p-8 rounded-2xl bg-zinc-900/40 border border-white/5 hover:border-accent/30 transition-all duration-300"
>
<FeatureIcon icon={item.icon} color={item.color} />
<h3 className="text-xl font-bold text-white mb-3">{item.title}</h3>
<p className="text-zinc-400 text-sm leading-relaxed">{item.desc}</p>
</div>
))}
</div>
</div>
</section>
);
}

View file

@ -0,0 +1,11 @@
interface BadgeProps {
children: React.ReactNode;
}
export function Badge({ children }: BadgeProps) {
return (
<span className="inline-flex px-3 py-1 rounded-full bg-accent/10 border border-accent/20 text-accent text-xs font-mono font-medium">
{children}
</span>
);
}

View file

@ -0,0 +1,49 @@
import type { ReactNode } from 'react';
interface ButtonProps {
children: ReactNode;
variant?: 'primary' | 'secondary' | 'ghost';
size?: 'sm' | 'md' | 'lg';
href?: string;
onClick?: () => void;
className?: string;
}
export function Button({
children,
variant = 'primary',
size = 'md',
href,
onClick,
className = ''
}: ButtonProps) {
const baseStyles = 'inline-flex items-center justify-center font-bold rounded-lg transition-all';
const variants = {
primary: 'bg-white text-black hover:bg-zinc-200',
secondary: 'bg-zinc-900 border border-white/10 text-white hover:bg-zinc-800',
ghost: 'text-zinc-400 hover:text-white',
};
const sizes = {
sm: 'h-9 px-4 text-sm',
md: 'h-12 px-8 text-sm',
lg: 'h-14 px-10 text-base',
};
const classes = `${baseStyles} ${variants[variant]} ${sizes[size]} ${className}`;
if (href) {
return (
<a href={href} className={classes}>
{children}
</a>
);
}
return (
<button onClick={onClick} className={classes}>
{children}
</button>
);
}

View file

@ -0,0 +1,32 @@
'use client';
import { useState } from 'react';
import { Copy, CheckCircle2 } from 'lucide-react';
interface CopyButtonProps {
text: string;
}
export function CopyButton({ text }: CopyButtonProps) {
const [copied, setCopied] = useState(false);
const handleCopy = async () => {
await navigator.clipboard.writeText(text);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
};
return (
<button
onClick={handleCopy}
className="p-2 hover:bg-white/10 rounded-md transition-colors text-zinc-500 hover:text-white"
aria-label="Copy to clipboard"
>
{copied ? (
<CheckCircle2 className="w-4 h-4 text-green-400" />
) : (
<Copy className="w-4 h-4" />
)}
</button>
);
}

View file

@ -0,0 +1,23 @@
import type { LucideIcon } from 'lucide-react';
interface FeatureIconProps {
icon: LucideIcon;
color?: string;
bgColor?: string;
hoverBgColor?: string;
glowShadow?: string;
}
export function FeatureIcon({
icon: Icon,
color = 'text-accent',
bgColor = 'bg-accent/10',
hoverBgColor = 'group-hover:bg-accent/20',
glowShadow = 'group-hover:shadow-[0_0_15px_rgba(59,130,246,0.5)]'
}: FeatureIconProps) {
return (
<div className={`rounded ${bgColor} p-2 ${color} transition-all duration-500 ${hoverBgColor} ${glowShadow}`}>
<Icon className="h-4 w-4" />
</div>
);
}