mirror of
https://github.com/harivansh-afk/sandbox-agent.git
synced 2026-04-16 00:03:04 +00:00
chore(site): added site
This commit is contained in:
parent
29b159ca20
commit
d5b5efebb6
41 changed files with 1887 additions and 43 deletions
114
website/src/components/CTASection.tsx
Normal file
114
website/src/components/CTASection.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
191
website/src/components/FeatureGrid.tsx
Normal file
191
website/src/components/FeatureGrid.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
135
website/src/components/Footer.tsx
Normal file
135
website/src/components/Footer.tsx
Normal 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">
|
||||
© {new Date().getFullYear()} Rivet Gaming, Inc. All rights reserved.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
78
website/src/components/GitHubStars.tsx
Normal file
78
website/src/components/GitHubStars.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
161
website/src/components/Hero.tsx
Normal file
161
website/src/components/Hero.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
35
website/src/components/Integrations.tsx
Normal file
35
website/src/components/Integrations.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
144
website/src/components/Navigation.tsx
Normal file
144
website/src/components/Navigation.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
224
website/src/components/PainPoints.tsx
Normal file
224
website/src/components/PainPoints.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
53
website/src/components/ProblemsSolved.tsx
Normal file
53
website/src/components/ProblemsSolved.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
11
website/src/components/ui/Badge.tsx
Normal file
11
website/src/components/ui/Badge.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
49
website/src/components/ui/Button.tsx
Normal file
49
website/src/components/ui/Button.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
32
website/src/components/ui/CopyButton.tsx
Normal file
32
website/src/components/ui/CopyButton.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
23
website/src/components/ui/FeatureIcon.tsx
Normal file
23
website/src/components/ui/FeatureIcon.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue