chore(site): site updates and seo (#158)

This commit is contained in:
NicholasKissel 2026-02-11 08:36:10 +00:00
parent a33b1323ff
commit 70287ec471
17 changed files with 756 additions and 542 deletions

View file

@ -1,11 +1,14 @@
import { defineConfig } from 'astro/config'; import { defineConfig } from 'astro/config';
import react from '@astrojs/react'; import react from '@astrojs/react';
import tailwind from '@astrojs/tailwind'; import tailwind from '@astrojs/tailwind';
import sitemap from '@astrojs/sitemap';
export default defineConfig({ export default defineConfig({
site: 'https://sandbox-agent.dev',
output: 'static', output: 'static',
integrations: [ integrations: [
react(), react(),
tailwind() tailwind(),
sitemap()
] ]
}); });

View file

@ -13,6 +13,7 @@
}, },
"dependencies": { "dependencies": {
"@astrojs/react": "^4.2.0", "@astrojs/react": "^4.2.0",
"@astrojs/sitemap": "^3.2.0",
"@astrojs/tailwind": "^6.0.0", "@astrojs/tailwind": "^6.0.0",
"astro": "^5.1.0", "astro": "^5.1.0",
"framer-motion": "^12.0.0", "framer-motion": "^12.0.0",

View file

@ -0,0 +1,4 @@
User-agent: *
Allow: /
Sitemap: https://sandbox-agent.dev/sitemap-index.xml

View file

@ -1,112 +0,0 @@
'use client';
import { useState, useEffect } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { ArrowRight, Terminal, Check } from 'lucide-react';
const CTA_TITLES = [
'Run coding agents in sandboxes. Control them over HTTP.',
'A server inside your sandbox. An API for your app.',
'Claude Code, Codex, OpenCode, Amp, Pi — one HTTP API.',
'Your app connects remotely. The coding agent runs isolated.',
'Streaming events. Handling permissions. Managing sessions.',
'Install with curl. Connect over HTTP. Control any coding agent.',
'The bridge between your app and sandboxed coding agents.',
];
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 = 'curl -sSL https://sandboxagent.dev/install | sh';
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'>
<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'
>
A server that runs inside isolated environments. <br className='hidden md:block' />
Your app connects remotely to control any coding agent.
</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

@ -61,14 +61,14 @@ function FAQItem({ question, answer }: { question: string; answer: string }) {
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
return ( return (
<div className="border-b border-white/5"> <div className="border-t border-white/10 first:border-t-0">
<button <button
onClick={() => setIsOpen(!isOpen)} onClick={() => setIsOpen(!isOpen)}
className="flex w-full items-center justify-between py-5 text-left" className="group flex w-full items-center justify-between py-5 text-left"
> >
<span className="text-base font-medium text-white pr-4">{question}</span> <span className="text-base font-normal text-white pr-4 group-hover:text-zinc-300 transition-colors">{question}</span>
<ChevronDown <ChevronDown
className={`h-5 w-5 shrink-0 text-zinc-500 transition-transform duration-200 ${ className={`h-4 w-4 shrink-0 text-zinc-500 transition-transform duration-200 ${
isOpen ? 'rotate-180' : '' isOpen ? 'rotate-180' : ''
}`} }`}
/> />
@ -82,7 +82,7 @@ function FAQItem({ question, answer }: { question: string; answer: string }) {
transition={{ duration: 0.2 }} transition={{ duration: 0.2 }}
className="overflow-hidden" className="overflow-hidden"
> >
<p className="pb-5 text-sm leading-relaxed text-zinc-400" dangerouslySetInnerHTML={{ __html: answer }} /> <p className="pb-5 text-sm leading-relaxed text-zinc-500" dangerouslySetInnerHTML={{ __html: answer }} />
</motion.div> </motion.div>
)} )}
</AnimatePresence> </AnimatePresence>
@ -92,22 +92,40 @@ function FAQItem({ question, answer }: { question: string; answer: string }) {
export function FAQ() { export function FAQ() {
return ( return (
<section className="relative overflow-hidden border-t border-white/5 py-24"> <section className="border-t border-white/10 py-48">
<div className="mx-auto max-w-3xl px-6"> <div className="mx-auto max-w-7xl px-6">
<div className="mb-12 text-center"> <div className="mb-12 text-center">
<h2 className="mb-4 text-3xl font-medium tracking-tight text-white"> <motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5 }}
className="mb-2 text-2xl font-normal tracking-tight text-white md:text-4xl"
>
Frequently Asked Questions Frequently Asked Questions
</h2> </motion.h2>
<p className="text-zinc-400"> <motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: 0.1 }}
className="mx-auto max-w-xl text-base leading-relaxed text-zinc-500"
>
Common questions about running agents in sandboxes. Common questions about running agents in sandboxes.
</p> </motion.p>
</div> </div>
<div className="divide-y divide-white/5 rounded-2xl border border-white/5 bg-zinc-900/30 px-6"> <motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: 0.2 }}
className="mx-auto max-w-3xl"
>
{faqs.map((faq, index) => ( {faqs.map((faq, index) => (
<FAQItem key={index} question={faq.question} answer={faq.answer} /> <FAQItem key={index} question={faq.question} answer={faq.answer} />
))} ))}
</div> </motion.div>
</div> </div>
</section> </section>
); );

View file

@ -1,169 +1,120 @@
'use client'; 'use client';
import { motion } from 'framer-motion';
import { Workflow, Server, Database, Download, Globe, Plug } from 'lucide-react'; import { Workflow, Server, Database, Download, Globe, Plug } from 'lucide-react';
import { FeatureIcon } from './ui/FeatureIcon';
export function FeatureGrid() { export function FeatureGrid() {
return ( return (
<section id="features" className="relative overflow-hidden border-t border-white/5 py-32"> <section id="features" className="border-t border-white/10 py-48">
<div className="relative z-10 mx-auto max-w-7xl px-6"> <div className="mx-auto max-w-7xl px-6">
<div className="mb-16"> <div className="mb-12">
<h2 className="mb-4 text-3xl font-medium tracking-tight text-white md:text-5xl"> <motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5 }}
className="mb-2 text-2xl font-normal tracking-tight text-white md:text-4xl"
>
How it works. How it works.
</h2> </motion.h2>
<p className="text-lg leading-relaxed text-zinc-400"> <motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: 0.1 }}
className="max-w-xl text-base leading-relaxed text-zinc-500"
>
A server runs inside your sandbox. Your app connects over HTTP to control any coding agent. A server runs inside your sandbox. Your app connects over HTTP to control any coding agent.
</p> </motion.p>
</div> </div>
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4"> <motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5 }}
className="grid gap-4 md:grid-cols-2 lg:grid-cols-4"
>
{/* Universal Agent API - Span full width */} {/* Universal Agent API - Span full width */}
<div className="col-span-full 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"> <div className="group col-span-full flex flex-col gap-4 rounded-2xl border border-white/10 bg-white/[0.02] p-6 transition-colors hover:border-white/20">
{/* Top Shine Highlight */} <div className="flex items-center gap-3">
<div className="absolute left-0 right-0 top-0 z-10 h-[1px] bg-gradient-to-r from-transparent via-white/20 to-transparent" /> <div className="text-zinc-500 transition-colors group-hover:text-orange-400">
{/* Top Left Reflection/Glow */} <Workflow className="h-4 w-4" />
<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> </div>
<p className="text-zinc-400 leading-relaxed text-lg max-w-2xl"> <h4 className="text-base font-normal text-white">Universal Agent API</h4>
</div>
<p className="text-zinc-500 leading-relaxed text-base max-w-2xl">
Claude Code, Codex, OpenCode, Amp, and Pi each have different APIs. We provide a single, Claude Code, Codex, OpenCode, Amp, and Pi each have different APIs. We provide a single,
unified interface to control them all. unified interface to control them all.
</p> </p>
</div> </div>
</div>
{/* Streaming Events */}
<div className="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"> {/* Streaming Events */}
<FeatureIcon <div className="group flex flex-col gap-4 rounded-2xl border border-white/10 bg-white/[0.02] p-6 transition-colors hover:border-white/20">
icon={Server} <div className="flex items-center gap-3">
color="text-green-400" <div className="text-zinc-500 transition-colors group-hover:text-green-400">
bgColor="bg-green-500/10" <Server className="h-4 w-4" />
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">Streaming Events</h4>
</div> </div>
<p className="text-zinc-400 text-sm leading-relaxed"> <h4 className="text-base font-normal text-white">Streaming Events</h4>
</div>
<p className="text-zinc-500 text-sm leading-relaxed">
Real-time SSE stream of everything the agent does. Persist to your storage, replay sessions, audit everything. Real-time SSE stream of everything the agent does. Persist to your storage, replay sessions, audit everything.
</p> </p>
</div> </div>
{/* Handling Permissions */} {/* Universal Schema */}
<div className="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"> <div className="group flex flex-col gap-4 rounded-2xl border border-white/10 bg-white/[0.02] p-6 transition-colors hover:border-white/20">
{/* Top Shine Highlight */} <div className="flex items-center gap-3">
<div className="absolute left-0 right-0 top-0 z-10 h-[1px] bg-gradient-to-r from-transparent via-white/20 to-transparent" /> <div className="text-zinc-500 transition-colors group-hover:text-purple-400">
{/* Top Left Reflection/Glow */} <Database className="h-4 w-4" />
<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> </div>
<p className="text-zinc-400 text-sm leading-relaxed"> <h4 className="text-base font-normal text-white">Universal Schema</h4>
Standardized session schema that covers all features of all agents. Includes tool calls, permission requests, file edits, etc. Approve or deny tool executions remotely over HTTP. </div>
<p className="text-zinc-500 text-sm leading-relaxed">
Standardized session schema that covers all features of all agents. Includes tool calls, permission requests, file edits, etc.
</p> </p>
</div> </div>
{/* Runs Inside Any Sandbox */} {/* Runs Inside Any Sandbox */}
<div className="lg:col-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"> <div className="group lg:col-span-2 flex flex-col gap-4 rounded-2xl border border-white/10 bg-white/[0.02] p-6 transition-colors hover:border-white/20">
{/* Top Shine Highlight */} <div className="flex items-center gap-3">
<div className="absolute left-0 right-0 top-0 z-10 h-[1px] bg-gradient-to-r from-transparent via-white/20 to-transparent" /> <div className="text-zinc-500 transition-colors group-hover:text-blue-400">
{/* Top Left Reflection/Glow */} <Globe className="h-4 w-4" />
<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">Runs Inside Any Sandbox</h4>
</div> </div>
<p className="text-zinc-400 text-sm leading-relaxed"> <h4 className="text-base font-normal text-white">Runs Inside Any Sandbox</h4>
</div>
<p className="text-zinc-500 text-sm leading-relaxed">
Lightweight static binary. One curl command to install inside E2B, Daytona, Vercel Sandboxes, or Docker. Lightweight static binary. One curl command to install inside E2B, Daytona, Vercel Sandboxes, or Docker.
</p> </p>
</div> </div>
{/* Automatic Agent Installation */} {/* Session Management */}
<div className="lg:col-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"> <div className="group lg:col-span-2 flex flex-col gap-4 rounded-2xl border border-white/10 bg-white/[0.02] p-6 transition-colors hover:border-white/20">
{/* Top Shine Highlight */} <div className="flex items-center gap-3">
<div className="absolute left-0 right-0 top-0 z-10 h-[1px] bg-gradient-to-r from-transparent via-white/20 to-transparent" /> <div className="text-zinc-500 transition-colors group-hover:text-amber-400">
{/* Top Left Reflection/Glow */} <Download className="h-4 w-4" />
<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="relative z-10 mb-2 flex items-center gap-3">
<FeatureIcon
icon={Download}
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">Automatic Agent Installation</h4>
</div> </div>
<p className="text-zinc-400 text-sm leading-relaxed"> <h4 className="text-base font-normal text-white">Session Management</h4>
</div>
<p className="text-zinc-500 text-sm leading-relaxed">
Create sessions, send messages, persist transcripts. Full session lifecycle management over HTTP. Create sessions, send messages, persist transcripts. Full session lifecycle management over HTTP.
</p> </p>
</div> </div>
{/* OpenCode SDK & UI Support */} {/* OpenCode SDK & UI Support */}
<div className="lg:col-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"> <div className="group lg:col-span-2 flex flex-col gap-4 rounded-2xl border border-white/10 bg-white/[0.02] p-6 transition-colors hover:border-white/20">
{/* Top Shine Highlight */} <div className="flex items-center gap-3">
<div className="absolute left-0 right-0 top-0 z-10 h-[1px] bg-gradient-to-r from-transparent via-white/20 to-transparent" /> <div className="text-zinc-500 transition-colors group-hover:text-pink-400">
{/* Top Left Reflection/Glow */} <Plug className="h-4 w-4" />
<div className="pointer-events-none absolute inset-0 bg-[radial-gradient(circle_at_top_left,rgba(236,72,153,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-pink-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={Plug}
color="text-pink-400"
bgColor="bg-pink-500/10"
hoverBgColor="group-hover:bg-pink-500/20"
glowShadow="group-hover:shadow-[0_0_15px_rgba(236,72,153,0.5)]"
/>
<h4 className="text-sm font-medium uppercase tracking-wider text-white">OpenCode SDK & UI Support</h4>
<span className="rounded-full bg-pink-500/20 px-2 py-0.5 text-xs font-medium text-pink-300">Experimental</span>
</div> </div>
<p className="text-zinc-400 text-sm leading-relaxed"> <h4 className="text-base font-normal text-white">OpenCode Support</h4>
<span className="rounded-full border border-white/10 px-2 py-0.5 text-[10px] font-medium text-zinc-500 transition-colors group-hover:text-pink-400 group-hover:border-pink-400/30">Experimental</span>
</div>
<p className="text-zinc-500 text-sm leading-relaxed">
Connect OpenCode CLI, SDK, or web UI to control agents through familiar OpenCode tooling. Connect OpenCode CLI, SDK, or web UI to control agents through familiar OpenCode tooling.
</p> </p>
</div> </div>
</div> </motion.div>
</div> </div>
</section> </section>
); );

View file

@ -1,5 +1,7 @@
'use client'; 'use client';
import { motion } from 'framer-motion';
const footer = { const footer = {
products: [ products: [
{ name: 'Actors', href: 'https://rivet.dev/docs/actors' }, { name: 'Actors', href: 'https://rivet.dev/docs/actors' },
@ -48,16 +50,22 @@ const footer = {
export function Footer() { export function Footer() {
return ( return (
<footer className="border-t border-white/10 bg-zinc-950"> <footer className="border-t border-white/10 bg-black">
<div className="mx-auto max-w-7xl px-6 py-12 lg:py-16"> <div className="mx-auto max-w-6xl px-6 py-16 lg:py-20">
<div className="xl:grid xl:grid-cols-12 xl:gap-16"> <div className="xl:grid xl:grid-cols-12 xl:gap-16">
{/* Logo & Social */} {/* Logo & Social */}
<div className="space-y-6 xl:col-span-4"> <motion.div
<a href="https://rivet.dev"> initial={{ opacity: 0, y: 20 }}
<img src="/rivet-logo-text-white.svg" alt="Rivet" className="h-6 w-auto" /> whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5 }}
className="space-y-6 xl:col-span-4"
>
<a href="https://rivet.dev" className="inline-block">
<img src="/rivet-logo-text-white.svg" alt="Rivet" className="h-6 w-auto opacity-90 hover:opacity-100 transition-opacity" />
</a> </a>
<p className="text-sm leading-6 text-zinc-400"> <p className="text-sm leading-6 text-zinc-500">
Build and scale stateful workloads Infrastructure for software that thinks
</p> </p>
<div className="flex space-x-4"> <div className="flex space-x-4">
{footer.social.map((item) => ( {footer.social.map((item) => (
@ -73,64 +81,87 @@ export function Footer() {
</a> </a>
))} ))}
</div> </div>
</div> </motion.div>
{/* Links */} {/* Links */}
<div className="mt-12 grid grid-cols-2 gap-8 md:grid-cols-3 xl:col-span-8 xl:mt-0"> <div className="mt-12 grid grid-cols-2 gap-8 md:grid-cols-3 xl:col-span-8 xl:mt-0">
<div> <motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: 0.1 }}
>
<h3 className="text-sm font-semibold leading-6 text-white">Products</h3> <h3 className="text-sm font-semibold leading-6 text-white">Products</h3>
<ul role="list" className="mt-4 space-y-3"> <ul role="list" className="mt-4 space-y-3">
{footer.products.map((item) => ( {footer.products.map((item) => (
<li key={item.name}> <li key={item.name}>
<a <a
href={item.href} href={item.href}
className="text-sm leading-6 text-zinc-400 hover:text-white transition-colors" className="text-sm leading-6 text-zinc-500 hover:text-white transition-colors"
> >
{item.name} {item.name}
</a> </a>
</li> </li>
))} ))}
</ul> </ul>
</div> </motion.div>
<div>
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: 0.15 }}
>
<h3 className="text-sm font-semibold leading-6 text-white">Developers</h3> <h3 className="text-sm font-semibold leading-6 text-white">Developers</h3>
<ul role="list" className="mt-4 space-y-3"> <ul role="list" className="mt-4 space-y-3">
{footer.developers.map((item) => ( {footer.developers.map((item) => (
<li key={item.name}> <li key={item.name}>
<a <a
href={item.href} href={item.href}
className="text-sm leading-6 text-zinc-400 hover:text-white transition-colors" className="text-sm leading-6 text-zinc-500 hover:text-white transition-colors"
> >
{item.name} {item.name}
</a> </a>
</li> </li>
))} ))}
</ul> </ul>
</div> </motion.div>
<div>
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: 0.2 }}
>
<h3 className="text-sm font-semibold leading-6 text-white">Legal</h3> <h3 className="text-sm font-semibold leading-6 text-white">Legal</h3>
<ul role="list" className="mt-4 space-y-3"> <ul role="list" className="mt-4 space-y-3">
{footer.legal.map((item) => ( {footer.legal.map((item) => (
<li key={item.name}> <li key={item.name}>
<a <a
href={item.href} href={item.href}
className="text-sm leading-6 text-zinc-400 hover:text-white transition-colors" className="text-sm leading-6 text-zinc-500 hover:text-white transition-colors"
> >
{item.name} {item.name}
</a> </a>
</li> </li>
))} ))}
</ul> </ul>
</div> </motion.div>
</div> </div>
</div> </div>
{/* Bottom */} {/* Bottom */}
<div className="mt-12 border-t border-white/10 pt-8"> <motion.div
<p className="text-xs text-zinc-500 text-center"> initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: 0.3 }}
className="mt-12 border-t border-white/10 pt-8"
>
<p className="text-xs text-zinc-600 text-center">
&copy; {new Date().getFullYear()} Rivet Gaming, Inc. All rights reserved. &copy; {new Date().getFullYear()} Rivet Gaming, Inc. All rights reserved.
</p> </p>
</div> </motion.div>
</div> </div>
</footer> </footer>
); );

View file

@ -1,5 +1,6 @@
'use client'; 'use client';
import { motion } from 'framer-motion';
import { Code, Server, GitBranch } from 'lucide-react'; import { Code, Server, GitBranch } from 'lucide-react';
import { CopyButton } from './ui/CopyButton'; import { CopyButton } from './ui/CopyButton';
@ -94,44 +95,55 @@ cargo run -p sandbox-agent --release`;
export function GetStarted() { export function GetStarted() {
return ( return (
<section id="get-started" className="relative overflow-hidden border-t border-white/5 py-32"> <section id="get-started" className="border-t border-white/10 py-48">
<div className="relative z-10 mx-auto max-w-7xl px-6"> <div className="mx-auto max-w-7xl px-6">
<div className="mb-16 text-center"> <div className="mb-12">
<h2 className="mb-4 text-3xl font-medium tracking-tight text-white md:text-5xl"> <motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5 }}
className="mb-2 text-2xl font-normal tracking-tight text-white md:text-4xl"
>
Get Started Get Started
</h2> </motion.h2>
<p className="text-lg text-zinc-400"> <motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: 0.1 }}
className="max-w-xl text-base leading-relaxed text-zinc-500"
>
Choose the installation method that works best for your use case. Choose the installation method that works best for your use case.
</p> </motion.p>
<p className="mt-4 text-sm text-zinc-500">
Quick OpenCode attach: <span className="font-mono text-white">npx @sandbox-agent/gigacode</span>
</p>
</div> </div>
<div className="grid grid-cols-1 gap-6 md:grid-cols-3"> <motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5 }}
className="grid grid-cols-1 gap-4 md:grid-cols-3"
>
{/* Option 1: SDK */} {/* Option 1: SDK */}
<div 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"> <div className="group flex flex-col rounded-2xl border border-white/10 bg-white/[0.02] p-6 transition-colors hover:border-white/20">
<div className="absolute left-0 right-0 top-0 z-10 h-[1px] bg-gradient-to-r from-transparent via-white/20 to-transparent" /> <div className="mb-4 flex items-center gap-3">
<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" /> <div className="text-zinc-500">
<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" /> <Code className="h-4 w-4" />
<div className="relative z-10 mb-4 flex items-center gap-3">
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-blue-500/10 text-blue-400 transition-all duration-300 group-hover:bg-blue-500/20 group-hover:shadow-[0_0_15px_rgba(59,130,246,0.5)]">
<Code className="h-5 w-5" />
</div> </div>
<div> <div>
<h3 className="text-lg font-semibold text-white">TypeScript SDK</h3> <h3 className="text-base font-normal text-white">TypeScript SDK</h3>
<p className="text-xs text-zinc-500">Embed in your application</p> <p className="text-xs text-zinc-500">Embed in your application</p>
</div> </div>
</div> </div>
<p className="relative z-10 mb-4 text-sm leading-relaxed text-zinc-400 min-h-[4.5rem]"> <p className="mb-4 text-sm leading-relaxed text-zinc-500">
Import the TypeScript SDK directly into your Node or browser application. Full type safety and streaming support. Import the TypeScript SDK directly into your Node or browser application. Full type safety and streaming support.
</p> </p>
<div className="relative z-10 flex-1 flex flex-col"> <div className="flex-1 flex flex-col">
<div className="overflow-hidden rounded-lg border border-white/5 bg-black/50 flex-1 flex flex-col"> <div className="overflow-hidden rounded-lg border border-white/10 bg-black/50 flex-1 flex flex-col">
<div className="flex items-center justify-between border-b border-white/5 bg-white/5 px-3 py-2"> <div className="flex items-center justify-between border-b border-white/10 bg-white/5 px-3 py-2">
<span className="text-[10px] font-medium text-zinc-500">example.ts</span> <span className="text-[10px] font-medium text-zinc-500">example.ts</span>
<CopyButton text={sdkCodeRaw} /> <CopyButton text={sdkCodeRaw} />
</div> </div>
@ -140,29 +152,25 @@ export function GetStarted() {
</div> </div>
</div> </div>
{/* Option 2: Sandbox */} {/* Option 2: HTTP API */}
<div 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"> <div className="group flex flex-col rounded-2xl border border-white/10 bg-white/[0.02] p-6 transition-colors hover:border-white/20">
<div className="absolute left-0 right-0 top-0 z-10 h-[1px] bg-gradient-to-r from-transparent via-white/20 to-transparent" /> <div className="mb-4 flex items-center gap-3">
<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" /> <div className="text-zinc-500">
<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" /> <Server className="h-4 w-4" />
<div className="relative z-10 mb-4 flex items-center gap-3">
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-green-500/10 text-green-400 transition-all duration-300 group-hover:bg-green-500/20 group-hover:shadow-[0_0_15px_rgba(34,197,94,0.5)]">
<Server className="h-5 w-5" />
</div> </div>
<div> <div>
<h3 className="text-lg font-semibold text-white">HTTP API</h3> <h3 className="text-base font-normal text-white">HTTP API</h3>
<p className="text-xs text-zinc-500">Run as a server</p> <p className="text-xs text-zinc-500">Run as a server</p>
</div> </div>
</div> </div>
<p className="relative z-10 mb-4 text-sm leading-relaxed text-zinc-400 min-h-[4.5rem]"> <p className="mb-4 text-sm leading-relaxed text-zinc-500">
Run as an HTTP server and connect from any language. Deploy to E2B, Daytona, Vercel, or your own infrastructure. Run as an HTTP server and connect from any language. Deploy to E2B, Daytona, Vercel, or your own infrastructure.
</p> </p>
<div className="relative z-10 flex-1 flex flex-col"> <div className="flex-1 flex flex-col">
<div className="overflow-hidden rounded-lg border border-white/5 bg-black/50 flex-1 flex flex-col"> <div className="overflow-hidden rounded-lg border border-white/10 bg-black/50 flex-1 flex flex-col">
<div className="flex items-center justify-between border-b border-white/5 bg-white/5 px-3 py-2"> <div className="flex items-center justify-between border-b border-white/10 bg-white/5 px-3 py-2">
<span className="text-[10px] font-medium text-zinc-500">terminal</span> <span className="text-[10px] font-medium text-zinc-500">terminal</span>
<CopyButton text={sandboxCommand} /> <CopyButton text={sandboxCommand} />
</div> </div>
@ -181,29 +189,25 @@ export function GetStarted() {
</div> </div>
</div> </div>
{/* Option 3: Build from Source */} {/* Option 3: Open Source */}
<div 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"> <div className="group flex flex-col rounded-2xl border border-white/10 bg-white/[0.02] p-6 transition-colors hover:border-white/20">
<div className="absolute left-0 right-0 top-0 z-10 h-[1px] bg-gradient-to-r from-transparent via-white/20 to-transparent" /> <div className="mb-4 flex items-center gap-3">
<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" /> <div className="text-zinc-500">
<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" /> <GitBranch className="h-4 w-4" />
<div className="relative z-10 mb-4 flex items-center gap-3">
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-amber-500/10 text-amber-400 transition-all duration-300 group-hover:bg-amber-500/20 group-hover:shadow-[0_0_15px_rgba(245,158,11,0.5)]">
<GitBranch className="h-5 w-5" />
</div> </div>
<div> <div>
<h3 className="text-lg font-semibold text-white">Open Source</h3> <h3 className="text-base font-normal text-white">Open Source</h3>
<p className="text-xs text-zinc-500">Full control</p> <p className="text-xs text-zinc-500">Full control</p>
</div> </div>
</div> </div>
<p className="relative z-10 mb-4 text-sm leading-relaxed text-zinc-400 min-h-[4.5rem]"> <p className="mb-4 text-sm leading-relaxed text-zinc-500">
Clone the repo and build with Cargo. Customize, contribute, or embed directly in your Rust project. Clone the repo and build with Cargo. Customize, contribute, or embed directly in your Rust project.
</p> </p>
<div className="relative z-10 flex-1 flex flex-col"> <div className="flex-1 flex flex-col">
<div className="overflow-hidden rounded-lg border border-white/5 bg-black/50 flex-1 flex flex-col"> <div className="overflow-hidden rounded-lg border border-white/10 bg-black/50 flex-1 flex flex-col">
<div className="flex items-center justify-between border-b border-white/5 bg-white/5 px-3 py-2"> <div className="flex items-center justify-between border-b border-white/10 bg-white/5 px-3 py-2">
<span className="text-[10px] font-medium text-zinc-500">terminal</span> <span className="text-[10px] font-medium text-zinc-500">terminal</span>
<CopyButton text={sourceCommands} /> <CopyButton text={sourceCommands} />
</div> </div>
@ -226,7 +230,7 @@ export function GetStarted() {
</div> </div>
</div> </div>
</div> </div>
</div> </motion.div>
</div> </div>
</section> </section>
); );

View file

@ -1,14 +1,15 @@
'use client'; 'use client';
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { motion } from 'framer-motion';
import { Terminal, Check, ArrowRight } from 'lucide-react'; import { Terminal, Check, ArrowRight } from 'lucide-react';
const ADAPTERS = [ const ADAPTERS = [
{ label: 'Claude Code', color: '#D97757', x: 35, y: 30, logo: '/logos/claude.svg' }, { label: 'Claude Code', color: '#D97757', x: 20, y: 70, logo: '/logos/claude.svg' },
{ label: 'Codex', color: '#10A37F', x: 185, y: 30, logo: 'openai' }, { label: 'Codex', color: '#10A37F', x: 132, y: 70, logo: 'openai' },
{ label: 'Amp', color: '#F59E0B', x: 35, y: 115, logo: '/logos/amp.svg' }, { label: 'Pi', color: '#06B6D4', x: 244, y: 70, logo: 'pi' },
{ label: 'OpenCode', color: '#8B5CF6', x: 185, y: 115, logo: 'opencode' }, { label: 'Amp', color: '#F59E0B', x: 76, y: 155, logo: '/logos/amp.svg' },
{ label: 'Pi', color: '#38BDF8', x: 110, y: 200, logo: '/logos/pi.svg' }, { label: 'OpenCode', color: '#8B5CF6', x: 188, y: 155, logo: 'opencode' },
]; ];
function UniversalAPIDiagram() { function UniversalAPIDiagram() {
@ -22,23 +23,16 @@ function UniversalAPIDiagram() {
}, []); }, []);
return ( return (
<div className="relative w-full aspect-[16/9] bg-[#050505] rounded-xl border border-white/10 overflow-hidden flex items-center justify-center"> <div className="relative w-full aspect-[16/9] bg-[#050505] rounded-2xl border border-white/10 overflow-hidden flex items-center justify-center shadow-2xl">
{/* Background Grid */} {/* Background Dots - color changes with active adapter */}
<div <div
className="absolute inset-0 opacity-[0.03] pointer-events-none" className="absolute inset-0 opacity-[0.15] pointer-events-none transition-all duration-1000"
style={{ style={{
backgroundImage: backgroundImage: `radial-gradient(circle, ${ADAPTERS[activeIndex].color} 1px, transparent 1px)`,
'linear-gradient(#fff 1px, transparent 1px), linear-gradient(90deg, #fff 1px, transparent 1px)', backgroundSize: '24px 24px',
backgroundSize: '40px 40px',
}} }}
/> />
{/* Dynamic Background Glow */}
<div
className="absolute top-1/2 right-1/4 -translate-y-1/2 w-64 h-64 blur-[100px] rounded-full transition-colors duration-1000 opacity-20"
style={{ backgroundColor: ADAPTERS[activeIndex].color }}
/>
<svg viewBox="0 0 800 450" className="w-full h-full relative z-10"> <svg viewBox="0 0 800 450" className="w-full h-full relative z-10">
<defs> <defs>
<filter id="glow" x="-20%" y="-20%" width="140%" height="140%"> <filter id="glow" x="-20%" y="-20%" width="140%" height="140%">
@ -50,13 +44,14 @@ function UniversalAPIDiagram() {
</filter> </filter>
</defs> </defs>
{/* YOUR APP NODE */} {/* YOUR APP NODE - Glass dark effect with backdrop blur */}
<g transform="translate(60, 175)"> <foreignObject x="60" y="175" width="180" height="100">
<rect width="180" height="100" rx="16" fill="#0A0A0A" stroke="#333" strokeWidth="2" /> <div
<text x="90" y="55" fill="#FFFFFF" textAnchor="middle" fontSize="20" fontWeight="700"> className="w-full h-full rounded-2xl border border-white/10 bg-black/40 backdrop-blur-md flex items-center justify-center"
Your App >
</text> <span className="text-white text-xl font-bold">Your App</span>
</g> </div>
</foreignObject>
{/* HTTP/SSE LINE */} {/* HTTP/SSE LINE */}
<g> <g>
@ -74,21 +69,21 @@ function UniversalAPIDiagram() {
</text> </text>
</g> </g>
{/* SANDBOX BOUNDARY */} {/* SANDBOX BOUNDARY - Glass dark effect with backdrop blur */}
<g transform="translate(360, 45)"> <foreignObject x="360" y="45" width="410" height="360">
<rect width="380" height="360" rx="24" fill="#080808" stroke="#333" strokeWidth="1.5" /> <div className="w-full h-full rounded-3xl border border-white/10 bg-black/40 backdrop-blur-md">
<rect width="380" height="45" rx="12" fill="rgba(255,255,255,0.02)" /> <div className="text-white text-sm font-extrabold tracking-[0.2em] text-center pt-4">
<text x="190" y="28" fill="#FFFFFF" textAnchor="middle" fontSize="14" fontWeight="800" letterSpacing="0.2em">
SANDBOX SANDBOX
</text> </div>
</div>
</foreignObject>
{/* SANDBOX AGENT SDK */} {/* SANDBOX AGENT SDK */}
<g transform="translate(25, 65)"> <g transform="translate(385, 110)">
<rect width="330" height="270" rx="20" fill="#0D0D0F" stroke="#3B82F6" strokeWidth="2" /> <rect width="360" height="270" rx="20" fill="rgba(0,0,0,0.4)" stroke="rgba(255,255,255,0.2)" strokeWidth="1" />
<text x="165" y="35" fill="#FFFFFF" textAnchor="middle" fontSize="18" fontWeight="800"> <text x="180" y="35" fill="#FFFFFF" textAnchor="middle" fontSize="18" fontWeight="800">
Sandbox Agent Server Sandbox Agent Server
</text> </text>
<line x1="40" y1="50" x2="290" y2="50" stroke="#333" strokeWidth="1" />
{/* PROVIDER ADAPTERS */} {/* PROVIDER ADAPTERS */}
{ADAPTERS.map((p, i) => { {ADAPTERS.map((p, i) => {
@ -96,33 +91,38 @@ function UniversalAPIDiagram() {
return ( return (
<g key={i} transform={`translate(${p.x}, ${p.y})`}> <g key={i} transform={`translate(${p.x}, ${p.y})`}>
<rect <rect
width="110" width="95"
height="65" height="58"
rx="12" rx="10"
fill={isActive ? '#1A1A1E' : '#111'} fill={isActive ? '#1A1A1E' : '#111'}
stroke={isActive ? p.color : '#333'} stroke={isActive ? p.color : '#333'}
strokeWidth={isActive ? 2 : 1.5} strokeWidth={isActive ? 2 : 1.5}
/> />
<g opacity={isActive ? 1 : 0.4}> <g opacity={isActive ? 1 : 0.4}>
{p.logo === 'openai' ? ( {p.logo === 'openai' ? (
<svg x="43" y="10" width="24" height="24" viewBox="0 0 24 24" fill="none"> <svg x="36.75" y="8" width="22" height="22" viewBox="0 0 24 24" fill="none">
<path d="M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.0651 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 12.6081a4.4755 4.4755 0 0 1-2.8764-1.0408l.1419-.0804 4.7783-2.7582a.7948.7948 0 0 0 .3927-.6813v-6.7369l2.02 1.1686a.071.071 0 0 1 .038.052v5.5826a4.504 4.504 0 0 1-4.4945 4.4944zm-9.6607-4.1254a4.4708 4.4708 0 0 1-.5346-3.0137l.142.0852 4.783 2.7582a.7712.7712 0 0 0 .7806 0l5.8428-3.3685v2.3324a.0804.0804 0 0 1-.0332.0615L9.74 19.9502a4.4992 4.4992 0 0 1-6.1408-1.6464zM2.3408 7.8956a4.485 4.485 0 0 1 2.3655-1.9728V11.6a.7664.7664 0 0 0 .3879.6765l5.8144 3.3543-2.0201 1.1685a.0757.0757 0 0 1-.071 0l-4.8303-2.7865A4.504 4.504 0 0 1 2.3408 7.872zm16.5963 3.8558L13.1038 8.364 15.1192 7.2a.0757.0757 0 0 1 .071 0l4.8303 2.7913a4.4944 4.4944 0 0 1-.6765 8.1042v-5.6772a.79.79 0 0 0-.407-.667zm2.0107-3.0231l-.142-.0852-4.7735-2.7818a.7759.7759 0 0 0-.7854 0L9.409 9.2297V6.8974a.0662.0662 0 0 1 .0284-.0615l4.8303-2.7866a4.4992 4.4992 0 0 1 6.6802 4.66zM8.3065 12.863l-2.02-1.1638a.0804.0804 0 0 1-.038-.0567V6.0742a4.4992 4.4992 0 0 1 7.3757-3.4537l-.142.0805L8.704 5.459a.7948.7948 0 0 0-.3927.6813zm1.0976-2.3654l2.602-1.4998 2.6069 1.4998v2.9994l-2.5974 1.4997-2.6067-1.4997Z" fill="#ffffff" /> <path d="M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.0651 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 12.6081a4.4755 4.4755 0 0 1-2.8764-1.0408l.1419-.0804 4.7783-2.7582a.7948.7948 0 0 0 .3927-.6813v-6.7369l2.02 1.1686a.071.071 0 0 1 .038.052v5.5826a4.504 4.504 0 0 1-4.4945 4.4944zm-9.6607-4.1254a4.4708 4.4708 0 0 1-.5346-3.0137l.142.0852 4.783 2.7582a.7712.7712 0 0 0 .7806 0l5.8428-3.3685v2.3324a.0804.0804 0 0 1-.0332.0615L9.74 19.9502a4.4992 4.4992 0 0 1-6.1408-1.6464zM2.3408 7.8956a4.485 4.485 0 0 1 2.3655-1.9728V11.6a.7664.7664 0 0 0 .3879.6765l5.8144 3.3543-2.0201 1.1685a.0757.0757 0 0 1-.071 0l-4.8303-2.7865A4.504 4.504 0 0 1 2.3408 7.872zm16.5963 3.8558L13.1038 8.364 15.1192 7.2a.0757.0757 0 0 1 .071 0l4.8303 2.7913a4.4944 4.4944 0 0 1-.6765 8.1042v-5.6772a.79.79 0 0 0-.407-.667zm2.0107-3.0231l-.142-.0852-4.7735-2.7818a.7759.7759 0 0 0-.7854 0L9.409 9.2297V6.8974a.0662.0662 0 0 1 .0284-.0615l4.8303-2.7866a4.4992 4.4992 0 0 1 6.6802 4.66zM8.3065 12.863l-2.02-1.1638a.0804.0804 0 0 1-.038-.0567V6.0742a4.4992 4.4992 0 0 1 7.3757-3.4537l-.142.0805L8.704 5.459a.7948.7948 0 0 0-.3927.6813zm1.0976-2.3654l2.602-1.4998 2.6069 1.4998v2.9994l-2.5974 1.4997-2.6067-1.4997Z" fill="#ffffff" />
</svg> </svg>
) : p.logo === 'opencode' ? ( ) : p.logo === 'opencode' ? (
<svg x="43" y="10" width="19" height="24" viewBox="0 0 32 40" fill="none"> <svg x="38.5" y="8" width="17" height="22" viewBox="0 0 32 40" fill="none">
<path d="M24 32H8V16H24V32Z" fill="#4B4646"/> <path d="M24 32H8V16H24V32Z" fill="#4B4646"/>
<path d="M24 8H8V32H24V8ZM32 40H0V0H32V40Z" fill="#F1ECEC"/> <path d="M24 8H8V32H24V8ZM32 40H0V0H32V40Z" fill="#F1ECEC"/>
</svg> </svg>
) : p.logo === 'pi' ? (
<svg x="36.75" y="8" width="22" height="22" viewBox="0 0 800 800" fill="none">
<path fill="#fff" fillRule="evenodd" d="M165.29 165.29H517.36V400H400V517.36H282.65V634.72H165.29ZM282.65 282.65V400H400V282.65Z"/>
<path fill="#fff" d="M517.36 400H634.72V634.72H517.36Z"/>
</svg>
) : ( ) : (
<image href={p.logo} x="43" y="10" width="24" height="24" filter="url(#invert-white)" /> <image href={p.logo} x="36.75" y="8" width="22" height="22" filter="url(#invert-white)" />
)} )}
</g> </g>
<text <text
x="55" x="47.5"
y="52" y="46"
fill="#FFFFFF" fill="#FFFFFF"
textAnchor="middle" textAnchor="middle"
fontSize="11" fontSize="10"
fontWeight="600" fontWeight="600"
opacity={isActive ? 1 : 0.4} opacity={isActive ? 1 : 0.4}
> >
@ -134,11 +134,11 @@ function UniversalAPIDiagram() {
{/* Active Agent Label */} {/* Active Agent Label */}
<text <text
x="165" x="180"
y="250" y="250"
fill={ADAPTERS[activeIndex].color} fill={ADAPTERS[activeIndex].color}
textAnchor="middle" textAnchor="middle"
fontSize="10" fontSize="12"
fontWeight="800" fontWeight="800"
fontFamily="monospace" fontFamily="monospace"
letterSpacing="0.1em" letterSpacing="0.1em"
@ -146,7 +146,6 @@ function UniversalAPIDiagram() {
CONNECTED TO {ADAPTERS[activeIndex].label.toUpperCase()} CONNECTED TO {ADAPTERS[activeIndex].label.toUpperCase()}
</text> </text>
</g> </g>
</g>
</svg> </svg>
</div> </div>
); );
@ -167,47 +166,104 @@ const CopyInstallButton = () => {
}; };
return ( return (
<div className="relative group w-full sm:w-auto">
<button <button
onClick={handleCopy} 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' className="w-full sm:w-auto inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md border border-white/10 px-4 py-2 text-sm text-zinc-300 transition-colors hover:border-white/20 hover:text-white font-mono"
> >
{copied ? <Check className='h-4 w-4' /> : <Terminal className='h-4 w-4' />} {copied ? <Check className="h-4 w-4 text-green-400" /> : <Terminal className="h-4 w-4" />}
{installCommand} {installCommand}
</button> </button>
<div className="absolute left-1/2 -translate-x-1/2 top-full mt-3 opacity-0 translate-y-2 group-hover:opacity-100 group-hover:translate-y-0 transition-all duration-200 ease-out text-xs text-zinc-500 whitespace-nowrap pointer-events-none font-mono">
Give this to your coding agent
</div>
</div>
); );
}; };
export function Hero() { export function Hero() {
return ( const [scrollOpacity, setScrollOpacity] = useState(1);
<section className="relative pt-44 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-3xl font-medium leading-[1.1] tracking-tight text-white sm:text-4xl md:text-5xl lg:text-6xl">
Run Coding Agents in Sandboxes.<br />
<span className="text-zinc-400">Control Them Over HTTP.</span>
</h1>
<p className="mt-6 text-lg text-zinc-500 leading-relaxed max-w-xl mx-auto lg:mx-0">
The Sandbox Agent SDK is a server that runs inside your sandbox. Your app connects remotely to control Claude Code, Codex, OpenCode, Amp, or Pi streaming events, handling permissions, managing sessions.
</p>
<div className="mt-10 flex flex-col items-center gap-4 sm:flex-row sm:justify-center lg:justify-start"> useEffect(() => {
const handleScroll = () => {
const scrollY = window.scrollY;
const windowHeight = window.innerHeight;
const isMobile = window.innerWidth < 1024;
const fadeStart = windowHeight * (isMobile ? 0.3 : 0.15);
const fadeEnd = windowHeight * (isMobile ? 0.7 : 0.5);
const opacity = 1 - Math.min(1, Math.max(0, (scrollY - fadeStart) / (fadeEnd - fadeStart)));
setScrollOpacity(opacity);
};
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, []);
return (
<section className="relative flex min-h-screen flex-col overflow-hidden">
{/* Background gradient */}
<div className="absolute inset-0 bg-gradient-to-b from-zinc-900/20 via-transparent to-transparent pointer-events-none" />
{/* Main content */}
<div
className="flex flex-1 flex-col justify-start pt-32 lg:justify-center lg:pt-0 lg:pb-20 px-6"
style={{ opacity: scrollOpacity, filter: `blur(${(1 - scrollOpacity) * 8}px)` }}
>
<div className="mx-auto w-full max-w-7xl">
<div className="flex flex-col gap-12 lg:flex-row lg:items-center lg:justify-between lg:gap-16 xl:gap-24">
{/* Left side - Text content */}
<div className="max-w-xl lg:max-w-2xl">
<motion.h1
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5 }}
className="mb-6 text-3xl font-medium leading-[1.1] tracking-tight text-white md:text-5xl"
>
Run Coding Agents in Sandboxes.
<br />
<span className="text-zinc-400">Control Them Over HTTP.</span>
</motion.h1>
<motion.p
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: 0.1 }}
className="mb-8 text-lg text-zinc-500 leading-relaxed"
>
The Sandbox Agent SDK is a server that runs inside your sandbox. Your app connects remotely to control Claude Code, Codex, OpenCode, Amp, or Pi streaming events, handling permissions, managing sessions.
</motion.p>
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: 0.2 }}
className="flex flex-col gap-3 sm:flex-row"
>
<a <a
href="/docs" 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' className="selection-dark inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md bg-white px-5 py-2.5 text-sm font-medium text-black transition-colors hover:bg-zinc-200"
> >
Read the Docs Read the Docs
<ArrowRight className='h-4 w-4' /> <ArrowRight className="h-4 w-4" />
</a> </a>
<CopyInstallButton /> <CopyInstallButton />
</motion.div>
</div>
{/* Right side - Diagram */}
<motion.div
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.8, delay: 0.3 }}
className="flex-1 w-full max-w-2xl"
>
<UniversalAPIDiagram />
</motion.div>
</div>
</div> </div>
</div> </div>
<div className="flex-1 w-full max-w-2xl">
<UniversalAPIDiagram />
</div>
</div>
</div>
</section> </section>
); );
} }

View file

@ -1,23 +1,45 @@
'use client'; 'use client';
import { motion } from 'framer-motion';
export function Inspector() { export function Inspector() {
return ( return (
<section className="relative overflow-hidden border-t border-white/5 py-24"> <section className="border-t border-white/10 py-48">
<div className="mx-auto max-w-4xl px-6 text-center"> <div className="mx-auto max-w-7xl px-6">
<h2 className="mb-4 text-3xl font-medium tracking-tight text-white md:text-5xl"> <div className="mb-12 text-center">
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5 }}
className="mb-2 text-2xl font-normal tracking-tight text-white md:text-4xl"
>
Built-in Debugger Built-in Debugger
</h2> </motion.h2>
<p className="mb-12 text-lg text-zinc-400"> <motion.p
Inspect sessions, view event payloads, and troubleshoot without writing code. initial={{ opacity: 0, y: 20 }}
</p> whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: 0.1 }}
className="mx-auto max-w-xl text-base leading-relaxed text-zinc-500"
>
Inspect sessions, view event payloads, and troubleshoot without writing&nbsp;code.
</motion.p>
</div>
<div className="overflow-hidden rounded-2xl border border-white/10 shadow-2xl"> <motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: 0.2 }}
className="overflow-hidden rounded-2xl border border-white/10"
>
<img <img
src="/images/inspector.png" src="/images/inspector.png"
alt="Sandbox Agent Inspector" alt="Sandbox Agent Inspector"
className="w-full" className="w-full"
/> />
</div> </motion.div>
</div> </div>
</section> </section>
); );

View file

@ -6,11 +6,12 @@ import { GitHubStars } from './GitHubStars';
function NavItem({ href, children }: { href: string; children: React.ReactNode }) { function NavItem({ href, children }: { href: string; children: React.ReactNode }) {
return ( return (
<div className="px-2.5 py-2 opacity-60 hover:opacity-100 transition-all duration-200"> <a
<a href={href} className="text-white text-sm"> href={href}
className="px-3 py-2 text-sm font-medium text-zinc-400 transition-colors duration-200 hover:text-white"
>
{children} {children}
</a> </a>
</div>
); );
} }
@ -34,15 +35,17 @@ export function Navigation() {
isScrolled ? "before:border-white/10" : "before:border-transparent" isScrolled ? "before:border-white/10" : "before:border-transparent"
}`} }`}
> >
{/* Background with blur */}
<div <div
className={`absolute inset-0 -z-[1] hidden overflow-hidden rounded-2xl transition-all duration-300 ease-in-out md:block ${ className={`absolute inset-0 -z-[1] hidden overflow-hidden rounded-2xl transition-all duration-300 ease-in-out md:block ${
isScrolled isScrolled
? "bg-black/80 backdrop-blur-lg" ? "bg-black/80 backdrop-blur-lg"
: "bg-black backdrop-blur-none" : "bg-transparent backdrop-blur-none"
}`} }`}
/> />
<header <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 ${ className={`bg-black/60 border-b-transparent sticky top-0 z-10 flex flex-col items-center border-b backdrop-blur-md pt-2 pb-2 md:static md:bg-transparent md:rounded-2xl md:max-w-[1200px] md:border-transparent md:backdrop-blur-none transition-all hover:opacity-100 ${
isScrolled ? "opacity-100" : "opacity-80" isScrolled ? "opacity-100" : "opacity-80"
}`} }`}
> >
@ -53,24 +56,24 @@ export function Navigation() {
<a href="https://rivet.dev" className="flex items-center"> <a href="https://rivet.dev" className="flex items-center">
<img src="/rivet-icon.svg" alt="Rivet" className="size-8" /> <img src="/rivet-icon.svg" alt="Rivet" className="size-8" />
</a> </a>
<span className="text-white/30">|</span> <span className="text-white/20">|</span>
<a href="/" className="flex items-center"> <a href="/" className="flex items-center">
<img src="/logos/sandboxagent.svg" alt="Sandbox Agent SDK" className="h-6 w-auto" /> <img src="/logos/sandboxagent.svg" alt="Sandbox Agent SDK" className="h-6 w-auto" />
</a> </a>
</div> </div>
{/* Desktop Nav */} {/* Desktop Nav */}
<div className="hidden md:flex items-center"> <div className="hidden md:flex items-center ml-2">
<NavItem href="/docs">Docs</NavItem> <NavItem href="/docs">Docs</NavItem>
<NavItem href="https://github.com/rivet-dev/sandbox-agent/releases">Changelog</NavItem> <NavItem href="https://github.com/rivet-dev/sandbox-agent/releases">Changelog</NavItem>
</div> </div>
</div> </div>
{/* Right side */} {/* Right side */}
<div className="hidden md:flex flex-row items-center"> <div className="hidden md:flex flex-row items-center gap-2">
<a <a
href="https://discord.gg/auCecybynK" href="https://discord.gg/auCecybynK"
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" className="inline-flex items-center justify-center whitespace-nowrap rounded-md border border-white/10 px-4 py-2 h-10 text-sm hover:border-white/20 text-white/90 hover:text-white transition-colors"
aria-label="Discord" aria-label="Discord"
> >
<svg <svg
@ -90,7 +93,7 @@ export function Navigation() {
{/* Mobile menu button */} {/* Mobile menu button */}
<button <button
className="md:hidden text-zinc-400 hover:text-white p-2" className="md:hidden text-zinc-400 hover:text-white p-2 transition-colors"
onClick={() => setMobileMenuOpen(!mobileMenuOpen)} onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
> >
{mobileMenuOpen ? <X className="w-6 h-6" /> : <Menu className="w-6 h-6" />} {mobileMenuOpen ? <X className="w-6 h-6" /> : <Menu className="w-6 h-6" />}
@ -101,26 +104,26 @@ export function Navigation() {
{/* Mobile menu */} {/* Mobile menu */}
{mobileMenuOpen && ( {mobileMenuOpen && (
<div className="md:hidden border border-white/10 bg-black/95 backdrop-blur-lg rounded-2xl mt-2 mx-2"> <div className="md:hidden border border-white/10 bg-black/95 backdrop-blur-lg rounded-2xl mt-2 mx-2 shadow-xl">
<div className="px-4 py-4 space-y-2"> <div className="px-4 py-4 space-y-1">
<a <a
href="/docs" href="/docs"
className="block py-2 px-3 text-white/80 hover:text-white hover:bg-white/5 rounded-lg transition-colors" className="block py-2.5 px-3 text-white/80 hover:text-white hover:bg-white/5 rounded-lg transition-colors font-medium"
onClick={() => setMobileMenuOpen(false)} onClick={() => setMobileMenuOpen(false)}
> >
Docs Docs
</a> </a>
<a <a
href="https://github.com/rivet-dev/sandbox-agent/releases" href="https://github.com/rivet-dev/sandbox-agent/releases"
className="block py-2 px-3 text-white/80 hover:text-white hover:bg-white/5 rounded-lg transition-colors" className="block py-2.5 px-3 text-white/80 hover:text-white hover:bg-white/5 rounded-lg transition-colors font-medium"
onClick={() => setMobileMenuOpen(false)} onClick={() => setMobileMenuOpen(false)}
> >
Changelog Changelog
</a> </a>
<div className="border-t border-white/10 pt-2 mt-2 space-y-2"> <div className="border-t border-white/10 pt-3 mt-3 space-y-1">
<a <a
href="https://discord.gg/auCecybynK" href="https://discord.gg/auCecybynK"
className="flex items-center gap-2 py-2 px-3 text-white/80 hover:text-white hover:bg-white/5 rounded-lg transition-colors" className="flex items-center gap-3 py-2.5 px-3 text-white/80 hover:text-white hover:bg-white/5 rounded-lg transition-colors"
onClick={() => setMobileMenuOpen(false)} onClick={() => setMobileMenuOpen(false)}
aria-label="Discord" aria-label="Discord"
> >
@ -132,11 +135,11 @@ export function Navigation() {
> >
<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" /> <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> </svg>
<span>Discord</span> <span className="font-medium">Discord</span>
</a> </a>
<GitHubStars <GitHubStars
repo="rivet-dev/sandbox-agent" 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" className="flex items-center gap-3 py-2.5 px-3 text-white/80 hover:text-white hover:bg-white/5 rounded-lg transition-colors w-full"
onClick={() => setMobileMenuOpen(false)} onClick={() => setMobileMenuOpen(false)}
/> />
</div> </div>

View file

@ -1,133 +1,91 @@
'use client'; 'use client';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import { X, Check } from 'lucide-react'; import { Shield, Layers, Database, X, Check } from 'lucide-react';
const frictions = [ const frictions = [
{ {
number: '01', icon: Shield,
title: 'Coding Agents Need Sandboxes', title: 'Coding Agents Need Sandboxes',
problem: problem:
"You can't let AI execute arbitrary code on your production servers. Coding agents need isolated environments, but existing SDKs assume local execution.", "You can't let AI execute arbitrary code on your production servers. Coding agents need isolated environments, but existing SDKs assume local execution.",
solution: 'A server that runs inside the sandbox and exposes HTTP/SSE.', solution: 'A server that runs inside the sandbox and exposes HTTP/SSE.',
accentColor: 'orange',
}, },
{ {
number: '02', icon: Layers,
title: 'Every Coding Agent is Different', title: 'Every Coding Agent is Different',
problem: problem:
'Claude Code, Codex, OpenCode, Amp, and Pi each have proprietary APIs, event formats, and behaviors. Swapping coding agents means rewriting your entire integration.', 'Claude Code, Codex, OpenCode, Amp, and Pi each have proprietary APIs, event formats, and behaviors. Swapping coding agents means rewriting your entire integration.',
solution: 'One HTTP API. Write your code once, swap coding agents with a config change.', solution: 'One HTTP API. Write your code once, swap coding agents with a config change.',
accentColor: 'purple',
}, },
{ {
number: '03', icon: Database,
title: 'Sessions Are Ephemeral', title: 'Sessions Are Ephemeral',
problem: problem:
'Coding agent transcripts live in the sandbox. When the process ends, you lose everything. Debugging and replay become impossible.', 'Coding agent transcripts live in the sandbox. When the process ends, you lose everything. Debugging and replay become impossible.',
solution: 'Universal event schema streams to your storage. Persist to Postgres or Rivet, replay later, audit everything.', solution: 'Universal event schema streams to your storage. Persist to Postgres or Rivet, replay later, audit everything.',
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() { export function PainPoints() {
return ( return (
<section className="relative overflow-hidden border-t border-white/5 py-32"> <section className="border-t border-white/10 py-48">
<div className="mx-auto max-w-7xl px-6"> <div className="mx-auto max-w-7xl px-6">
<div className="mb-12">
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5 }}
className="mb-2 text-2xl font-normal tracking-tight text-white md:text-4xl"
>
Running coding agents remotely is hard.
</motion.h2>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: 0.1 }}
className="max-w-2xl text-base leading-relaxed text-zinc-500"
>
The Sandbox Agent SDK is a server that runs inside your sandbox. Your app connects remotely to control Claude Code, Codex, OpenCode, Amp, or Pi streaming events, handling permissions, managing sessions.
</motion.p>
</div>
<motion.div <motion.div
initial={{ opacity: 0, y: 20 }} initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }} whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }} viewport={{ once: true }}
transition={{ duration: 0.5 }} transition={{ duration: 0.5 }}
className="mb-16" className="grid grid-cols-1 gap-8 md:grid-cols-3"
> >
<h2 className="mb-6 text-3xl font-medium tracking-tight text-white md:text-5xl"> {frictions.map((friction) => (
Running coding agents remotely is hard. <div key={friction.title} className="flex flex-col border-t border-white/10 pt-6">
</h2> <div className="mb-3 text-zinc-500">
<p className="max-w-2xl text-lg leading-relaxed text-zinc-400"> <friction.icon className="h-4 w-4" />
Coding agents need sandboxes, but existing SDKs assume local execution. SSH breaks, CLI wrappers are fragile, and building from scratch means reimplementing everything for each coding agent. </div>
</p> <h3 className="mb-4 text-base font-normal text-white">{friction.title}</h3>
</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 flex flex-col h-full">
{/* Title */}
<h3 className="mb-4 text-xl font-medium text-white">{friction.title}</h3>
{/* Problem */}
<div className="mb-4"> <div className="mb-4">
<div className="flex items-center gap-2 mb-2"> <div className="flex items-center gap-2 mb-2">
<div className="flex items-center justify-center w-5 h-5 rounded-full bg-red-500/20"> <X className="h-3 w-3 text-zinc-600" />
<X className="w-3 h-3 text-red-400" /> <span className="text-[10px] font-medium uppercase tracking-wider text-zinc-600">Problem</span>
</div> </div>
<span className="text-xs font-semibold uppercase tracking-wider text-red-400">Problem</span> <p className="text-sm leading-relaxed text-zinc-500">
{friction.problem}
</p>
</div> </div>
<p className="text-sm leading-relaxed text-zinc-500">{friction.problem}</p> <div className="mt-auto border-t border-white/5 pt-4">
</div>
{/* Solution */}
<div className="mt-auto pt-4 border-t border-white/5">
<div className="flex items-center gap-2 mb-2"> <div className="flex items-center gap-2 mb-2">
<div className="flex items-center justify-center w-5 h-5 rounded-full bg-green-500/20"> <Check className="h-3 w-3 text-green-400" />
<Check className="w-3 h-3 text-green-400" /> <span className="text-[10px] font-medium uppercase tracking-wider text-zinc-400">Solution</span>
</div> </div>
<span className="text-xs font-semibold uppercase tracking-wider text-green-400">Solution</span> <p className="text-sm leading-relaxed text-zinc-300">
</div> {friction.solution}
<p className="text-sm font-medium leading-relaxed text-zinc-300">{friction.solution}</p> </p>
</div> </div>
</div> </div>
))}
</motion.div> </motion.div>
);
})}
</div>
</div> </div>
</section> </section>
); );

View file

@ -4,32 +4,81 @@ interface Props {
description?: string; description?: string;
} }
const { title, description = "Universal SDK for coding agents. Control Claude Code, Codex, OpenCode, and Amp with unified events and sessions." } = Astro.props; const { title, description = "Universal SDK for coding agents. Control Claude Code, Codex, OpenCode, Amp, and Pi with unified events and sessions." } = Astro.props;
const canonicalURL = new URL(Astro.url.pathname, 'https://sandbox-agent.dev');
const ogImageURL = new URL('/og.png', 'https://sandbox-agent.dev');
const structuredData = {
"@context": "https://schema.org",
"@type": "SoftwareApplication",
"name": "Sandbox Agent SDK",
"applicationCategory": "DeveloperApplication",
"operatingSystem": "Linux, macOS, Windows",
"description": description,
"url": "https://sandbox-agent.dev",
"author": {
"@type": "Organization",
"name": "Rivet",
"url": "https://rivet.dev"
},
"offers": {
"@type": "Offer",
"price": "0",
"priceCurrency": "USD"
},
"keywords": "coding agents, AI SDK, Claude Code, Codex, OpenCode, Amp, sandbox, remote code execution, developer tools"
};
--- ---
<!doctype html> <!doctype html>
<html lang="en"> <html lang="en" class="dark">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content={description} /> <meta name="description" content={description} />
<meta name="keywords" content="coding agents, AI SDK, Claude Code, Codex, OpenCode, Amp, Pi, sandbox, remote code execution, developer tools, AI coding assistant, code automation" />
<meta name="author" content="Rivet" />
<meta name="robots" content="index, follow" />
<meta name="theme-color" content="#000000" />
<link rel="canonical" href={canonicalURL} />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" /> <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<!-- Preconnect to font providers -->
<link rel="preconnect" href="https://fonts.googleapis.com" /> <link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link rel="preconnect" href="https://api.fontshare.com" crossorigin />
<!-- Satoshi for headings (from Fontshare) -->
<link href="https://api.fontshare.com/v2/css?f[]=satoshi@700,900&display=swap" rel="stylesheet" />
<!-- Open Sans + JetBrains Mono (from Google Fonts) -->
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&family=Open+Sans:wght@400;500;600;700&display=swap" rel="stylesheet" /> <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&family=Open+Sans:wght@400;500;600;700&display=swap" rel="stylesheet" />
<title>{title}</title> <title>{title}</title>
<!-- Open Graph -->
<meta property="og:title" content={title} /> <meta property="og:title" content={title} />
<meta property="og:description" content={description} /> <meta property="og:description" content={description} />
<meta property="og:type" content="website" /> <meta property="og:type" content="website" />
<meta property="og:image" content="/og.png" /> <meta property="og:url" content={canonicalURL} />
<meta property="og:site_name" content="Sandbox Agent SDK" />
<meta property="og:image" content={ogImageURL} />
<meta property="og:image:width" content="2400" /> <meta property="og:image:width" content="2400" />
<meta property="og:image:height" content="1260" /> <meta property="og:image:height" content="1260" />
<meta property="og:image:alt" content="Sandbox Agent SDK - Run Coding Agents in Sandboxes. Control Them Over HTTP." /> <meta property="og:image:alt" content="Sandbox Agent SDK - Run Coding Agents in Sandboxes. Control Them Over HTTP." />
<!-- Twitter -->
<meta name="twitter:card" content="summary_large_image" /> <meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:image" content="/og.png" /> <meta name="twitter:site" content="@rivet_dev" />
<meta name="twitter:creator" content="@rivet_dev" />
<meta name="twitter:title" content={title} />
<meta name="twitter:description" content={description} />
<meta name="twitter:image" content={ogImageURL} />
<!-- Structured Data -->
<script type="application/ld+json" set:html={JSON.stringify(structuredData)} />
</head> </head>
<body class="min-h-screen"> <body class="min-h-screen bg-background text-foreground antialiased">
<slot /> <slot />
</body> </body>
</html> </html>

View file

@ -11,7 +11,6 @@ import { Footer } from '../components/Footer';
--- ---
<Layout title="Sandbox Agent SDK - Run Coding Agents in Sandboxes. Control Them Over HTTP."> <Layout title="Sandbox Agent SDK - Run Coding Agents in Sandboxes. Control Them Over HTTP.">
<div class="min-h-screen bg-black text-white selection:bg-accent/30">
<Navigation client:load /> <Navigation client:load />
<main> <main>
<Hero client:load /> <Hero client:load />
@ -22,5 +21,4 @@ import { Footer } from '../components/Footer';
<FAQ client:visible /> <FAQ client:visible />
</main> </main>
<Footer client:visible /> <Footer client:visible />
</div>
</Layout> </Layout>

View file

@ -3,13 +3,61 @@
@tailwind utilities; @tailwind utilities;
@layer base { @layer base {
:root {
--header-height: 3.5rem;
/* Theme colors (HSL for flexibility) */
--background: 20 14.3% 4.1%;
--foreground: 60 9.1% 97.8%;
--primary: 18.5 100% 50%;
--primary-foreground: 60 9.1% 97.8%;
--muted: 34 10% 10%;
--muted-foreground: 24 5.4% 63.9%;
--border: 12 6.5% 15.1%;
--card: 0 9.09% 6.47%;
/* Shiki syntax highlighting */
--shiki-color-text: theme('colors.white');
--shiki-foreground: hsl(var(--foreground));
--shiki-token-constant: theme('colors.violet.300');
--shiki-token-string: theme('colors.violet.300');
--shiki-token-comment: theme('colors.zinc.500');
--shiki-token-keyword: theme('colors.sky.300');
--shiki-token-parameter: theme('colors.pink.300');
--shiki-token-function: theme('colors.violet.300');
--shiki-token-string-expression: theme('colors.violet.300');
--shiki-token-punctuation: theme('colors.zinc.200');
}
* {
@apply border-white/10;
}
body { body {
@apply bg-black text-white antialiased; @apply bg-black text-white antialiased;
font-family: 'Open Sans', system-ui, sans-serif; font-family: 'Open Sans', system-ui, sans-serif;
} }
/* Text selection - matches rivet.dev */
::selection { ::selection {
@apply bg-accent/30 text-white; background-color: rgba(255, 79, 0, 0.3);
color: #fed7aa;
}
::-moz-selection {
background-color: rgba(255, 79, 0, 0.3);
color: #fed7aa;
}
/* Selection style for white/light backgrounds */
.selection-dark::selection {
background-color: #18181b;
color: white;
}
.selection-dark::-moz-selection {
background-color: #18181b;
color: white;
} }
/* Firefox scrollbar */ /* Firefox scrollbar */
@ -65,6 +113,7 @@
} }
@layer components { @layer components {
/* Glass morphism effects */
.glass { .glass {
@apply bg-white/[0.02] backdrop-blur-md border border-white/10; @apply bg-white/[0.02] backdrop-blur-md border border-white/10;
} }
@ -72,4 +121,123 @@
.glass-hover { .glass-hover {
@apply hover:bg-white/[0.04] hover:border-white/20 transition-all; @apply hover:bg-white/[0.04] hover:border-white/20 transition-all;
} }
.glass-strong {
@apply bg-black/95 backdrop-blur-lg border border-white/10;
}
/* Bento box card effects */
.bento-box {
transition: border-color 0.3s ease;
}
.bento-box:hover {
border-color: rgba(255, 255, 255, 0.2);
}
/* Scroll-triggered animations */
.animate-on-scroll {
opacity: 0;
transition: opacity 0.8s ease-out, transform 0.8s cubic-bezier(0.19, 1, 0.22, 1);
transition-delay: var(--scroll-delay, 0s);
will-change: opacity, transform;
}
.animate-fade-up {
transform: translateY(30px);
}
.animate-on-scroll.is-visible {
opacity: 1;
transform: translateY(0);
}
/* Delay utilities for staggered animations */
.delay-100 { --scroll-delay: 100ms; }
.delay-200 { --scroll-delay: 200ms; }
.delay-300 { --scroll-delay: 300ms; }
.delay-400 { --scroll-delay: 400ms; }
.delay-500 { --scroll-delay: 500ms; }
.delay-600 { --scroll-delay: 600ms; }
/* Top shine highlight for cards */
.shine-top {
position: relative;
}
.shine-top::after {
content: "";
position: absolute;
left: 0;
right: 0;
top: 0;
height: 1px;
background: linear-gradient(to right, transparent, rgba(255, 255, 255, 0.2), transparent);
pointer-events: none;
}
/* Glow effect for buttons and interactive elements */
.glow-accent {
box-shadow: 0 0 20px rgba(255, 69, 0, 0.3);
}
.glow-accent-hover:hover {
box-shadow: 0 0 30px rgba(255, 69, 0, 0.5);
}
/* Code highlight styling */
.code-highlight-ref {
position: relative;
transition: background-color 0.3s ease-out;
display: block;
margin: 0 -1.5rem;
padding: 0 1.5rem;
}
.code-highlight-ref.is-active {
background-color: rgba(255, 69, 0, 0.1);
}
.code-highlight-ref.is-active::before {
content: "";
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 2px;
background-color: #ff4500;
}
/* Hide scrollbar */
.scrollbar-hide::-webkit-scrollbar {
display: none;
}
.scrollbar-hide {
-ms-overflow-style: none;
scrollbar-width: none;
}
}
@layer utilities {
/* Gradient text */
.text-gradient-accent {
@apply bg-gradient-to-r from-orange-400 to-orange-600 bg-clip-text text-transparent;
}
/* Backdrop with blur */
.backdrop-glow {
@apply backdrop-blur-lg bg-black/80;
}
/* Better focus ring */
.focus-ring {
@apply focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2 focus-visible:ring-offset-black;
}
}
/* View transition disable (for smooth prefetching) */
::view-transition-old(root),
::view-transition-new(root) {
animation: none;
} }

View file

@ -4,21 +4,59 @@ export default {
theme: { theme: {
extend: { extend: {
colors: { colors: {
// Primary accent (OrangeRed)
accent: '#FF4500', accent: '#FF4500',
// Extended color palette
background: '#000000',
'text-primary': '#FAFAFA',
'text-secondary': '#A0A0A0',
border: '#252525',
// Code syntax highlighting
'code-keyword': '#c084fc',
'code-function': '#60a5fa',
'code-string': '#4ade80',
'code-comment': '#737373',
}, },
fontFamily: { fontFamily: {
sans: ['Open Sans', 'system-ui', 'sans-serif'], sans: ['Open Sans', 'system-ui', 'sans-serif'],
heading: ['Satoshi', 'Open Sans', 'system-ui', 'sans-serif'],
mono: ['JetBrains Mono', 'monospace'], mono: ['JetBrains Mono', 'monospace'],
}, },
animation: { animation: {
'fade-in-up': 'fade-in-up 0.6s ease-out forwards', 'fade-in-up': 'fade-in-up 0.8s ease-out forwards',
'pulse-slow': 'pulse 3s ease-in-out infinite', 'hero-line': 'hero-line 1s cubic-bezier(0.19, 1, 0.22, 1) forwards',
'hero-p': 'hero-p 0.8s ease-out 0.6s forwards',
'hero-cta': 'hero-p 0.8s ease-out 0.8s forwards',
'hero-visual': 'hero-p 0.8s ease-out 1s forwards',
'infinite-scroll': 'infinite-scroll 25s linear infinite',
'pulse-slow': 'pulse-slow 3s cubic-bezier(0.4, 0, 0.6, 1) infinite',
}, },
keyframes: { keyframes: {
'fade-in-up': { 'fade-in-up': {
'0%': { opacity: '0', transform: 'translateY(20px)' }, from: { opacity: '0', transform: 'translateY(24px)' },
'100%': { opacity: '1', transform: 'translateY(0)' }, to: { opacity: '1', transform: 'translateY(0)' },
}, },
'hero-line': {
'0%': { opacity: '0', transform: 'translateY(100%) skewY(6deg)' },
'100%': { opacity: '1', transform: 'translateY(0) skewY(0deg)' },
},
'hero-p': {
from: { opacity: '0', transform: 'translateY(20px)' },
to: { opacity: '1', transform: 'translateY(0)' },
},
'infinite-scroll': {
from: { transform: 'translateX(0)' },
to: { transform: 'translateX(-50%)' },
},
'pulse-slow': {
'50%': { opacity: '.5' },
},
},
spacing: {
header: 'var(--header-height, 3.5rem)',
},
borderRadius: {
'4xl': '2rem',
}, },
}, },
}, },

22
pi.svg Normal file
View file

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 800">
<!-- P shape: outer boundary clockwise, inner hole counter-clockwise -->
<path fill="#fff" fill-rule="evenodd" d="
M165.29 165.29
H517.36
V400
H400
V517.36
H282.65
V634.72
H165.29
Z
M282.65 282.65
V400
H400
V282.65
Z
"/>
<!-- i dot -->
<path fill="#fff" d="M517.36 400 H634.72 V634.72 H517.36 Z"/>
</svg>

After

Width:  |  Height:  |  Size: 473 B